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:
co-authored by
Claude Opus 5.5
parent
5266b00c6e
commit
668f48d6ec
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user