From 80e479611f63d973b58475f39dba356bf9e45110 Mon Sep 17 00:00:00 2001 From: Brian Hoem Date: Tue, 30 Jun 2026 13:01:02 -0700 Subject: [PATCH] Include merc normals for glb extraction (#4271) Namely all I did was include the normals from MercVertex in with the glb exporter. I also converted parts of MercVertex to use union structs for faster casting. The union structs should just work on most compilers from what I researched, if not I can revert that and go a different route for that. Co-authored-by: Hat Kid <6624576+Hat-Kid@users.noreply.github.com> --- common/custom_data/Tfrag3Data.h | 18 ++++++++-- decompiler/level_extractor/fr3_to_gltf.cpp | 38 ++++++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/common/custom_data/Tfrag3Data.h b/common/custom_data/Tfrag3Data.h index 360c3a1e3b..d1cac4c6d9 100644 --- a/common/custom_data/Tfrag3Data.h +++ b/common/custom_data/Tfrag3Data.h @@ -517,11 +517,23 @@ struct CollisionMesh { // MERC -struct MercVertex { - alignas(32) float pos[3]; +struct alignas(32) MercVertex { + union { + float pos[3]; + struct { + float x, y, z; + }; + math::Vector3f pos_vec; + }; float pad0; - float normal[3]; + union { + float normal[3]; + struct { + float nx, ny, nz; + }; + math::Vector3f normal_vec; + }; float pad1; float weights[3]; diff --git a/decompiler/level_extractor/fr3_to_gltf.cpp b/decompiler/level_extractor/fr3_to_gltf.cpp index 663883b812..c75e3202fb 100644 --- a/decompiler/level_extractor/fr3_to_gltf.cpp +++ b/decompiler/level_extractor/fr3_to_gltf.cpp @@ -806,6 +806,42 @@ int make_weights_accessor(const std::vector& vertices, tinyg return accessor_idx; } +int make_normal_buffer_accessor(const std::vector& vertices, + tinygltf::Model& model) { + // first create a buffer: + int buffer_idx = (int)model.buffers.size(); + auto& buffer = model.buffers.emplace_back(); + buffer.data.resize(sizeof(float) * 3 * vertices.size()); + + // and fill it + u8* buffer_ptr = buffer.data.data(); + for (const auto& vtx : vertices) { + auto tmp_vtx = vtx.normal_vec.normalized(); + memcpy(buffer_ptr, tmp_vtx.data(), 3 * sizeof(float)); + buffer_ptr += 3 * sizeof(float); + } + + // create a view of this buffer + int buffer_view_idx = (int)model.bufferViews.size(); + auto& buffer_view = model.bufferViews.emplace_back(); + buffer_view.name = "NORMAL"; + buffer_view.buffer = buffer_idx; + buffer_view.byteOffset = 0; + buffer_view.byteLength = buffer.data.size(); + buffer_view.byteStride = 0; // tightly packed + buffer_view.target = TINYGLTF_TARGET_ARRAY_BUFFER; + + int accessor_idx = (int)model.accessors.size(); + auto& accessor = model.accessors.emplace_back(); + accessor.name = "NORMAL"; + accessor.bufferView = buffer_view_idx; + accessor.byteOffset = 0; + accessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT; + accessor.count = vertices.size(); + accessor.type = TINYGLTF_TYPE_VEC3; + return accessor_idx; +} + int make_bones_accessor(const std::vector& vertices, tinygltf::Model& model) { // first create a buffer: int buffer_idx = (int)model.buffers.size(); @@ -1302,6 +1338,7 @@ void add_merc(const tfrag3::Level& level, auto joints_accessor = make_bones_accessor(mverts, model); auto weights_accessor = make_weights_accessor(mverts, model); + auto normal_buffer_accessor = make_normal_buffer_accessor(mverts, model); const auto& art = art_data.find(mmodel.name); int node_idx = (int)model.nodes.size(); @@ -1418,6 +1455,7 @@ void add_merc(const tfrag3::Level& level, prim.attributes["COLOR_0"] = colors; prim.attributes["JOINTS_0"] = joints_accessor; prim.attributes["WEIGHTS_0"] = weights_accessor; + prim.attributes["NORMAL"] = normal_buffer_accessor; prim.mode = TINYGLTF_MODE_TRIANGLES; } }