From a1bc96daaa9ac335f76ede3a4f18aa29f3520dd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20Beb=C3=B6k?= Date: Sun, 14 Dec 2025 16:52:10 +0100 Subject: [PATCH 1/6] chg: add namespace to converter --- .../src/converter/animConverter.cpp | 24 +- tools/gltf_importer/src/converter/converter.h | 6 +- .../src/converter/meshConverter.cpp | 38 +- tools/gltf_importer/src/converter/mse.h | 12 +- tools/gltf_importer/src/main.cpp | 34 +- tools/gltf_importer/src/math/quantizer.h | 2 +- tools/gltf_importer/src/optimizer/meshBVH.cpp | 2 +- .../src/optimizer/meshOptimizer.cpp | 16 +- tools/gltf_importer/src/optimizer/optimizer.h | 7 +- tools/gltf_importer/src/parser.cpp | 6 +- tools/gltf_importer/src/parser.h | 5 +- tools/gltf_importer/src/parser/animParser.cpp | 14 +- tools/gltf_importer/src/parser/boneParser.cpp | 6 +- .../src/parser/materialParser.cpp | 22 +- tools/gltf_importer/src/parser/nodeParser.cpp | 2 +- tools/gltf_importer/src/parser/parser.h | 11 +- tools/gltf_importer/src/structs.h | 488 +++++++++--------- 17 files changed, 357 insertions(+), 338 deletions(-) diff --git a/tools/gltf_importer/src/converter/animConverter.cpp b/tools/gltf_importer/src/converter/animConverter.cpp index c5fd21db..234388ed 100644 --- a/tools/gltf_importer/src/converter/animConverter.cpp +++ b/tools/gltf_importer/src/converter/animConverter.cpp @@ -17,7 +17,7 @@ namespace { return (uint16_t)roundf(t * 60.0f); } - bool hasConstValue(const std::vector &keyframes, bool isRotation) { + bool hasConstValue(const std::vector &keyframes, bool isRotation) { if(!isRotation) { float lastValue = keyframes[0].valScalar; for(int i=1; i < keyframes.size(); ++i) { @@ -33,16 +33,16 @@ namespace { } // Filters any channel that has a constant value, and is also the identity value of the specific target - bool isEmptyChannel(AnimChannelMapping &channel) + bool isEmptyChannel(T3DM::AnimChannelMapping &channel) { if(hasConstValue(channel.keyframes, channel.isRotation())) { bool isIdentity = false; const auto &kf = channel.keyframes[0]; switch(channel.targetType) { - case AnimChannelTarget::TRANSLATION : isIdentity = fabsf(kf.valScalar) < MIN_VALUE_DELTA; break; - case AnimChannelTarget::ROTATION : isIdentity = kf.valQuat.isIdentity(); break; - case AnimChannelTarget::SCALE_UNIFORM: - case AnimChannelTarget::SCALE : isIdentity = kf.valScalar == 1.0f; break; + case T3DM::AnimChannelTarget::TRANSLATION : isIdentity = fabsf(kf.valScalar) < MIN_VALUE_DELTA; break; + case T3DM::AnimChannelTarget::ROTATION : isIdentity = kf.valQuat.isIdentity(); break; + case T3DM::AnimChannelTarget::SCALE_UNIFORM: + case T3DM::AnimChannelTarget::SCALE : isIdentity = kf.valScalar == 1.0f; break; } //if(isIdentity)printf(" - Channel %s %d has identity value\n", channel.targetName.c_str(), channel.targetType); return isIdentity; @@ -54,7 +54,7 @@ namespace { * Optimizes the keyframes of a channel. * This will attempt to remove keyframes while staying within a certain error threshold. */ - void optimizeChannel(AnimChannelMapping &channel, float time) { + void optimizeChannel(T3DM::AnimChannelMapping &channel, float time) { if(channel.keyframes.size() < 2)return; auto channelOrg = channel; float mse = calcMSE(channel.keyframes, channelOrg.keyframes, 0, time, channel.isRotation()); @@ -82,7 +82,7 @@ namespace { } } - void quantizeRotation(Keyframe &kf) + void quantizeRotation(T3DM::Keyframe &kf) { uint32_t quatQuant = Quantizer::quatTo32Bit(kf.valQuat); if(quatQuant == 0) { @@ -94,7 +94,7 @@ namespace { } } -void convertAnimation(Anim &anim, const std::unordered_map &nodeMap) +void convertAnimation(T3DM::Anim &anim, const std::unordered_map &nodeMap) { // remove all empty channels anim.channelMap.erase( @@ -148,7 +148,7 @@ void convertAnimation(Anim &anim, const std::unordered_map &matrices, bool uvAdjust ); -ModelChunked chunkUpModel(const Model& model); +T3DM::ModelChunked chunkUpModel(const T3DM::Model& model); -void convertAnimation(Anim &anim, const std::unordered_map &nodeMap); \ No newline at end of file +void convertAnimation(T3DM::Anim &anim, const std::unordered_map &nodeMap); \ No newline at end of file diff --git a/tools/gltf_importer/src/converter/meshConverter.cpp b/tools/gltf_importer/src/converter/meshConverter.cpp index bbb9604e..e2ef9fb1 100644 --- a/tools/gltf_importer/src/converter/meshConverter.cpp +++ b/tools/gltf_importer/src/converter/meshConverter.cpp @@ -15,7 +15,7 @@ namespace { constexpr uint16_t INVALID_INDEX = 0xFFFF; - uint16_t getVertexIndex(const ModelChunked &model, const VertexT3D &v, uint16_t startIndex) + uint16_t getVertexIndex(const T3DM::ModelChunked &model, const T3DM::VertexT3D &v, uint16_t startIndex) { auto idxMaybe = model.vertIdxMap.find(v.hash); if(idxMaybe != model.vertIdxMap.end()) { @@ -26,7 +26,7 @@ namespace return 0xFFFF; } - uint16_t emitVertex(ModelChunked &model, const VertexT3D &v) + uint16_t emitVertex(T3DM::ModelChunked &model, const T3DM::VertexT3D &v) { auto oldIdx = model.vertices.size(); model.vertices.push_back(v); @@ -34,7 +34,7 @@ namespace return oldIdx; } - int triConnectionCount(const TriangleT3D &tri, const ModelChunked &model, uint32_t chunkOffset) + int triConnectionCount(const T3DM::TriangleT3D &tri, const T3DM::ModelChunked &model, uint32_t chunkOffset) { int connCount = 0; for(const auto & vi : tri.vert) { @@ -46,7 +46,7 @@ namespace } } -uint64_t hashVertex(const VertexT3D &vT3D, uint32_t boneIndex) +uint64_t hashVertex(const T3DM::VertexT3D &vT3D, uint32_t boneIndex) { uint64_t h = 0xcbf29ce484222325ULL; auto mix = [&](uint64_t v) { @@ -65,7 +65,7 @@ uint64_t hashVertex(const VertexT3D &vT3D, uint32_t boneIndex) } void convertVertex( - float modelScale, float texSizeX, float texSizeY, const VertexNorm &v, VertexT3D &vT3D, + float modelScale, float texSizeX, float texSizeY, const T3DM::VertexNorm &v, T3DM::VertexT3D &vT3D, const Mat4 &mat, const std::vector &matrices, bool uvAdjust ) { //auto posInt = mat * v.pos * modelScale; @@ -129,14 +129,14 @@ void convertVertex( vT3D.boneIndex = v.boneIndex; } -ModelChunked chunkUpModel(const Model &model) +T3DM::ModelChunked chunkUpModel(const T3DM::Model &model) { - ModelChunked res{ + T3DM::ModelChunked res{ .aabbMin = { 32767, 32767, 32767 }, .aabbMax = { -32768, -32768, -32768 } }; - res.chunks.reserve(model.triangles.size() * 3 / MAX_VERTEX_COUNT); - res.chunks.push_back(MeshChunk{}); + res.chunks.reserve(model.triangles.size() * 3 / T3DM::MAX_VERTEX_COUNT); + res.chunks.push_back(T3DM::MeshChunk{}); res.chunks.back().material = model.material; res.chunks.back().name = model.name; @@ -146,7 +146,7 @@ ModelChunked chunkUpModel(const Model &model) // Emits a new chunk of data. This contains a set of indices referencing the global vertex buffer auto checkAndEmitChunk = [&](bool forceEmit) { - if(emittedVerts >= MAX_VERTEX_COUNT || forceEmit) { + if(emittedVerts >= T3DM::MAX_VERTEX_COUNT || forceEmit) { if(emittedVerts == 0 || res.vertices.empty())return false; // no need to emit empty chunks // make sure vertices can be interleaved later @@ -158,8 +158,8 @@ ModelChunked chunkUpModel(const Model &model) ++emittedVerts; } - if(emittedVerts > MAX_VERTEX_COUNT) { - printf("Error: Too many vertices: %d (total: %d)\n", emittedVerts, res.vertices.size()); + if(emittedVerts > T3DM::MAX_VERTEX_COUNT) { + printf("Error: Too many vertices: %d (total: %ld)\n", emittedVerts, res.vertices.size()); throw std::runtime_error("Too many vertices!"); } @@ -171,7 +171,7 @@ ModelChunked chunkUpModel(const Model &model) // All except the last will only load vertices, but draw no faces. The last one will do the drawing. // iterate over all new verts and re-collect them into buffers - std::unordered_map> vertsByBone{}; + std::unordered_map> vertsByBone{}; for(uint32_t v=chunkOffset; v<(chunkOffset+emittedVerts); ++v) { auto &vert = res.vertices[v]; @@ -235,7 +235,7 @@ ModelChunked chunkUpModel(const Model &model) } } - res.chunks.push_back(MeshChunk{.material = model.material, .name = model.name}); + res.chunks.push_back(T3DM::MeshChunk{.material = model.material, .name = model.name}); chunkOffset += emittedVerts; emittedVerts = 0; @@ -245,7 +245,7 @@ ModelChunked chunkUpModel(const Model &model) }; // Emit a single triangle into the local buffer - auto emitTriangle = [&](const TriangleT3D &tri, bool onlyExisting) + auto emitTriangle = [&](const T3DM::TriangleT3D &tri, bool onlyExisting) { // try to get existing indices in the local buffer uint16_t idx[3]; @@ -260,7 +260,7 @@ ModelChunked chunkUpModel(const Model &model) } // check if triangle would still fit into the buffer - if((emittedVerts + needsEmit.size()) >= MAX_VERTEX_COUNT) { + if((emittedVerts + needsEmit.size()) >= T3DM::MAX_VERTEX_COUNT) { //printf("Warning: Skipping triangle, not enough space for vertices!\n"); return false; } @@ -331,7 +331,7 @@ ModelChunked chunkUpModel(const Model &model) // First check 3 (no new vertex needed), then the ones with 2, then 1 for(int maxCount=3; maxCount>0; --maxCount) { - auto freeVertLeft = MAX_VERTEX_COUNT - emittedVerts; + auto freeVertLeft = T3DM::MAX_VERTEX_COUNT - emittedVerts; if(freeVertLeft < maxCount)break; for(int triIdx= t + 1; triIdx < model.triangles.size(); ++triIdx) @@ -360,7 +360,7 @@ ModelChunked chunkUpModel(const Model &model) checkAndEmitChunk(true); // remove empty chunks - res.chunks.erase(std::remove_if(res.chunks.begin(), res.chunks.end(), [](const MeshChunk &chunk) { + res.chunks.erase(std::remove_if(res.chunks.begin(), res.chunks.end(), [](const T3DM::MeshChunk &chunk) { return chunk.vertexCount == 0; }), res.chunks.end()); @@ -373,7 +373,7 @@ ModelChunked chunkUpModel(const Model &model) // we can go a little bit OOB (there is a tmp buffer after it, and the DMA doesn't overlap) // this may be needed to split vertices with bones properly - assert((chunk.vertexDestOffset + chunk.vertexCount) <= (MAX_VERTEX_COUNT+1)); + assert((chunk.vertexDestOffset + chunk.vertexCount) <= (T3DM::MAX_VERTEX_COUNT+1)); } // calculate AABB diff --git a/tools/gltf_importer/src/converter/mse.h b/tools/gltf_importer/src/converter/mse.h index 88479aeb..d4cd154c 100644 --- a/tools/gltf_importer/src/converter/mse.h +++ b/tools/gltf_importer/src/converter/mse.h @@ -5,13 +5,13 @@ #pragma once #include "../structs.h" -inline const Keyframe& safeKf(const std::vector &kfs, int idx) { +inline const T3DM::Keyframe& safeKf(const std::vector &kfs, int idx) { if(idx < 0)return kfs[0]; if(idx >= kfs.size())return kfs.back(); return kfs[idx]; } -inline float calcMSE(const std::vector &kfsNew, const std::vector &kfsOrg, float timeStart, float timeEnd, bool isRotation) { +inline float calcMSE(const std::vector &kfsNew, const std::vector &kfsOrg, float timeStart, float timeEnd, bool isRotation) { float sampleRate = 60.0f; float timeStep = 1.0f / sampleRate; @@ -30,10 +30,10 @@ inline float calcMSE(const std::vector &kfsNew, const std::vector= kfsOrg.size())break; } - const Keyframe &kfOrg = safeKf(kfsOrg, idxOrg); - const Keyframe &kfOrgNext = safeKf(kfsOrg, idxOrg + 1); - const Keyframe &kfNew = safeKf(kfsNew, idxNew); - const Keyframe &kfNewNext = safeKf(kfsNew, idxNew + 1); + const T3DM::Keyframe &kfOrg = safeKf(kfsOrg, idxOrg); + const T3DM::Keyframe &kfOrgNext = safeKf(kfsOrg, idxOrg + 1); + const T3DM::Keyframe &kfNew = safeKf(kfsNew, idxNew); + const T3DM::Keyframe &kfNewNext = safeKf(kfsNew, idxNew + 1); float tDiffOrg = kfOrgNext.time - kfOrg.time; float tDiffNew = kfNewNext.time - kfNew.time; diff --git a/tools/gltf_importer/src/main.cpp b/tools/gltf_importer/src/main.cpp index b3176bbf..eca978d4 100644 --- a/tools/gltf_importer/src/main.cpp +++ b/tools/gltf_importer/src/main.cpp @@ -18,7 +18,10 @@ #include "parser/rdp.h" #include "optimizer/optimizer.h" -Config config; +namespace T3DM +{ + constinit Config config{}; +} namespace fs = std::filesystem; @@ -33,14 +36,14 @@ namespace { return strPos; } - int writeBone(BinaryFile &file, const Bone &bone, std::string &stringTable, int level) { + int writeBone(BinaryFile &file, const T3DM::Bone &bone, std::string &stringTable, int level) { //printf("Bone[%d]: %s -> %d\n", bone.index, bone.name.c_str(), bone.parentIndex); file.write(insertString(stringTable, bone.name)); file.write(bone.parentIndex); file.write(level); // level - auto normPos = bone.pos * config.globalScale; + auto normPos = bone.pos * T3DM::config.globalScale; file.writeArray(bone.scale.data, 3); file.writeArray(bone.rot.data, 4); file.writeArray(normPos.data, 3); @@ -84,6 +87,7 @@ int main(int argc, char* argv[]) const std::string gltfPath = args.getFilenameArg(0); const std::string t3dmPath = args.getFilenameArg(1); + auto &config = T3DM::config; config.globalScale = (float)args.getU32Arg("--base-scale", 64); config.ignoreMaterials = args.checkArg("--ignore-materials"); config.ignoreTransforms = args.checkArg("--ignore-transforms"); @@ -105,12 +109,12 @@ int main(int argc, char* argv[]) config.animSampleRate = 60; - auto t3dm = parseGLTF(gltfPath.c_str(), config.globalScale); + auto t3dm = T3DM::parseGLTF(gltfPath.c_str(), config.globalScale); fs::path gltfBasePath{gltfPath}; // sort models by transparency mode (opaque -> cutout -> transparent) // within the same transparency mode, sort by material - std::sort(t3dm.models.begin(), t3dm.models.end(), [](const Model &a, const Model &b) { + std::sort(t3dm.models.begin(), t3dm.models.end(), [](const T3DM::Model &a, const T3DM::Model &b) { bool isTranspA = a.material.blendMode == RDP::BLEND::MULTIPLY; bool isTranspB = b.material.blendMode == RDP::BLEND::MULTIPLY; if(isTranspA == isTranspB) { @@ -129,7 +133,7 @@ int main(int argc, char* argv[]) // de-dupe materials and determine material indices std::unordered_map materialUUIDMap{}; - std::vector usedMaterials{}; + std::vector usedMaterials{}; { uint32_t nextMatIndex = 0; for(auto &model : t3dm.models) { @@ -147,19 +151,19 @@ int main(int argc, char* argv[]) uint32_t chunkCount = 2; // vertices + indices if(config.createBVH)chunkCount += 1; chunkCount += usedMaterials.size(); - std::vector modelChunks{}; + std::vector modelChunks{}; modelChunks.reserve(t3dm.models.size()); for(const auto & model : t3dm.models) { auto chunks = chunkUpModel(model); if(config.verbose) { - printf("[%s] Vertices out: %d\n", model.name.c_str(), chunks.vertices.size()); + printf("[%s] Vertices out: %ld\n", model.name.c_str(), chunks.vertices.size()); } optimizeModelChunk(chunks); if(config.verbose) { int totalIdx=0, totalStrips=0, totalStripCmd = 0; for(auto &c : chunks.chunks) { - printf("[%s:part-%ld] Vert: %d | Idx-Tris: %d | Idx-Strip: %d %d %d %d\n", + printf("[%s:part-%ld] Vert: %d | Idx-Tris: %ld | Idx-Strip: %ld %ld %ld %ld\n", model.name.c_str(), &c - &chunks.chunks[0], c.vertexCount, @@ -194,7 +198,7 @@ int main(int argc, char* argv[]) // Main file BinaryFile file{}; file.writeChars("T3M", 3); - file.write(T3DM_VERSION); + file.write(T3DM::T3DM_VERSION); file.write(chunkCount); // chunk count file.write(0); // total vertex count (set later) @@ -288,9 +292,9 @@ int main(int argc, char* argv[]) f->write(insertString(stringTable, material.name)); // @TODO: refactor materials to match file/runtime structure - std::vector materials{&material.texA, &material.texB}; - for(const MaterialTexture* mat_ : materials) { - const MaterialTexture&mat = *mat_; + std::vector materials{&material.texA, &material.texB}; + for(const T3DM::MaterialTexture* mat_ : materials) { + const T3DM::MaterialTexture&mat = *mat_; f->write(mat.texReference); std::string texPath = ""; @@ -326,7 +330,7 @@ int main(int argc, char* argv[]) f->write((uint16_t)mat.texWidth); f->write((uint16_t)mat.texHeight); - auto writeTile = [&](const TileParam &tile) { + auto writeTile = [&](const T3DM::TileParam &tile) { f->write(tile.low); f->write(tile.high); f->write(tile.mask); @@ -364,7 +368,7 @@ int main(int argc, char* argv[]) for(const auto& chunk : chunks.chunks) { //printf(" t3d_vert_load(vertices, %d, %d);\n", chunk.vertexOffset, chunk.vertexCount); - uint32_t partVertOffset = (chunk.vertexOffset * VertexT3D::byteSize()); + uint32_t partVertOffset = (chunk.vertexOffset * T3DM::VertexT3D::byteSize()); partVertOffset += chunkVerts.getPos(); file.write(partVertOffset); diff --git a/tools/gltf_importer/src/math/quantizer.h b/tools/gltf_importer/src/math/quantizer.h index fcdef429..886e8188 100644 --- a/tools/gltf_importer/src/math/quantizer.h +++ b/tools/gltf_importer/src/math/quantizer.h @@ -15,7 +15,7 @@ namespace Quantizer return (uint16_t)round((double)(value - offset) / scale * 65535.0); } - void floatsGetOffsetScale(const std::vector &keyframes, float &offset, float &scale) { + inline void floatsGetOffsetScale(const std::vector &keyframes, float &offset, float &scale) { float valMin = INFINITY; float valMax = -INFINITY; for(auto &kf : keyframes) { diff --git a/tools/gltf_importer/src/optimizer/meshBVH.cpp b/tools/gltf_importer/src/optimizer/meshBVH.cpp index 5f6ba4b6..98cf1cae 100644 --- a/tools/gltf_importer/src/optimizer/meshBVH.cpp +++ b/tools/gltf_importer/src/optimizer/meshBVH.cpp @@ -59,7 +59,7 @@ namespace * The result is a list of 16bit ints encoding both nodes, indices and AABB extends * @param modelChunks */ -std::vector createMeshBVH(const std::vector &modelChunks) +std::vector T3DM::createMeshBVH(const std::vector &modelChunks) { std::vector aabbs; std::vector centers; diff --git a/tools/gltf_importer/src/optimizer/meshOptimizer.cpp b/tools/gltf_importer/src/optimizer/meshOptimizer.cpp index a995bdf7..9e5c97f7 100644 --- a/tools/gltf_importer/src/optimizer/meshOptimizer.cpp +++ b/tools/gltf_importer/src/optimizer/meshOptimizer.cpp @@ -20,7 +20,7 @@ namespace { // get amount of times each vertex is used in the input triangle list auto getVertexUsage(const TriList &tris) { - std::array usedVerts{}; + std::array usedVerts{}; for(auto &tri : tris) { for(int i=0; i<3; ++i) { ++usedVerts[tri[i]]; @@ -30,7 +30,7 @@ namespace { } auto getVertexUsage(const std::vector> strips) { - std::array usedVerts{}; + std::array usedVerts{}; for(auto &strip : strips) { for(auto idx : strip) { ++usedVerts[idx]; @@ -39,9 +39,9 @@ namespace { return usedVerts; } - int countFreeVertsAtEnd(const std::array &usedVerts) { + int countFreeVertsAtEnd(const std::array &usedVerts) { int freeVertsEnd = 0; - for(int i=MAX_VERTEX_COUNT-1; i>=0; --i) { + for(int i=T3DM::MAX_VERTEX_COUNT-1; i>=0; --i) { if(usedVerts[i] > 0)break; ++freeVertsEnd; } @@ -49,7 +49,7 @@ namespace { } int calcUsableIndices(int freeVertices) { - int res = freeVertices * CACHE_VERTEX_SIZE / 2; + int res = freeVertices * T3DM::CACHE_VERTEX_SIZE / 2; // a single vertex is not aligned (2 are), sub. 8 bytes to not overwrite stuff if(freeVertices % 2 != 0)res -= 8 / 2; return res; @@ -148,7 +148,7 @@ namespace { } } -void optimizeModelChunk(ModelChunked &model) +void T3DM::optimizeModelChunk(ModelChunked &model) { for(auto &chunk : model.chunks) { @@ -183,7 +183,7 @@ void optimizeModelChunk(ModelChunked &model) && (seq.tris.size() < (tris.size() / 2)) ) { - if(config.verbose) { + if(T3DM::config.verbose) { printf("Sequence:\n Tris: "); for(auto &tri : seq.tris) { printf("%d %d %d | ", tri[0], tri[1], tri[2]); @@ -242,7 +242,7 @@ void optimizeModelChunk(ModelChunked &model) // now free until the last slots are free if(freeVertsEnd < targetFreeVerts) { - for(int i=MAX_VERTEX_COUNT-targetFreeVerts; i createMeshBVH(const std::vector &modelChunks); \ No newline at end of file +namespace T3DM +{ + void optimizeModelChunk(ModelChunked &model); + std::vector createMeshBVH(const std::vector &modelChunks); +} \ No newline at end of file diff --git a/tools/gltf_importer/src/parser.cpp b/tools/gltf_importer/src/parser.cpp index 9c817215..d75cafbe 100644 --- a/tools/gltf_importer/src/parser.cpp +++ b/tools/gltf_importer/src/parser.cpp @@ -17,7 +17,7 @@ #include "parser/parser.h" #include "converter/converter.h" -void printBoneTree(const Bone &bone, int depth) +void printBoneTree(const T3DM::Bone &bone, int depth) { for(int i=0; iname, vertexCount); - printf("[%s] Indices input: %d\n", mesh->name, indices.size()); + printf("[%s] Indices input: %ld\n", mesh->name, indices.size()); } } } diff --git a/tools/gltf_importer/src/parser.h b/tools/gltf_importer/src/parser.h index fab2291c..f629b1d2 100644 --- a/tools/gltf_importer/src/parser.h +++ b/tools/gltf_importer/src/parser.h @@ -7,4 +7,7 @@ #include "structs.h" -T3DMData parseGLTF(const char* gltfPath, float modelScale); \ No newline at end of file +namespace T3DM +{ + T3DMData parseGLTF(const char* gltfPath, float modelScale); +} \ No newline at end of file diff --git a/tools/gltf_importer/src/parser/animParser.cpp b/tools/gltf_importer/src/parser/animParser.cpp index ca654152..d4f07f34 100644 --- a/tools/gltf_importer/src/parser/animParser.cpp +++ b/tools/gltf_importer/src/parser/animParser.cpp @@ -8,11 +8,11 @@ namespace { - AnimChannelTarget getTarget(cgltf_animation_path_type type) { + T3DM::AnimChannelTarget getTarget(cgltf_animation_path_type type) { switch(type) { - case cgltf_animation_path_type_translation: return AnimChannelTarget::TRANSLATION; - case cgltf_animation_path_type_rotation : return AnimChannelTarget::ROTATION; - case cgltf_animation_path_type_scale : return AnimChannelTarget::SCALE; + case cgltf_animation_path_type_translation: return T3DM::AnimChannelTarget::TRANSLATION; + case cgltf_animation_path_type_rotation : return T3DM::AnimChannelTarget::ROTATION; + case cgltf_animation_path_type_scale : return T3DM::AnimChannelTarget::SCALE; default: printf("Unknown animation target: %d\n", type); throw std::runtime_error("Unknown animation target"); @@ -30,9 +30,9 @@ namespace }; } - void insertScalarKeyframe(Anim &anim, float time, uint32_t chIdx, Vec3 value, bool isTranslate) { + void insertScalarKeyframe(T3DM::Anim &anim, float time, uint32_t chIdx, Vec3 value, bool isTranslate) { value = cleanupVector(value); - if(isTranslate)value *= config.globalScale; + if(isTranslate)value *= T3DM::config.globalScale; for(int i=0; i<3; ++i) { anim.channelMap[chIdx + i].keyframes.push_back({.time = time, .valScalar = value[i]}); @@ -42,7 +42,7 @@ namespace } } -Anim parseAnimation(const cgltf_animation &anim, const std::unordered_map &nodeMap, uint32_t sampleRate) +T3DM::Anim T3DM::parseAnimation(const cgltf_animation &anim, const std::unordered_map &nodeMap, uint32_t sampleRate) { Anim res{ .name = std::string(anim.name), diff --git a/tools/gltf_importer/src/parser/boneParser.cpp b/tools/gltf_importer/src/parser/boneParser.cpp index f93aaaf5..932f1fd3 100644 --- a/tools/gltf_importer/src/parser/boneParser.cpp +++ b/tools/gltf_importer/src/parser/boneParser.cpp @@ -14,8 +14,8 @@ namespace { } } -Bone parseBoneTree(const cgltf_node *rootBone, Bone *parentBone, int &count) { - Bone bone; +T3DM::Bone T3DM::parseBoneTree(const cgltf_node *rootBone, Bone *parentBone, int &count) { + Bone bone{}; bone.name = rootBone->name; bone.pos = rootBone->has_translation ? Vec3( @@ -42,7 +42,7 @@ Bone parseBoneTree(const cgltf_node *rootBone, Bone *parentBone, int &count) { bone.index = count; count += 1; - if(config.verbose) + if(T3DM::config.verbose) { printf("Bone[%d]: %s (parent: %d | %d)\n", count-1, bone.name.c_str(), bone.parentIndex, parentBone ? parentBone->index : -1); printf(" t: %.4f %.4f %.4f\n", rootBone->translation[0], rootBone->translation[1], rootBone->translation[2]); diff --git a/tools/gltf_importer/src/parser/materialParser.cpp b/tools/gltf_importer/src/parser/materialParser.cpp index 4d6bf55d..6bee40aa 100644 --- a/tools/gltf_importer/src/parser/materialParser.cpp +++ b/tools/gltf_importer/src/parser/materialParser.cpp @@ -29,7 +29,7 @@ namespace { std::vector scannedTextures{}; - void readMaterialTileAxisFromJson(TileParam ¶m, const json &tex) + void readMaterialTileAxisFromJson(T3DM::TileParam ¶m, const json &tex) { if(tex.empty())return; param.clamp = tex.value("clamp", 0); @@ -40,7 +40,7 @@ namespace { param.shift = tex["shift"].get(); } - void readMaterialFromJson(MaterialTexture &material, const json &tex, const fs::path &gltfPath) + void readMaterialFromJson(T3DM::MaterialTexture &material, const json &tex, const fs::path &gltfPath) { if(tex.contains("S"))readMaterialTileAxisFromJson(material.s, tex["S"]); if(tex.contains("T"))readMaterialTileAxisFromJson(material.t, tex["T"]); @@ -81,14 +81,14 @@ namespace { if(error) { // texture not found, try finding another one with the same name if(!scannedTextures.size()) { - if(config.verbose)printf("Scanning textures...\n"); - for(auto &entry : fs::recursive_directory_iterator(config.assetPathFull)) { + if(T3DM::config.verbose)printf("Scanning textures...\n"); + for(auto &entry : fs::recursive_directory_iterator(T3DM::config.assetPathFull)) { if(entry.path().extension() == ".png") { std::string filePath = entry.path().string(); // force linux forward slashes, runtime paths are forced to used that too std::replace(filePath.begin(), filePath.end(), '\\', '/'); scannedTextures.push_back(filePath); - if(config.verbose)printf("Found texture: %s\n", filePath.c_str()); + if(T3DM::config.verbose)printf("Found texture: %s\n", filePath.c_str()); } } } @@ -117,9 +117,9 @@ namespace { } } - ColorCombiner readCCFromJson(const json &cc) + T3DM::ColorCombiner readCCFromJson(const json &cc) { - ColorCombiner res{}; + T3DM::ColorCombiner res{}; res.a = cc["A"].get(); res.b = cc["B"].get(); res.c = cc["C"].get(); @@ -135,7 +135,9 @@ namespace { return res; } - bool isCCUsingTexture(const ColorCombiner &cc) + namespace CC = T3DM::CC; + + bool isCCUsingTexture(const T3DM::ColorCombiner &cc) { if(cc.a == CC::TEX0 || cc.b == CC::TEX0 || cc.c == CC::TEX0 || cc.d == CC::TEX0)return true; if(cc.a == CC::TEX1 || cc.b == CC::TEX1 || cc.c == CC::TEX1 || cc.d == CC::TEX1)return true; @@ -148,7 +150,7 @@ namespace { return false; } - bool isUsingShade(const ColorCombiner &cc) + bool isUsingShade(const T3DM::ColorCombiner &cc) { if(cc.a == CC::SHADE || cc.b == CC::SHADE || cc.c == CC::SHADE || cc.d == CC::SHADE)return true; if(cc.aAlpha == CC::SHADE || cc.bAlpha == CC::SHADE || cc.cAlpha == CC::SHADE || cc.dAlpha == CC::SHADE)return true; @@ -178,7 +180,7 @@ namespace { } } -void parseMaterial(const fs::path &gltfBasePath, int i, int j, Model &model, cgltf_primitive *prim) { +void T3DM::parseMaterial(const fs::path &gltfBasePath, int i, int j, Model &model, cgltf_primitive *prim) { model.material.uuid = j * 1000 + i; if(prim->material->name) { model.material.uuid = stringHash(prim->material->name); diff --git a/tools/gltf_importer/src/parser/nodeParser.cpp b/tools/gltf_importer/src/parser/nodeParser.cpp index da0ad074..bdde2a72 100644 --- a/tools/gltf_importer/src/parser/nodeParser.cpp +++ b/tools/gltf_importer/src/parser/nodeParser.cpp @@ -5,7 +5,7 @@ #include "parser.h" -Mat4 parseNodeMatrix(const cgltf_node *node, bool recursive) +Mat4 T3DM::parseNodeMatrix(const cgltf_node *node, bool recursive) { Mat4 matScale{}; if(node->has_scale)matScale.setScale({node->scale[0], node->scale[1], node->scale[2]}); diff --git a/tools/gltf_importer/src/parser/parser.h b/tools/gltf_importer/src/parser/parser.h index c59e2610..4e32ecac 100644 --- a/tools/gltf_importer/src/parser/parser.h +++ b/tools/gltf_importer/src/parser/parser.h @@ -16,7 +16,10 @@ namespace fs = std::filesystem; -void parseMaterial(const fs::path &gltfBasePath, int i, int j, Model &model, cgltf_primitive *prim); -Mat4 parseNodeMatrix(const cgltf_node *node, bool recursive); -Bone parseBoneTree(const cgltf_node *rootBone, Bone *parentBone, int &count); -Anim parseAnimation(const cgltf_animation &anim, const std::unordered_map &nodeMap, uint32_t sampleRate); \ No newline at end of file +namespace T3DM +{ + void parseMaterial(const fs::path &gltfBasePath, int i, int j, Model &model, cgltf_primitive *prim); + Mat4 parseNodeMatrix(const cgltf_node *node, bool recursive); + Bone parseBoneTree(const cgltf_node *rootBone, Bone *parentBone, int &count); + Anim parseAnimation(const cgltf_animation &anim, const std::unordered_map &nodeMap, uint32_t sampleRate); +} \ No newline at end of file diff --git a/tools/gltf_importer/src/structs.h b/tools/gltf_importer/src/structs.h index b04cd5c9..0c66f6f7 100644 --- a/tools/gltf_importer/src/structs.h +++ b/tools/gltf_importer/src/structs.h @@ -16,250 +16,254 @@ #include "math/vec3.h" #include "math/mat4.h" -namespace DrawFlags { - constexpr uint32_t DEPTH = 1 << 0; - constexpr uint32_t TEXTURED = 1 << 1; - constexpr uint32_t SHADED = 1 << 2; - constexpr uint32_t CULL_FRONT = 1 << 3; - constexpr uint32_t CULL_BACK = 1 << 4; -} - -namespace CC { - constexpr uint32_t COMBINED = 0; - constexpr uint32_t TEX0 = 1; - constexpr uint32_t TEX1 = 2; - constexpr uint32_t PRIM = 3; - constexpr uint32_t SHADE = 4; - constexpr uint32_t ENV = 5; - constexpr uint32_t NOISE = 6; - constexpr uint32_t TEX0_ALPHA = 8; - constexpr uint32_t TEX1_ALPHA = 9; -} - -namespace FogMode { - constexpr uint8_t DEFAULT = 0; - constexpr uint8_t DISABLED = 1; - constexpr uint8_t ACTIVE = 2; - - constexpr uint8_t INVALID = 0xFF; -} - -namespace UvGenFunc { - constexpr uint8_t NONE = 0; - constexpr uint8_t SPHERE = 1; -} - -// Normalized vertex, this is then used to generate the final vertex data -struct VertexNorm { - Vec3 pos{}; - Vec3 norm{}; - float color[4]{}; - Vec2 uv{}; - int32_t boneIndex{-1}; -}; - -struct VertexT3D { - /* 0x00 */ int16_t pos[3]{}; // 16.0 fixed point - /* 0x06 */ uint16_t norm{}; // 5,5,5 compressed normal - /* 0x08 */ uint32_t rgba{}; // RGBA8 color - /* 0x0C */ int16_t s{}; // 10.6 fixed point (pixel coords) - /* 0x0E */ int16_t t{}; // 10.6 fixed point (pixel coords) - - // Extra attributes not used in the final vertex data: - uint64_t hash{}; - int32_t boneIndex{}; - uint32_t originalIndex{}; - - bool operator==(const VertexT3D& v) const { - return hash == v.hash; +namespace T3DM +{ + namespace DrawFlags { + constexpr uint32_t DEPTH = 1 << 0; + constexpr uint32_t TEXTURED = 1 << 1; + constexpr uint32_t SHADED = 1 << 2; + constexpr uint32_t CULL_FRONT = 1 << 3; + constexpr uint32_t CULL_BACK = 1 << 4; } - constexpr static uint32_t byteSize() { - return sizeof(VertexT3D) - sizeof(hash) - sizeof(boneIndex) - sizeof(originalIndex); + namespace CC { + constexpr uint32_t COMBINED = 0; + constexpr uint32_t TEX0 = 1; + constexpr uint32_t TEX1 = 2; + constexpr uint32_t PRIM = 3; + constexpr uint32_t SHADE = 4; + constexpr uint32_t ENV = 5; + constexpr uint32_t NOISE = 6; + constexpr uint32_t TEX0_ALPHA = 8; + constexpr uint32_t TEX1_ALPHA = 9; } - //bool operator<=>(const VertexT3D&) const = default; -}; - -static_assert(VertexT3D::byteSize() == 0x10, "VertexT3D has wrong size"); - -struct TriangleT3D { - VertexT3D vert[3]{}; - //bool operator<=>(const TriangleT3D&) const = default; -}; - -struct TileParam { - float low{}; - float high{}; - uint8_t clamp{}; - uint8_t mirror{}; - int8_t mask{}; - int8_t shift{}; -}; - -struct ColorCombiner { - uint8_t a{}; - uint8_t b{}; - uint8_t c{}; - uint8_t d{}; - uint8_t aAlpha{}; - uint8_t bAlpha{}; - uint8_t cAlpha{}; - uint8_t dAlpha{}; -}; - -struct MaterialTexture { - std::string texPath{}; - uint32_t texWidth{}; - uint32_t texHeight{}; - uint32_t texReference{}; - - TileParam s{}; - TileParam t{}; -}; - -struct Material { - uint32_t index{}; - MaterialTexture texA; - MaterialTexture texB; - std::string name{}; - - uint64_t colorCombiner{}; - uint64_t otherModeValue{}; - uint64_t otherModeMask{}; - uint32_t blendMode{}; - uint32_t drawFlags{}; - - uint32_t uuid{}; - uint8_t fogMode{}; - uint8_t vertexFxFunc{}; - - uint8_t primColor[4]{}; - uint8_t envColor[4]{}; - uint8_t blendColor[4]{}; - - bool setPrimColor{false}; - bool setEnvColor{false}; - bool setBlendColor{false}; - bool uvFilterAdjust{false}; -}; - -struct MeshChunk { - std::vector indices{}; - std::vector stripIndices[4]{}; - uint8_t seqStart{0}; - uint8_t seqCount{0}; - Material material{}; - uint32_t vertexOffset{0}; - uint32_t vertexCount{0}; - uint32_t vertexDestOffset{0}; - uint32_t boneIndex{0}; - uint32_t boneCount{0}; - std::string name{}; -}; - -struct Model { - std::vector triangles{}; - std::string name{}; - Material material{}; -}; - -struct ModelChunked { - std::vector vertices{}; - std::vector chunks{}; - - std::unordered_map vertIdxMap{}; - - Material materialA{}; - Material materialB{}; - - s16 aabbMin[3]{}; - s16 aabbMax[3]{}; - u16 triCount{}; -}; - -struct Bone { - std::string name; - Vec3 pos; - Quat rot; - Vec3 scale; - - Mat4 parentMatrix; - Mat4 modelMatrix; - Mat4 inverseBindPose; - - uint32_t index; - uint32_t parentIndex; - std::vector> children; -}; - -typedef enum AnimChannelTarget : u8 { - TRANSLATION, - SCALE, - SCALE_UNIFORM, - ROTATION -} AnimChannelTarget; - -struct Keyframe { - float time{}; - float timeNeeded{}; - float timeNextInChannel{}; - - uint16_t timeTicks{}; - uint16_t timeNeededTicks{}; - uint16_t timeNextInChannelTicks{}; - - uint32_t chanelIdx; - Quat valQuat; - float valScalar; - - uint32_t valQuantSize = 0; - uint16_t valQuant[2]; -}; - -struct AnimChannelMapping { - std::string targetName{}; - uint16_t targetIdx{}; - AnimChannelTarget targetType{}; - uint8_t attributeIdx{}; - - float valueMin{INFINITY}; - float valueMax{-INFINITY}; - - std::vector keyframes{}; // temp. storage after parsing - - [[nodiscard]] constexpr bool isRotation() const { - return targetType == AnimChannelTarget::ROTATION; + namespace FogMode { + constexpr uint8_t DEFAULT = 0; + constexpr uint8_t DISABLED = 1; + constexpr uint8_t ACTIVE = 2; + + constexpr uint8_t INVALID = 0xFF; + } + + namespace UvGenFunc { + constexpr uint8_t NONE = 0; + constexpr uint8_t SPHERE = 1; } -}; - -struct Anim { - std::string name{}; - float duration{}; - uint32_t channelCountQuat{}; - uint32_t channelCountScalar{}; - std::vector keyframes{}; // output used for writing to the file - std::vector channelMap{}; -}; - -struct T3DMData { - std::vector models{}; - std::vector skeletons{}; - std::vector animations{}; -}; - -struct Config { - float globalScale{64.0f}; - uint32_t animSampleRate{30}; - bool ignoreMaterials{false}; - bool createBVH{false}; - bool verbose{false}; - bool ignoreTransforms{false}; - std::string assetPath{}; - std::string assetPathFull{}; -}; -extern Config config; - -constexpr int MAX_VERTEX_COUNT = 70; -constexpr int CACHE_VERTEX_SIZE = 36; -constexpr u8 T3DM_VERSION = 0x04; \ No newline at end of file + + // Normalized vertex, this is then used to generate the final vertex data + struct VertexNorm { + Vec3 pos{}; + Vec3 norm{}; + float color[4]{}; + Vec2 uv{}; + int32_t boneIndex{-1}; + }; + + struct VertexT3D { + /* 0x00 */ int16_t pos[3]{}; // 16.0 fixed point + /* 0x06 */ uint16_t norm{}; // 5,5,5 compressed normal + /* 0x08 */ uint32_t rgba{}; // RGBA8 color + /* 0x0C */ int16_t s{}; // 10.6 fixed point (pixel coords) + /* 0x0E */ int16_t t{}; // 10.6 fixed point (pixel coords) + + // Extra attributes not used in the final vertex data: + uint64_t hash{}; + int32_t boneIndex{}; + uint32_t originalIndex{}; + + bool operator==(const VertexT3D& v) const { + return hash == v.hash; + } + + constexpr static uint32_t byteSize() { + return sizeof(VertexT3D) - sizeof(hash) - sizeof(boneIndex) - sizeof(originalIndex); + } + + //bool operator<=>(const VertexT3D&) const = default; + }; + + static_assert(VertexT3D::byteSize() == 0x10, "VertexT3D has wrong size"); + + struct TriangleT3D { + VertexT3D vert[3]{}; + //bool operator<=>(const TriangleT3D&) const = default; + }; + + struct TileParam { + float low{}; + float high{}; + uint8_t clamp{}; + uint8_t mirror{}; + int8_t mask{}; + int8_t shift{}; + }; + + struct ColorCombiner { + uint8_t a{}; + uint8_t b{}; + uint8_t c{}; + uint8_t d{}; + uint8_t aAlpha{}; + uint8_t bAlpha{}; + uint8_t cAlpha{}; + uint8_t dAlpha{}; + }; + + struct MaterialTexture { + std::string texPath{}; + uint32_t texWidth{}; + uint32_t texHeight{}; + uint32_t texReference{}; + + TileParam s{}; + TileParam t{}; + }; + + struct Material { + uint32_t index{}; + MaterialTexture texA; + MaterialTexture texB; + std::string name{}; + + uint64_t colorCombiner{}; + uint64_t otherModeValue{}; + uint64_t otherModeMask{}; + uint32_t blendMode{}; + uint32_t drawFlags{}; + + uint32_t uuid{}; + uint8_t fogMode{}; + uint8_t vertexFxFunc{}; + + uint8_t primColor[4]{}; + uint8_t envColor[4]{}; + uint8_t blendColor[4]{}; + + bool setPrimColor{false}; + bool setEnvColor{false}; + bool setBlendColor{false}; + bool uvFilterAdjust{false}; + }; + + struct MeshChunk { + std::vector indices{}; + std::vector stripIndices[4]{}; + uint8_t seqStart{0}; + uint8_t seqCount{0}; + Material material{}; + uint32_t vertexOffset{0}; + uint32_t vertexCount{0}; + uint32_t vertexDestOffset{0}; + uint32_t boneIndex{0}; + uint32_t boneCount{0}; + std::string name{}; + }; + + struct Model { + std::vector triangles{}; + std::string name{}; + Material material{}; + }; + + struct ModelChunked { + std::vector vertices{}; + std::vector chunks{}; + + std::unordered_map vertIdxMap{}; + + Material materialA{}; + Material materialB{}; + + s16 aabbMin[3]{}; + s16 aabbMax[3]{}; + u16 triCount{}; + }; + + struct Bone { + std::string name; + Vec3 pos; + Quat rot; + Vec3 scale; + + Mat4 parentMatrix; + Mat4 modelMatrix; + Mat4 inverseBindPose; + + uint32_t index; + uint32_t parentIndex; + std::vector> children; + }; + + typedef enum AnimChannelTarget : u8 { + TRANSLATION, + SCALE, + SCALE_UNIFORM, + ROTATION + } AnimChannelTarget; + + struct Keyframe { + float time{}; + float timeNeeded{}; + float timeNextInChannel{}; + + uint16_t timeTicks{}; + uint16_t timeNeededTicks{}; + uint16_t timeNextInChannelTicks{}; + + uint32_t chanelIdx; + Quat valQuat; + float valScalar; + + uint32_t valQuantSize = 0; + uint16_t valQuant[2]; + }; + + struct AnimChannelMapping { + std::string targetName{}; + uint16_t targetIdx{}; + AnimChannelTarget targetType{}; + uint8_t attributeIdx{}; + + float valueMin{INFINITY}; + float valueMax{-INFINITY}; + + std::vector keyframes{}; // temp. storage after parsing + + [[nodiscard]] constexpr bool isRotation() const { + return targetType == AnimChannelTarget::ROTATION; + } + }; + + struct Anim { + std::string name{}; + float duration{}; + uint32_t channelCountQuat{}; + uint32_t channelCountScalar{}; + std::vector keyframes{}; // output used for writing to the file + std::vector channelMap{}; + }; + + struct T3DMData { + std::vector models{}; + std::vector skeletons{}; + std::vector animations{}; + }; + + struct Config { + float globalScale{64.0f}; + uint32_t animSampleRate{30}; + bool ignoreMaterials{false}; + bool createBVH{false}; + bool verbose{false}; + bool ignoreTransforms{false}; + std::string assetPath{}; + std::string assetPathFull{}; + }; + + constexpr int MAX_VERTEX_COUNT = 70; + constexpr int CACHE_VERTEX_SIZE = 36; + constexpr u8 T3DM_VERSION = 0x04; + + extern Config config; +} \ No newline at end of file From a62e147425ee483dde36d1fba496eb5dc1febf30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20Beb=C3=B6k?= Date: Sun, 14 Dec 2025 20:31:32 +0100 Subject: [PATCH 2/6] chg: gltf converter add file writer API, allow custom chunks --- tools/gltf_importer/src/main.cpp | 46 ++++++++++++++---------------- tools/gltf_importer/src/parser.cpp | 24 ++++++++++++++++ tools/gltf_importer/src/structs.h | 14 ++++++++- 3 files changed, 59 insertions(+), 25 deletions(-) diff --git a/tools/gltf_importer/src/main.cpp b/tools/gltf_importer/src/main.cpp index eca978d4..2144c16a 100644 --- a/tools/gltf_importer/src/main.cpp +++ b/tools/gltf_importer/src/main.cpp @@ -15,12 +15,11 @@ #include "binaryFile.h" #include "converter/converter.h" -#include "parser/rdp.h" #include "optimizer/optimizer.h" namespace T3DM { - constinit Config config{}; + thread_local Config config{}; } namespace fs = std::filesystem; @@ -110,30 +109,20 @@ int main(int argc, char* argv[]) config.animSampleRate = 60; auto t3dm = T3DM::parseGLTF(gltfPath.c_str(), config.globalScale); - fs::path gltfBasePath{gltfPath}; - - // sort models by transparency mode (opaque -> cutout -> transparent) - // within the same transparency mode, sort by material - std::sort(t3dm.models.begin(), t3dm.models.end(), [](const T3DM::Model &a, const T3DM::Model &b) { - bool isTranspA = a.material.blendMode == RDP::BLEND::MULTIPLY; - bool isTranspB = b.material.blendMode == RDP::BLEND::MULTIPLY; - if(isTranspA == isTranspB) { - if(a.material.uuid == b.material.uuid) { - return a.name < b.name; - } - return a.material.uuid < b.material.uuid; - } - if(!isTranspA && !isTranspB) { - int isDecalA = (a.material.otherModeValue & RDP::SOM::ZMODE_DECAL) ? 1 : 0; - int isDecalB = (b.material.otherModeValue & RDP::SOM::ZMODE_DECAL) ? 1 : 0; - return isDecalA < isDecalB; - } - return isTranspB; - }); + writeT3DM(t3dm, t3dmPath); +} + +void T3DM::writeT3DM( + const T3DM::T3DMData &t3dm, + const std::string &t3dmPath, + const std::vector &customChunks +) +{ + auto &config = T3DM::config; // de-dupe materials and determine material indices std::unordered_map materialUUIDMap{}; - std::vector usedMaterials{}; + std::vector usedMaterials{}; { uint32_t nextMatIndex = 0; for(auto &model : t3dm.models) { @@ -147,11 +136,14 @@ int main(int argc, char* argv[]) int16_t aabbMin[3] = {32767, 32767, 32767}; int16_t aabbMax[3] = {-32768, -32768, -32768}; + uint32_t chunkIndex = 0; uint32_t chunkCount = 2; // vertices + indices if(config.createBVH)chunkCount += 1; chunkCount += usedMaterials.size(); - std::vector modelChunks{}; + chunkCount += customChunks.size(); + + std::vector modelChunks{}; modelChunks.reserve(t3dm.models.size()); for(const auto & model : t3dm.models) { auto chunks = chunkUpModel(model); @@ -512,6 +504,12 @@ int main(int argc, char* argv[]) file.writeMemFile(chunkSkel); } + for(const auto &custom : customChunks) { + file.align(8); + addToChunkTable(custom.type); + file.writeArray(custom.data.data(), custom.data.size()); + } + // String table file.align(4); uint32_t stringTableOffset = file.getPos(); diff --git a/tools/gltf_importer/src/parser.cpp b/tools/gltf_importer/src/parser.cpp index d75cafbe..4e63c718 100644 --- a/tools/gltf_importer/src/parser.cpp +++ b/tools/gltf_importer/src/parser.cpp @@ -15,6 +15,10 @@ #include "lib/meshopt/meshoptimizer.h" #include "math/mat4.h" #include "parser/parser.h" + +#include + +#include "parser/rdp.h" #include "converter/converter.h" void printBoneTree(const T3DM::Bone &bone, int depth) @@ -306,5 +310,25 @@ T3DM::T3DMData T3DM::parseGLTF(const char *gltfPath, float modelScale) } cgltf_free(data); + + // sort models by transparency mode (opaque -> cutout -> transparent) + // within the same transparency mode, sort by material + std::sort(t3dm.models.begin(), t3dm.models.end(), [](const T3DM::Model &a, const T3DM::Model &b) { + bool isTranspA = a.material.blendMode == RDP::BLEND::MULTIPLY; + bool isTranspB = b.material.blendMode == RDP::BLEND::MULTIPLY; + if(isTranspA == isTranspB) { + if(a.material.uuid == b.material.uuid) { + return a.name < b.name; + } + return a.material.uuid < b.material.uuid; + } + if(!isTranspA && !isTranspB) { + int isDecalA = (a.material.otherModeValue & RDP::SOM::ZMODE_DECAL) ? 1 : 0; + int isDecalB = (b.material.otherModeValue & RDP::SOM::ZMODE_DECAL) ? 1 : 0; + return isDecalA < isDecalB; + } + return isTranspB; + }); + return t3dm; } diff --git a/tools/gltf_importer/src/structs.h b/tools/gltf_importer/src/structs.h index 0c66f6f7..e9acb657 100644 --- a/tools/gltf_importer/src/structs.h +++ b/tools/gltf_importer/src/structs.h @@ -244,6 +244,12 @@ namespace T3DM std::vector channelMap{}; }; + struct CustomChunk + { + char type{}; + std::vector data{}; + }; + struct T3DMData { std::vector models{}; std::vector skeletons{}; @@ -265,5 +271,11 @@ namespace T3DM constexpr int CACHE_VERTEX_SIZE = 36; constexpr u8 T3DM_VERSION = 0x04; - extern Config config; + extern thread_local Config config; + + void writeT3DM( + const T3DMData &t3dm, + const std::string &t3dmPath, + const std::vector &customChunks = {} + ); } \ No newline at end of file From dbd5eefb2e4fc7eb8cb509564436712e8253d5c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20Beb=C3=B6k?= Date: Sun, 14 Dec 2025 23:08:53 +0100 Subject: [PATCH 3/6] chg: gltf converter add file writer API, allow custom chunks --- tools/gltf_importer/Makefile | 2 +- tools/gltf_importer/src/main.cpp | 471 ---------------------------- tools/gltf_importer/src/writer.cpp | 483 +++++++++++++++++++++++++++++ 3 files changed, 484 insertions(+), 472 deletions(-) create mode 100644 tools/gltf_importer/src/writer.cpp diff --git a/tools/gltf_importer/Makefile b/tools/gltf_importer/Makefile index d17b6e5c..d185b079 100644 --- a/tools/gltf_importer/Makefile +++ b/tools/gltf_importer/Makefile @@ -3,7 +3,7 @@ OBJDIR = build SRCDIR = src INSTALLDIR = $(N64_INST) -OBJ = build/parser.o build/main.o build/lib/lodepng.o \ +OBJ = build/parser.o build/writer.o build/main.o build/lib/lodepng.o \ build/parser/materialParser.o build/parser/boneParser.o build/parser/nodeParser.o \ build/optimizer/meshOptimizer.o \ build/optimizer/meshBVH.o \ diff --git a/tools/gltf_importer/src/main.cpp b/tools/gltf_importer/src/main.cpp index 2144c16a..f89acc52 100644 --- a/tools/gltf_importer/src/main.cpp +++ b/tools/gltf_importer/src/main.cpp @@ -5,18 +5,11 @@ #include #include #include -#include -#include #include "structs.h" #include "parser.h" -#include "hash.h" #include "args.h" -#include "binaryFile.h" -#include "converter/converter.h" -#include "optimizer/optimizer.h" - namespace T3DM { thread_local Config config{}; @@ -24,50 +17,6 @@ namespace T3DM namespace fs = std::filesystem; -namespace { - uint32_t insertString(std::string &stringTable, const std::string &newString) { - auto strPos = stringTable.find(newString); - if(strPos == std::string::npos) { - strPos = stringTable.size(); - stringTable += newString; - stringTable.push_back('\0'); - } - return strPos; - } - - int writeBone(BinaryFile &file, const T3DM::Bone &bone, std::string &stringTable, int level) { - //printf("Bone[%d]: %s -> %d\n", bone.index, bone.name.c_str(), bone.parentIndex); - - file.write(insertString(stringTable, bone.name)); - file.write(bone.parentIndex); - file.write(level); // level - - auto normPos = bone.pos * T3DM::config.globalScale; - file.writeArray(bone.scale.data, 3); - file.writeArray(bone.rot.data, 4); - file.writeArray(normPos.data, 3); - - int boneCount = 1; - for(const auto& child : bone.children) { - boneCount += writeBone(file, *child, stringTable, level+1); - } - return boneCount; - }; - - std::string getRomPath(const std::string &path) { - if(path.find("filesystem/") == 0) { - return std::string("rom:/") + path.substr(11); - } - return path; - } - - std::string getStreamDataPath(const char* filePath, uint32_t idx) { - auto sdataPath = std::string(filePath).substr(0, std::string(filePath).size()-5); - std::replace(sdataPath.begin(), sdataPath.end(), '\\', '/'); - return sdataPath + "." + std::to_string(idx) + ".sdata"; - } -} - int main(int argc, char* argv[]) { EnvArgs args{argc, argv}; @@ -111,423 +60,3 @@ int main(int argc, char* argv[]) auto t3dm = T3DM::parseGLTF(gltfPath.c_str(), config.globalScale); writeT3DM(t3dm, t3dmPath); } - -void T3DM::writeT3DM( - const T3DM::T3DMData &t3dm, - const std::string &t3dmPath, - const std::vector &customChunks -) -{ - auto &config = T3DM::config; - - // de-dupe materials and determine material indices - std::unordered_map materialUUIDMap{}; - std::vector usedMaterials{}; - { - uint32_t nextMatIndex = 0; - for(auto &model : t3dm.models) { - auto matIdxIt = materialUUIDMap.find(model.material.uuid); - if(matIdxIt == materialUUIDMap.end()) { - materialUUIDMap.emplace(model.material.uuid, nextMatIndex++); - usedMaterials.push_back(&model.material); - } - } - } - - int16_t aabbMin[3] = {32767, 32767, 32767}; - int16_t aabbMax[3] = {-32768, -32768, -32768}; - - uint32_t chunkIndex = 0; - uint32_t chunkCount = 2; // vertices + indices - if(config.createBVH)chunkCount += 1; - chunkCount += usedMaterials.size(); - chunkCount += customChunks.size(); - - std::vector modelChunks{}; - modelChunks.reserve(t3dm.models.size()); - for(const auto & model : t3dm.models) { - auto chunks = chunkUpModel(model); - if(config.verbose) { - printf("[%s] Vertices out: %ld\n", model.name.c_str(), chunks.vertices.size()); - } - optimizeModelChunk(chunks); - - if(config.verbose) { - int totalIdx=0, totalStrips=0, totalStripCmd = 0; - for(auto &c : chunks.chunks) { - printf("[%s:part-%ld] Vert: %d | Idx-Tris: %ld | Idx-Strip: %ld %ld %ld %ld\n", - model.name.c_str(), - &c - &chunks.chunks[0], - c.vertexCount, - c.indices.size(), - c.stripIndices[0].size(), c.stripIndices[1].size(), - c.stripIndices[2].size(), c.stripIndices[3].size() - ); - totalIdx += c.indices.size(); - totalStrips += c.stripIndices[0].size() + c.stripIndices[1].size() + c.stripIndices[2].size() + c.stripIndices[3].size(); - totalStripCmd += !c.stripIndices[0].empty() + !c.stripIndices[1].empty() + !c.stripIndices[2].empty() + !c.stripIndices[3].empty(); - } - printf("[%s] Idx-Tris: %d, Idx-Strip: %d (commands: %d)\n", model.name.c_str(), totalIdx, totalStrips, totalStripCmd); - } - - chunks.triCount = model.triangles.size(); - modelChunks.push_back(chunks); - chunkCount += 1; // object - - aabbMin[0] = std::min(aabbMin[0], chunks.aabbMin[0]); - aabbMin[1] = std::min(aabbMin[1], chunks.aabbMin[1]); - aabbMin[2] = std::min(aabbMin[2], chunks.aabbMin[2]); - - aabbMax[0] = std::max(aabbMax[0], chunks.aabbMax[0]); - aabbMax[1] = std::max(aabbMax[1], chunks.aabbMax[1]); - aabbMax[2] = std::max(aabbMax[2], chunks.aabbMax[2]); - } - chunkCount += t3dm.skeletons.empty() ? 0 : 1; - chunkCount += t3dm.animations.size(); - - std::vector streamFiles{}; - - // Main file - BinaryFile file{}; - file.writeChars("T3M", 3); - file.write(T3DM::T3DM_VERSION); - file.write(chunkCount); // chunk count - - file.write(0); // total vertex count (set later) - file.write(0); // total index count (set later) - - uint32_t offsetChunkTypeTable = file.getPos(); - file.skip(3 * sizeof(uint32_t)); // chunk type indices (filled later) - - uint32_t offsetStringTablePtr = file.getPos(); - file.skip(sizeof(uint32_t)); // string table offset (filled later) - - file.write(0); // block, set by users at runtime - file.writeArray(aabbMin, 3); - file.writeArray(aabbMax, 3); - - uint32_t offsetChunkTable = file.getPos(); - file.skip(chunkCount * sizeof(uint32_t)); // chunk-table - - auto addToChunkTable = [&](char type) { - uint32_t offset = file.posPush(); - file.setPos(offsetChunkTable); - file.writeChunkPointer(type, offset); - offsetChunkTable = file.getPos(); - file.posPop(); - ++chunkIndex; - }; - - auto addChunkTypeIndex = [&]() { - file.posPush(); - file.setPos(offsetChunkTypeTable); - file.write(chunkIndex); - offsetChunkTypeTable = file.getPos(); - file.posPop(); - }; - - // Chunks - BinaryFile chunkVerts{}; - BinaryFile chunkIndices{}; - BinaryFile chunkBVH{}; - std::vector> chunkMaterials{}; - std::vector chunkSkeletons{}; - - std::string stringTable = "S"; - - // now write out each model (aka. collection of mesh-parts + materials) - int m=0; - uint16_t totalVertCount = 0; - uint16_t totalIndexCount = 0; - - if(!t3dm.skeletons.empty()) - { - auto &chunkBone = chunkSkeletons.emplace_back(); - chunkBone.skip(4); // size, filed later - - int boneCount = 0; - for(auto &skel : t3dm.skeletons) { - boneCount += writeBone(chunkBone, skel, stringTable, 0); - } - - chunkBone.setPos(0); - chunkBone.write(boneCount); - } - - if(config.createBVH) { - auto bvhData = createMeshBVH(modelChunks); - chunkBVH.writeArray(bvhData.data(), bvhData.size()); - } - - // write used materials - for(auto &material_ : usedMaterials) { - auto &material = *material_; - auto f = std::make_shared(); - f->write(material.colorCombiner); - f->write(material.otherModeValue); - f->write(material.otherModeMask); - f->write(material.blendMode); - f->write(material.drawFlags); - - f->write(0); - f->write(material.fogMode); - f->write( - material.setPrimColor | - (material.setEnvColor << 1) | - (material.setBlendColor << 2) - ); - f->write(material.vertexFxFunc); - - f->writeArray(material.primColor, 4); - f->writeArray(material.envColor, 4); - f->writeArray(material.blendColor, 4); - f->write(insertString(stringTable, material.name)); - - // @TODO: refactor materials to match file/runtime structure - std::vector materials{&material.texA, &material.texB}; - for(const T3DM::MaterialTexture* mat_ : materials) { - const T3DM::MaterialTexture&mat = *mat_; - - f->write(mat.texReference); - std::string texPath = ""; - if(!mat.texPath.empty()) { - texPath = fs::relative(mat.texPath, std::filesystem::current_path()).string(); - std::replace(texPath.begin(), texPath.end(), '\\', '/'); - - if(texPath.find(config.assetPath) == 0) { - texPath.replace(0, config.assetPath.size(), "rom:/"); - } - if(texPath.find(".png") != std::string::npos) { - texPath.replace(texPath.find(".png"), 4, ".sprite"); - } - } - - if(!texPath.empty()) { - // check if string already exits - auto strPos = insertString(stringTable, texPath); - - uint32_t hash = stringHash(texPath); - //printf("Texture: %s (%d)\n", texPath.c_str(), hash); - f->write((uint32_t)strPos); - f->write(hash); - - } else { - f->write(0); - // if no texture is set, use the reference as hash - // this is needed to force a reevaluation of the texture state - f->write(mat.texReference); - } - - f->write((uint32_t)0); // runtime pointer - f->write((uint16_t)mat.texWidth); - f->write((uint16_t)mat.texHeight); - - auto writeTile = [&](const T3DM::TileParam &tile) { - f->write(tile.low); - f->write(tile.high); - f->write(tile.mask); - f->write(tile.shift); - f->write(tile.mirror); - f->write(tile.clamp); - }; - writeTile(mat.s); - writeTile(mat.t); - } - - chunkMaterials.push_back(f); - } - - file.align(8); - for(auto &model : t3dm.models) - { - addToChunkTable('O'); - uint32_t matIdx = materialUUIDMap[model.material.uuid]; - - // write object chunk - const auto &chunks = modelChunks[m]; - file.write(insertString(stringTable, chunks.chunks.back().name)); - file.write((uint16_t)chunks.chunks.size()); - file.write(chunks.triCount); - file.write(matIdx); - file.write(0); // block, set at runtime - file.write(0); // visibility, set at runtime + padding - file.writeArray(chunks.aabbMin, 3); - file.writeArray(chunks.aabbMax, 3); - - //printf("Object %d: %d vert offset\n", m, chunkVerts.getPos()); - - // Write parts, these are a collection of indices after a vertex-slice load - for(const auto& chunk : chunks.chunks) - { - //printf(" t3d_vert_load(vertices, %d, %d);\n", chunk.vertexOffset, chunk.vertexCount); - uint32_t partVertOffset = (chunk.vertexOffset * T3DM::VertexT3D::byteSize()); - partVertOffset += chunkVerts.getPos(); - - file.write(partVertOffset); - file.write(chunk.vertexCount); - file.write(chunk.vertexDestOffset); - file.write(chunkIndices.getPos()); - file.write((uint16_t)chunk.indices.size()); - file.write(chunk.boneIndex); // Matrix/Bone index - file.write((uint8_t)chunk.stripIndices[0].size()); - file.write((uint8_t)chunk.stripIndices[1].size()); - file.write((uint8_t)chunk.stripIndices[2].size()); - file.write((uint8_t)chunk.stripIndices[3].size()); - file.write(chunk.seqStart); - file.write(chunk.seqCount); - file.write(0); - file.write(0); - - // write indices data - chunkIndices.writeArray(chunk.indices.data(), chunk.indices.size()); - for(const auto & stripIndex : chunk.stripIndices) { - if(stripIndex.empty())break; - chunkIndices.align(8); - chunkIndices.writeArray(stripIndex.data(), stripIndex.size()); - } - - totalIndexCount += chunk.indices.size(); - } - - // vertex buffer - //printf(" Verts: %d\n", chunks.vertices.size()); - for(auto v=0; v(anim.duration); - file.write(anim.keyframes.size()); - file.write(anim.channelCountQuat); - file.write(anim.channelCountScalar); - file.write(insertString(stringTable, - getRomPath(getStreamDataPath(t3dmPath.c_str(), animIdx)) - )); - - std::unordered_set channelHasKF{}; - for(int k=0; k= anim.keyframes.size()-1); - const auto &kf = anim.keyframes[k]; - const auto &kfNext = isLastKF ? kf : anim.keyframes[k+1]; - - bool nextIsLarge = kfNext.valQuantSize > 1; - - uint16_t timeNext = kf.timeNextInChannelTicks; - assert(timeNext < (1 << 15)); // prevent conflicts with size flag - if(nextIsLarge)timeNext |= (1 << 15); // encode size of the next KF here - - //printf("KF[%d]: %.4f, needed: %.4f, next: %.4f\n", k, kf.time, kf.timeNeeded, kf.timeNextInChannel); - - streamFile.write(timeNext); - streamFile.write(kf.chanelIdx); - for(int v=0; v(kf.valQuant[v]); - } - - // force the first keyframe to have 2 values, this is to have a known initial state - if(k == 0 && kf.valQuantSize == 1) { - streamFile.write(0); - } - } - streamFiles.push_back(streamFile); - - for(const auto &ch : anim.channelMap) { - file.write(ch.targetIdx); - file.write(ch.targetType); - file.write(ch.attributeIdx); - file.write((ch.valueMax - ch.valueMin) / (float)0xFFFF); - file.write(ch.valueMin); - } - - ++animIdx; - } - - // Now patch all chunks together and write out the chunk-table - - if(config.createBVH) { - file.align(8); - addToChunkTable('B'); - file.writeMemFile(chunkBVH); - } - - file.align(16); - addChunkTypeIndex(); - addToChunkTable('V'); - file.writeMemFile(chunkVerts); - - file.align(4); - addChunkTypeIndex(); - addToChunkTable('I'); - file.writeMemFile(chunkIndices); - - addChunkTypeIndex(); - for(auto &f : chunkMaterials) { - file.align(8); - addToChunkTable('M'); - file.writeMemFile(*f); - } - - for(const auto &chunkSkel : chunkSkeletons) { - file.align(8); - addToChunkTable('S'); - file.writeMemFile(chunkSkel); - } - - for(const auto &custom : customChunks) { - file.align(8); - addToChunkTable(custom.type); - file.writeArray(custom.data.data(), custom.data.size()); - } - - // String table - file.align(4); - uint32_t stringTableOffset = file.getPos(); - file.write(stringTable); - - file.setPos(offsetStringTablePtr); - file.write(stringTableOffset); - - // patch vertex/index count - file.setPos(0x08); - file.write(totalVertCount); - file.write(totalIndexCount); - - // write to actual file - file.writeToFile(t3dmPath.c_str()); - - for(int s=0; s +#include +#include +#include +#include + +#include "structs.h" +#include "hash.h" +#include "args.h" + +#include "binaryFile.h" +#include "converter/converter.h" +#include "optimizer/optimizer.h" + +namespace fs = std::filesystem; + +namespace { + uint32_t insertString(std::string &stringTable, const std::string &newString) { + auto strPos = stringTable.find(newString); + if(strPos == std::string::npos) { + strPos = stringTable.size(); + stringTable += newString; + stringTable.push_back('\0'); + } + return strPos; + } + + int writeBone(BinaryFile &file, const T3DM::Bone &bone, std::string &stringTable, int level) { + //printf("Bone[%d]: %s -> %d\n", bone.index, bone.name.c_str(), bone.parentIndex); + + file.write(insertString(stringTable, bone.name)); + file.write(bone.parentIndex); + file.write(level); // level + + auto normPos = bone.pos * T3DM::config.globalScale; + file.writeArray(bone.scale.data, 3); + file.writeArray(bone.rot.data, 4); + file.writeArray(normPos.data, 3); + + int boneCount = 1; + for(const auto& child : bone.children) { + boneCount += writeBone(file, *child, stringTable, level+1); + } + return boneCount; + }; + + std::string getRomPath(const std::string &path) { + if(path.find("filesystem/") == 0) { + return std::string("rom:/") + path.substr(11); + } + return path; + } + + std::string getStreamDataPath(const char* filePath, uint32_t idx) { + auto sdataPath = std::string(filePath).substr(0, std::string(filePath).size()-5); + std::replace(sdataPath.begin(), sdataPath.end(), '\\', '/'); + return sdataPath + "." + std::to_string(idx) + ".sdata"; + } +} + +void T3DM::writeT3DM( + const T3DMData &t3dm, + const std::string &t3dmPath, + const std::vector &customChunks +) +{ + auto &config = T3DM::config; + + // de-dupe materials and determine material indices + std::unordered_map materialUUIDMap{}; + std::vector usedMaterials{}; + { + uint32_t nextMatIndex = 0; + for(auto &model : t3dm.models) { + auto matIdxIt = materialUUIDMap.find(model.material.uuid); + if(matIdxIt == materialUUIDMap.end()) { + materialUUIDMap.emplace(model.material.uuid, nextMatIndex++); + usedMaterials.push_back(&model.material); + } + } + } + + int16_t aabbMin[3] = {32767, 32767, 32767}; + int16_t aabbMax[3] = {-32768, -32768, -32768}; + + uint32_t chunkIndex = 0; + uint32_t chunkCount = 2; // vertices + indices + if(config.createBVH)chunkCount += 1; + chunkCount += usedMaterials.size(); + chunkCount += customChunks.size(); + + std::vector modelChunks{}; + modelChunks.reserve(t3dm.models.size()); + for(const auto & model : t3dm.models) { + auto chunks = chunkUpModel(model); + if(config.verbose) { + printf("[%s] Vertices out: %ld\n", model.name.c_str(), chunks.vertices.size()); + } + optimizeModelChunk(chunks); + + if(config.verbose) { + int totalIdx=0, totalStrips=0, totalStripCmd = 0; + for(auto &c : chunks.chunks) { + printf("[%s:part-%ld] Vert: %d | Idx-Tris: %ld | Idx-Strip: %ld %ld %ld %ld\n", + model.name.c_str(), + &c - &chunks.chunks[0], + c.vertexCount, + c.indices.size(), + c.stripIndices[0].size(), c.stripIndices[1].size(), + c.stripIndices[2].size(), c.stripIndices[3].size() + ); + totalIdx += c.indices.size(); + totalStrips += c.stripIndices[0].size() + c.stripIndices[1].size() + c.stripIndices[2].size() + c.stripIndices[3].size(); + totalStripCmd += !c.stripIndices[0].empty() + !c.stripIndices[1].empty() + !c.stripIndices[2].empty() + !c.stripIndices[3].empty(); + } + printf("[%s] Idx-Tris: %d, Idx-Strip: %d (commands: %d)\n", model.name.c_str(), totalIdx, totalStrips, totalStripCmd); + } + + chunks.triCount = model.triangles.size(); + modelChunks.push_back(chunks); + chunkCount += 1; // object + + aabbMin[0] = std::min(aabbMin[0], chunks.aabbMin[0]); + aabbMin[1] = std::min(aabbMin[1], chunks.aabbMin[1]); + aabbMin[2] = std::min(aabbMin[2], chunks.aabbMin[2]); + + aabbMax[0] = std::max(aabbMax[0], chunks.aabbMax[0]); + aabbMax[1] = std::max(aabbMax[1], chunks.aabbMax[1]); + aabbMax[2] = std::max(aabbMax[2], chunks.aabbMax[2]); + } + chunkCount += t3dm.skeletons.empty() ? 0 : 1; + chunkCount += t3dm.animations.size(); + + std::vector streamFiles{}; + + // Main file + BinaryFile file{}; + file.writeChars("T3M", 3); + file.write(T3DM::T3DM_VERSION); + file.write(chunkCount); // chunk count + + file.write(0); // total vertex count (set later) + file.write(0); // total index count (set later) + + uint32_t offsetChunkTypeTable = file.getPos(); + file.skip(3 * sizeof(uint32_t)); // chunk type indices (filled later) + + uint32_t offsetStringTablePtr = file.getPos(); + file.skip(sizeof(uint32_t)); // string table offset (filled later) + + file.write(0); // block, set by users at runtime + file.writeArray(aabbMin, 3); + file.writeArray(aabbMax, 3); + + uint32_t offsetChunkTable = file.getPos(); + file.skip(chunkCount * sizeof(uint32_t)); // chunk-table + + auto addToChunkTable = [&](char type) { + uint32_t offset = file.posPush(); + file.setPos(offsetChunkTable); + file.writeChunkPointer(type, offset); + offsetChunkTable = file.getPos(); + file.posPop(); + ++chunkIndex; + }; + + auto addChunkTypeIndex = [&]() { + file.posPush(); + file.setPos(offsetChunkTypeTable); + file.write(chunkIndex); + offsetChunkTypeTable = file.getPos(); + file.posPop(); + }; + + // Chunks + BinaryFile chunkVerts{}; + BinaryFile chunkIndices{}; + BinaryFile chunkBVH{}; + std::vector> chunkMaterials{}; + std::vector chunkSkeletons{}; + + std::string stringTable = "S"; + + // now write out each model (aka. collection of mesh-parts + materials) + int m=0; + uint16_t totalVertCount = 0; + uint16_t totalIndexCount = 0; + + if(!t3dm.skeletons.empty()) + { + auto &chunkBone = chunkSkeletons.emplace_back(); + chunkBone.skip(4); // size, filed later + + int boneCount = 0; + for(auto &skel : t3dm.skeletons) { + boneCount += writeBone(chunkBone, skel, stringTable, 0); + } + + chunkBone.setPos(0); + chunkBone.write(boneCount); + } + + if(config.createBVH) { + auto bvhData = createMeshBVH(modelChunks); + chunkBVH.writeArray(bvhData.data(), bvhData.size()); + } + + // write used materials + for(auto &material_ : usedMaterials) { + auto &material = *material_; + auto f = std::make_shared(); + f->write(material.colorCombiner); + f->write(material.otherModeValue); + f->write(material.otherModeMask); + f->write(material.blendMode); + f->write(material.drawFlags); + + f->write(0); + f->write(material.fogMode); + f->write( + material.setPrimColor | + (material.setEnvColor << 1) | + (material.setBlendColor << 2) + ); + f->write(material.vertexFxFunc); + + f->writeArray(material.primColor, 4); + f->writeArray(material.envColor, 4); + f->writeArray(material.blendColor, 4); + f->write(insertString(stringTable, material.name)); + + // @TODO: refactor materials to match file/runtime structure + std::vector materials{&material.texA, &material.texB}; + for(const T3DM::MaterialTexture* mat_ : materials) { + const T3DM::MaterialTexture&mat = *mat_; + + f->write(mat.texReference); + std::string texPath = ""; + if(!mat.texPath.empty()) { + texPath = fs::relative(mat.texPath, std::filesystem::current_path()).string(); + std::replace(texPath.begin(), texPath.end(), '\\', '/'); + + if(texPath.find(config.assetPath) == 0) { + texPath.replace(0, config.assetPath.size(), "rom:/"); + } + if(texPath.find(".png") != std::string::npos) { + texPath.replace(texPath.find(".png"), 4, ".sprite"); + } + } + + if(!texPath.empty()) { + // check if string already exits + auto strPos = insertString(stringTable, texPath); + + uint32_t hash = stringHash(texPath); + //printf("Texture: %s (%d)\n", texPath.c_str(), hash); + f->write((uint32_t)strPos); + f->write(hash); + + } else { + f->write(0); + // if no texture is set, use the reference as hash + // this is needed to force a reevaluation of the texture state + f->write(mat.texReference); + } + + f->write((uint32_t)0); // runtime pointer + f->write((uint16_t)mat.texWidth); + f->write((uint16_t)mat.texHeight); + + auto writeTile = [&](const T3DM::TileParam &tile) { + f->write(tile.low); + f->write(tile.high); + f->write(tile.mask); + f->write(tile.shift); + f->write(tile.mirror); + f->write(tile.clamp); + }; + writeTile(mat.s); + writeTile(mat.t); + } + + chunkMaterials.push_back(f); + } + + file.align(8); + for(auto &model : t3dm.models) + { + addToChunkTable('O'); + uint32_t matIdx = materialUUIDMap[model.material.uuid]; + + // write object chunk + const auto &chunks = modelChunks[m]; + file.write(insertString(stringTable, chunks.chunks.back().name)); + file.write((uint16_t)chunks.chunks.size()); + file.write(chunks.triCount); + file.write(matIdx); + file.write(0); // block, set at runtime + file.write(0); // visibility, set at runtime + padding + file.writeArray(chunks.aabbMin, 3); + file.writeArray(chunks.aabbMax, 3); + + //printf("Object %d: %d vert offset\n", m, chunkVerts.getPos()); + + // Write parts, these are a collection of indices after a vertex-slice load + for(const auto& chunk : chunks.chunks) + { + //printf(" t3d_vert_load(vertices, %d, %d);\n", chunk.vertexOffset, chunk.vertexCount); + uint32_t partVertOffset = (chunk.vertexOffset * T3DM::VertexT3D::byteSize()); + partVertOffset += chunkVerts.getPos(); + + file.write(partVertOffset); + file.write(chunk.vertexCount); + file.write(chunk.vertexDestOffset); + file.write(chunkIndices.getPos()); + file.write((uint16_t)chunk.indices.size()); + file.write(chunk.boneIndex); // Matrix/Bone index + file.write((uint8_t)chunk.stripIndices[0].size()); + file.write((uint8_t)chunk.stripIndices[1].size()); + file.write((uint8_t)chunk.stripIndices[2].size()); + file.write((uint8_t)chunk.stripIndices[3].size()); + file.write(chunk.seqStart); + file.write(chunk.seqCount); + file.write(0); + file.write(0); + + // write indices data + chunkIndices.writeArray(chunk.indices.data(), chunk.indices.size()); + for(const auto & stripIndex : chunk.stripIndices) { + if(stripIndex.empty())break; + chunkIndices.align(8); + chunkIndices.writeArray(stripIndex.data(), stripIndex.size()); + } + + totalIndexCount += chunk.indices.size(); + } + + // vertex buffer + //printf(" Verts: %d\n", chunks.vertices.size()); + for(auto v=0; v(anim.duration); + file.write(anim.keyframes.size()); + file.write(anim.channelCountQuat); + file.write(anim.channelCountScalar); + file.write(insertString(stringTable, + getRomPath(getStreamDataPath(t3dmPath.c_str(), animIdx)) + )); + + std::unordered_set channelHasKF{}; + for(int k=0; k= anim.keyframes.size()-1); + const auto &kf = anim.keyframes[k]; + const auto &kfNext = isLastKF ? kf : anim.keyframes[k+1]; + + bool nextIsLarge = kfNext.valQuantSize > 1; + + uint16_t timeNext = kf.timeNextInChannelTicks; + assert(timeNext < (1 << 15)); // prevent conflicts with size flag + if(nextIsLarge)timeNext |= (1 << 15); // encode size of the next KF here + + //printf("KF[%d]: %.4f, needed: %.4f, next: %.4f\n", k, kf.time, kf.timeNeeded, kf.timeNextInChannel); + + streamFile.write(timeNext); + streamFile.write(kf.chanelIdx); + for(int v=0; v(kf.valQuant[v]); + } + + // force the first keyframe to have 2 values, this is to have a known initial state + if(k == 0 && kf.valQuantSize == 1) { + streamFile.write(0); + } + } + streamFiles.push_back(streamFile); + + for(const auto &ch : anim.channelMap) { + file.write(ch.targetIdx); + file.write(ch.targetType); + file.write(ch.attributeIdx); + file.write((ch.valueMax - ch.valueMin) / (float)0xFFFF); + file.write(ch.valueMin); + } + + ++animIdx; + } + + // Now patch all chunks together and write out the chunk-table + + if(config.createBVH) { + file.align(8); + addToChunkTable('B'); + file.writeMemFile(chunkBVH); + } + + file.align(16); + addChunkTypeIndex(); + addToChunkTable('V'); + file.writeMemFile(chunkVerts); + + file.align(4); + addChunkTypeIndex(); + addToChunkTable('I'); + file.writeMemFile(chunkIndices); + + addChunkTypeIndex(); + for(auto &f : chunkMaterials) { + file.align(8); + addToChunkTable('M'); + file.writeMemFile(*f); + } + + for(const auto &chunkSkel : chunkSkeletons) { + file.align(8); + addToChunkTable('S'); + file.writeMemFile(chunkSkel); + } + + for(const auto &custom : customChunks) { + file.align(8); + addToChunkTable(custom.type); + file.writeArray(custom.data.data(), custom.data.size()); + } + + // String table + file.align(4); + uint32_t stringTableOffset = file.getPos(); + file.write(stringTable); + + file.setPos(offsetStringTablePtr); + file.write(stringTableOffset); + + // patch vertex/index count + file.setPos(0x08); + file.write(totalVertCount); + file.write(totalIndexCount); + + // write to actual file + file.writeToFile(t3dmPath.c_str()); + + for(int s=0; s Date: Sun, 14 Dec 2025 23:30:10 +0100 Subject: [PATCH 4/6] chg: gltf converter add file writer API, allow custom chunks --- tools/gltf_importer/src/main.cpp | 2 +- tools/gltf_importer/src/parser.cpp | 4 ++-- tools/gltf_importer/src/parser.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/gltf_importer/src/main.cpp b/tools/gltf_importer/src/main.cpp index f89acc52..9250528e 100644 --- a/tools/gltf_importer/src/main.cpp +++ b/tools/gltf_importer/src/main.cpp @@ -57,6 +57,6 @@ int main(int argc, char* argv[]) config.animSampleRate = 60; - auto t3dm = T3DM::parseGLTF(gltfPath.c_str(), config.globalScale); + auto t3dm = T3DM::parseGLTF(gltfPath.c_str()); writeT3DM(t3dm, t3dmPath); } diff --git a/tools/gltf_importer/src/parser.cpp b/tools/gltf_importer/src/parser.cpp index 4e63c718..d5ba41a0 100644 --- a/tools/gltf_importer/src/parser.cpp +++ b/tools/gltf_importer/src/parser.cpp @@ -31,7 +31,7 @@ void printBoneTree(const T3DM::Bone &bone, int depth) } } -T3DM::T3DMData T3DM::parseGLTF(const char *gltfPath, float modelScale) +T3DM::T3DMData T3DM::parseGLTF(const char *gltfPath) { T3DMData t3dm{}; fs::path gltfBasePath{gltfPath}; @@ -282,7 +282,7 @@ T3DM::T3DMData T3DM::parseGLTF(const char *gltfPath, float modelScale) Mat4 mat = config.ignoreTransforms ? Mat4{} : parseNodeMatrix(node, true); for(int k = 0; k < vertices.size(); k++) { convertVertex( - modelScale, texSizeX, texSizeY, vertices[k], verticesT3D[k], + config.globalScale, texSizeX, texSizeY, vertices[k], verticesT3D[k], mat, matrixStack, model.material.uvFilterAdjust ); } diff --git a/tools/gltf_importer/src/parser.h b/tools/gltf_importer/src/parser.h index f629b1d2..2c422311 100644 --- a/tools/gltf_importer/src/parser.h +++ b/tools/gltf_importer/src/parser.h @@ -9,5 +9,5 @@ namespace T3DM { - T3DMData parseGLTF(const char* gltfPath, float modelScale); + T3DMData parseGLTF(const char* gltfPath); } \ No newline at end of file From 8c2cd6581d9053e56a7b1c98a2b491d561cb34c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20Beb=C3=B6k?= Date: Mon, 15 Dec 2025 01:55:04 +0100 Subject: [PATCH 5/6] chg: gltf converter add file writer API, allow custom chunks --- tools/gltf_importer/src/main.cpp | 2 +- tools/gltf_importer/src/structs.h | 2 ++ tools/gltf_importer/src/writer.cpp | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/gltf_importer/src/main.cpp b/tools/gltf_importer/src/main.cpp index 9250528e..789228fa 100644 --- a/tools/gltf_importer/src/main.cpp +++ b/tools/gltf_importer/src/main.cpp @@ -58,5 +58,5 @@ int main(int argc, char* argv[]) config.animSampleRate = 60; auto t3dm = T3DM::parseGLTF(gltfPath.c_str()); - writeT3DM(t3dm, t3dmPath); + writeT3DM(t3dm, t3dmPath, std::filesystem::current_path()); } diff --git a/tools/gltf_importer/src/structs.h b/tools/gltf_importer/src/structs.h index e9acb657..050eb94b 100644 --- a/tools/gltf_importer/src/structs.h +++ b/tools/gltf_importer/src/structs.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include #include @@ -276,6 +277,7 @@ namespace T3DM void writeT3DM( const T3DMData &t3dm, const std::string &t3dmPath, + const std::filesystem::path &projectPath, const std::vector &customChunks = {} ); } \ No newline at end of file diff --git a/tools/gltf_importer/src/writer.cpp b/tools/gltf_importer/src/writer.cpp index acbc380c..b5e6651a 100644 --- a/tools/gltf_importer/src/writer.cpp +++ b/tools/gltf_importer/src/writer.cpp @@ -65,6 +65,7 @@ namespace { void T3DM::writeT3DM( const T3DMData &t3dm, const std::string &t3dmPath, + const std::filesystem::path &projectPath, const std::vector &customChunks ) { @@ -241,7 +242,7 @@ void T3DM::writeT3DM( f->write(mat.texReference); std::string texPath = ""; if(!mat.texPath.empty()) { - texPath = fs::relative(mat.texPath, std::filesystem::current_path()).string(); + texPath = fs::relative(mat.texPath, projectPath).string(); std::replace(texPath.begin(), texPath.end(), '\\', '/'); if(texPath.find(config.assetPath) == 0) { From e834d3905112f4b2bfc385b04bcac4481e54e141 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Max=20Beb=C3=B6k?= Date: Mon, 22 Dec 2025 12:52:01 +0100 Subject: [PATCH 6/6] fix: point-light pos/size precision --- examples/15_pointlight/main.c | 19 +++++++++---------- examples/16_light_clip/main.c | 4 ++-- examples/21_fresnel/main.c | 6 +++--- .../24_hdr_bloom/src/actors/lightBlock.cpp | 4 ++-- .../24_hdr_bloom/src/actors/pointGlobe.cpp | 4 ++-- src/t3d/rsp/rsp_tiny3d.S | 6 +++--- src/t3d/rsp/rsp_tiny3d.rspl | 4 ++-- src/t3d/t3d.c | 16 +++++++++++----- src/t3d/t3d.h | 7 +++---- 9 files changed, 37 insertions(+), 33 deletions(-) diff --git a/examples/15_pointlight/main.c b/examples/15_pointlight/main.c index 22708161..114a233a 100644 --- a/examples/15_pointlight/main.c +++ b/examples/15_pointlight/main.c @@ -73,13 +73,12 @@ int main() ); PointLight pointLights[5] = { // XYZ, strength, color - {{{ 150.0f, 20.0f, -5.0f}}, 0.055f, {0xFF, 0xFF, 0x1F, 0xFF}}, - {{{-150.0f, 20.0f, -5.0f}}, 0.055f, {0x1F, 0xFF, 0x1F, 0xFF}}, - {{{ 0.0f, 40.0f, -190.0f}}, 0.300f, {0x1F, 0x1F, 0xFF, 0xFF}}, - {{{ 0.0f, 20.0f, 30.0f}}, 0.055f, {0xFF, 0x1F, 0x1F, 0xFF}}, - {{{ 0.0f, 25.0f, 0.0f}}, 0.150f, {0xFF, 0x2F, 0x1F, 0xFF}}, + {{{ 150.0f, 20.0f, -5.0f}}, 40.0f, {0xFF, 0xFF, 0x1F, 0xFF}}, + {{{-150.0f, 20.0f, -5.0f}}, 40.0f, {0x1F, 0xFF, 0x1F, 0xFF}}, + {{{ 0.0f, 40.0f, -190.0f}}, 80.0f, {0x1F, 0x1F, 0xFF, 0xFF}}, + {{{ 0.0f, 20.0f, 30.0f}}, 40.0f, {0xFF, 0x1F, 0x1F, 0xFF}}, + {{{ 0.0f, 25.0f, 0.0f}}, 60.0f, {0xFF, 0x2F, 0x1F, 0xFF}}, }; - color_t lightColorOff = {0,0,0, 0xFF}; uint32_t currLight = 0; uint8_t colorAmbient[4] = {20, 20, 20, 0xFF}; @@ -131,8 +130,8 @@ int main() // Light controls float moveSpeed = deltaTime * 0.75f; - light->strength += (float)joypad.cstick_x * deltaTime * 0.002f; - light->strength = fminf(1.0f, fmaxf(0.0f, light->strength)); + light->strength += (float)joypad.cstick_x * deltaTime * 0.4f; + light->strength = fminf(190.0f, fmaxf(0.0f, light->strength)); light->pos.v[0] -= camDir.v[2] * (float)joypad.stick_x * -moveSpeed; light->pos.v[2] += camDir.v[0] * (float)joypad.stick_x * -moveSpeed; @@ -157,7 +156,7 @@ int main() // flickering and wobble of the crystal in the center of the room pointLights[4].pos.v[1] = 24.0f + (sinf(time*2.0f) * 3.5f); - pointLights[4].strength = 0.2f + pointLights[4].strength = 100.2f + (fm_sinf(time * 5.1f) * 0.015f) + ((fm_cosf(time * 6.2f)+1) * 0.0025f) + ((fm_sinf(time * 2.2f)+0.9f) * 0.01f) @@ -238,7 +237,7 @@ int main() rspq_block_run(dplDraw); for(int i=0; i> 8; - vmadn $v24, $v00, $v00 ## L:700 | 35 | ptPos = ptPos:sint >> 8; - sll $t7, $t7, 13 ## L:694 | ^ | lightSize <<= 13; + vmudm $v23, $v23, $v31.e5 ## L:700 | ^ | ptPos = ptPos:sint >> 6; + vmadn $v24, $v00, $v00 ## L:700 | 35 | ptPos = ptPos:sint >> 6; + sll $t7, $t7, 16 ## L:694 | ^ | lightSize <<= 16; vsubc $v24, $v24, $v28.v ## L:701 | ***39 | ptPos = ptPos - vertPos; vsub $v23, $v23, $v27.v ## L:701 | 40 | ptPos = ptPos - vertPos; vmudl $v29, $v24, $v24.v ## L:704 | **43 | lightVertDot = ptPos * ptPos; diff --git a/src/t3d/rsp/rsp_tiny3d.rspl b/src/t3d/rsp/rsp_tiny3d.rspl index d0471eab..712dab4e 100644 --- a/src/t3d/rsp/rsp_tiny3d.rspl +++ b/src/t3d/rsp/rsp_tiny3d.rspl @@ -562,13 +562,13 @@ function pointLight( } lightSize &= 0x7FFF; // MSB is "use-normal" flag - lightSize <<= 13; // shift s16 into s16 range + lightSize <<= 16; // shift s16 into s16 range // get light position ptPos:sint.xyzw = load(ptrLight, 8).xyzw; ptPos:sint.XYZW = load(ptrLight, 8).xyzw; - ptPos = ptPos:sint >> 8; // light position into 16.16 + ptPos = ptPos:sint >> 6; // light position into 16.16 ptPos = ptPos - vertPos; // get distance from light to vertex // calc. dot product and normalize distance vector diff --git a/src/t3d/t3d.c b/src/t3d/t3d.c index 347d8b1b..398ee305 100644 --- a/src/t3d/t3d.c +++ b/src/t3d/t3d.c @@ -209,16 +209,22 @@ void t3d_light_set_point(int index, const uint8_t *color, const T3DVec3 *pos, fl T3DVec4 posView; t3d_mat4_mul_vec3(&posView, ¤tViewport->matCamera, pos); - size *= 0.5f; + size = size * (1.0f / 0x2000); size = fmaxf(size, 0.0f); size = fminf(size, 0.5f); int32_t posFP[4] = { - (int32_t)(posView.v[0] * 16.0f) & 0xFFFF, - (int32_t)(posView.v[1] * 16.0f) & 0xFFFF, - (int32_t)(posView.v[2] * 16.0f) & 0xFFFF, - (int32_t)(size * 0xFFFF) & 0xFFFF + (int32_t)(posView.v[0] * 4.0f), + (int32_t)(posView.v[1] * 4.0f), + (int32_t)(posView.v[2] * 4.0f), + (int32_t)(size * 0xFFFF) }; + + for(int i=0; i<4; ++i) { + posFP[i] = CLAMP(posFP[i], -0x8000, 0x7FFF); + posFP[i] &= 0xFFFF; + } + if((posFP[3] & 0xFF) == 0)posFP[3] |= 1; // non-zero size is used as point-light detection if(ignoreNormals)posFP[3] |= 0x8000; // ignore normals diff --git a/src/t3d/t3d.h b/src/t3d/t3d.h index 2e40d624..eb256e7c 100644 --- a/src/t3d/t3d.h +++ b/src/t3d/t3d.h @@ -474,14 +474,13 @@ void t3d_light_set_directional(int index, const uint8_t *color, const T3DVec3 *d * The position is expected to be in world-space, and will be transformed internally. * Before calling this function, make sure to have a viewport attached. * - * The size argument is a normalized distance, in the range 0.0 - 1.0. - * Internally in the ucode, this maps to scaling factors in eye-space. - * So unlike 'pos', it doesn't have any concrete units. + * The size argument is roughly the maximum radius in world-space the light will cover. + * Note that due to precision limitations and the fact lighting happens per vertex, the size is not exact. * * @param index index (0-6) * @param color color in RGBA8 format * @param pos position in world-space - * @param size distance, in range 0.0 - 1.0 + * @param size distance, in world-space * @param ignoreNormals if true, the light will only check the distance, not the angle (useful for cutouts) */ void t3d_light_set_point(int index, const uint8_t *color, const T3DVec3 *pos, float size, bool ignoreNormals);