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:
@@ -10,6 +10,18 @@
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
|
||||
#include <godot_cpp/classes/mesh_instance3d.hpp>
|
||||
#include <godot_cpp/classes/os.hpp>
|
||||
#include <godot_cpp/classes/skin.hpp>
|
||||
|
||||
static double _tdiff(const godot::Transform3D &a, const godot::Transform3D &b) {
|
||||
double d = 0.0;
|
||||
for (int r = 0; r < 3; ++r)
|
||||
for (int c = 0; c < 3; ++c)
|
||||
d = godot::Math::max(d, (double)godot::Math::abs(a.basis[r][c] - b.basis[r][c]));
|
||||
return godot::Math::max(d, (double)(a.origin - b.origin).length());
|
||||
}
|
||||
|
||||
using namespace godot;
|
||||
|
||||
namespace mtgodot {
|
||||
@@ -95,6 +107,31 @@ void Metin2AnimPlayer::reload() {
|
||||
anim_file = std::move(f);
|
||||
const gr2::Animation &an = anim_file->file_info().animations[0];
|
||||
duration = an.duration;
|
||||
|
||||
// Diagnostic: how many anim tracks map to a model-skeleton bone by name?
|
||||
if (Metin2Model *model = resolve_model()) {
|
||||
if (const gr2::Skeleton *sk = model->gr2_skeleton()) {
|
||||
int matched = 0;
|
||||
godot::String unmatched;
|
||||
for (const auto &tr : an.tracks) {
|
||||
bool hit = false;
|
||||
for (const auto &b : sk->bones) {
|
||||
if (b.name == tr.bone_name) {
|
||||
hit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hit) {
|
||||
++matched;
|
||||
} else if (unmatched.length() < 300) {
|
||||
unmatched += String(tr.bone_name.c_str()) + " ";
|
||||
}
|
||||
}
|
||||
UtilityFunctions::print(vformat(
|
||||
"[Metin2AnimPlayer] track->bone match: %d/%d (skel bones=%d) unmatched: %s",
|
||||
matched, (int)an.tracks.size(), (int)sk->bones.size(), unmatched));
|
||||
}
|
||||
}
|
||||
last_info = vformat("anim '%s' dur=%.3f tracks=%d anims=%d",
|
||||
String(an.name.c_str()), duration, (int)an.tracks.size(),
|
||||
(int)anim_file->file_info().animations.size());
|
||||
@@ -127,9 +164,43 @@ void Metin2AnimPlayer::apply_pose(double t) {
|
||||
|
||||
gr2::sample_pose(*sk, an, (float)tt, world_buf, skin_buf);
|
||||
|
||||
const int n = (int)std::min<size_t>(world_buf.size(), (size_t)skel->get_bone_count());
|
||||
if (godot::OS::get_singleton()->get_environment("MTGODOT_CPUSKIN") == "1") {
|
||||
model->enable_cpu_skin(true);
|
||||
model->cpu_skin(skin_buf);
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert gr2 global poses -> Godot *local* bone poses and set those.
|
||||
// (Setting global poses directly hits a parent-ordering hazard in
|
||||
// Skeleton3D::set_bone_global_pose that detaches head/upper-body meshes
|
||||
// once bones leave the bind pose.)
|
||||
const int n = (int)std::min<size_t>(world_buf.size(),
|
||||
std::min<size_t>(sk->bones.size(), (size_t)skel->get_bone_count()));
|
||||
for (int i = 0; i < n; ++i) {
|
||||
skel->set_bone_global_pose(i, gr2_to_godot(world_buf[i]));
|
||||
const godot::Transform3D t_i = gr2_to_godot(world_buf[i]);
|
||||
const int parent = sk->bones[i].parent;
|
||||
if (parent < 0 || parent >= n) {
|
||||
skel->set_bone_pose(i, t_i);
|
||||
} else {
|
||||
skel->set_bone_pose(i, gr2_to_godot(world_buf[parent]).affine_inverse() * t_i);
|
||||
}
|
||||
}
|
||||
|
||||
if (!verified_ && OS::get_singleton()->get_environment("MTGODOT_VERIFY") == "1") {
|
||||
verified_ = true;
|
||||
skel->force_update_all_bone_transforms();
|
||||
double d_pose = 0.0;
|
||||
int w_pose = -1;
|
||||
for (int i = 0; i < n; ++i) {
|
||||
double dp = _tdiff(skel->get_bone_global_pose(i), gr2_to_godot(world_buf[i]));
|
||||
if (dp > d_pose) {
|
||||
d_pose = dp;
|
||||
w_pose = i;
|
||||
}
|
||||
}
|
||||
UtilityFunctions::print(vformat(
|
||||
"[Metin2AnimPlayer] verify: max |global_pose - conv(world)| = %.6f @ bone %d '%s'",
|
||||
d_pose, w_pose, w_pose >= 0 ? String(sk->bones[w_pose].name.c_str()) : String()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user