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)

<img width="1630" height="1716" alt="image"
src="https://github.com/user-attachments/assets/2ce31795-a445-4a0d-982d-413fd51833b2"
/>
<img width="497" height="291" alt="image"
src="https://github.com/user-attachments/assets/a8b44073-a33e-4c40-a5d7-2c7a328a86b4"
/>
<img width="484" height="750" alt="image"
src="https://github.com/user-attachments/assets/15392b94-60f3-4fee-97c5-cc6a50d298d5"
/>
<img width="525" height="684" alt="image"
src="https://github.com/user-attachments/assets/e3814105-7538-4028-9129-63b21d432528"
/>
<img width="1221" height="1298" alt="image"
src="https://github.com/user-attachments/assets/7aefa662-890a-4be8-9540-3e63fff52f28"
/>
This commit is contained in:
Hat Kid
2026-07-31 06:15:24 +02:00
committed by GitHub
parent a4cb1ac1f0
commit 641fb572e8
4 changed files with 422 additions and 98 deletions
+1 -2
View File
@@ -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++;
+4 -5
View File
@@ -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();
}
+5 -8
View File
@@ -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) {
+412 -83
View File
@@ -1,6 +1,8 @@
#include "fr3_to_gltf.h"
#include <algorithm>
#include <map>
#include <tuple>
#include "common/custom_data/Tfrag3Data.h"
#include "common/math/Vector.h"
@@ -92,33 +94,74 @@ void unstrip_tie_wind(std::vector<u32>& unstripped,
}
}
float merc_should_swap_tris(const std::vector<u32>& tris,
const std::vector<tfrag3::MercVertex>& vertices) {
float sum = 0;
for (size_t i = 0; i + 2 < tris.size(); i += 3) {
const auto& a = vertices[tris[i]];
const auto& b = vertices[tris[i + 1]];
const auto& c = vertices[tris[i + 2]];
const auto geometric = (b.pos_vec - a.pos_vec).cross(c.pos_vec - a.pos_vec);
const auto from_vertices = a.normal_vec + b.normal_vec + c.normal_vec;
const float scale = geometric.length() * from_vertices.length();
if (scale > 0) {
sum += geometric.dot(from_vertices) / scale;
}
}
return sum;
}
/*!
* Convert merc strips. Doesn't assume anything about strips. Output is [effect][draw] format (done
* for each model)
*/
void unstrip_merc_draws(const std::vector<u32>& stripped_indices,
const tfrag3::MercModel& model,
const std::vector<tfrag3::MercVertex>& vertices,
std::vector<u32>& unstripped,
std::vector<std::vector<u32>>& draw_to_start,
std::vector<std::vector<u32>>& draw_to_count) {
// triangles of the strip we're currently in, held back so we can flip the whole thing at once
std::vector<u32> strip;
auto flush_strip = [&]() {
if (strip.empty()) {
return;
}
if (merc_should_swap_tris(strip, vertices) < 0) {
for (size_t i = 0; i + 2 < strip.size(); i += 3) {
std::swap(strip[i], strip[i + 1]);
}
}
unstripped.insert(unstripped.end(), strip.begin(), strip.end());
strip.clear();
};
for (auto& effect : model.effects) {
auto& effect_dts = draw_to_start.emplace_back();
auto& effect_dtc = draw_to_count.emplace_back();
for (auto& draw : effect.all_draws) {
effect_dts.push_back(unstripped.size());
// a triangle strip flips every other triangle, so un-flip them here. the toggle restarts
// with each strip.
bool toggle = false;
for (size_t i = 2; i < draw.index_count; i++) {
int idx = i + draw.first_index;
u32 a = stripped_indices[idx];
u32 b = stripped_indices[idx - 1];
u32 c = stripped_indices[idx - 2];
if (a == UINT32_MAX || b == UINT32_MAX || c == UINT32_MAX) {
flush_strip();
toggle = false;
continue;
}
unstripped.push_back(a);
unstripped.push_back(b);
unstripped.push_back(c);
strip.push_back(a);
strip.push_back(toggle ? b : c);
strip.push_back(toggle ? c : b);
toggle = !toggle;
}
flush_strip();
effect_dtc.push_back(unstripped.size() - effect_dts.back());
}
}
@@ -483,11 +526,12 @@ int make_shrub_index_buffer_view(const std::vector<u32>& indices,
int make_merc_index_buffer_view(const std::vector<u32>& indices,
const tfrag3::MercModel& mmodel,
const std::vector<tfrag3::MercVertex>& vertices,
tinygltf::Model& model,
std::vector<std::vector<u32>>& draw_to_start,
std::vector<std::vector<u32>>& draw_to_count) {
std::vector<u32> unstripped;
unstrip_merc_draws(indices, mmodel, unstripped, draw_to_start, draw_to_count);
unstrip_merc_draws(indices, mmodel, vertices, unstripped, draw_to_start, draw_to_count);
// first create a buffer:
int buffer_idx = (int)model.buffers.size();
@@ -536,11 +580,38 @@ int make_index_buffer_accessor(tinygltf::Model& model, u32 start, u32 count, int
return accessor_idx;
}
enum class TexImageKind {
RGBA,
ENVMAP_STRENGTH,
};
using TexImageMap = std::map<std::pair<int, TexImageKind>, int>;
struct EnvmapInfo {
int texture_idx = -1;
DrawMode mode;
};
bool blends_with_src_alpha(const DrawMode& mode) {
if (!mode.get_ab_enable()) {
return false;
}
switch (mode.get_alpha_blend()) {
case DrawMode::AlphaBlend::SRC_DST_SRC_DST:
case DrawMode::AlphaBlend::SRC_0_SRC_DST:
case DrawMode::AlphaBlend::ZERO_SRC_SRC_DST:
return true;
default:
return false;
}
}
int add_image_for_tex(const tfrag3::Level& level,
tinygltf::Model& model,
int tex_idx,
std::unordered_map<int, int>& tex_image_map) {
const auto& existing = tex_image_map.find(tex_idx);
TexImageMap& tex_image_map,
TexImageKind kind) {
const auto& existing = tex_image_map.find({tex_idx, kind});
if (existing != tex_image_map.end()) {
return existing->second;
}
@@ -558,15 +629,49 @@ int add_image_for_tex(const tfrag3::Level& level,
image.name = tex.debug_name;
memcpy(image.image.data(), tex.data.data(), tex.data.size() * 4);
tex_image_map[tex_idx] = image_idx;
if (kind == TexImageKind::ENVMAP_STRENGTH) {
image.name = tex.debug_name + "-envmap-strength";
for (size_t i = 0; i < tex.data.size(); i++) {
const u8 metallic = (u8)std::min<u32>(255, (u32)image.image[i * 4 + 3] * 2);
image.image[i * 4 + 0] = 255;
image.image[i * 4 + 1] = 0;
image.image[i * 4 + 2] = metallic;
image.image[i * 4 + 3] = 255;
}
}
tex_image_map[{tex_idx, kind}] = image_idx;
return image_idx;
}
int add_gltf_texture(tinygltf::Model& model,
int image_idx,
const std::string& name,
const DrawMode& draw_mode) {
int texture_idx = (int)model.textures.size();
auto& gltf_texture = model.textures.emplace_back();
gltf_texture.name = name;
gltf_texture.source = image_idx;
gltf_texture.sampler = (int)model.samplers.size();
auto& sampler = model.samplers.emplace_back();
sampler.minFilter = draw_mode.get_filt_enable() ? TINYGLTF_TEXTURE_FILTER_LINEAR
: TINYGLTF_TEXTURE_FILTER_NEAREST;
sampler.magFilter = draw_mode.get_filt_enable() ? TINYGLTF_TEXTURE_FILTER_LINEAR
: TINYGLTF_TEXTURE_FILTER_NEAREST;
sampler.wrapS = draw_mode.get_clamp_s_enable() ? TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE
: TINYGLTF_TEXTURE_WRAP_REPEAT;
sampler.wrapT = draw_mode.get_clamp_t_enable() ? TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE
: TINYGLTF_TEXTURE_WRAP_REPEAT;
sampler.name = name;
return texture_idx;
}
int add_material_for_tex(const tfrag3::Level& level,
tinygltf::Model& model,
int tex_idx,
std::unordered_map<int, int>& tex_image_map,
const DrawMode& draw_mode) {
TexImageMap& tex_image_map,
const DrawMode& draw_mode,
const EnvmapInfo* envmap = nullptr) {
if (tex_idx < 0) {
return 0;
}
@@ -579,29 +684,54 @@ int add_material_for_tex(const tfrag3::Level& level,
// the 2.0 here compensates for the ps2's weird blending where 0.5 behaves like 1.0
mat.pbrMetallicRoughness.baseColorFactor = {2.0, 2.0, 2.0, 2.0};
mat.pbrMetallicRoughness.baseColorTexture.texCoord = 0; // TEXCOORD_0, I think
mat.pbrMetallicRoughness.baseColorTexture.index = model.textures.size();
mat.pbrMetallicRoughness.baseColorTexture.index = add_gltf_texture(
model, add_image_for_tex(level, model, tex_idx, tex_image_map, TexImageKind::RGBA),
tex.debug_name, draw_mode);
mat.alphaMode = draw_mode.get_ab_enable() ? "BLEND" : "MASK";
// the foreground and background renderers both use this cutoff
mat.alphaCutoff = (float)0x26 / 255.f;
auto& gltf_texture = model.textures.emplace_back();
gltf_texture.name = tex.debug_name;
gltf_texture.sampler = model.samplers.size();
auto& sampler = model.samplers.emplace_back();
sampler.minFilter = draw_mode.get_filt_enable() ? TINYGLTF_TEXTURE_FILTER_LINEAR
: TINYGLTF_TEXTURE_FILTER_NEAREST;
sampler.magFilter = draw_mode.get_filt_enable() ? TINYGLTF_TEXTURE_FILTER_LINEAR
: TINYGLTF_TEXTURE_FILTER_NEAREST;
sampler.wrapS = draw_mode.get_clamp_s_enable() ? TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE
: TINYGLTF_TEXTURE_WRAP_REPEAT;
sampler.wrapT = draw_mode.get_clamp_t_enable() ? TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE
: TINYGLTF_TEXTURE_WRAP_REPEAT;
sampler.name = tex.debug_name;
// metallic is reserved for the envmap strength below
mat.pbrMetallicRoughness.metallicFactor = 0.0;
gltf_texture.source = add_image_for_tex(level, model, tex_idx, tex_image_map);
if (envmap && envmap->texture_idx >= 0) {
mat.name = tex.debug_name + "-envmap";
if (!blends_with_src_alpha(draw_mode)) {
mat.alphaMode = "OPAQUE";
}
mat.pbrMetallicRoughness.metallicFactor = 1.0;
mat.pbrMetallicRoughness.roughnessFactor = 1.0;
mat.pbrMetallicRoughness.metallicRoughnessTexture.texCoord = 0;
mat.pbrMetallicRoughness.metallicRoughnessTexture.index = add_gltf_texture(
model,
add_image_for_tex(level, model, tex_idx, tex_image_map, TexImageKind::ENVMAP_STRENGTH),
tex.debug_name + "-envmap-strength", draw_mode);
const auto& envmap_tex = level.textures.at(envmap->texture_idx);
tinygltf::Value::Object specular_color_texture;
specular_color_texture["index"] = tinygltf::Value(add_gltf_texture(
model,
add_image_for_tex(level, model, envmap->texture_idx, tex_image_map, TexImageKind::RGBA),
envmap_tex.debug_name, envmap->mode));
tinygltf::Value::Object specular;
specular["specularColorTexture"] = tinygltf::Value(specular_color_texture);
mat.extensions["KHR_materials_specular"] = tinygltf::Value(specular);
if (std::find(model.extensionsUsed.begin(), model.extensionsUsed.end(),
"KHR_materials_specular") == model.extensionsUsed.end()) {
model.extensionsUsed.push_back("KHR_materials_specular");
}
}
return mat_idx;
}
std::string texture_name(const tfrag3::Level& level, s32 tree_tex_id) {
if (tree_tex_id < 0) {
return fmt::format("anim-slot-{}", -(tree_tex_id + 1));
}
return level.textures.at(tree_tex_id).debug_name;
}
constexpr int kMaxColor = 1;
/*!
* Add the given tfrag data to a node under tfrag_root.
@@ -609,7 +739,7 @@ constexpr int kMaxColor = 1;
void add_tfrag(const tfrag3::Level& level,
const tfrag3::TfragTree& tfrag_in,
tinygltf::Model& model,
std::unordered_map<int, int>& tex_image_map) {
TexImageMap& tex_image_map) {
// copy and unpack in place
tfrag3::TfragTree tfrag = tfrag_in;
tfrag.unpack();
@@ -617,12 +747,10 @@ void add_tfrag(const tfrag3::Level& level,
// we'll make a Node, Mesh, Primitive, then add the data to the primitive.
int node_idx = (int)model.nodes.size();
auto& node = model.nodes.emplace_back();
node.name =
fmt::format("{}-tfrag-{}", level.level_name, tfrag3::tfrag_tree_names[(int)tfrag.kind]);
model.scenes.at(0).nodes.push_back(node_idx);
int mesh_idx = (int)model.meshes.size();
auto& mesh = model.meshes.emplace_back();
node.mesh = mesh_idx;
int position_buffer_accessor = make_position_buffer_accessor(tfrag.unpacked.vertices, model);
int texture_buffer_accessor = make_tex_buffer_accessor(tfrag.unpacked.vertices, model, 1.f);
std::vector<u32> index_map;
@@ -634,23 +762,132 @@ void add_tfrag(const tfrag3::Level& level,
colors[i] = make_color_buffer_accessor(tfrag.unpacked.vertices, model, tfrag, i);
}
for (auto& draw : tfrag.draws) {
auto& prim = mesh.primitives.emplace_back();
prim.material = add_material_for_tex(level, model, draw.tree_tex_id, tex_image_map, draw.mode);
prim.indices = make_index_buffer_accessor(model, draw, index_map, index_buffer_view);
prim.attributes["POSITION"] = position_buffer_accessor;
prim.attributes["TEXCOORD_0"] = texture_buffer_accessor;
for (int i = 0; i < kMaxColor; i++) {
prim.attributes[fmt::format("COLOR_{}", i)] = colors[i];
std::vector<s32> tex_order;
std::map<s32, std::vector<size_t>> draws_by_tex;
for (size_t draw_idx = 0; draw_idx < tfrag.draws.size(); draw_idx++) {
const s32 tex = tfrag.draws[draw_idx].tree_tex_id;
if (draws_by_tex.find(tex) == draws_by_tex.end()) {
tex_order.push_back(tex);
}
prim.mode = TINYGLTF_MODE_TRIANGLES;
draws_by_tex[tex].push_back(draw_idx);
}
for (s32 tex : tex_order) {
int c_node_idx = (int)model.nodes.size();
auto& c_node = model.nodes.emplace_back();
c_node.name = texture_name(level, tex);
model.nodes[node_idx].children.push_back(c_node_idx);
int mesh_idx = (int)model.meshes.size();
model.meshes.emplace_back();
c_node.mesh = mesh_idx;
for (size_t draw_idx : draws_by_tex.at(tex)) {
const auto& draw = tfrag.draws[draw_idx];
auto& prim = model.meshes[mesh_idx].primitives.emplace_back();
prim.material =
add_material_for_tex(level, model, draw.tree_tex_id, tex_image_map, draw.mode);
prim.indices = make_index_buffer_accessor(model, draw, index_map, index_buffer_view);
prim.attributes["POSITION"] = position_buffer_accessor;
prim.attributes["TEXCOORD_0"] = texture_buffer_accessor;
for (int i = 0; i < kMaxColor; i++) {
prim.attributes[fmt::format("COLOR_{}", i)] = colors[i];
}
prim.mode = TINYGLTF_MODE_TRIANGLES;
}
}
}
std::unordered_map<u32, EnvmapInfo> tie_envmap_by_run_start(const tfrag3::TieTree& tie) {
std::unordered_map<u32, EnvmapInfo> result;
for (int cat = 0; cat < tfrag3::kNumTieCategories; cat++) {
if (!tfrag3::is_envmap_second_draw_category((tfrag3::TieCategory)cat)) {
continue;
}
for (u32 draw_idx = tie.category_draw_indices[cat];
draw_idx < tie.category_draw_indices[cat + 1]; draw_idx++) {
const auto& draw = tie.static_draws.at(draw_idx);
for (const auto& run : draw.runs) {
auto& info = result[run.vertex0];
info.texture_idx = draw.tree_tex_id;
info.mode = draw.mode;
}
}
}
return result;
}
// a contiguous run of the gltf index buffer, in indices
struct IndexRange {
u32 start = 0;
u32 count = 0;
};
// the part of a tie draw that belongs to a single prototype
struct TieProtoDraw {
u16 proto_idx = 0;
u32 draw_idx = 0;
bool operator<(const TieProtoDraw& other) const {
return std::tie(proto_idx, draw_idx) < std::tie(other.proto_idx, other.draw_idx);
}
};
int make_tie_index_buffer_view_by_proto(const tfrag3::TieTree& tie,
const std::vector<u32>& draws_to_emit,
tinygltf::Model& model,
std::map<TieProtoDraw, IndexRange>& ranges_out) {
std::vector<u32> unstripped, old_to_new;
unstrip_tfrag_tie(tie.unpacked.indices, extract_positions(tie.unpacked.vertices), unstripped,
old_to_new);
std::map<TieProtoDraw, std::vector<IndexRange>> src_ranges;
for (u32 draw_idx : draws_to_emit) {
const auto& draw = tie.static_draws.at(draw_idx);
u32 old_start = draw.unpacked.idx_of_first_idx_in_full_buffer;
for (const auto& grp : draw.vis_groups) {
if (grp.num_tris) {
src_ranges[{grp.tie_proto_idx, draw_idx}].push_back(
{old_to_new.at(old_start), grp.num_tris * 3});
}
old_start += grp.num_inds;
}
}
std::vector<u32> grouped;
for (const auto& [key, ranges] : src_ranges) {
const u32 start = grouped.size();
for (const auto& [src, count] : ranges) {
grouped.insert(grouped.end(), unstripped.begin() + src, unstripped.begin() + src + count);
}
ranges_out[key] = {start, (u32)grouped.size() - start};
}
int buffer_idx = (int)model.buffers.size();
auto& buffer = model.buffers.emplace_back();
buffer.data.resize(sizeof(u32) * grouped.size());
memcpy(buffer.data.data(), grouped.data(), buffer.data.size());
int buffer_view_idx = (int)model.bufferViews.size();
auto& buffer_view = model.bufferViews.emplace_back();
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_ELEMENT_ARRAY_BUFFER;
return buffer_view_idx;
}
std::string proto_name(const std::vector<std::string>& names, u16 proto_idx, const char* kind) {
if (proto_idx < names.size() && !names[proto_idx].empty()) {
return names[proto_idx];
}
return fmt::format("{}-proto-{}", kind, proto_idx);
}
void add_tie(const tfrag3::Level& level,
const tfrag3::TieTree& tie_in,
size_t tree_idx,
tinygltf::Model& model,
std::unordered_map<int, int>& tex_image_map) {
TexImageMap& tex_image_map) {
// copy and unpack in place
tfrag3::TieTree tie = tie_in;
tie.unpack();
@@ -658,27 +895,68 @@ void add_tie(const tfrag3::Level& level,
// we'll make a Node, Mesh, Primitive, then add the data to the primitive.
int node_idx = (int)model.nodes.size();
auto& node = model.nodes.emplace_back();
node.name = fmt::format("{}-tie-{}", level.level_name, tree_idx);
model.scenes.at(0).nodes.push_back(node_idx);
int mesh_idx = (int)model.meshes.size();
auto& mesh = model.meshes.emplace_back();
node.mesh = mesh_idx;
int position_buffer_accessor = make_position_buffer_accessor(tie.unpacked.vertices, model);
int texture_buffer_accessor = make_tex_buffer_accessor(tie.unpacked.vertices, model, 1.f);
std::vector<u32> index_map;
int index_buffer_view = make_tfrag_tie_index_buffer_view(
tie.unpacked.indices, extract_positions(tie.unpacked.vertices), model, index_map);
int colors[kMaxColor];
for (int i = 0; i < kMaxColor; i++) {
colors[i] = make_color_buffer_accessor(tie.unpacked.vertices, model, tie, i);
}
for (auto& draw : tie.static_draws) {
auto& prim = mesh.primitives.emplace_back();
prim.material = add_material_for_tex(level, model, draw.tree_tex_id, tex_image_map, draw.mode);
prim.indices = make_index_buffer_accessor(model, draw, index_map, index_buffer_view);
const auto envmap_by_run_start = tie_envmap_by_run_start(tie);
std::vector<u32> draws_to_emit;
std::map<u32, int> draw_material;
for (int cat = 0; cat < tfrag3::kNumTieCategories; cat++) {
const auto category = (tfrag3::TieCategory)cat;
if (tfrag3::is_envmap_second_draw_category(category)) {
continue;
}
for (u32 draw_idx = tie.category_draw_indices[cat];
draw_idx < tie.category_draw_indices[cat + 1]; draw_idx++) {
const auto& draw = tie.static_draws[draw_idx];
EnvmapInfo envmap_info;
const EnvmapInfo* envmap = nullptr;
if (tfrag3::is_envmap_first_draw_category(category) && !draw.runs.empty()) {
const auto& first_run = draw.runs.front();
const auto it = envmap_by_run_start.find(first_run.vertex0 + first_run.length);
if (it != envmap_by_run_start.end() && it->second.texture_idx >= 0 &&
(size_t)it->second.texture_idx < level.textures.size()) {
envmap_info = it->second;
envmap = &envmap_info;
}
}
draws_to_emit.push_back(draw_idx);
draw_material[draw_idx] =
add_material_for_tex(level, model, draw.tree_tex_id, tex_image_map, draw.mode, envmap);
}
}
std::map<TieProtoDraw, IndexRange> ranges;
int index_buffer_view = make_tie_index_buffer_view_by_proto(tie, draws_to_emit, model, ranges);
int mesh_idx = -1;
int prev_proto = -1;
for (const auto& [key, range] : ranges) {
if (key.proto_idx != prev_proto) {
prev_proto = key.proto_idx;
int c_node_idx = (int)model.nodes.size();
auto& c_node = model.nodes.emplace_back();
c_node.name = proto_name(tie.proto_names, key.proto_idx, "tie");
model.nodes[node_idx].children.push_back(c_node_idx);
mesh_idx = (int)model.meshes.size();
model.meshes.emplace_back();
c_node.mesh = mesh_idx;
}
auto& prim = model.meshes[mesh_idx].primitives.emplace_back();
prim.material = draw_material.at(key.draw_idx);
prim.indices = make_index_buffer_accessor(model, range.start, range.count, index_buffer_view);
prim.attributes["POSITION"] = position_buffer_accessor;
prim.attributes["TEXCOORD_0"] = texture_buffer_accessor;
for (int i = 0; i < kMaxColor; i++) {
@@ -700,6 +978,7 @@ void add_tie(const tfrag3::Level& level,
const auto& grp = wind_draw.instance_groups[grp_idx];
int c_node_idx = (int)model.nodes.size();
auto& c_node = model.nodes.emplace_back();
c_node.name = fmt::format("wind-group-{}-{}", draw_idx, grp_idx);
model.nodes[node_idx].children.push_back(c_node_idx);
int c_mesh_idx = (int)model.meshes.size();
auto& c_mesh = model.meshes.emplace_back();
@@ -731,8 +1010,9 @@ void add_tie(const tfrag3::Level& level,
void add_shrub(const tfrag3::Level& level,
const tfrag3::ShrubTree& shrub_in,
size_t tree_idx,
tinygltf::Model& model,
std::unordered_map<int, int>& tex_image_map) {
TexImageMap& tex_image_map) {
// copy and unpack in place
tfrag3::ShrubTree shrub = shrub_in;
shrub.unpack();
@@ -740,12 +1020,9 @@ void add_shrub(const tfrag3::Level& level,
// we'll make a Node, Mesh, Primitive, then add the data to the primitive.
int node_idx = (int)model.nodes.size();
auto& node = model.nodes.emplace_back();
node.name = fmt::format("{}-shrub-{}", level.level_name, tree_idx);
model.scenes.at(0).nodes.push_back(node_idx);
int mesh_idx = (int)model.meshes.size();
auto& mesh = model.meshes.emplace_back();
node.mesh = mesh_idx;
int position_buffer_accessor = make_position_buffer_accessor(shrub.unpacked.vertices, model);
int texture_buffer_accessor =
make_tex_buffer_accessor(shrub.unpacked.vertices, model, 1.f / 4096.f);
@@ -757,19 +1034,34 @@ void add_shrub(const tfrag3::Level& level,
colors[i] = make_color_buffer_accessor(shrub.unpacked.vertices, model, shrub, i);
}
// for (auto& draw : shrub.static_draws) {
std::map<u16, std::vector<size_t>> draws_by_proto;
for (size_t draw_idx = 0; draw_idx < shrub.static_draws.size(); draw_idx++) {
auto& draw = shrub.static_draws[draw_idx];
auto& prim = mesh.primitives.emplace_back();
prim.material = add_material_for_tex(level, model, draw.tree_tex_id, tex_image_map, draw.mode);
prim.indices = make_index_buffer_accessor(model, draw_to_start.at(draw_idx),
draw_to_count.at(draw_idx), index_buffer_view);
prim.attributes["POSITION"] = position_buffer_accessor;
prim.attributes["TEXCOORD_0"] = texture_buffer_accessor;
for (int i = 0; i < kMaxColor; i++) {
prim.attributes[fmt::format("COLOR_{}", i)] = colors[i];
draws_by_proto[shrub.static_draws[draw_idx].proto_idx].push_back(draw_idx);
}
for (const auto& [proto_idx, draw_indices] : draws_by_proto) {
int c_node_idx = (int)model.nodes.size();
auto& c_node = model.nodes.emplace_back();
c_node.name = proto_name(shrub.proto_names, proto_idx, "shrub");
model.nodes[node_idx].children.push_back(c_node_idx);
int mesh_idx = (int)model.meshes.size();
model.meshes.emplace_back();
c_node.mesh = mesh_idx;
for (size_t draw_idx : draw_indices) {
auto& draw = shrub.static_draws[draw_idx];
auto& prim = model.meshes[mesh_idx].primitives.emplace_back();
prim.material =
add_material_for_tex(level, model, draw.tree_tex_id, tex_image_map, draw.mode);
prim.indices = make_index_buffer_accessor(model, draw_to_start.at(draw_idx),
draw_to_count.at(draw_idx), index_buffer_view);
prim.attributes["POSITION"] = position_buffer_accessor;
prim.attributes["TEXCOORD_0"] = texture_buffer_accessor;
for (int i = 0; i < kMaxColor; i++) {
prim.attributes[fmt::format("COLOR_{}", i)] = colors[i];
}
prim.mode = TINYGLTF_MODE_TRIANGLES;
}
prim.mode = TINYGLTF_MODE_TRIANGLES;
}
}
@@ -816,8 +1108,9 @@ int make_normal_buffer_accessor(const std::vector<tfrag3::MercVertex>& vertices,
// 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));
const float len = vtx.normal_vec.length();
const auto normal = len > 0 ? vtx.normal_vec / len : math::Vector3f(0, 1, 0);
memcpy(buffer_ptr, normal.data(), 3 * sizeof(float));
buffer_ptr += 3 * sizeof(float);
}
@@ -846,18 +1139,18 @@ int make_bones_accessor(const std::vector<tfrag3::MercVertex>& vertices, tinyglt
// first create a buffer:
int buffer_idx = (int)model.buffers.size();
auto& buffer = model.buffers.emplace_back();
buffer.data.resize(sizeof(float) * 4 * vertices.size());
buffer.data.resize(4 * vertices.size());
// and fill it
u8* buffer_ptr = buffer.data.data();
for (const auto& vtx : vertices) {
s32 indices[4];
u8 indices[4];
for (int i = 0; i < 3; i++) {
indices[i] = vtx.mats[i] ? vtx.mats[i] - 1 : 0;
}
indices[3] = 0;
memcpy(buffer_ptr, indices, 4 * sizeof(s32));
buffer_ptr += 4 * sizeof(s32);
memcpy(buffer_ptr, indices, 4);
buffer_ptr += 4;
}
// create a view of this buffer
@@ -873,7 +1166,7 @@ int make_bones_accessor(const std::vector<tfrag3::MercVertex>& vertices, tinyglt
auto& accessor = model.accessors.emplace_back();
accessor.bufferView = buffer_view_idx;
accessor.byteOffset = 0;
accessor.componentType = TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT; // blender doesn't support INT...
accessor.componentType = TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE;
accessor.count = vertices.size();
accessor.type = TINYGLTF_TYPE_VEC4;
return accessor_idx;
@@ -1323,7 +1616,7 @@ void add_merc(const tfrag3::Level& level,
const std::map<std::string, level_tools::ArtData>& art_data,
const tfrag3::MercModel& mmodel,
tinygltf::Model& model,
std::unordered_map<int, int>& tex_image_map,
TexImageMap& tex_image_map,
const std::unordered_map<int, int>& anim_slot_to_base_tex) {
const auto& mverts = level.merc_data.vertices;
@@ -1332,8 +1625,8 @@ void add_merc(const tfrag3::Level& level,
int texture_buffer_accessor = make_tex_buffer_accessor(mverts, model, 1.f);
std::vector<std::vector<u32>> draw_to_start, draw_to_count;
int index_buffer_view = make_merc_index_buffer_view(level.merc_data.indices, mmodel, model,
draw_to_start, draw_to_count);
int index_buffer_view = make_merc_index_buffer_view(level.merc_data.indices, mmodel, mverts,
model, draw_to_start, draw_to_count);
int colors = make_color_buffer_accessor(mverts, model);
auto joints_accessor = make_bones_accessor(mverts, model);
@@ -1437,6 +1730,15 @@ void add_merc(const tfrag3::Level& level,
for (size_t effect_idx = 0; effect_idx < mmodel.effects.size(); effect_idx++) {
const auto& effect = mmodel.effects[effect_idx];
EnvmapInfo envmap_info;
const EnvmapInfo* envmap = nullptr;
if (effect.has_envmap && effect.envmap_texture < level.textures.size()) {
envmap_info.texture_idx = (int)effect.envmap_texture;
envmap_info.mode = effect.envmap_mode;
envmap = &envmap_info;
}
for (size_t draw_idx = 0; draw_idx < effect.all_draws.size(); draw_idx++) {
const auto& draw = effect.all_draws[draw_idx];
auto& prim = mesh.primitives.emplace_back();
@@ -1446,7 +1748,7 @@ void add_merc(const tfrag3::Level& level,
const auto it = anim_slot_to_base_tex.find(-(tex_id + 1));
tex_id = it != anim_slot_to_base_tex.end() ? it->second : draw.tree_tex_id;
}
prim.material = add_material_for_tex(level, model, tex_id, tex_image_map, draw.mode);
prim.material = add_material_for_tex(level, model, tex_id, tex_image_map, draw.mode, envmap);
prim.indices =
make_index_buffer_accessor(model, draw_to_start[effect_idx][draw_idx],
draw_to_count[effect_idx][draw_idx], index_buffer_view);
@@ -1462,6 +1764,29 @@ void add_merc(const tfrag3::Level& level,
add_blerc_targets(level, mmodel, mesh, model);
}
void consolidate_buffers(tinygltf::Model& model) {
if (model.buffers.size() <= 1) {
return;
}
std::vector<size_t> buffer_start(model.buffers.size());
std::vector<u8> merged;
for (size_t i = 0; i < model.buffers.size(); i++) {
merged.resize((merged.size() + 3) & ~(size_t)3);
buffer_start[i] = merged.size();
const auto& data = model.buffers[i].data;
merged.insert(merged.end(), data.begin(), data.end());
}
for (auto& view : model.bufferViews) {
view.byteOffset += buffer_start.at(view.buffer);
view.buffer = 0;
}
model.buffers.clear();
model.buffers.emplace_back().data = std::move(merged);
}
} // namespace
/*!
@@ -1479,25 +1804,27 @@ void save_level_background_as_gltf(const tfrag3::Level& level, const fs::path& g
// hack, add a default material.
tinygltf::Material mat;
mat.pbrMetallicRoughness.baseColorFactor = {1.0f, 0.9f, 0.9f, 1.0f};
mat.pbrMetallicRoughness.metallicFactor = 0.0;
mat.doubleSided = true;
model.materials.push_back(mat);
std::unordered_map<int, int> tex_image_map;
TexImageMap tex_image_map;
// add all hi-lod tfrag trees
for (const auto& tfrag : level.tfrag_trees.at(0)) {
add_tfrag(level, tfrag, model, tex_image_map);
}
for (const auto& tie : level.tie_trees.at(0)) {
add_tie(level, tie, model, tex_image_map);
for (size_t i = 0; i < level.tie_trees.at(0).size(); i++) {
add_tie(level, level.tie_trees.at(0)[i], i, model, tex_image_map);
}
for (const auto& shrub : level.shrub_trees) {
add_shrub(level, shrub, model, tex_image_map);
for (size_t i = 0; i < level.shrub_trees.size(); i++) {
add_shrub(level, level.shrub_trees[i], i, model, tex_image_map);
}
model.asset.generator = "opengoal";
consolidate_buffers(model);
tinygltf::TinyGLTF gltf;
gltf.WriteGltfSceneToFile(&model, glb_file.string(),
true, // embedImages
@@ -1534,14 +1861,16 @@ void save_level_foreground_as_gltf(
// hack, add a default material.
tinygltf::Material mat;
mat.pbrMetallicRoughness.baseColorFactor = {1.0f, 0.9f, 0.9f, 1.0f};
mat.pbrMetallicRoughness.metallicFactor = 0.0;
mat.doubleSided = true;
model.materials.push_back(mat);
std::unordered_map<int, int> tex_image_map;
TexImageMap tex_image_map;
add_merc(level, art_data, mmodel, model, tex_image_map, anim_slot_to_base_tex);
model.asset.generator = "opengoal";
consolidate_buffers(model);
auto glb_file = glb_path / fmt::format("{}.glb", mmodel.name);
file_util::create_dir_if_needed_for_file(glb_file);