M1+M2: gr2 -> Skeleton3D/ArrayMesh/Skin + libgr2-driven animation
- gr2_bridge: gr2 Mat4 (row-major) -> Godot Transform3D (4x4 transpose); build_skeleton (rest from local_transform, initial_placement folded into roots), build_skin (bind i -> bone i, pose = inverse_world), build_mesh (surface per gr2 mesh, ARRAY_BONES = skeleton idx via bone_bindings, rigid meshes bound to bone_bindings[0]). - dxt: DDS DXT1/3/5 decoder ported from xrender-poc. - Metin2Model: load_gr2 -> Skeleton3D + MeshInstance3D + StandardMaterial3D (runtime DDS albedo, face/body split by surface name). Z-up cm -> Y-up m conversion on the node transform (unit_scale, flip_z, flip_winding props). - Metin2AnimPlayer: _process samples a (separate) anim .gr2 with gr2::sample_pose(model.skeleton, anim, t) -> set_bone_global_pose per bone; Godot GPU-skins via skin_matrix = global_pose(i) * bind_pose(i) == gr2 deformer matrix. selfcheck() NaN-scans all anims. Verified: warrior_cheongrin renders (75 bones, 5 surfaces, 3324 verts, textured, upright). dance01 (dur 28.3s, 74 tracks) plays, 0 NaN over 25 samples, distinct poses at t=2/9/16/23. Screenshots in test/golden/. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaHYEY9rwLWt21PULiYjeJ
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include <godot_cpp/classes/node3d.hpp>
|
||||
#include <godot_cpp/variant/node_path.hpp>
|
||||
#include <godot_cpp/variant/string.hpp>
|
||||
|
||||
#include <gr2/gr2.h>
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace mtgodot {
|
||||
|
||||
class Metin2Model;
|
||||
|
||||
// Drives a Metin2Model's Skeleton3D each frame by sampling a (possibly
|
||||
// separate) animation .gr2 with libgr2, then writing per-bone global poses.
|
||||
//
|
||||
// world[i] = gr2::sample_pose(model.skeleton, anim, t)
|
||||
// skel.set_bone_global_pose(i, gr2_to_godot(world[i]))
|
||||
//
|
||||
// Godot's renderer then computes skin matrix = global_pose(i) * bind_pose(i)
|
||||
// which equals gr2's deformer matrix (see gr2_bridge.h).
|
||||
class Metin2AnimPlayer : public godot::Node3D {
|
||||
GDCLASS(Metin2AnimPlayer, godot::Node3D)
|
||||
|
||||
public:
|
||||
Metin2AnimPlayer();
|
||||
~Metin2AnimPlayer() override;
|
||||
|
||||
void _ready() override;
|
||||
void _process(double delta) override;
|
||||
|
||||
void set_anim_path(const godot::String &p);
|
||||
godot::String get_anim_path() const { return anim_path; }
|
||||
|
||||
void set_model_path(const godot::NodePath &p);
|
||||
godot::NodePath get_model_path() const { return model_path; }
|
||||
|
||||
void set_playing(bool v) { playing = v; }
|
||||
bool get_playing() const { return playing; }
|
||||
|
||||
void set_time_scale(double s) { time_scale = s; }
|
||||
double get_time_scale() const { return time_scale; }
|
||||
|
||||
void set_time(double t);
|
||||
double get_time() const { return time; }
|
||||
|
||||
double get_duration() const { return duration; }
|
||||
void reload();
|
||||
godot::String get_info() const { return last_info; }
|
||||
|
||||
// NaN-scan every animation in anim_path across [0,dur]; returns a report string.
|
||||
godot::String selfcheck(int samples = 24);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
private:
|
||||
godot::String anim_path;
|
||||
godot::NodePath model_path;
|
||||
bool playing = true;
|
||||
double time_scale = 1.0;
|
||||
double time = 0.0;
|
||||
double duration = 0.0;
|
||||
godot::String last_info;
|
||||
|
||||
std::optional<gr2::File> anim_file;
|
||||
std::vector<gr2::Mat4> world_buf;
|
||||
std::vector<gr2::Mat4> skin_buf;
|
||||
|
||||
Metin2Model *resolve_model() const;
|
||||
void apply_pose(double t);
|
||||
};
|
||||
|
||||
} // namespace mtgodot
|
||||
Reference in New Issue
Block a user