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
@@ -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;
|
||||
|
||||
+57
-41
@@ -98,11 +98,47 @@ bool File::Impl::extract_animation(Ref anim_obj, Ref anim_type, const Skeleton*
|
||||
out.name = rd_str(member_slot(anim_obj, *m));
|
||||
if (const TypeMember* m = find_member(ams, "Duration"))
|
||||
out.duration = rd_f32(member_slot(anim_obj, *m));
|
||||
if (const TypeMember* m = find_member(ams, "TimeStep"))
|
||||
out.time_step = rd_f32(member_slot(anim_obj, *m));
|
||||
if (const TypeMember* m = find_member(ams, "Oversampling"))
|
||||
out.oversampling = rd_f32(member_slot(anim_obj, *m));
|
||||
|
||||
const TypeMember* mTG = find_member(ams, "TrackGroups");
|
||||
if (!mTG) return true;
|
||||
auto groups = read_array_of_refs(anim_obj, *mTG);
|
||||
out.track_group_count = int32_t(groups.size());
|
||||
const auto& tgms = parse_type(mTG->ref_type);
|
||||
|
||||
// TrackGroups[0].TextTracks: RefToArray text_track{String Name; RefToArray Entries(
|
||||
// text_track_entry{Real32 TimeStamp; String Text})}(motion event 用)
|
||||
if (!groups.empty() && groups[0].valid())
|
||||
if (const TypeMember* mTx = find_member(tgms, "TextTracks")) {
|
||||
Ref xb; uint32_t xc = 0, xs = 0;
|
||||
if (read_ref_to_array(groups[0], *mTx, xb, xc, xs) && xc && xs) {
|
||||
const auto& xms = parse_type(mTx->ref_type);
|
||||
const TypeMember* mXN = find_member(xms, "Name");
|
||||
const TypeMember* mXE = find_member(xms, "Entries");
|
||||
for (uint32_t i = 0; i < xc && i < 4096; ++i) {
|
||||
Ref x = {xb.section, xb.offset + i * xs};
|
||||
TextTrack tt;
|
||||
if (mXN) tt.name = rd_str(member_slot(x, *mXN));
|
||||
Ref eb; uint32_t ec = 0, es = 0;
|
||||
if (mXE && read_ref_to_array(x, *mXE, eb, ec, es) && ec && es) {
|
||||
const auto& ems = parse_type(mXE->ref_type);
|
||||
const TypeMember* mTS = find_member(ems, "TimeStamp");
|
||||
const TypeMember* mTT = find_member(ems, "Text");
|
||||
for (uint32_t k = 0; k < ec && k < 65536; ++k) {
|
||||
Ref e = {eb.section, eb.offset + k * es};
|
||||
TextTrackEntry te;
|
||||
if (mTS) te.time_stamp = rd_f32(member_slot(e, *mTS));
|
||||
if (mTT) te.text = rd_str(member_slot(e, *mTT));
|
||||
tt.entries.push_back(std::move(te));
|
||||
}
|
||||
}
|
||||
out.text_tracks.push_back(std::move(tt));
|
||||
}
|
||||
}
|
||||
}
|
||||
const TypeMember* mTT = find_member(tgms, "TransformTracks");
|
||||
if (!mTT) return true;
|
||||
|
||||
@@ -225,49 +261,29 @@ void Curve::eval(float t, float* out) const {
|
||||
}
|
||||
}
|
||||
|
||||
// t 处每个 track 的局部 4x4(BuildCompositeTransform4x4 语义:C 上 3x3 = transpose(R3*SS3),
|
||||
// 平移在 elem 12..14)。position 缺省 (0,0,0),orientation 缺省单位四元数,scaleshear 缺省 I。
|
||||
void Animation::sample_local(float t, std::vector<Mat4>& out) const {
|
||||
out.assign(tracks.size(), Mat4{1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1});
|
||||
for (size_t ti = 0; ti < tracks.size(); ++ti) {
|
||||
const BoneTrack& tr = tracks[ti];
|
||||
float pos[3] = {0, 0, 0};
|
||||
float ori[4] = {0, 0, 0, 1};
|
||||
float ss[9] = {1,0,0, 0,1,0, 0,0,1};
|
||||
if (!tr.position.empty()) tr.position.eval(t, pos);
|
||||
if (!tr.orientation.empty()) tr.orientation.eval(t, ori);
|
||||
bool have_ss = false;
|
||||
if (!tr.scale_shear.empty()) {
|
||||
if (tr.scale_shear.dim == 9) { tr.scale_shear.eval(t, ss); have_ss = true; }
|
||||
else if (tr.scale_shear.dim == 3) {
|
||||
float s[3]; tr.scale_shear.eval(t, s);
|
||||
ss[0] = s[0]; ss[4] = s[1]; ss[8] = s[2]; have_ss = true;
|
||||
}
|
||||
// t 处的 SRT。position 缺省 (0,0,0),orientation 缺省单位四元数,scaleshear 缺省 I。
|
||||
void BoneTrack::sample(float t, Transform& out) const {
|
||||
out = Transform{};
|
||||
if (!position.empty()) { position.eval(t, out.position); out.flags |= 0x1; }
|
||||
if (!orientation.empty()) { orientation.eval(t, out.orientation); out.flags |= 0x2; }
|
||||
if (!scale_shear.empty()) {
|
||||
if (scale_shear.dim == 9) { scale_shear.eval(t, out.scale_shear); out.flags |= 0x4; }
|
||||
else if (scale_shear.dim == 3) {
|
||||
float s[3]; scale_shear.eval(t, s);
|
||||
out.scale_shear[0] = s[0]; out.scale_shear[4] = s[1]; out.scale_shear[8] = s[2];
|
||||
out.flags |= 0x4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// R3 (行主序) from quat
|
||||
const float xx = ori[0]*ori[0], yy = ori[1]*ori[1], zz = ori[2]*ori[2];
|
||||
const float xy = ori[0]*ori[1], xz = ori[0]*ori[2], yz = ori[1]*ori[2];
|
||||
const float wx = ori[3]*ori[0], wy = ori[3]*ori[1], wz = ori[3]*ori[2];
|
||||
float r3[9] = {
|
||||
1 - 2*(yy+zz), 2*(xy-wz), 2*(xz+wy),
|
||||
2*(xy+wz), 1 - 2*(xx+zz), 2*(yz-wx),
|
||||
2*(xz-wy), 2*(yz+wx), 1 - 2*(xx+yy)
|
||||
};
|
||||
float u3[9];
|
||||
if (have_ss) {
|
||||
for (int r = 0; r < 3; ++r)
|
||||
for (int c = 0; c < 3; ++c)
|
||||
u3[r*3+c] = r3[r*3+0]*ss[0*3+c] + r3[r*3+1]*ss[1*3+c] + r3[r*3+2]*ss[2*3+c];
|
||||
} else {
|
||||
for (int k = 0; k < 9; ++k) u3[k] = r3[k];
|
||||
}
|
||||
Mat4& m = out[ti];
|
||||
m = Mat4{};
|
||||
m[0] = u3[0]; m[1] = u3[3]; m[2] = u3[6]; m[3] = 0;
|
||||
m[4] = u3[1]; m[5] = u3[4]; m[6] = u3[7]; m[7] = 0;
|
||||
m[8] = u3[2]; m[9] = u3[5]; m[10] = u3[8]; m[11] = 0;
|
||||
m[12] = pos[0]; m[13] = pos[1]; m[14] = pos[2]; m[15] = 1;
|
||||
// t 处每个 track 的局部 4x4(BuildCompositeTransform4x4 语义:C 上 3x3 = transpose(R3*SS3),
|
||||
// 平移在 elem 12..14)。
|
||||
void Animation::sample_local(float t, std::vector<Mat4>& out) const {
|
||||
out.resize(tracks.size());
|
||||
Transform x;
|
||||
for (size_t ti = 0; ti < tracks.size(); ++ti) {
|
||||
tracks[ti].sample(t, x);
|
||||
out[ti] = compose(x);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+45
-11
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+29
-18
@@ -57,32 +57,43 @@ void m3_mul(const float a[9], const float b[9], float r[9]) {
|
||||
} // namespace
|
||||
|
||||
// granny_transform (68B: u32 Flags; f32 Position[3]; f32 Orientation[4]; f32 ScaleShear[3][3])
|
||||
// → 行主序 4x4,上 3x3 = transpose(U3),平移在 elem 12..14。BuildCompositeTransform4x4 语义。
|
||||
void File::Impl::transform_to_composite(Ref tf, Mat4& out, uint32_t& flags) const {
|
||||
void File::Impl::read_transform(Ref tf, Transform& out) const {
|
||||
out = Transform{};
|
||||
const uint8_t* p = at(tf, 68);
|
||||
out = Mat4{};
|
||||
out[0] = out[5] = out[10] = out[15] = 1.0f;
|
||||
flags = 0;
|
||||
if (!p) return;
|
||||
std::memcpy(&out.flags, p, 4);
|
||||
std::memcpy(out.position, p + 4, 12);
|
||||
std::memcpy(out.orientation, p + 16, 16);
|
||||
std::memcpy(out.scale_shear, p + 32, 36);
|
||||
}
|
||||
|
||||
auto f32 = [&](size_t off) { float v; std::memcpy(&v, p + off, 4); return v; };
|
||||
flags = rd_u32(tf);
|
||||
float pos[3] = { f32(4), f32(8), f32(12) };
|
||||
float ori[4] = { f32(16), f32(20), f32(24), f32(28) };
|
||||
float ss[9];
|
||||
for (int i = 0; i < 9; ++i) ss[i] = f32(32 + size_t(i) * 4);
|
||||
|
||||
// → 行主序 4x4,上 3x3 = transpose(U3),平移在 elem 12..14。BuildCompositeTransform4x4 语义。
|
||||
Mat4 compose(const Transform& t) {
|
||||
float q3[9];
|
||||
quat_to_m3(ori, q3);
|
||||
quat_to_m3(t.orientation, q3);
|
||||
float u3[9];
|
||||
if (flags & 0x4 /* HasScaleShear */) m3_mul(q3, ss, u3);
|
||||
if (t.flags & 0x4 /* HasScaleShear */) m3_mul(q3, t.scale_shear, u3);
|
||||
else std::memcpy(u3, q3, sizeof u3);
|
||||
|
||||
Mat4 out{};
|
||||
// C 的上 3x3 = transpose(U3)
|
||||
out[0] = u3[0]; out[1] = u3[3]; out[2] = u3[6]; out[3] = 0;
|
||||
out[4] = u3[1]; out[5] = u3[4]; out[6] = u3[7]; out[7] = 0;
|
||||
out[8] = u3[2]; out[9] = u3[5]; out[10] = u3[8]; out[11] = 0;
|
||||
out[12] = pos[0]; out[13] = pos[1]; out[14] = pos[2]; out[15] = 1;
|
||||
out[12] = t.position[0]; out[13] = t.position[1]; out[14] = t.position[2]; out[15] = 1;
|
||||
return out;
|
||||
}
|
||||
|
||||
void File::Impl::transform_to_composite(Ref tf, Mat4& out, uint32_t& flags) const {
|
||||
Transform t;
|
||||
read_transform(tf, t);
|
||||
if (!at(tf, 68)) {
|
||||
out = Mat4{1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1};
|
||||
flags = 0;
|
||||
return;
|
||||
}
|
||||
out = compose(t);
|
||||
flags = t.flags;
|
||||
}
|
||||
|
||||
// skeleton: {String Name; RefToArray Bones(bone[]); int32 LODType; VariantRef ExtendedData}
|
||||
@@ -118,9 +129,9 @@ bool File::Impl::extract_skeleton(Ref skel_obj, Ref skel_type, Skeleton& out) {
|
||||
Bone& bo = out.bones[i];
|
||||
if (mName) bo.name = rd_str(member_slot(b, *mName));
|
||||
if (mParent) bo.parent = rd_i32(member_slot(b, *mParent));
|
||||
uint32_t flags = 0;
|
||||
transform_to_composite(member_slot(b, *mLocal), bo.local_transform, flags);
|
||||
bo.lt_flags = flags;
|
||||
read_transform(member_slot(b, *mLocal), bo.local);
|
||||
bo.local_transform = compose(bo.local);
|
||||
bo.lt_flags = bo.local.flags;
|
||||
const uint8_t* iw = at(member_slot(b, *mInvW), 64);
|
||||
if (iw) for (int k = 0; k < 16; ++k) std::memcpy(&bo.inverse_world[k], iw + k * 4, 4);
|
||||
else bo.inverse_world = Mat4{};
|
||||
|
||||
@@ -125,6 +125,15 @@ struct File::Impl {
|
||||
|
||||
// granny_transform (68B) → 行主序 4x4(BuildCompositeTransform4x4 语义)
|
||||
void transform_to_composite(Ref tf, Mat4& out, uint32_t& flags) const;
|
||||
void read_transform(Ref tf, Transform& out) const; // 68B → SRT 分量
|
||||
|
||||
// granny_material 图:按对象 Ref 去重,缺的追加到 mats_out(子材质 / 只经 mesh 可达的材质)。
|
||||
// 返回 FileInfo::materials 索引;无效 / 递归太深 = -1。
|
||||
int32_t intern_material(Ref mat_obj, Ref mat_type, int depth = 0);
|
||||
std::vector<Material>* mats_out = nullptr;
|
||||
std::vector<Ref> mat_refs; // 与 *mats_out 平行
|
||||
// ExtendedData(VariantRef = {type ptr; object ptr})里取名为 name 的 Int32。
|
||||
bool extended_i32(Ref variant_slot, const char* name, int32_t& out);
|
||||
|
||||
std::vector<Skeleton> skels_cache; // self-check 复用
|
||||
std::vector<Ref> skel_refs; // 与 skels_cache 平行:各 skeleton 对象的 Ref(model 关联用)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <vector>
|
||||
|
||||
int main() {
|
||||
// ScaleShearCurve uses 9 values per control point. A quadratic span reads
|
||||
@@ -24,5 +25,32 @@ int main() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// BoneTrack::sample fills SRT components and flags; sample_local = compose(sample).
|
||||
gr2::BoneTrack track;
|
||||
track.position.degree = 1;
|
||||
track.position.dim = 3;
|
||||
track.position.knots = {0.0f, 1.0f};
|
||||
track.position.controls = {0.0f, 0.0f, 0.0f, 2.0f, 4.0f, 6.0f};
|
||||
track.orientation.degree = 0;
|
||||
track.orientation.dim = 4;
|
||||
track.orientation.knots = {0.0f};
|
||||
track.orientation.controls = {0.0f, 0.0f, 0.70710678f, 0.70710678f}; // 90 deg about Z
|
||||
gr2::Transform x;
|
||||
track.sample(0.5f, x);
|
||||
if (x.flags != 3 || std::fabs(x.position[0] - 1.0f) > 1e-5f || std::fabs(x.position[2] - 3.0f) > 1e-5f) {
|
||||
std::fprintf(stderr, "BoneTrack::sample flags=%u pos=%f,%f,%f\n", x.flags, x.position[0], x.position[1], x.position[2]);
|
||||
return 1;
|
||||
}
|
||||
gr2::Animation anim;
|
||||
anim.tracks.push_back(track);
|
||||
std::vector<gr2::Mat4> local;
|
||||
anim.sample_local(0.5f, local);
|
||||
const gr2::Mat4 m = gr2::compose(x);
|
||||
// Row-major composite: row 0 is the rotated X axis (0,1,0); translation in 12..14.
|
||||
if (local.size() != 1 || local[0] != m || std::fabs(m[1] - 1.0f) > 1e-5f || std::fabs(m[12] - 1.0f) > 1e-5f) {
|
||||
std::fprintf(stderr, "compose mismatch m[1]=%f m[12]=%f\n", m[1], m[12]);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user