// M0 T4 —— FileInfo 汇总(驱动 T5 骨架 / T6 网格 / T7a 曲线分类)。 // 见 docs/reference/steps/M0-gr2-reader.md // 规格:granny_file_info.cpp (FileInfoType) / granny_animation.cpp (AnimationType) #include "internal.h" #include #include namespace gr2 { using detail::Ref; using detail::TypeMember; bool File::Impl::build_fileinfo(FileInfo& out, LoadError* err) { if (!root_obj.valid() || !root_type.valid()) { if (err) *err = {"fileinfo", "no root object / type"}; return false; } const auto& rms = parse_type(root_type); if (rms.empty()) { if (err) *err = {"fileinfo", "root type has no members"}; return false; } auto count_of = [&](const char* name) -> uint32_t { const TypeMember* m = find_member(rms, name); if (!m) return 0; int32_t c = rd_i32(member_slot(root_obj, *m)); return (c > 0 && c < (1 << 24)) ? uint32_t(c) : 0; }; if (const TypeMember* m = find_member(rms, "FromFileName")) out.from_file_name = rd_str(member_slot(root_obj, *m)); out.texture_count = count_of("Textures"); out.material_count = count_of("Materials"); out.model_count = count_of("Models"); // ── 顶层材质表(调试 / 备用查找)──────────────────────────────────── if (const TypeMember* mMa = find_member(rms, "Materials")) { auto refs = read_array_of_refs(root_obj, *mMa); const auto& mams = parse_type(mMa->ref_type); const TypeMember* mMN = find_member(mams, "Name"); for (Ref r : refs) { if (!r.valid()) continue; Material mat; if (mMN) mat.name = rd_str(member_slot(r, *mMN)); mat.diffuse_texture = material_texture_name(r, mMa->ref_type, 0); out.materials.push_back(std::move(mat)); } } // ── 骨架(T5)──────────────────────────────────────────────────── skels_cache.clear(); skel_refs.clear(); if (const TypeMember* mSk = find_member(rms, "Skeletons")) { auto refs = read_array_of_refs(root_obj, *mSk); for (Ref r : refs) { Skeleton sk; if (extract_skeleton(r, mSk->ref_type, sk)) { skels_cache.push_back(std::move(sk)); skel_refs.push_back(r); } } } // ── 模型 → 骨架关联:把 model.InitialPlacement 写进对应 skeleton ───── // (很多 Metin2 骨架把 model 空间偏移放在 InitialPlacement 而非 root 骨骼的 // LocalTransform;不带上它,bind pose 自洽检查会对这些文件失败) if (const TypeMember* mMo = find_member(rms, "Models")) { auto refs = read_array_of_refs(root_obj, *mMo); const auto& moms = parse_type(mMo->ref_type); const TypeMember* mSkRef = find_member(moms, "Skeleton"); const TypeMember* mIP = find_member(moms, "InitialPlacement"); for (Ref r : refs) { if (!r.valid() || !mSkRef) continue; Ref sref = read_reference(r, *mSkRef); for (size_t k = 0; k < skel_refs.size(); ++k) { if (skel_refs[k].section == sref.section && skel_refs[k].offset == sref.offset) { if (mIP) { uint32_t fl = 0; transform_to_composite(member_slot(r, *mIP), skels_cache[k].initial_placement, fl); } break; } } } } out.skeletons = skels_cache; // ── 网格(T6)──────────────────────────────────────────────────── if (const TypeMember* mMe = find_member(rms, "Meshes")) { auto refs = read_array_of_refs(root_obj, *mMe); for (Ref r : refs) { Mesh me; bool ok = extract_mesh(r, mMe->ref_type, skels_cache, me); if (getenv("GR2_DEBUG")) fprintf(stderr, "[gr2] mesh ref (%d,%u) -> %s name=%s verts=%zu idx=%zu\n", r.section, r.offset, ok ? "OK" : "FAIL", me.name.c_str(), me.vertices.size(), me.indices.size()); if (ok) out.meshes.push_back(std::move(me)); } } // ── 动画(T7a:名字 + 时长 + 每 track 的曲线子类型)───────────────── if (const TypeMember* mAn = find_member(rms, "Animations")) { auto refs = read_array_of_refs(root_obj, *mAn); const Skeleton* sk0 = skels_cache.empty() ? nullptr : &skels_cache[0]; for (Ref r : refs) { if (!r.valid()) continue; Animation an; if (extract_animation(r, mAn->ref_type, sk0, an)) out.animations.push_back(std::move(an)); } } return true; } } // namespace gr2