Files
mtgodot-poc/libgr2/src/gr2_mesh.cpp
T

314 lines
15 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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));
}
}
}
// ── BoneBindingsmesh 局部骨骼槽 → 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