// 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"); // ── 材质图:root Materials[] 先入表(索引 = 文件顺序),子材质 / 只经 mesh 可达的追加 ── mats_out = &out.materials; mat_refs.clear(); if (const TypeMember* mMa = find_member(rms, "Materials")) { auto refs = read_array_of_refs(root_obj, *mMa); for (Ref r : refs) intern_material(r, mMa->ref_type); } // ── 骨架(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)──────────────────────────────────────────────────── std::vector mesh_refs; // 与 out.meshes 平行(model.MeshBindings 关联用) 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)); mesh_refs.push_back(r); } } } // ── 模型:granny_model{String Name; Reference Skeleton; Transform InitialPlacement; // RefToArray MeshBindings(model_mesh_binding{Reference Mesh}); VariantRef ExtendedData} 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* mN = find_member(moms, "Name"); const TypeMember* mSk = find_member(moms, "Skeleton"); const TypeMember* mIP = find_member(moms, "InitialPlacement"); const TypeMember* mMB = find_member(moms, "MeshBindings"); auto same = [](Ref a, Ref b) { return a.section == b.section && a.offset == b.offset; }; for (Ref r : refs) { if (!r.valid()) continue; Model mo; if (mN) mo.name = rd_str(member_slot(r, *mN)); if (mSk) { Ref sref = read_reference(r, *mSk); for (size_t k = 0; k < skel_refs.size(); ++k) if (same(skel_refs[k], sref)) { mo.skeleton = int32_t(k); break; } } if (mIP) read_transform(member_slot(r, *mIP), mo.initial_placement); Ref bb; uint32_t bc = 0, bs = 0; if (mMB && read_ref_to_array(r, *mMB, bb, bc, bs) && bc && bs) { const auto& bms = parse_type(mMB->ref_type); const TypeMember* mMesh = find_member(bms, "Mesh"); for (uint32_t i = 0; i < bc && i < 4096; ++i) { int32_t idx = -1; if (mMesh) { Ref me = read_reference({bb.section, bb.offset + i * bs}, *mMesh); for (size_t k = 0; k < mesh_refs.size(); ++k) if (same(mesh_refs[k], me)) { idx = int32_t(k); break; } } mo.mesh_bindings.push_back(idx); } } out.models.push_back(std::move(mo)); } } // ── 动画(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)); } } mats_out = nullptr; return true; } } // namespace gr2