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
+66
View File
@@ -134,6 +134,13 @@ bool File::Impl::extract_mesh(Ref mesh_obj, Ref mesh_type,
if ((uint64_t)vdata.offset + (uint64_t)vcount * stride > sections[vdata.section].data.size())
return false;
out.vertex_stride = stride;
for (const auto& m : vfmt)
out.vertex_members.push_back({m.name, m.type, m.array_width, m.offset});
{
const uint8_t* vb = sections[vdata.section].data.data() + vdata.offset;
out.raw_vertices.assign(vb, vb + size_t(vcount) * stride);
}
out.vertices.resize(vcount);
for (int32_t i = 0; i < vcount; ++i) {
const uint8_t* base = sections[vdata.section].data.data() + vdata.offset + size_t(i) * stride;
@@ -220,11 +227,14 @@ bool File::Impl::extract_mesh(Ref mesh_obj, Ref mesh_type,
for (uint32_t i = 0; i < mc; ++i) {
Ref e = {mb.section, mb.offset + i * ms};
std::string tn;
int32_t mi = -1;
if (mMat) {
Ref mat = read_reference(e, *mMat);
tn = material_texture_name(mat, mMat->ref_type, 0);
mi = intern_material(mat, mMat->ref_type);
}
out.material_textures.push_back(std::move(tn));
out.material_bindings.push_back(mi);
}
}
}
@@ -269,6 +279,7 @@ bool File::Impl::extract_mesh(Ref mesh_obj, Ref mesh_type,
if (dangling == 0) break;
}
out.bone_bindings = std::move(best);
out.bone_binding_names = std::move(names);
}
}
return true;
@@ -310,4 +321,59 @@ std::string File::Impl::material_texture_name(Ref mat_obj, Ref mat_type, int dep
return {};
}
// granny_material {String Name; RefToArray Maps(material_map{String Usage; Reference Material});
// Reference Texture; VariantRef ExtendedData}
int32_t File::Impl::intern_material(Ref mat_obj, Ref mat_type, int depth) {
if (!mats_out || !mat_obj.valid() || !mat_type.valid() || depth > 8) return -1;
for (size_t k = 0; k < mat_refs.size(); ++k)
if (mat_refs[k].section == mat_obj.section && mat_refs[k].offset == mat_obj.offset)
return int32_t(k);
const int32_t idx = int32_t(mats_out->size());
mats_out->emplace_back();
mat_refs.push_back(mat_obj);
const auto& mms = parse_type(mat_type);
Material mat;
if (const TypeMember* m = find_member(mms, "Name")) mat.name = rd_str(member_slot(mat_obj, *m));
mat.diffuse_texture = material_texture_name(mat_obj, mat_type, 0);
if (const TypeMember* mTex = find_member(mms, "Texture")) {
Ref tex = read_reference(mat_obj, *mTex);
if (tex.valid())
if (const TypeMember* mFN = find_member(parse_type(mTex->ref_type), "FromFileName"))
mat.texture = rd_str(member_slot(tex, *mFN));
}
if (const TypeMember* mE = find_member(mms, "ExtendedData"))
mat.has_two_sided = extended_i32(member_slot(mat_obj, *mE), "Two-sided", mat.two_sided);
if (const TypeMember* mMaps = find_member(mms, "Maps")) {
Ref b; uint32_t c = 0, s = 0;
if (read_ref_to_array(mat_obj, *mMaps, b, c, s) && c && s) {
const auto& mapT = parse_type(mMaps->ref_type);
const TypeMember* mU = find_member(mapT, "Usage");
const TypeMember* mSub = find_member(mapT, "Material");
if (!mSub) mSub = find_member(mapT, "Map");
for (uint32_t i = 0; i < c && i < 64; ++i) {
Ref e = {b.section, b.offset + i * s};
MaterialMap mm;
if (mU) mm.usage = rd_str(member_slot(e, *mU));
if (mSub) mm.material = intern_material(read_reference(e, *mSub), mSub->ref_type, depth + 1);
mat.maps.push_back(std::move(mm));
}
}
}
(*mats_out)[idx] = std::move(mat);
return idx;
}
// VariantRef 在 32 位文件里 = {ptr Type; ptr Object}。
bool File::Impl::extended_i32(Ref slot, const char* name, int32_t& out) {
Ref type = follow(slot);
Ref obj = follow({slot.section, slot.offset + 4});
if (!type.valid() || !obj.valid()) return false;
const TypeMember* m = find_member(parse_type(type), name);
if (!m || (m->type != detail::MT_Int32 && m->type != detail::MT_UInt32)) return false;
out = rd_i32(member_slot(obj, *m));
return true;
}
} // namespace gr2