From 641fb572e876750d8618434d9f7c1a531b43b366 Mon Sep 17 00:00:00 2001
From: Hat Kid <6624576+Hat-Kid@users.noreply.github.com>
Date: Fri, 31 Jul 2026 06:15:24 +0200
Subject: [PATCH] decompiler: misc gltf exporter fixes (#4371)
This PR adds various fixes to the GLTF exporter:
- Merc and TIE envmaps now properly extract
- Changed joint offset in `convert_per_vertex_data` since we now export
the `align` bone
- The bone accessor for merc models now uses
`TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE`, fixing a decompiler crash when
attempting to reimport an extracted model into the game without first
importing it into Blender and re-exporting
- All model buffers now get merged into one large buffer before export,
saving some space and increasing parsing speed of GLB files
- Tfrags, TIEs and shrubs are now no longer stored in one giant mesh per
tree and get grouped into categories:
- Tfrags will be grouped by the tfrag tree name they come from (normal,
trans, dirt, ice, etc.) and get separated by the texture their material
uses (tfrags are not instanced, so this is the next best solution)
- TIEs and shrubs get grouped by tree index and separated by their proto
name (TIE wind will also be grouped separately)
---
common/util/gltf_util.cpp | 3 +-
decompiler/level_extractor/extract_shrub.cpp | 9 +-
decompiler/level_extractor/extract_tie.cpp | 13 +-
decompiler/level_extractor/fr3_to_gltf.cpp | 495 +++++++++++++++----
4 files changed, 422 insertions(+), 98 deletions(-)
diff --git a/common/util/gltf_util.cpp b/common/util/gltf_util.cpp
index 682fe224bf..12f8508e78 100644
--- a/common/util/gltf_util.cpp
+++ b/common/util/gltf_util.cpp
@@ -160,8 +160,7 @@ JointsAndWeights convert_per_vertex_data(const math::Vector4f& weights,
if (src == discard_idx) {
continue;
}
- // this +1 is to account for align not existing in the gltf.
- ret.joints[dst] = joints[src] + 2;
+ ret.joints[dst] = joints[src] + 1;
ret.weights[dst] = weights[src];
sum += ret.weights[dst];
dst++;
diff --git a/decompiler/level_extractor/extract_shrub.cpp b/decompiler/level_extractor/extract_shrub.cpp
index ec54718cf4..34591183be 100644
--- a/decompiler/level_extractor/extract_shrub.cpp
+++ b/decompiler/level_extractor/extract_shrub.cpp
@@ -511,8 +511,7 @@ void make_draws(tfrag3::Level& lev,
if (existing_draws_in_tex != static_draws_by_tex.end()) {
for (auto idx : existing_draws_in_tex->second) {
auto& candidate_draw_out = tree_out.static_draws.at(idx);
- if (candidate_draw_out.mode == mode && (!tree_out.has_per_proto_visibility_toggle ||
- candidate_draw_out.proto_idx == proto_idx)) {
+ if (candidate_draw_out.mode == mode && candidate_draw_out.proto_idx == proto_idx) {
draw_to_add_to = &tree_out.static_draws[idx];
verts_to_add_to = &indices_regrouped_by_draw[idx];
}
@@ -526,9 +525,9 @@ void make_draws(tfrag3::Level& lev,
draw_to_add_to = &tree_out.static_draws.back();
draw_to_add_to->mode = mode;
draw_to_add_to->tree_tex_id = idx_in_lev_data;
- if (tree_out.has_per_proto_visibility_toggle) {
- draw_to_add_to->proto_idx = proto_idx;
- }
+ // jak 1 has no per-proto visibility toggle, but the gltf exporter still uses this to
+ // split the tree back up into one mesh per prototype
+ draw_to_add_to->proto_idx = proto_idx;
verts_to_add_to = &indices_regrouped_by_draw.emplace_back();
}
diff --git a/decompiler/level_extractor/extract_tie.cpp b/decompiler/level_extractor/extract_tie.cpp
index 5d3d8d63e4..db7b71bd5c 100644
--- a/decompiler/level_extractor/extract_tie.cpp
+++ b/decompiler/level_extractor/extract_tie.cpp
@@ -2496,11 +2496,10 @@ void handle_draw_for_strip(tfrag3::TieTree& tree,
ASSERT(inst.vis_id < UINT16_MAX);
vgroup.vis_idx_in_pc_bvh = inst.vis_id; // associate with the instance for culling
- // only bother with tie proto idx if we use it
- if (tree.has_per_proto_visibility_toggle) {
- ASSERT(proto_idx < UINT16_MAX);
- vgroup.tie_proto_idx = proto_idx;
- }
+ // jak 1 has no per-proto visibility toggle, but the gltf exporter still uses this to split the
+ // tree back up into one mesh per prototype
+ ASSERT(proto_idx < UINT16_MAX);
+ vgroup.tie_proto_idx = proto_idx;
vgroup.num_inds = strip.verts.size() + 1; // one for the primitive restart!
vgroup.num_tris = strip.verts.size() - 2;
@@ -2560,9 +2559,7 @@ void add_vertices_and_static_draw(tfrag3::TieTree& tree,
// loop over all prototypes
for (size_t proto_idx = 0; proto_idx < protos.size(); proto_idx++) {
const auto& proto = protos[proto_idx];
- if (tree.has_per_proto_visibility_toggle) {
- tree.proto_names.push_back(proto.name);
- }
+ tree.proto_names.push_back(proto.name);
TieCategoryInfo info;
switch (version) {
diff --git a/decompiler/level_extractor/fr3_to_gltf.cpp b/decompiler/level_extractor/fr3_to_gltf.cpp
index c75e3202fb..acf73dc5de 100644
--- a/decompiler/level_extractor/fr3_to_gltf.cpp
+++ b/decompiler/level_extractor/fr3_to_gltf.cpp
@@ -1,6 +1,8 @@
#include "fr3_to_gltf.h"
#include
+#include