Implement the Granny SDK 2.11 API the client uses on top of libgr2 (platform/EterGrnLib/GrannyRuntime.cpp, shim granny.h declared from the SDK header): file sections, type-definition vertex conversion, material textures by Usage, mesh/bone bindings, deformer, control clock/loop/ease and weighted SRT sampling. Port EterGrnLib (LODController, Material, Mesh, Model, ModelInstance*, Motion, Thing, ThingInstance, Util) plus EterLib GrpObjectInstance/GrpCollisionObject/CollisionData verbatim and drop their platform skeletons and pending stand-ins; register the gr2 resource factory; vertex/index buffers are CPU-side until the 2V2-e render adapter. app.UpdateGame/RenderGame now run 40250's bodies, reaching the CPythonApplication members through their singletons (the platform has no app object); CPythonGraphic joins GameSingletons in member order, and GetMousePosition reads the host cursor. port.login_flow asserts the main instance's PART_MAIN model has meshes, its diffuse image loaded and the WAIT motion moves the bones between frames; offline and live pass, port_gate macos PASS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
154 lines
6.8 KiB
C++
154 lines
6.8 KiB
C++
// 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 <cstdio>
|
|
#include <cstdlib>
|
|
|
|
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<Ref> 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
|