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
+53
View File
@@ -12,6 +12,17 @@ namespace gr2 {
using Mat4 = std::array<float, 16>; // 行主序,Granny 原生。坐标约定见 docs/reference/steps/M0-gr2-reader.md「约定」
// granny_transform(SRT 分量原样;Flags: 1=HasPosition 2=HasOrientation 4=HasScaleShear)。
// 动画混合要在分量上做(GrannySampleModelAnimations 累加 SRT 再归一),不能在 4x4 上做。
struct Transform {
uint32_t flags = 0;
float position[3] = {0, 0, 0};
float orientation[4] = {0, 0, 0, 1}; // x,y,z,w
float scale_shear[9] = {1,0,0, 0,1,0, 0,0,1}; // 行主序 3x3
};
// BuildCompositeTransform4x4:上 3x3 = transpose(R3·SS3),平移在 elem 12..14。
Mat4 compose(const Transform& t);
// ── 骨架 ──────────────────────────────────────────────────────────────
struct Bone {
std::string name;
@@ -19,6 +30,7 @@ struct Bone {
Mat4 local_transform{}; // granny_transform SRT → 4x4(组合顺序照 granny_transform.cpp)
Mat4 inverse_world{}; // InverseWorld4x4,来自文件
uint32_t lt_flags = 0; // granny_transform.Flags:哪部分非单位
Transform local; // LocalTransform 的 SRT 分量(local_transform = compose(local))
};
struct Skeleton {
@@ -44,6 +56,14 @@ struct TriGroup {
int32_t tri_count;
};
// 顶点类型的一个成员(granny_data_type_definition 的平铺子集;type = granny_member_type 序数)。
struct VertexMember {
std::string name;
int32_t type = 0;
int32_t array_width = 0; // 0 => 1
uint32_t offset = 0; // 在顶点里的字节偏移(打包)
};
struct Mesh {
struct BoneBounds {
float min[3] = {}, max[3] = {};
@@ -63,6 +83,12 @@ struct Mesh {
// tri_groups[i].material_index 索引本表。
std::vector<std::string> material_textures;
bool rigid = false; // GrannyMeshIsRigid
std::vector<std::string> bone_binding_names; // granny_bone_binding.BoneName,与 bone_bounds 平行
std::vector<int32_t> material_bindings; // granny_mesh.MaterialBindings[i].Material → FileInfo::materials 索引(-1 = 空)
// 原始顶点:文件声明的顶点类型 + 按它打包的字节(Granny 运行时按名字做布局转换用)
std::vector<VertexMember> vertex_members;
uint32_t vertex_stride = 0;
std::vector<uint8_t> raw_vertices; // vertices.size() * vertex_stride
};
// ── 动画 ──────────────────────────────────────────────────────────────
@@ -82,6 +108,9 @@ struct Curve {
void eval(float t, float* out) const;
};
struct TextTrackEntry { float time_stamp = 0.0f; std::string text; };
struct TextTrack { std::string name; std::vector<TextTrackEntry> entries; };
struct BoneTrack {
std::string bone_name;
int32_t bone_index = -1; // 映射到 skeleton(未映射 = -1)
@@ -91,11 +120,17 @@ struct BoneTrack {
Curve position; // dim 3
Curve orientation; // dim 4(四元数 x,y,z,w)
Curve scale_shear; // dim 9(3x3)或 dim 3
// t 处的 SRT(缺的曲线取单位分量,flags 只标出有曲线的分量)。sample_local 的 4x4 = compose(这个)。
void sample(float t, Transform& out) const;
};
struct Animation {
std::string name;
float duration = 0.0f;
float time_step = 0.0f;
float oversampling = 1.0f;
int32_t track_group_count = 0;
std::vector<TextTrack> text_tracks; // TrackGroups[0].TextTracks
std::vector<BoneTrack> tracks;
// t 处每个 track 的局部 4x4(granny_transform 组合语义,行主序)。
// out 按 tracks 顺序;bone_index 见各 track。
@@ -103,9 +138,26 @@ struct Animation {
};
// 顶层 granny_material(driven by root Materials[])。name + 解析出的漫反射贴图。
// root Materials[] 之外、只经 Maps[] / mesh MaterialBindings 可达的子材质追加在后面。
struct MaterialMap {
std::string usage; // granny_material_map.Usage("Diffuse Color" 等)
int32_t material = -1; // FileInfo::materials 索引
};
struct Material {
std::string name;
std::string diffuse_texture; // granny_texture.FromFileName 原样
std::string texture; // 本材质自己的 Texture.FromFileName(空 = 无 Texture)
std::vector<MaterialMap> maps; // granny_material.Maps
int32_t two_sided = 0; // ExtendedData 的 Int32 "Two-sided"
bool has_two_sided = false;
};
// granny_model:Skeleton + InitialPlacement + MeshBindings。
struct Model {
std::string name;
int32_t skeleton = -1; // FileInfo::skeletons 索引
Transform initial_placement;
std::vector<int32_t> mesh_bindings; // FileInfo::meshes 索引(-1 = 未解析)
};
// ── FileInfo 汇总(T4)────────────────────────────────────────────────
@@ -115,6 +167,7 @@ struct FileInfo {
std::vector<Mesh> meshes;
std::vector<Animation> animations;
std::vector<Material> materials;
std::vector<Model> models;
uint32_t material_count = 0;
uint32_t texture_count = 0;
uint32_t model_count = 0;