Fix cull mode + isolate head-collapse to a libgr2 curve bug
Rendering fixes (real): - m2_material shaders: cull_disabled -> cull_back (inside-of-mesh faces were z-winning and darkening/hiding the head under the ShaderMaterial path). - drop `* COLOR` from the shader (meshes carry no ARRAY_COLOR). Animation: convert gr2 global poses to Godot *local* bone poses and set via set_bone_pose (was set_bone_global_pose). MTGODOT_VERIFY=1 confirms get_bone_global_pose(i) == conv(world[i]) for all 75 bones (<=2.6e-5). Head/upper-armor collapse on dance_1 is NOT a Godot-route bug: - reproduced identically in xrender-poc's bgfx demo at the same anim/t - reproduced by the new MTGODOT_CPUSKIN=1 reference path (same LBS math as xrender's validated skin_mesh) - `general/wait` / `run` render correctly in all three => upstream libgr2 Curve::eval (degree-2 quaternion B-spline) vs Granny. Demo default animation switched to general/wait; documented in MIDREVIEW §4 and README. Debug scaffolding gated behind MTGODOT_VERIFY / MTGODOT_CPUSKIN. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WaHYEY9rwLWt21PULiYjeJ
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
#include <godot_cpp/classes/image.hpp>
|
||||
#include <godot_cpp/classes/image_texture.hpp>
|
||||
#include <godot_cpp/classes/mesh_instance3d.hpp>
|
||||
#include <godot_cpp/classes/os.hpp>
|
||||
#include <godot_cpp/classes/shader_material.hpp>
|
||||
#include <godot_cpp/classes/skeleton3d.hpp>
|
||||
#include <godot_cpp/classes/skin.hpp>
|
||||
@@ -184,7 +185,11 @@ void Metin2Model::reload() {
|
||||
mi->set_owner(get_owner() ? get_owner() : this);
|
||||
if (skel && sk) {
|
||||
mi->set_skeleton_path(mi->get_path_to(skel));
|
||||
mi->set_skin(build_skin(*sk));
|
||||
if (OS::get_singleton()->get_environment("MTGODOT_AUTOSKIN") == "1") {
|
||||
// let Godot derive the skin from skeleton rest poses
|
||||
} else {
|
||||
mi->set_skin(build_skin(*sk));
|
||||
}
|
||||
}
|
||||
mi->set_custom_aabb(bounds.grow(0.01));
|
||||
}
|
||||
@@ -435,6 +440,124 @@ const gr2::Skeleton *Metin2Model::gr2_skeleton() const {
|
||||
return fi.skeletons.empty() ? nullptr : &fi.skeletons[0];
|
||||
}
|
||||
|
||||
const gr2::FileInfo *Metin2Model::gr2_fileinfo() const {
|
||||
return (file && *file) ? &(*file)->file_info() : nullptr;
|
||||
}
|
||||
|
||||
namespace {
|
||||
// row-major 4x4 (translation elem 12..14), row-vector: out = [v 1] * M
|
||||
inline void xform_pt(const gr2::Mat4 &M, const float *v, float *o) {
|
||||
o[0] = v[0] * M[0] + v[1] * M[4] + v[2] * M[8] + M[12];
|
||||
o[1] = v[0] * M[1] + v[1] * M[5] + v[2] * M[9] + M[13];
|
||||
o[2] = v[0] * M[2] + v[1] * M[6] + v[2] * M[10] + M[14];
|
||||
}
|
||||
inline void xform_dir(const gr2::Mat4 &M, const float *v, float *o) {
|
||||
o[0] = v[0] * M[0] + v[1] * M[4] + v[2] * M[8];
|
||||
o[1] = v[0] * M[1] + v[1] * M[5] + v[2] * M[9];
|
||||
o[2] = v[0] * M[2] + v[1] * M[6] + v[2] * M[10];
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void Metin2Model::enable_cpu_skin(bool on) {
|
||||
if (!mi || !file || !*file) {
|
||||
return;
|
||||
}
|
||||
if (on && cpu_mesh.is_null()) {
|
||||
AABB b;
|
||||
cpu_mesh = build_mesh((*file)->file_info(), flip_winding, b);
|
||||
cpu_surf_mesh.clear();
|
||||
const gr2::FileInfo &fi = (*file)->file_info();
|
||||
for (int i = 0; i < (int)fi.meshes.size(); ++i) {
|
||||
if (!fi.meshes[i].vertices.empty() && !fi.meshes[i].indices.empty()) {
|
||||
cpu_surf_mesh.push_back(i);
|
||||
}
|
||||
}
|
||||
mi->set_skeleton_path(NodePath());
|
||||
mi->set_skin(Ref<Skin>());
|
||||
mi->set_mesh(cpu_mesh);
|
||||
if (skel) {
|
||||
skel->set_show_rest_only(true);
|
||||
}
|
||||
UtilityFunctions::print("[Metin2Model] CPU-skin mode ON");
|
||||
}
|
||||
}
|
||||
|
||||
void Metin2Model::cpu_skin(const std::vector<gr2::Mat4> &skin) {
|
||||
if (cpu_mesh.is_null() || !file || !*file) {
|
||||
return;
|
||||
}
|
||||
const gr2::FileInfo &fi = (*file)->file_info();
|
||||
static const gr2::Mat4 I{ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 };
|
||||
|
||||
cpu_mesh->clear_surfaces();
|
||||
for (int s = 0; s < (int)cpu_surf_mesh.size(); ++s) {
|
||||
const gr2::Mesh &m = fi.meshes[cpu_surf_mesh[s]];
|
||||
const int nv = (int)m.vertices.size();
|
||||
PackedVector3Array pos;
|
||||
PackedVector3Array nrm;
|
||||
PackedVector2Array uv;
|
||||
PackedInt32Array idx;
|
||||
pos.resize(nv);
|
||||
nrm.resize(nv);
|
||||
uv.resize(nv);
|
||||
idx.resize((int)m.indices.size());
|
||||
Vector3 *pw = pos.ptrw();
|
||||
Vector3 *nw = nrm.ptrw();
|
||||
Vector2 *uw = uv.ptrw();
|
||||
int32_t *iw = idx.ptrw();
|
||||
auto bone_of = [&](int slot) -> const gr2::Mat4 & {
|
||||
if (slot < 0 || slot >= (int)m.bone_bindings.size()) {
|
||||
return I;
|
||||
}
|
||||
int b = m.bone_bindings[slot];
|
||||
return (b < 0 || b >= (int)skin.size()) ? I : skin[b];
|
||||
};
|
||||
for (int i = 0; i < nv; ++i) {
|
||||
const gr2::Vertex &v = m.vertices[i];
|
||||
float wq[4];
|
||||
float sw = 0;
|
||||
for (int k = 0; k < 4; ++k) {
|
||||
sw += v.bone_weight[k];
|
||||
}
|
||||
for (int k = 0; k < 4; ++k) {
|
||||
wq[k] = (sw > 0) ? v.bone_weight[k] / sw : (k == 0 ? 1.f : 0.f);
|
||||
}
|
||||
gr2::Mat4 M{};
|
||||
for (int k = 0; k < 4; ++k) {
|
||||
const gr2::Mat4 &bm = bone_of(v.bone_index[k]);
|
||||
for (int e = 0; e < 16; ++e) {
|
||||
M[e] += wq[k] * bm[e];
|
||||
}
|
||||
}
|
||||
gr2::Mat4 M3 = M;
|
||||
M3[12] = M3[13] = M3[14] = 0;
|
||||
float p[3], nn[3];
|
||||
xform_pt(M, v.pos, p);
|
||||
xform_dir(M3, v.normal, nn);
|
||||
float ln = std::sqrt(nn[0] * nn[0] + nn[1] * nn[1] + nn[2] * nn[2]);
|
||||
if (ln > 1e-8f) {
|
||||
nn[0] /= ln;
|
||||
nn[1] /= ln;
|
||||
nn[2] /= ln;
|
||||
}
|
||||
pw[i] = Vector3(p[0], p[1], p[2]);
|
||||
nw[i] = Vector3(nn[0], nn[1], nn[2]);
|
||||
uw[i] = Vector2(v.uv0[0], v.uv0[1]);
|
||||
}
|
||||
for (size_t t = 0; t < m.indices.size(); ++t) {
|
||||
iw[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_INDEX] = idx;
|
||||
cpu_mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arrays);
|
||||
}
|
||||
_apply_materials();
|
||||
}
|
||||
|
||||
String Metin2Model::get_info() const {
|
||||
return last_info;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user