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,182 @@
|
||||
#include "gr2_bridge.h"
|
||||
|
||||
#include <godot_cpp/classes/array_mesh.hpp>
|
||||
#include <godot_cpp/classes/mesh.hpp>
|
||||
#include <godot_cpp/classes/skeleton3d.hpp>
|
||||
#include <godot_cpp/classes/skin.hpp>
|
||||
#include <godot_cpp/core/math.hpp>
|
||||
#include <godot_cpp/core/memory.hpp>
|
||||
#include <godot_cpp/variant/packed_float32_array.hpp>
|
||||
#include <godot_cpp/variant/packed_int32_array.hpp>
|
||||
#include <godot_cpp/variant/packed_vector2_array.hpp>
|
||||
#include <godot_cpp/variant/packed_vector3_array.hpp>
|
||||
#include <godot_cpp/variant/utility_functions.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
using namespace godot;
|
||||
|
||||
namespace mtgodot {
|
||||
|
||||
Transform3D gr2_to_godot(const gr2::Mat4 &m) {
|
||||
// gr2 row-major, row-vector: basis columns are (m0,m1,m2),(m4,m5,m6),(m8,m9,m10);
|
||||
// translation is the 4th row (m12,m13,m14).
|
||||
Basis b(
|
||||
Vector3(m[0], m[1], m[2]),
|
||||
Vector3(m[4], m[5], m[6]),
|
||||
Vector3(m[8], m[9], m[10]));
|
||||
return Transform3D(b, Vector3(m[12], m[13], m[14]));
|
||||
}
|
||||
|
||||
Transform3D make_conv(float unit_scale, bool flip_z) {
|
||||
// Z-up -> Y-up: rotate -90 deg about X. Then uniform scale. Optional Z flip.
|
||||
Basis b;
|
||||
b = b.rotated(Vector3(1, 0, 0), Math::deg_to_rad(-90.0));
|
||||
b = b.scaled(Vector3(unit_scale, unit_scale, flip_z ? -unit_scale : unit_scale));
|
||||
return Transform3D(b, Vector3());
|
||||
}
|
||||
|
||||
Skeleton3D *build_skeleton(const gr2::Skeleton &sk) {
|
||||
if (sk.bones.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
Skeleton3D *skel = memnew(Skeleton3D);
|
||||
skel->set_name("Skeleton3D");
|
||||
|
||||
const Transform3D ip = gr2_to_godot(sk.initial_placement);
|
||||
|
||||
for (size_t i = 0; i < sk.bones.size(); ++i) {
|
||||
skel->add_bone(String(sk.bones[i].name.c_str()));
|
||||
}
|
||||
for (size_t i = 0; i < sk.bones.size(); ++i) {
|
||||
const gr2::Bone &bn = sk.bones[i];
|
||||
skel->set_bone_parent((int)i, bn.parent);
|
||||
Transform3D rest = gr2_to_godot(bn.local_transform);
|
||||
if (bn.parent < 0) {
|
||||
rest = ip * rest; // fold initial_placement into root bones
|
||||
}
|
||||
skel->set_bone_rest((int)i, rest);
|
||||
skel->reset_bone_pose((int)i);
|
||||
}
|
||||
return skel;
|
||||
}
|
||||
|
||||
Ref<Skin> build_skin(const gr2::Skeleton &sk) {
|
||||
Ref<Skin> skin;
|
||||
skin.instantiate();
|
||||
for (size_t i = 0; i < sk.bones.size(); ++i) {
|
||||
skin->add_bind((int)i, gr2_to_godot(sk.bones[i].inverse_world));
|
||||
skin->set_bind_name((int)i, StringName(sk.bones[i].name.c_str()));
|
||||
}
|
||||
return skin;
|
||||
}
|
||||
|
||||
Ref<ArrayMesh> build_mesh(const gr2::FileInfo &fi, bool flip_winding, AABB &out_bounds) {
|
||||
Ref<ArrayMesh> am;
|
||||
am.instantiate();
|
||||
am->set_name("ArrayMesh");
|
||||
|
||||
bool have_bounds = false;
|
||||
|
||||
for (size_t mi = 0; mi < fi.meshes.size(); ++mi) {
|
||||
const gr2::Mesh &m = fi.meshes[mi];
|
||||
if (m.vertices.empty() || m.indices.empty()) {
|
||||
continue;
|
||||
}
|
||||
const int vcount = (int)m.vertices.size();
|
||||
|
||||
PackedVector3Array pos;
|
||||
PackedVector3Array nrm;
|
||||
PackedVector2Array uv;
|
||||
PackedInt32Array bones;
|
||||
PackedFloat32Array weights;
|
||||
pos.resize(vcount);
|
||||
nrm.resize(vcount);
|
||||
uv.resize(vcount);
|
||||
bones.resize(vcount * 4);
|
||||
weights.resize(vcount * 4);
|
||||
|
||||
Vector3 *pos_w = pos.ptrw();
|
||||
Vector3 *nrm_w = nrm.ptrw();
|
||||
Vector2 *uv_w = uv.ptrw();
|
||||
int32_t *bn_w = bones.ptrw();
|
||||
float *wt_w = weights.ptrw();
|
||||
|
||||
auto slot_to_bone = [&](int slot) -> int {
|
||||
if (slot < 0 || slot >= (int)m.bone_bindings.size()) {
|
||||
return 0;
|
||||
}
|
||||
int b = m.bone_bindings[slot];
|
||||
return (b < 0) ? 0 : b;
|
||||
};
|
||||
|
||||
const int rigid_bone = m.rigid ? slot_to_bone(m.bone_bindings.empty() ? -1 : 0) : 0;
|
||||
|
||||
for (int i = 0; i < vcount; ++i) {
|
||||
const gr2::Vertex &v = m.vertices[i];
|
||||
pos_w[i] = Vector3(v.pos[0], v.pos[1], v.pos[2]);
|
||||
nrm_w[i] = Vector3(v.normal[0], v.normal[1], v.normal[2]);
|
||||
uv_w[i] = Vector2(v.uv0[0], v.uv0[1]);
|
||||
|
||||
if (!have_bounds) {
|
||||
out_bounds.position = pos_w[i];
|
||||
out_bounds.size = Vector3();
|
||||
have_bounds = true;
|
||||
} else {
|
||||
out_bounds = out_bounds.expand(pos_w[i]);
|
||||
}
|
||||
|
||||
if (m.rigid) {
|
||||
bn_w[i * 4 + 0] = rigid_bone;
|
||||
bn_w[i * 4 + 1] = bn_w[i * 4 + 2] = bn_w[i * 4 + 3] = 0;
|
||||
wt_w[i * 4 + 0] = 1.0f;
|
||||
wt_w[i * 4 + 1] = wt_w[i * 4 + 2] = wt_w[i * 4 + 3] = 0.0f;
|
||||
continue;
|
||||
}
|
||||
|
||||
float wsum = 0.0f;
|
||||
for (int k = 0; k < 4; ++k) {
|
||||
wsum += v.bone_weight[k];
|
||||
}
|
||||
for (int k = 0; k < 4; ++k) {
|
||||
bn_w[i * 4 + k] = slot_to_bone(v.bone_index[k]);
|
||||
wt_w[i * 4 + k] = (wsum > 0.0f) ? (v.bone_weight[k] / (float)wsum) : (k == 0 ? 1.0f : 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
PackedInt32Array idx;
|
||||
idx.resize((int)m.indices.size());
|
||||
int32_t *idx_w = idx.ptrw();
|
||||
if (flip_winding) {
|
||||
for (size_t t = 0; t + 2 < m.indices.size(); t += 3) {
|
||||
idx_w[t + 0] = (int)m.indices[t + 0];
|
||||
idx_w[t + 1] = (int)m.indices[t + 2];
|
||||
idx_w[t + 2] = (int)m.indices[t + 1];
|
||||
}
|
||||
} else {
|
||||
for (size_t t = 0; t < m.indices.size(); ++t) {
|
||||
idx_w[t] = (int)m.indices[t];
|
||||
}
|
||||
}
|
||||
|
||||
Array arrays;
|
||||
arrays.resize(Mesh::ARRAY_MAX);
|
||||
arrays[Mesh::ARRAY_VERTEX] = pos;
|
||||
arrays[Mesh::ARRAY_NORMAL] = nrm;
|
||||
arrays[Mesh::ARRAY_TEX_UV] = uv;
|
||||
arrays[Mesh::ARRAY_BONES] = bones;
|
||||
arrays[Mesh::ARRAY_WEIGHTS] = weights;
|
||||
arrays[Mesh::ARRAY_INDEX] = idx;
|
||||
|
||||
am->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arrays);
|
||||
am->surface_set_name(am->get_surface_count() - 1,
|
||||
m.name.empty() ? String("surf_") + itos((int)mi) : String(m.name.c_str()));
|
||||
}
|
||||
|
||||
if (!have_bounds) {
|
||||
out_bounds = AABB();
|
||||
}
|
||||
return am;
|
||||
}
|
||||
|
||||
} // namespace mtgodot
|
||||
Reference in New Issue
Block a user