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 +#include #include "common/custom_data/Tfrag3Data.h" #include "common/math/Vector.h" @@ -92,33 +94,74 @@ void unstrip_tie_wind(std::vector& unstripped, } } +float merc_should_swap_tris(const std::vector& tris, + const std::vector& 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& stripped_indices, const tfrag3::MercModel& model, + const std::vector& vertices, std::vector& unstripped, std::vector>& draw_to_start, std::vector>& draw_to_count) { + // triangles of the strip we're currently in, held back so we can flip the whole thing at once + std::vector 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& indices, int make_merc_index_buffer_view(const std::vector& indices, const tfrag3::MercModel& mmodel, + const std::vector& vertices, tinygltf::Model& model, std::vector>& draw_to_start, std::vector>& draw_to_count) { std::vector 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, 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& 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(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& 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& 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 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 tex_order; + std::map> 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 tie_envmap_by_run_start(const tfrag3::TieTree& tie) { + std::unordered_map 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& draws_to_emit, + tinygltf::Model& model, + std::map& ranges_out) { + std::vector unstripped, old_to_new; + unstrip_tfrag_tie(tie.unpacked.indices, extract_positions(tie.unpacked.vertices), unstripped, + old_to_new); + + std::map> 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 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& 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& 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 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 draws_to_emit; + std::map 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 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& 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> 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& 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& 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& 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& art_data, const tfrag3::MercModel& mmodel, tinygltf::Model& model, - std::unordered_map& tex_image_map, + TexImageMap& tex_image_map, const std::unordered_map& 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> 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 buffer_start(model.buffers.size()); + std::vector 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 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 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);