From c476b841828ecc1d3a6dcf6a234d80f8efc15c59 Mon Sep 17 00:00:00 2001 From: Hat Kid <6624576+Hat-Kid@users.noreply.github.com> Date: Mon, 9 Dec 2024 20:49:56 +0100 Subject: [PATCH] custom models: option to copy mod/eye draws from original model (#3800) Two new flags were added to the Blender plugin to allow reusing the mod and/or eye draws of the original model that is being replaced. Works pretty well for eyes, but the blerc draws can cause some Z-fighting with the non-moving parts of the model. Also a small refactor to the merc replacement code to de-duplicate some code by moving stuff to `gltf_util.cpp`. --- common/util/gltf_util.cpp | 85 ++++++ common/util/gltf_util.h | 31 +++ custom_assets/blender_plugins/opengoal.py | 4 + decompiler/IR2/FormExpressionAnalysis.cpp | 3 - decompiler/level_extractor/extract_level.cpp | 10 + decompiler/level_extractor/extract_level.h | 7 + decompiler/level_extractor/extract_merc.cpp | 38 +-- .../level_extractor/merc_replacement.cpp | 242 +++++++++++++----- decompiler/level_extractor/merc_replacement.h | 38 ++- .../opengl_renderer/foreground/Merc2.cpp | 16 +- goalc/build_actor/common/MercExtract.cpp | 99 +------ goalc/build_actor/common/MercExtract.h | 32 +-- 12 files changed, 372 insertions(+), 233 deletions(-) diff --git a/common/util/gltf_util.cpp b/common/util/gltf_util.cpp index 1a8f4de9573..4f7e3bb3a0b 100644 --- a/common/util/gltf_util.cpp +++ b/common/util/gltf_util.cpp @@ -756,4 +756,89 @@ tfrag3::PackedTimeOfDay pack_time_of_day(const std::vector>& return colors; } +void process_normal_merc_draw(const tinygltf::Model& model, + MercExtractData& out, + u32 tex_offset, + tfrag3::MercEffect& eff, + int mat_idx, + const tfrag3::MercDraw& d_) { + const auto& mat = model.materials[mat_idx]; + eff.all_draws.push_back(d_); + auto& draw = eff.all_draws.back(); + draw.mode = gltf_util::make_default_draw_mode(); + + if (mat_idx == -1) { + lg::warn("Draw had a material index of -1, using default texture."); + draw.tree_tex_id = 0; + return; + } + int tex_idx = mat.pbrMetallicRoughness.baseColorTexture.index; + if (tex_idx == -1) { + lg::warn("Material {} has no texture, using default texture.", mat.name); + draw.tree_tex_id = 0; + return; + } + + const auto& tex = model.textures[tex_idx]; + ASSERT(tex.sampler >= 0); + ASSERT(tex.source >= 0); + gltf_util::setup_draw_mode_from_sampler(model.samplers.at(tex.sampler), &draw.mode); + gltf_util::setup_alpha_from_material(mat, &draw.mode); + + const auto& img = model.images[tex.source]; + draw.tree_tex_id = tex_offset + texture_pool_add_texture(&out.tex_pool, img); +}; + +void process_envmap_merc_draw(const tinygltf::Model& model, + MercExtractData& out, + u32 tex_offset, + tfrag3::MercEffect& eff, + int mat_idx, + const tfrag3::MercDraw& d_) { + const auto& mat = model.materials[mat_idx]; + eff.all_draws.push_back(d_); + auto& draw = eff.all_draws.back(); + draw.mode = gltf_util::make_default_draw_mode(); + + if (mat_idx == -1) { + lg::warn("Envmap draw had a material index of -1, using default texture."); + draw.tree_tex_id = texture_pool_debug_checker(&out.tex_pool); + return; + } + int base_tex_idx = mat.pbrMetallicRoughness.baseColorTexture.index; + if (base_tex_idx == -1) { + lg::warn("Envmap material {} has no texture, using default texture.", mat.name); + draw.tree_tex_id = texture_pool_debug_checker(&out.tex_pool); + return; + } + + const auto& base_tex = model.textures[base_tex_idx]; + ASSERT(base_tex.sampler >= 0); + ASSERT(base_tex.source >= 0); + gltf_util::setup_draw_mode_from_sampler(model.samplers.at(base_tex.sampler), &draw.mode); + gltf_util::setup_alpha_from_material(mat, &draw.mode); + const auto& roughness_tex = + model.textures.at(mat.pbrMetallicRoughness.metallicRoughnessTexture.index); + ASSERT(roughness_tex.sampler >= 0); + ASSERT(roughness_tex.source >= 0); + + draw.tree_tex_id = + tex_offset + gltf_util::texture_pool_add_envmap_control_texture( + &out.tex_pool, model, base_tex.source, roughness_tex.source, + !draw.mode.get_clamp_s_enable(), !draw.mode.get_clamp_t_enable()); + + // now, setup envmap draw: + auto envmap_settings = gltf_util::envmap_settings_from_gltf(mat); + const auto& envmap_tex = model.textures[envmap_settings.texture_idx]; + ASSERT(envmap_tex.sampler >= 0); + ASSERT(envmap_tex.source >= 0); + auto env_mode = gltf_util::make_default_draw_mode(); + gltf_util::setup_draw_mode_from_sampler(model.samplers.at(envmap_tex.sampler), &env_mode); + eff.envmap_texture = tex_offset + gltf_util::texture_pool_add_texture( + &out.tex_pool, model.images[envmap_tex.source]); + env_mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_0_DST_DST); + env_mode.enable_ab(); + eff.envmap_mode = env_mode; +}; + } // namespace gltf_util \ No newline at end of file diff --git a/common/util/gltf_util.h b/common/util/gltf_util.h index ae0ca8be5ed..f92f56c4b61 100644 --- a/common/util/gltf_util.h +++ b/common/util/gltf_util.h @@ -180,4 +180,35 @@ math::Matrix4f matrix_from_trs(const math::Vector3f& trans, tfrag3::PackedTimeOfDay pack_time_of_day(const std::vector>& color_palette); +struct MercExtractData { + TexturePool tex_pool; + std::vector new_indices; + std::vector new_vertices; + std::vector> new_colors; + std::vector normals; + std::vector joints_and_weights; + tfrag3::MercModel new_model; +}; + +// Data produced by loading a replacement model +struct MercSwapData { + std::vector new_indices; + std::vector new_vertices; + std::vector new_textures; + tfrag3::MercModel new_model; +}; + +void process_normal_merc_draw(const tinygltf::Model& model, + MercExtractData& out, + u32 tex_offset, + tfrag3::MercEffect& eff, + int mat_idx, + const tfrag3::MercDraw& d_); +void process_envmap_merc_draw(const tinygltf::Model& model, + MercExtractData& out, + u32 tex_offset, + tfrag3::MercEffect& eff, + int mat_idx, + const tfrag3::MercDraw& d_); + } // namespace gltf_util \ No newline at end of file diff --git a/custom_assets/blender_plugins/opengoal.py b/custom_assets/blender_plugins/opengoal.py index 4452ddc4323..41517b40fe5 100644 --- a/custom_assets/blender_plugins/opengoal.py +++ b/custom_assets/blender_plugins/opengoal.py @@ -91,6 +91,8 @@ def draw_func_ob(self, context): ob = context.object layout.prop(ob, "set_invisible") layout.prop(ob, "enable_custom_weights") + layout.prop(ob, "copy_eye_draws") + layout.prop(ob, "copy_mod_draws") layout.prop(ob, "set_collision") if (ob.set_collision): layout.prop(ob, "ignore") @@ -118,6 +120,8 @@ def register(): bpy.types.Object.set_invisible = bpy.props.BoolProperty(name="Invisible") bpy.types.Object.set_collision = bpy.props.BoolProperty(name="Apply Collision Properties") bpy.types.Object.enable_custom_weights = bpy.props.BoolProperty(name="Use Custom Bone Weights") + bpy.types.Object.copy_eye_draws = bpy.props.BoolProperty(name="Copy Eye Draws") + bpy.types.Object.copy_mod_draws = bpy.props.BoolProperty(name="Copy Mod Draws") bpy.types.Object.ignore = bpy.props.BoolProperty(name="ignore") bpy.types.Object.noedge = bpy.props.BoolProperty(name="No-Edge") bpy.types.Object.noentity = bpy.props.BoolProperty(name="No-Entity") diff --git a/decompiler/IR2/FormExpressionAnalysis.cpp b/decompiler/IR2/FormExpressionAnalysis.cpp index f0e93089ed0..4210b6bee99 100644 --- a/decompiler/IR2/FormExpressionAnalysis.cpp +++ b/decompiler/IR2/FormExpressionAnalysis.cpp @@ -2603,9 +2603,6 @@ void SetVarElement::push_to_stack(const Env& env, FormPool& pool, FormStack& sta ASSERT(x->parent_form == m_src); } - if (auto test0 = m_src->to_string(env) == "(* 0.00024414062 (-> arg0 y))") { - printf(""); - } if (m_src->is_single_element()) { auto src_as_se = dynamic_cast(m_src->back()); if (src_as_se) { diff --git a/decompiler/level_extractor/extract_level.cpp b/decompiler/level_extractor/extract_level.cpp index 773d23f259a..4cb5d8ae181 100644 --- a/decompiler/level_extractor/extract_level.cpp +++ b/decompiler/level_extractor/extract_level.cpp @@ -113,6 +113,16 @@ void extract_art_groups_from_level(const ObjectFileDB& db, if (db.obj_files_by_dgo.count(dgo_name)) { const auto& files = db.obj_files_by_dgo.at(dgo_name); MercSwapInfo swapped_info; + // build list of models to replace + auto merc_replacements_path = file_util::get_jak_project_dir() / "custom_assets" / + game_version_names[db.version()] / "merc_replacements"; + if (file_util::file_exists(merc_replacements_path.string())) { + auto custom_models = + file_util::find_files_in_dir(merc_replacements_path, std::regex(".*\\.glb")); + for (auto& mdl : custom_models) { + swapped_info.add_to_swap_list(mdl.stem().string()); + } + } for (const auto& file : files) { if (file.name.length() > 3 && !file.name.compare(file.name.length() - 3, 3, "-ag")) { const auto& ag_file = db.lookup_record(file); diff --git a/decompiler/level_extractor/extract_level.h b/decompiler/level_extractor/extract_level.h index 202923a617f..fc87995e020 100644 --- a/decompiler/level_extractor/extract_level.h +++ b/decompiler/level_extractor/extract_level.h @@ -12,9 +12,14 @@ namespace decompiler { // info about what models have been replaced/added per level struct MercSwapInfo { + std::vector swap_list; std::map> per_level_merc_swaps; std::map> per_level_custom_mdls; + bool should_swap(const std::string& model) { + return std::find(swap_list.begin(), swap_list.end(), model) != swap_list.end(); + } + bool already_swapped(const std::string& model, const std::string& level) { auto mdls_it = per_level_merc_swaps.find(level); if (mdls_it != per_level_merc_swaps.end()) { @@ -35,6 +40,8 @@ struct MercSwapInfo { return false; } + void add_to_swap_list(const std::string& model) { swap_list.push_back(model); } + void add_to_swapped_list(const std::string& model, const std::string& level) { per_level_merc_swaps[level].push_back(model); } diff --git a/decompiler/level_extractor/extract_merc.cpp b/decompiler/level_extractor/extract_merc.cpp index e88b4fd0aa3..148b4ac4039 100644 --- a/decompiler/level_extractor/extract_merc.cpp +++ b/decompiler/level_extractor/extract_merc.cpp @@ -1624,7 +1624,7 @@ void replace_model(tfrag3::Level& lvl, tfrag3::MercModel& model, const fs::path& } } - auto swap_info = load_replacement_merc_model(model.name, lvl.merc_data.indices.size(), + auto swap_info = load_replacement_merc_model(model, lvl.merc_data.indices.size(), lvl.merc_data.vertices.size(), lvl.textures.size(), mdl_path.string(), old_verts, false); model = swap_info.new_model; @@ -1647,8 +1647,8 @@ void add_custom_model_to_level(tfrag3::Level& lvl, auto lvl_name = lvl.level_name == "" ? "common" : lvl.level_name; lg::info("Adding custom model {} to {}", name, lvl_name); auto merc_data = - load_replacement_merc_model(name, lvl.merc_data.indices.size(), lvl.merc_data.vertices.size(), - lvl.textures.size(), mdl_path.string(), {}, true); + load_custom_merc_model(name, lvl.merc_data.indices.size(), lvl.merc_data.vertices.size(), + lvl.textures.size(), mdl_path.string(), {}, true); for (auto& idx : merc_data.new_indices) { lvl.merc_data.indices.push_back(idx); } @@ -1798,23 +1798,25 @@ void extract_merc(const ObjectFileData& ag_data, // do model replacement if present for (auto& ctrl : ctrls) { - auto merc_replacements_path = file_util::get_jak_project_dir() / "custom_assets" / - game_version_names[version] / "merc_replacements"; - if (!swapped_info.already_swapped(ctrl.name, out.level_name)) { - if (file_util::file_exists(merc_replacements_path.string())) { - std::string file_name(ctrl.name + ".glb"); - auto mdl_path = merc_replacements_path / file_name; - if (file_util::file_exists(mdl_path.string())) { - auto it = std::find_if(out.merc_data.models.begin(), out.merc_data.models.end(), - [&](const auto& m) { return m.name == ctrl.name; }); - if (it != out.merc_data.models.end()) { - auto& model = *it; - replace_model(out, model, mdl_path); - swapped_info.add_to_swapped_list(ctrl.name, out.level_name); + if (swapped_info.should_swap(ctrl.name)) { + auto merc_replacements_path = file_util::get_jak_project_dir() / "custom_assets" / + game_version_names[version] / "merc_replacements"; + if (!swapped_info.already_swapped(ctrl.name, out.level_name)) { + if (file_util::file_exists(merc_replacements_path.string())) { + std::string file_name(ctrl.name + ".glb"); + auto mdl_path = merc_replacements_path / file_name; + if (file_util::file_exists(mdl_path.string())) { + auto it = std::find_if(out.merc_data.models.begin(), out.merc_data.models.end(), + [&](const auto& m) { return m.name == ctrl.name; }); + if (it != out.merc_data.models.end()) { + auto& model = *it; + replace_model(out, model, mdl_path); + swapped_info.add_to_swapped_list(ctrl.name, out.level_name); + } } + } else { + lg::info("{} in level {} was already swapped, skipping", ctrl.name, out.level_name); } - } else { - lg::info("{} in level {} was already swapped, skipping", ctrl.name, out.level_name); } } diff --git a/decompiler/level_extractor/merc_replacement.cpp b/decompiler/level_extractor/merc_replacement.cpp index d79a1acbf86..467048bd0da 100644 --- a/decompiler/level_extractor/merc_replacement.cpp +++ b/decompiler/level_extractor/merc_replacement.cpp @@ -4,7 +4,7 @@ using namespace gltf_util; namespace decompiler { -void extract(const std::string& name, +void extract(tfrag3::MercModel& mdl, MercExtractData& out, const tinygltf::Model& model, const std::vector& all_nodes, @@ -22,6 +22,8 @@ void extract(const std::string& name, if (skin_idx) { joints += get_joint_count(model, *skin_idx); } + bool copy_eye_draws = false; + bool copy_mod_draws = false; for (const auto& n : all_nodes) { const auto& node = model.nodes[n.node_idx]; @@ -33,6 +35,10 @@ void extract(const std::string& name, mesh_count++; has_custom_weights = node.extras.Has("enable_custom_weights") && node.extras.Get("enable_custom_weights").Get(); + copy_eye_draws = + node.extras.Has("copy_eye_draws") && node.extras.Get("copy_eye_draws").Get(); + copy_mod_draws = + node.extras.Has("copy_mod_draws") && node.extras.Get("copy_mod_draws").Get(); for (const auto& prim : mesh.primitives) { prim_count++; // extract index buffer @@ -80,94 +86,157 @@ void extract(const std::string& name, tfrag3::MercEffect e; tfrag3::MercEffect envmap_eff; envmap_eff.has_envmap = false; - out.new_model.name = name; + out.new_model.name = mdl.name; // if we have a skeleton, use that joint count, otherwise use a high default value since the model // we replace can have more out.new_model.max_bones = joints != 3 ? joints : 100; out.new_model.max_draws = 0; - auto process_normal_draw = [&](tfrag3::MercEffect& eff, int mat_idx, const tfrag3::MercDraw& d_) { + for (const auto& [mat_idx, d_] : draw_by_material) { const auto& mat = model.materials[mat_idx]; - eff.all_draws.push_back(d_); - auto& draw = eff.all_draws.back(); - draw.mode = gltf_util::make_default_draw_mode(); - - if (mat_idx == -1) { - lg::warn("Draw had a material index of -1, using default texture."); - draw.tree_tex_id = 0; - return; + if (mat_idx < 0 || !gltf_util::material_has_envmap(model.materials[mat_idx]) || + !gltf_util::envmap_is_valid(model.materials[mat_idx])) { + gltf_util::process_normal_merc_draw(model, out, tex_offset, e, mat_idx, d_); + } else { + envmap_eff.has_envmap = true; + gltf_util::process_envmap_merc_draw(model, out, tex_offset, envmap_eff, mat_idx, d_); } - int tex_idx = mat.pbrMetallicRoughness.baseColorTexture.index; - if (tex_idx == -1) { - lg::warn("Material {} has no texture, using default texture.", mat.name); - draw.tree_tex_id = 0; - return; + } + + // in case a model only has envmap draws, we don't push the normal merc effect + if (!e.all_draws.empty()) { + out.new_model.effects.push_back(e); + } + if (envmap_eff.has_envmap) { + out.new_model.effects.push_back(envmap_eff); + } + + // copy any effects from the old model that used mod or eye draws + if (copy_eye_draws || copy_mod_draws) { + for (auto& old_eff : mdl.effects) { + tfrag3::MercEffect eff; + bool has_eye_draw = false; + if (copy_eye_draws) { + for (auto& draw : old_eff.all_draws) { + if (draw.eye_id != 0xff) { + has_eye_draw = true; + eff.all_draws.push_back(draw); + } + } + } + if (copy_mod_draws && old_eff.has_mod_draw) { + eff.has_mod_draw = true; + eff.mod = old_eff.mod; + eff.mod.fix_draw.clear(); + } + if ((copy_eye_draws || copy_mod_draws) && (eff.has_mod_draw || has_eye_draw)) { + lg::info("adding old effect for {} (mod draw {}, eye draw {})", mdl.name, + old_eff.has_mod_draw, has_eye_draw); + out.new_model.effects.push_back(eff); + } } + } - const auto& tex = model.textures[tex_idx]; - ASSERT(tex.sampler >= 0); - ASSERT(tex.source >= 0); - gltf_util::setup_draw_mode_from_sampler(model.samplers.at(tex.sampler), &draw.mode); - gltf_util::setup_alpha_from_material(mat, &draw.mode); + for (auto& effect : out.new_model.effects) { + out.new_model.max_draws += effect.all_draws.size(); + } - const auto& img = model.images[tex.source]; - draw.tree_tex_id = tex_offset + texture_pool_add_texture(&out.tex_pool, img); - }; + lg::info("total of {} unique materials ({} normal, {} envmap)", out.new_model.max_draws, + e.all_draws.size(), envmap_eff.all_draws.size()); + lg::info("Merged {} meshes and {} prims into {} vertices", mesh_count, prim_count, + out.new_vertices.size()); +} - auto process_envmap_draw = [&](tfrag3::MercEffect& eff, int mat_idx, const tfrag3::MercDraw& d_) { - const auto& mat = model.materials[mat_idx]; - eff.all_draws.push_back(d_); - auto& draw = eff.all_draws.back(); - draw.mode = gltf_util::make_default_draw_mode(); - - if (mat_idx == -1) { - lg::warn("Envmap draw had a material index of -1, using default texture."); - draw.tree_tex_id = texture_pool_debug_checker(&out.tex_pool); - return; +void extract(const std::string& name, + MercExtractData& out, + const tinygltf::Model& model, + const std::vector& all_nodes, + u32 index_offset, + u32 vertex_offset, + u32 tex_offset, + bool& has_custom_weights) { + ASSERT(out.new_vertices.empty()); + + std::map draw_by_material; + int mesh_count = 0; + int prim_count = 0; + int joints = 3; + auto skin_idx = find_single_skin(model, all_nodes); + if (skin_idx) { + joints += get_joint_count(model, *skin_idx); + } + + for (const auto& n : all_nodes) { + const auto& node = model.nodes[n.node_idx]; + if (node.extras.Has("set_invisible") && node.extras.Get("set_invisible").Get()) { + continue; } - int base_tex_idx = mat.pbrMetallicRoughness.baseColorTexture.index; - if (base_tex_idx == -1) { - lg::warn("Envmap material {} has no texture, using default texture.", mat.name); - draw.tree_tex_id = texture_pool_debug_checker(&out.tex_pool); - return; + if (node.mesh >= 0) { + const auto& mesh = model.meshes[node.mesh]; + mesh_count++; + has_custom_weights = node.extras.Has("enable_custom_weights") && + node.extras.Get("enable_custom_weights").Get(); + for (const auto& prim : mesh.primitives) { + prim_count++; + // extract index buffer + std::vector prim_indices = gltf_util::gltf_index_buffer( + model, prim.indices, out.new_vertices.size() + vertex_offset); + ASSERT_MSG(prim.mode == TINYGLTF_MODE_TRIANGLES, "Unsupported triangle mode"); + // extract vertices + auto verts = + gltf_util::gltf_vertices(model, prim.attributes, n.w_T_node, true, true, mesh.name); + out.new_vertices.insert(out.new_vertices.end(), verts.vtx.begin(), verts.vtx.end()); + out.new_colors.insert(out.new_colors.end(), verts.vtx_colors.begin(), + verts.vtx_colors.end()); + out.normals.insert(out.normals.end(), verts.normals.begin(), verts.normals.end()); + ASSERT(out.new_colors.size() == out.new_vertices.size()); + + if (prim.attributes.count("JOINTS_0") && prim.attributes.count("WEIGHTS_0")) { + auto joints_and_weights = gltf_util::extract_and_flatten_joints_and_weights(model, prim); + ASSERT(joints_and_weights.size() == verts.vtx.size()); + out.joints_and_weights.insert(out.joints_and_weights.end(), joints_and_weights.begin(), + joints_and_weights.end()); + } else { + // add fake data for vertices without this data + gltf_util::JointsAndWeights dummy; + dummy.joints[0] = 3; + dummy.weights[0] = 1.f; + for (size_t i = 0; i < out.new_vertices.size(); i++) { + out.joints_and_weights.push_back(dummy); + } + } + + // TODO: just putting it all in one material + auto& draw = draw_by_material[prim.material]; + draw.mode = gltf_util::make_default_draw_mode(); // todo rm + draw.tree_tex_id = 0; // todo rm + draw.num_triangles += prim_indices.size() / 3; + draw.no_strip = true; + draw.index_count = prim_indices.size(); + draw.first_index = index_offset + out.new_indices.size(); + + out.new_indices.insert(out.new_indices.end(), prim_indices.begin(), prim_indices.end()); + } } + } - const auto& base_tex = model.textures[base_tex_idx]; - ASSERT(base_tex.sampler >= 0); - ASSERT(base_tex.source >= 0); - gltf_util::setup_draw_mode_from_sampler(model.samplers.at(base_tex.sampler), &draw.mode); - gltf_util::setup_alpha_from_material(mat, &draw.mode); - const auto& roughness_tex = - model.textures.at(mat.pbrMetallicRoughness.metallicRoughnessTexture.index); - ASSERT(roughness_tex.sampler >= 0); - ASSERT(roughness_tex.source >= 0); - - draw.tree_tex_id = - tex_offset + gltf_util::texture_pool_add_envmap_control_texture( - &out.tex_pool, model, base_tex.source, roughness_tex.source, - !draw.mode.get_clamp_s_enable(), !draw.mode.get_clamp_t_enable()); - - // now, setup envmap draw: - auto envmap_settings = gltf_util::envmap_settings_from_gltf(mat); - const auto& envmap_tex = model.textures[envmap_settings.texture_idx]; - ASSERT(envmap_tex.sampler >= 0); - ASSERT(envmap_tex.source >= 0); - auto env_mode = gltf_util::make_default_draw_mode(); - gltf_util::setup_draw_mode_from_sampler(model.samplers.at(envmap_tex.sampler), &env_mode); - eff.envmap_texture = tex_offset + gltf_util::texture_pool_add_texture( - &out.tex_pool, model.images[envmap_tex.source]); - env_mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_0_DST_DST); - env_mode.enable_ab(); - eff.envmap_mode = env_mode; - }; + tfrag3::MercEffect e; + tfrag3::MercEffect envmap_eff; + envmap_eff.has_envmap = false; + out.new_model.name = name; + // if we have a skeleton, use that joint count, otherwise use a high default value since the model + // we replace can have more + out.new_model.max_bones = joints != 3 ? joints : 100; + out.new_model.max_draws = 0; for (const auto& [mat_idx, d_] : draw_by_material) { const auto& mat = model.materials[mat_idx]; - if (!material_has_envmap(mat) || !envmap_is_valid(mat)) { - process_normal_draw(e, mat_idx, d_); + if (mat_idx < 0 || !gltf_util::material_has_envmap(model.materials[mat_idx]) || + !gltf_util::envmap_is_valid(model.materials[mat_idx])) { + gltf_util::process_normal_merc_draw(model, out, tex_offset, e, mat_idx, d_); } else { envmap_eff.has_envmap = true; - process_envmap_draw(envmap_eff, mat_idx, d_); + gltf_util::process_envmap_merc_draw(model, out, tex_offset, envmap_eff, mat_idx, d_); } } @@ -285,7 +354,7 @@ void merc_convert_custom(MercSwapData& out, const MercExtractData& in) { } } -MercSwapData load_replacement_merc_model(const std::string& name, +MercSwapData load_replacement_merc_model(tfrag3::MercModel& mdl, u32 current_idx_count, u32 current_vtx_count, u32 current_tex_count, @@ -303,6 +372,37 @@ MercSwapData load_replacement_merc_model(const std::string& name, ASSERT_MSG(res, "Failed to load GLTF file!"); auto all_nodes = flatten_nodes_from_all_scenes(model); + MercExtractData extract_data; + auto has_custom_weights = false; + extract(mdl, extract_data, model, all_nodes, current_idx_count, current_vtx_count, + current_tex_count, has_custom_weights); + if (custom_mdl) { + merc_convert_custom(result, extract_data); + } else { + merc_convert_replacement(result, extract_data, old_verts, has_custom_weights); + } + + return result; +} + +MercSwapData load_custom_merc_model(const std::string& name, + u32 current_idx_count, + u32 current_vtx_count, + u32 current_tex_count, + const std::string& path, + const std::vector& old_verts, + bool custom_mdl) { + MercSwapData result; + lg::info("Reading gltf mesh: {}", path); + tinygltf::TinyGLTF loader; + tinygltf::Model model; + std::string err, warn; + bool res = loader.LoadBinaryFromFile(&model, &err, &warn, path); + ASSERT_MSG(warn.empty(), warn.c_str()); + ASSERT_MSG(err.empty(), err.c_str()); + ASSERT_MSG(res, "Failed to load GLTF file!"); + auto all_nodes = flatten_nodes_from_all_scenes(model); + MercExtractData extract_data; auto has_custom_weights = false; extract(name, extract_data, model, all_nodes, current_idx_count, current_vtx_count, diff --git a/decompiler/level_extractor/merc_replacement.h b/decompiler/level_extractor/merc_replacement.h index c6f33483a35..6e2fd6c5572 100644 --- a/decompiler/level_extractor/merc_replacement.h +++ b/decompiler/level_extractor/merc_replacement.h @@ -4,29 +4,21 @@ #include "common/util/gltf_util.h" namespace decompiler { -struct MercExtractData { - gltf_util::TexturePool tex_pool; - std::vector new_indices; - std::vector new_vertices; - std::vector> new_colors; - std::vector normals; - std::vector joints_and_weights; - tfrag3::MercModel new_model; -}; -// Data produced by loading a replacement model -struct MercSwapData { - std::vector new_indices; - std::vector new_vertices; - std::vector new_textures; - tfrag3::MercModel new_model; -}; +gltf_util::MercSwapData load_replacement_merc_model( + tfrag3::MercModel& mdl, + u32 current_idx_count, + u32 current_vtx_count, + u32 current_tex_count, + const std::string& path, + const std::vector& old_verts, + bool custom_mdl); -MercSwapData load_replacement_merc_model(const std::string& name, - u32 current_idx_count, - u32 current_vtx_count, - u32 current_tex_count, - const std::string& path, - const std::vector& old_verts, - bool custom_mdl); +gltf_util::MercSwapData load_custom_merc_model(const std::string& name, + u32 current_idx_count, + u32 current_vtx_count, + u32 current_tex_count, + const std::string& path, + const std::vector& old_verts, + bool custom_mdl); } // namespace decompiler \ No newline at end of file diff --git a/game/graphics/opengl_renderer/foreground/Merc2.cpp b/game/graphics/opengl_renderer/foreground/Merc2.cpp index e738448d3b2..adf48defa21 100644 --- a/game/graphics/opengl_renderer/foreground/Merc2.cpp +++ b/game/graphics/opengl_renderer/foreground/Merc2.cpp @@ -538,8 +538,6 @@ void Merc2::handle_pc_model(const DmaTransfer& setup, auto* flags = (const PcMercFlags*)input_data; int num_effects = flags->effect_count; // mostly just a sanity check ASSERT(num_effects < kMaxEffect); - // hack for custom models to disable blerc/mod draws - bool is_custom_model = model->effects.at(0).all_draws.at(0).no_strip; u64 current_ignore_alpha_bits = flags->ignore_alpha_mask; // shader settings u64 current_effect_enable_bits = flags->enable_mask; // mask for game to disable an effect bool model_uses_mod = flags->bitflags & 1; // if we should update vertices from game. @@ -566,11 +564,18 @@ void Merc2::handle_pc_model(const DmaTransfer& setup, // Next is pointers to merc data, needed so we can update vertices + // custom models are likely to have a different number of effects than what GOAL reports, update + // the count here (after reading DMA) so we don't potentially go out of bounds when we do + // blerc/mod draws + if (model->effects.at(0).all_draws.at(0).no_strip) { + num_effects = model->effects.size(); + } + // will hold opengl buffers for the updated vertices ModBuffers mod_opengl_buffers[kMaxEffect]; - if (model_uses_pc_blerc && !is_custom_model) { + if (model_uses_pc_blerc) { model_mod_blerc_draws(num_effects, model, lev, mod_opengl_buffers, blerc_weights, stats); - } else if (model_uses_mod && !is_custom_model) { // only if we've enabled, this path is slow. + } else if (model_uses_mod) { // only if we've enabled, this path is slow. model_mod_draws(num_effects, model, lev, input_data, setup, mod_opengl_buffers, stats); } @@ -650,8 +655,7 @@ void Merc2::handle_pc_model(const DmaTransfer& setup, auto& effect = model->effects[ei]; bool should_envmap = effect.has_envmap && !model_disables_envmap; - bool should_mod = - !is_custom_model && ((model_uses_pc_blerc || model_uses_mod) && effect.has_mod_draw); + bool should_mod = (model_uses_pc_blerc || model_uses_mod) && effect.has_mod_draw; if (should_mod) { // draw as two parts, fixed and mod diff --git a/goalc/build_actor/common/MercExtract.cpp b/goalc/build_actor/common/MercExtract.cpp index 9d90ab0ecd4..84f7d7f7a49 100644 --- a/goalc/build_actor/common/MercExtract.cpp +++ b/goalc/build_actor/common/MercExtract.cpp @@ -6,7 +6,7 @@ #include "goalc/build_level/common/gltf_mesh_extract.h" void extract(const std::string& name, - MercExtractData& out, + gltf_util::MercExtractData& out, const tinygltf::Model& model, const std::vector& all_nodes, u32 index_offset, @@ -96,89 +96,14 @@ void extract(const std::string& name, out.new_model.max_bones = joints; out.new_model.max_draws = 0; - auto process_normal_draw = [&](tfrag3::MercEffect& eff, int mat_idx, const tfrag3::MercDraw& d_) { - eff.all_draws.push_back(d_); - auto& draw = eff.all_draws.back(); - draw.mode = gltf_util::make_default_draw_mode(); - - if (mat_idx == -1) { - lg::warn("Draw had a material index of -1, using default texture."); - draw.tree_tex_id = 0; - return; - } - const auto& mat = model.materials[mat_idx]; - - int tex_idx = mat.pbrMetallicRoughness.baseColorTexture.index; - if (tex_idx == -1) { - lg::warn("Material {} has no texture, using default texture.", mat.name); - draw.tree_tex_id = 0; - return; - } - - const auto& tex = model.textures[tex_idx]; - ASSERT(tex.sampler >= 0); - ASSERT(tex.source >= 0); - gltf_util::setup_draw_mode_from_sampler(model.samplers.at(tex.sampler), &draw.mode); - gltf_util::setup_alpha_from_material(mat, &draw.mode); - - const auto& img = model.images[tex.source]; - draw.tree_tex_id = tex_offset + texture_pool_add_texture(&out.tex_pool, img); - }; - - auto process_envmap_draw = [&](tfrag3::MercEffect& eff, int mat_idx, const tfrag3::MercDraw& d_) { - const auto& mat = model.materials[mat_idx]; - eff.all_draws.push_back(d_); - auto& draw = eff.all_draws.back(); - draw.mode = gltf_util::make_default_draw_mode(); - - if (mat_idx == -1) { - lg::warn("Envmap draw had a material index of -1, using default texture."); - draw.tree_tex_id = texture_pool_debug_checker(&out.tex_pool); - return; - } - int base_tex_idx = mat.pbrMetallicRoughness.baseColorTexture.index; - if (base_tex_idx == -1) { - lg::warn("Envmap material {} has no texture, using default texture.", mat.name); - draw.tree_tex_id = texture_pool_debug_checker(&out.tex_pool); - return; - } - - const auto& base_tex = model.textures[base_tex_idx]; - ASSERT(base_tex.sampler >= 0); - ASSERT(base_tex.source >= 0); - gltf_util::setup_draw_mode_from_sampler(model.samplers.at(base_tex.sampler), &draw.mode); - gltf_util::setup_alpha_from_material(mat, &draw.mode); - const auto& roughness_tex = - model.textures.at(mat.pbrMetallicRoughness.metallicRoughnessTexture.index); - ASSERT(roughness_tex.sampler >= 0); - ASSERT(roughness_tex.source >= 0); - - draw.tree_tex_id = - tex_offset + gltf_util::texture_pool_add_envmap_control_texture( - &out.tex_pool, model, base_tex.source, roughness_tex.source, - !draw.mode.get_clamp_s_enable(), !draw.mode.get_clamp_t_enable()); - - // now, setup envmap draw: - auto envmap_settings = gltf_util::envmap_settings_from_gltf(mat); - const auto& envmap_tex = model.textures[envmap_settings.texture_idx]; - ASSERT(envmap_tex.sampler >= 0); - ASSERT(envmap_tex.source >= 0); - auto env_mode = gltf_util::make_default_draw_mode(); - gltf_util::setup_draw_mode_from_sampler(model.samplers.at(envmap_tex.sampler), &env_mode); - eff.envmap_texture = tex_offset + gltf_util::texture_pool_add_texture( - &out.tex_pool, model.images[envmap_tex.source]); - env_mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_0_DST_DST); - env_mode.enable_ab(); - eff.envmap_mode = env_mode; - }; - for (const auto& [mat_idx, d_] : draw_by_material) { + const auto& mat = model.materials[mat_idx]; if (mat_idx < 0 || !gltf_util::material_has_envmap(model.materials[mat_idx]) || !gltf_util::envmap_is_valid(model.materials[mat_idx])) { - process_normal_draw(e, mat_idx, d_); + gltf_util::process_normal_merc_draw(model, out, tex_offset, e, mat_idx, d_); } else { envmap_eff.has_envmap = true; - process_envmap_draw(envmap_eff, mat_idx, d_); + gltf_util::process_envmap_merc_draw(model, out, tex_offset, envmap_eff, mat_idx, d_); } } @@ -200,7 +125,7 @@ void extract(const std::string& name, out.new_vertices.size()); } -void merc_convert(MercSwapData& out, const MercExtractData& in) { +void merc_convert(gltf_util::MercSwapData& out, const gltf_util::MercExtractData& in) { // easy out.new_model = in.new_model; out.new_indices = in.new_indices; @@ -231,12 +156,12 @@ void merc_convert(MercSwapData& out, const MercExtractData& in) { } } -MercSwapData load_merc_model(u32 current_idx_count, - u32 current_vtx_count, - u32 current_tex_count, - const std::string& path, - const std::string& name) { - MercSwapData result; +gltf_util::MercSwapData load_merc_model(u32 current_idx_count, + u32 current_vtx_count, + u32 current_tex_count, + const std::string& path, + const std::string& name) { + gltf_util::MercSwapData result; lg::info("Reading gltf mesh: {}", path); tinygltf::TinyGLTF loader; tinygltf::Model model; @@ -247,7 +172,7 @@ MercSwapData load_merc_model(u32 current_idx_count, ASSERT_MSG(res, "Failed to load GLTF file!"); auto all_nodes = gltf_util::flatten_nodes_from_all_scenes(model); - MercExtractData extract_data; + gltf_util::MercExtractData extract_data; extract(name, extract_data, model, all_nodes, current_idx_count, current_vtx_count, current_tex_count); merc_convert(result, extract_data); diff --git a/goalc/build_actor/common/MercExtract.h b/goalc/build_actor/common/MercExtract.h index 31adb08dd48..5c6b6808ec9 100644 --- a/goalc/build_actor/common/MercExtract.h +++ b/goalc/build_actor/common/MercExtract.h @@ -4,37 +4,19 @@ #include "goalc/build_actor/jak1/build_actor.h" -struct MercExtractData { - gltf_util::TexturePool tex_pool; - std::vector new_indices; - std::vector new_vertices; - std::vector> new_colors; - std::vector normals; - std::vector joints_and_weights; - tfrag3::MercModel new_model; -}; - -// Data produced by loading a replacement model -struct MercSwapData { - std::vector new_indices; - std::vector new_vertices; - std::vector new_textures; - tfrag3::MercModel new_model; -}; - void extract(const std::string& name, - MercExtractData& out, + gltf_util::MercExtractData& out, const tinygltf::Model& model, const std::vector& all_nodes, u32 index_offset, u32 vertex_offset, u32 tex_offset); -void merc_convert(MercSwapData& out, const MercExtractData& in); -MercSwapData load_merc_model(u32 current_idx_count, - u32 current_vtx_count, - u32 current_tex_count, - const std::string& path, - const std::string& name); +void merc_convert(gltf_util::MercSwapData& out, const gltf_util::MercExtractData& in); +gltf_util::MercSwapData load_merc_model(u32 current_idx_count, + u32 current_vtx_count, + u32 current_tex_count, + const std::string& path, + const std::string& name); std::vector gen_collide_mesh_from_model( const tinygltf::Model& model, const std::vector& all_nodes,