port 2V2-d.2: Granny runtime on libgr2, main character model animates

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>
This commit is contained in:
shenlei
2026-09-23 19:43:27 +09:00
co-authored by Claude Opus 5.5
parent 5266b00c6e
commit 668f48d6ec
74 changed files with 11404 additions and 2356 deletions
+45 -11
View File
@@ -35,18 +35,12 @@ bool File::Impl::build_fileinfo(FileInfo& out, LoadError* err) {
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);
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));
}
for (Ref r : refs) intern_material(r, mMa->ref_type);
}
// ── 骨架(T5)────────────────────────────────────────────────────
@@ -88,6 +82,7 @@ bool File::Impl::build_fileinfo(FileInfo& out, LoadError* err) {
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) {
@@ -97,7 +92,45 @@ bool File::Impl::build_fileinfo(FileInfo& out, LoadError* err) {
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));
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));
}
}
@@ -113,6 +146,7 @@ bool File::Impl::build_fileinfo(FileInfo& out, LoadError* err) {
}
}
mats_out = nullptr;
return true;
}