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:
Claude
2026-08-29 09:23:29 +09:00
parent 67109990ec
commit 27e39993f7
121 changed files with 1058 additions and 57 deletions
+59 -8
View File
@@ -3,14 +3,24 @@
#include <godot_cpp/classes/node3d.hpp>
#include <godot_cpp/variant/string.hpp>
#include <gr2/gr2.h>
#include <memory>
#include <optional>
namespace godot {
class Skeleton3D;
class MeshInstance3D;
} // namespace godot
namespace mtgodot {
// M0' placeholder node.
// Loads a Metin2 .gr2 and builds a Godot subtree:
// Metin2Model (transform = Z-up/cm -> Y-up/m conversion)
// └─ Skeleton3D (rest pose from gr2 bones)
// └─ MeshInstance3D (ArrayMesh, one surface per gr2 mesh; skinned)
//
// Purpose at this milestone: prove the GDExtension registers a class the Godot
// editor can instantiate, and that libgr2 is linked in and callable from here.
// M1 replaces the body with real Skeleton3D / ArrayMesh construction
// (see docs/GODOT-POC-PLAN.md §M1).
// M1: static bind-pose render. M2's Metin2AnimPlayer drives the Skeleton3D.
class Metin2Model : public godot::Node3D {
GDCLASS(Metin2Model, godot::Node3D)
@@ -18,14 +28,55 @@ public:
Metin2Model();
~Metin2Model() override;
// One-line tag proving libgr2 symbols resolved at link time.
godot::String libgr2_info() const;
void _ready() override;
// Parse a .gr2 with libgr2 and report a couple of counts (no scene build yet).
// --- inspector properties ---
void set_gr2_path(const godot::String &p);
godot::String get_gr2_path() const { return gr2_path; }
void set_texture_dir(const godot::String &p);
godot::String get_texture_dir() const { return texture_dir; }
void set_unit_scale(double s);
double get_unit_scale() const { return unit_scale; }
void set_flip_z(bool v);
bool get_flip_z() const { return flip_z; }
void set_flip_winding(bool v);
bool get_flip_winding() const { return flip_winding; }
// Rebuild the subtree from the current properties.
void reload();
// One-line summary (bones / meshes / verts / bounds).
godot::String get_info() const;
// --- C++ accessors for Metin2AnimPlayer (same extension) ---
const gr2::Skeleton *gr2_skeleton() const;
godot::Skeleton3D *skeleton_node() const { return skel; }
// libgr2 sanity probe (kept from M0').
godot::String probe_gr2(const godot::String &path) const;
protected:
static void _bind_methods();
private:
godot::String gr2_path;
godot::String texture_dir;
double unit_scale = 0.01;
bool flip_z = false;
bool flip_winding = false;
std::shared_ptr<std::optional<gr2::File>> file; // shared_ptr so accessor stays valid
godot::Skeleton3D *skel = nullptr;
godot::MeshInstance3D *mi = nullptr;
godot::String last_info;
void _clear_children();
void _apply_materials();
godot::String _guess_texture_dir() const;
};
} // namespace mtgodot