314 lines
15 KiB
C++
314 lines
15 KiB
C++
// M0 T6 —— 顶点 / 索引 / 三角组 / BoneBindings。见 docs/reference/steps/M0-gr2-reader.md
|
||
// 规格:granny_mesh.cpp (MeshType/BoneBindingType) / granny_vertex_data.cpp (VertexDataType
|
||
// + 各顶点格式) / granny_tri_topology.cpp (TriTopologyType/TriMaterialGroupType)
|
||
// 顶点格式是自描述的(Vertices 是 ReferenceToVariantArray,带 data_type_definition*),
|
||
// 按成员名(Position/Normal/TextureCoordinates0/1/BoneWeights/BoneIndices)取分量,不硬编码布局。
|
||
#include "internal.h"
|
||
#include <cmath>
|
||
#include <cstdio>
|
||
#include <cstdlib>
|
||
#include <cstring>
|
||
|
||
#define MDBG(...) do { if (getenv("GR2_DEBUG")) fprintf(stderr, "[mesh] " __VA_ARGS__); } while (0)
|
||
|
||
namespace gr2 {
|
||
|
||
using detail::Ref;
|
||
using detail::TypeMember;
|
||
|
||
namespace {
|
||
|
||
// member_type → (元素是浮点?, 元素字节数, 是否归一化整型)
|
||
struct Elem { bool is_float; int bytes; bool normalized; bool is_signed; };
|
||
Elem elem_of(int mt) {
|
||
switch (mt) {
|
||
case detail::MT_Real32: return {true, 4, false, true};
|
||
case detail::MT_Real16: return {false, 2, false, true}; // 半精度,少见,按需扩展
|
||
case detail::MT_Int8: return {false, 1, false, true};
|
||
case detail::MT_UInt8: return {false, 1, false, false};
|
||
case detail::MT_BinormInt8: return {false, 1, true, true};
|
||
case detail::MT_NormUInt8: return {false, 1, true, false};
|
||
case detail::MT_Int16: return {false, 2, false, true};
|
||
case detail::MT_UInt16: return {false, 2, false, false};
|
||
case detail::MT_BinormInt16: return {false, 2, true, true};
|
||
case detail::MT_NormUInt16: return {false, 2, true, false};
|
||
case detail::MT_Int32: return {false, 4, false, true};
|
||
case detail::MT_UInt32: return {false, 4, false, false};
|
||
default: return {false, 0, false, false};
|
||
}
|
||
}
|
||
|
||
float read_scalar(const uint8_t* p, const Elem& e) {
|
||
switch (e.bytes) {
|
||
case 4:
|
||
if (e.is_float) { float v; std::memcpy(&v, p, 4); return v; }
|
||
if (e.is_signed) { int32_t v; std::memcpy(&v, p, 4); return float(v); }
|
||
{ uint32_t v; std::memcpy(&v, p, 4); return float(v); }
|
||
case 2: {
|
||
if (e.is_signed) { int16_t v; std::memcpy(&v, p, 2); return e.normalized ? v / 32767.0f : float(v); }
|
||
uint16_t v; std::memcpy(&v, p, 2); return e.normalized ? v / 65535.0f : float(v);
|
||
}
|
||
case 1: {
|
||
if (e.is_signed) { int8_t v = (int8_t)p[0]; return e.normalized ? v / 127.0f : float(v); }
|
||
return e.normalized ? p[0] / 255.0f : float(p[0]);
|
||
}
|
||
default: return 0.0f;
|
||
}
|
||
}
|
||
|
||
} // namespace
|
||
|
||
// mesh: {String Name; Reference PrimaryVertexData; RefToArray MorphTargets;
|
||
// Reference PrimaryTopology; RefToArray MaterialBindings; RefToArray BoneBindings; ...}
|
||
bool File::Impl::extract_mesh(Ref mesh_obj, Ref mesh_type,
|
||
const std::vector<Skeleton>& skels, Mesh& out) {
|
||
if (!mesh_obj.valid() || !mesh_type.valid()) return false;
|
||
const auto& mms = parse_type(mesh_type);
|
||
if (getenv("GR2_DEBUG")) {
|
||
fprintf(stderr, "[mesh] mesh_type=(%d,%u) members=%zu:\n",
|
||
mesh_type.section, mesh_type.offset, mms.size());
|
||
for (auto& m : mms)
|
||
fprintf(stderr, "[mesh] %-20s type=%d aw=%d off=%u size=%u reftype=(%d,%u)\n",
|
||
m.name.c_str(), m.type, m.array_width, m.offset, m.size,
|
||
m.ref_type.section, m.ref_type.offset);
|
||
}
|
||
|
||
if (const TypeMember* mName = find_member(mms, "Name"))
|
||
out.name = rd_str(member_slot(mesh_obj, *mName));
|
||
|
||
// ── 顶点 ──────────────────────────────────────────────────────────
|
||
const TypeMember* mVD = find_member(mms, "PrimaryVertexData");
|
||
if (!mVD) { MDBG("no PrimaryVertexData member\n"); return false; }
|
||
MDBG("PrimaryVertexData m.offset=%u ref_type=(%d,%u)\n",
|
||
mVD->offset, mVD->ref_type.section, mVD->ref_type.offset);
|
||
Ref vd = read_reference(mesh_obj, *mVD);
|
||
if (!vd.valid()) { MDBG("PrimaryVertexData ref -> NULL (slot %d,%u)\n",
|
||
member_slot(mesh_obj, *mVD).section, member_slot(mesh_obj, *mVD).offset); return false; }
|
||
|
||
const auto& vdms = parse_type(mVD->ref_type);
|
||
const TypeMember* mVerts = find_member(vdms, "Vertices");
|
||
if (!mVerts) { MDBG("no Vertices member (vdms=%zu)\n", vdms.size()); return false; }
|
||
// ReferenceToVariantArray on-disk (32-bit): {def* Type; int32 Count; void* Ptr}
|
||
// (granny_data_type_definition.cpp: Ptr32 + int32 + Ptr32)
|
||
Ref vslot = member_slot(vd, *mVerts);
|
||
Ref vtype = follow({vslot.section, vslot.offset + 0});
|
||
int32_t vcount = rd_i32({vslot.section, vslot.offset + 4});
|
||
Ref vdata = follow({vslot.section, vslot.offset + 8});
|
||
MDBG("Vertices count=%d vtype=(%d,%u) vdata=(%d,%u)\n",
|
||
vcount, vtype.section, vtype.offset, vdata.section, vdata.offset);
|
||
if (vcount <= 0 || vcount > (1 << 24) || !vtype.valid() || !vdata.valid()) return false;
|
||
|
||
const auto& vfmt = parse_type(vtype);
|
||
uint32_t stride = 0;
|
||
for (const auto& m : vfmt) stride += m.size;
|
||
MDBG("vfmt members=%zu stride=%u\n", vfmt.size(), stride);
|
||
if (stride == 0) return false;
|
||
|
||
struct Comp { const TypeMember* m; Elem e; };
|
||
auto comp = [&](const char* n) -> Comp {
|
||
const TypeMember* m = find_member(vfmt, n);
|
||
if (!m) return {nullptr, {}};
|
||
return {m, elem_of(m->type)};
|
||
};
|
||
Comp cPos = comp("Position");
|
||
Comp cNrm = comp("Normal");
|
||
Comp cUv0 = comp("TextureCoordinates0");
|
||
Comp cUv1 = comp("TextureCoordinates1");
|
||
Comp cBW = comp("BoneWeights");
|
||
Comp cBI = comp("BoneIndices");
|
||
|
||
const bool skinned = (cBW.m && cBI.m);
|
||
out.rigid = !skinned;
|
||
if (skinned) {
|
||
out.source_bone_weight_slots = uint32_t(cBW.m->array_width > 0 ? cBW.m->array_width : 1);
|
||
out.source_bone_index_slots = (cBI.e.bytes == 4 && cBI.m->array_width <= 1)
|
||
? 4u
|
||
: uint32_t(cBI.m->array_width > 0 ? cBI.m->array_width : 1);
|
||
}
|
||
if (skinned && cUv1.m) out.kind = VertexKind::PNT3322_Skinned;
|
||
else if (skinned) out.kind = VertexKind::PNT332_Skinned;
|
||
else if (cUv1.m) out.kind = VertexKind::PNT3322;
|
||
else if (cPos.m && cNrm.m) out.kind = VertexKind::PNT332;
|
||
else out.kind = VertexKind::Unknown;
|
||
|
||
if ((uint64_t)vdata.offset + (uint64_t)vcount * stride > sections[vdata.section].data.size())
|
||
return false;
|
||
|
||
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;
|
||
Vertex& v = out.vertices[i];
|
||
auto rd = [&](const Comp& c, float* dst, int n) {
|
||
if (!c.m || !c.e.bytes) return;
|
||
int aw = c.m->array_width <= 0 ? 1 : c.m->array_width;
|
||
n = n < aw ? n : aw;
|
||
for (int k = 0; k < n; ++k)
|
||
dst[k] = read_scalar(base + c.m->offset + size_t(k) * c.e.bytes, c.e);
|
||
};
|
||
rd(cPos, v.pos, 3);
|
||
rd(cNrm, v.normal, 3);
|
||
rd(cUv0, v.uv0, 2);
|
||
rd(cUv1, v.uv1, 2);
|
||
if (skinned) {
|
||
float w[4] = {0,0,0,0}, bi[4] = {0,0,0,0};
|
||
rd(cBW, w, 4);
|
||
// BoneIndices 可能是 UInt8[4] 或 UInt32[N](PWN* 系列是单 uint32 packed 4×u8)
|
||
if (cBI.e.bytes == 1) {
|
||
rd(cBI, bi, 4);
|
||
} else if (cBI.e.bytes == 4 && cBI.e.is_float == false) {
|
||
uint32_t packed;
|
||
std::memcpy(&packed, base + cBI.m->offset, 4);
|
||
for (int k = 0; k < 4; ++k) bi[k] = float((packed >> (k * 8)) & 0xff);
|
||
}
|
||
for (int k = 0; k < 4; ++k) {
|
||
v.bone_index[k] = uint8_t(std::lround(bi[k]));
|
||
float wn = cBW.e.normalized ? w[k] : w[k]; // 已在 read_scalar 归一
|
||
v.bone_weight[k] = uint8_t(std::lround(std::fmin(std::fmax(wn, 0.0f), 1.0f) * 255.0f));
|
||
}
|
||
}
|
||
}
|
||
|
||
// ── 索引 + 三角组 ────────────────────────────────────────────────
|
||
const TypeMember* mTopo = find_member(mms, "PrimaryTopology");
|
||
if (mTopo) {
|
||
Ref tt = read_reference(mesh_obj, *mTopo);
|
||
if (tt.valid()) {
|
||
const auto& tms = parse_type(mTopo->ref_type);
|
||
if (const TypeMember* mG = find_member(tms, "Groups")) {
|
||
Ref gb; uint32_t gc = 0, gs = 0;
|
||
if (read_ref_to_array(tt, *mG, gb, gc, gs) && gc) {
|
||
const auto& gmT = parse_type(mG->ref_type);
|
||
const TypeMember* mMI = find_member(gmT, "MaterialIndex");
|
||
const TypeMember* mTF = find_member(gmT, "TriFirst");
|
||
const TypeMember* mTC = find_member(gmT, "TriCount");
|
||
for (uint32_t i = 0; i < gc; ++i) {
|
||
Ref g = {gb.section, gb.offset + i * gs};
|
||
TriGroup tg{};
|
||
if (mMI) tg.material_index = rd_i32(member_slot(g, *mMI));
|
||
if (mTF) tg.tri_first = rd_i32(member_slot(g, *mTF));
|
||
if (mTC) tg.tri_count = rd_i32(member_slot(g, *mTC));
|
||
out.tri_groups.push_back(tg);
|
||
}
|
||
}
|
||
}
|
||
auto load_idx = [&](const char* name, int elem_bytes) -> bool {
|
||
const TypeMember* mI = find_member(tms, name);
|
||
if (!mI) return false;
|
||
Ref ib; uint32_t ic = 0, is = 0;
|
||
if (!read_ref_to_array(tt, *mI, ib, ic, is) || ic == 0) return false;
|
||
const uint8_t* p = at(ib, size_t(ic) * elem_bytes);
|
||
if (!p) return false;
|
||
out.indices.resize(ic);
|
||
for (uint32_t i = 0; i < ic; ++i) {
|
||
if (elem_bytes == 2) { uint16_t v; std::memcpy(&v, p + i*2, 2); out.indices[i] = v; }
|
||
else { uint32_t v; std::memcpy(&v, p + i*4, 4); out.indices[i] = v; }
|
||
}
|
||
return true;
|
||
};
|
||
if (!load_idx("Indices", 4)) load_idx("Indices16", 2);
|
||
}
|
||
}
|
||
|
||
// ── MaterialBindings:与 tri_group.material_index 平行的贴图名表 ───
|
||
// granny_mesh.MaterialBindings = RefToArray of {Reference Material}。
|
||
if (const TypeMember* mMB = find_member(mms, "MaterialBindings")) {
|
||
Ref mb; uint32_t mc = 0, ms = 0;
|
||
if (read_ref_to_array(mesh_obj, *mMB, mb, mc, ms) && mc) {
|
||
const auto& bT = parse_type(mMB->ref_type);
|
||
const TypeMember* mMat = find_member(bT, "Material");
|
||
out.material_textures.reserve(mc);
|
||
for (uint32_t i = 0; i < mc; ++i) {
|
||
Ref e = {mb.section, mb.offset + i * ms};
|
||
std::string tn;
|
||
if (mMat) {
|
||
Ref mat = read_reference(e, *mMat);
|
||
tn = material_texture_name(mat, mMat->ref_type, 0);
|
||
}
|
||
out.material_textures.push_back(std::move(tn));
|
||
}
|
||
}
|
||
}
|
||
|
||
// ── BoneBindings:mesh 局部骨骼槽 → skeleton 骨骼索引 ─────────────
|
||
// 文件可能带多个 skeleton(本体 + 武器挂点等)。对每个 skeleton 试解析,
|
||
// 取"悬空最少"的那个。
|
||
if (const TypeMember* mBB = find_member(mms, "BoneBindings")) {
|
||
Ref bb; uint32_t bc = 0, bs = 0;
|
||
if (read_ref_to_array(mesh_obj, *mBB, bb, bc, bs) && bc) {
|
||
const auto& bbT = parse_type(mBB->ref_type);
|
||
const TypeMember* mBN = find_member(bbT, "BoneName");
|
||
const TypeMember* mMin = find_member(bbT, "OBBMin");
|
||
const TypeMember* mMax = find_member(bbT, "OBBMax");
|
||
out.bone_bounds.resize(bc);
|
||
std::vector<std::string> names(bc);
|
||
for (uint32_t i = 0; i < bc; ++i) {
|
||
Ref e = {bb.section, bb.offset + i * bs};
|
||
names[i] = mBN ? rd_str(member_slot(e, *mBN)) : std::string();
|
||
auto &bounds = out.bone_bounds[i];
|
||
if (mMin && mMax && mMin->type == detail::MT_Real32 && mMax->type == detail::MT_Real32 && mMin->array_width == 3 && mMax->array_width == 3) {
|
||
Ref lo = member_slot(e, *mMin), hi = member_slot(e, *mMax);
|
||
bounds.valid = true;
|
||
for (int axis = 0; axis < 3; ++axis) {
|
||
bounds.min[axis] = rd_f32({lo.section, lo.offset + uint32_t(axis * 4)});
|
||
bounds.max[axis] = rd_f32({hi.section, hi.offset + uint32_t(axis * 4)});
|
||
bounds.valid &= std::isfinite(bounds.min[axis]) && std::isfinite(bounds.max[axis]) && bounds.min[axis] <= bounds.max[axis];
|
||
}
|
||
}
|
||
}
|
||
std::vector<int32_t> best(bc, -1);
|
||
int best_dangling = int(bc) + 1;
|
||
for (const auto& sk : skels) {
|
||
std::vector<int32_t> cur(bc, -1);
|
||
int dangling = 0;
|
||
for (uint32_t i = 0; i < bc; ++i) {
|
||
for (size_t k = 0; k < sk.bones.size(); ++k)
|
||
if (sk.bones[k].name == names[i]) { cur[i] = int32_t(k); break; }
|
||
if (cur[i] < 0) ++dangling;
|
||
}
|
||
if (dangling < best_dangling) { best_dangling = dangling; best = cur; }
|
||
if (dangling == 0) break;
|
||
}
|
||
out.bone_bindings = std::move(best);
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
// granny_material {String Name; int MapCount; material_map* Maps; texture* Texture; ...}
|
||
// granny_material_map {String Usage; material* Material}
|
||
// granny_texture {String FromFileName; ...}
|
||
std::string File::Impl::material_texture_name(Ref mat_obj, Ref mat_type, int depth) {
|
||
if (!mat_obj.valid() || !mat_type.valid() || depth > 8) return {};
|
||
const auto& mms = parse_type(mat_type);
|
||
|
||
// 1) 直接挂 texture
|
||
if (const TypeMember* mTex = find_member(mms, "Texture")) {
|
||
Ref tex = read_reference(mat_obj, *mTex);
|
||
if (tex.valid()) {
|
||
const auto& tms = parse_type(mTex->ref_type);
|
||
if (const TypeMember* mFN = find_member(tms, "FromFileName")) {
|
||
std::string s = rd_str(member_slot(tex, *mFN));
|
||
if (!s.empty()) return s;
|
||
}
|
||
}
|
||
}
|
||
// 2) 递归 Maps[].Map(复合材质:granny_material_map = {String Usage; material* Map})
|
||
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) {
|
||
const auto& mapT = parse_type(mMaps->ref_type);
|
||
const TypeMember* mSub = find_member(mapT, "Map");
|
||
if (!mSub) mSub = find_member(mapT, "Material");
|
||
for (uint32_t i = 0; i < c && mSub; ++i) {
|
||
Ref e = {b.section, b.offset + i * s};
|
||
Ref sub = read_reference(e, *mSub);
|
||
std::string r = material_texture_name(sub, mSub->ref_type, depth + 1);
|
||
if (!r.empty()) return r;
|
||
}
|
||
}
|
||
}
|
||
return {};
|
||
}
|
||
|
||
} // namespace gr2
|