#include #include "extract_shrub.h" #include "decompiler/ObjectFile/LinkedObjectFile.h" #include "common/util/FileUtil.h" namespace decompiler { using namespace level_tools; /// /// Get the index of the first draw node in an array. Works for node or tfrag. /// /// /// u16 get_first_idx_shrub(const level_tools::DrawableInlineArray* array) { auto as_tie_instances = dynamic_cast(array); auto as_nodes = dynamic_cast(array); if (as_tie_instances) { return as_tie_instances->instances.at(0).id; } else if (as_nodes) { return as_nodes->draw_nodes.at(0).id; } else { assert(false); } } /// /// Verify node indices follow the patterns we expect. Takes start as the expected first, writes the /// end. /// /// /// /// /// bool verify_node_indices_from_array_shrub(const level_tools::DrawableInlineArray* array, u16 start, u16* end) { auto as_shrub_instances = dynamic_cast(array); auto as_nodes = dynamic_cast(array); if (as_shrub_instances) { for (auto& elt : as_shrub_instances->instances) { if (elt.id != start) { fmt::print("bad inst: exp {} got {}\n", start, elt.id); return false; } start++; } *end = start; return true; } else if (as_nodes) { for (auto& elt : as_nodes->draw_nodes) { if (elt.id != start) { fmt::print("bad node: exp {} got {}\n", start, elt.id); return false; } start++; } *end = start; return true; } else { fmt::print("bad node array type: {}\n", array->my_type()); return false; } } /// /// Verify all node indices in a tree. /// /// /// If valid or not bool verify_node_indices(const shrub_types::DrawableTreeInstanceShrub* tree) { u16 start = get_first_idx_shrub(tree->arrays.at(0).get()); for (auto& array : tree->arrays) { if (!verify_node_indices_from_array_shrub(array.get(), start, &start)) { return false; } start = (start + 31) & ~(31); // TODO - is this wrong for shrub (just copy pasted from tie for now) } return true; } /// /// Extract the visibility tree. This does not insert nodes for the bottom level. /// /// /// /// void extract_vis_data(const shrub_types::DrawableTreeInstanceShrub* tree, u16 first_child, tfrag3::ShrubTree& out) { out.bvh.first_leaf_node = first_child; out.bvh.last_leaf_node = first_child; if (tree->arrays.size() == 0) { // shouldn't hit this? } else if (tree->arrays.size() == 1) { auto array = dynamic_cast(tree->arrays.at(0).get()); assert(array); out.bvh.first_root = array->instances.at(0).id; out.bvh.num_roots = array->instances.size(); out.bvh.only_children = true; } else { auto array = dynamic_cast(tree->arrays.at(0).get()); assert(array); out.bvh.first_root = array->draw_nodes.at(0).id; out.bvh.num_roots = array->draw_nodes.size(); out.bvh.only_children = false; } out.bvh.vis_nodes.resize(first_child - out.bvh.first_root); // may run 0 times, if there are only children. for (int i = 0; i < ((int)tree->arrays.size()) - 1; i++) { bool expecting_leaves = i == ((int)tree->arrays.size()) - 2; auto array = dynamic_cast(tree->arrays.at(i).get()); assert(array); u16 idx = first_child; for (auto& elt : array->draw_nodes) { auto& vis = out.bvh.vis_nodes.at(elt.id - out.bvh.first_root); assert(vis.num_kids == 0xff); for (int j = 0; j < 4; j++) { vis.bsphere[j] = elt.bsphere.data[j]; } vis.num_kids = elt.child_count; vis.flags = elt.flags; assert(vis.flags == expecting_leaves ? 0 : 1); assert(vis.num_kids > 0); assert(vis.num_kids <= 8); assert(elt.children.size() == vis.num_kids); if (expecting_leaves) { for (int leaf = 0; leaf < (int)vis.num_kids; leaf++) { auto l = dynamic_cast(elt.children.at(leaf).get()); assert(l); assert(idx == l->id); assert(l->id >= out.bvh.first_leaf_node); if (leaf == 0) { vis.child_id = l->id; } out.bvh.last_leaf_node = std::max((u16)l->id, out.bvh.last_leaf_node); idx++; } } else { u16 arr_idx = 0; for (int child = 0; child < (int)vis.num_kids; child++) { auto l = dynamic_cast(elt.children.at(child).get()); assert(l); if (child == 0) { arr_idx = l->id; } else { assert(arr_idx < l->id); arr_idx = l->id; } if (child == 0) { vis.child_id = l->id; } assert(l->id < out.bvh.first_leaf_node); } } } } } struct ShrubInstanceFragInfo { // the color index table uploaded to VU. // this contains indices into the shared palette. std::vector color_indices; u16 color_index_offset_in_big_palette = -1; math::Vector lq_colors_ui(u32 qw) const { // note: this includes the unpack assert(qw >= 204); qw -= 204; qw *= 4; assert(qw + 4 <= color_indices.size()); math::Vector result; for (int i = 0; i < 4; i++) { result[i] = color_indices.at(qw + i); } return result; } }; struct ShrubInstanceInfo { // The index of the prototype (the geometry) that is used by this instance // note: we're going to trust that this lines up with bucket. // if this assumption is wrong, we'll be drawing with the wrong model and it will be super // obvious. u16 prototype_idx = 0; // our bsphere's index in the BVH tree u16 vis_id = 0; // not totally sure if we'll use this (currently unused in tfrag, but probably worth if we // actually cull using the tree) math::Vector4f bsphere; std::array mat; u16 wind_index = 0; float unknown_wind_related_value = 0.f; // w of the first mat vec. std::vector frags; // per-instance per-fragment info }; std::array extract_shrub_matrix(const u16* data) { std::array result; for (int i = 0; i < 4; i++) { s32 x = data[12 + i]; x <<= 16; x >>= 10; result[3][i] = x; } for (int vec = 0; vec < 3; vec++) { for (int i = 0; i < 4; i++) { s32 x = data[vec * 4 + i]; x <<= 16; x >>= 16; result[vec][i] = (float)x / 4096.f; } } return result; } struct ShrubProtoInfo { std::string name; std::vector instances; bool uses_generic = false; float stiffness = 0; u32 generic_flag; std::vector time_of_day_colors; }; constexpr int GEOM_IDX = 1; // todo 0 or 1?? std::vector collect_instance_info( const shrub_types::DrawableInlineArrayInstanceShrub* instances, const std::vector* protos) { std::vector result; for (auto& instance : instances->instances) { ShrubInstanceInfo info; info.prototype_idx = instance.bucket_index; info.vis_id = instance.id; for (int i = 0; i < 4; i++) { info.bsphere[i] = instance.bsphere.data[i]; } info.mat = extract_shrub_matrix(instance.origin.data); info.mat[3][0] += info.bsphere[0]; info.mat[3][1] += info.bsphere[1]; info.mat[3][2] += info.bsphere[2]; info.wind_index = instance.wind_index; // there's a value stashed here that we can get rid of // it is related to wind. // TODO - is this the same for shrubs? info.unknown_wind_related_value = info.mat[0][3]; info.mat[0][3] = 0.f; // TODO - from a type-level, the `instance-shrubbery` type also seems to store colors in the // same way // // However, there is no `base_qw` field, `prototype-bucket-shrub` is MUCH simpler // // For shrub, perhaps this is stored in the shrub's header? which is a `shrubbery`? /*auto& proto = protos->at(info.prototype_idx); u32 offset_bytes = proto.base_qw[GEOM_IDX] * 16; for (int frag_idx = 0; frag_idx < proto.frag_count[GEOM_IDX]; frag_idx++) { TieInstanceFragInfo frag_info; u32 num_color_qwc = proto.color_index_qwc.at(proto.index_start[GEOM_IDX] + frag_idx); for (u32 i = 0; i < num_color_qwc * 4; i++) { for (u32 j = 0; j < 4; j++) { frag_info.color_indices.push_back( instance.color_indices.data->words_by_seg.at(instance.color_indices.seg) .at(((offset_bytes + instance.color_indices.byte_offset) / 4) + i) .get_byte(j)); } } info.frags.push_back(std::move(frag_info)); assert(info.frags.back().color_indices.size() > 0); offset_bytes += num_color_qwc * 16; }*/ if (result.size() <= info.prototype_idx) { result.resize(info.prototype_idx + 1); } result[info.prototype_idx].instances.push_back(info); } return result; } void update_proto_info(std::vector* out, const std::vector& map, const TextureDB& tdb, const std::vector& protos) { out->resize(std::max(out->size(), protos.size())); for (size_t i = 0; i < protos.size(); i++) { const auto& proto = protos[i]; auto& info = out->at(i); assert(proto.flags == 0 || proto.flags == 2); info.uses_generic = (proto.flags == 2); info.name = proto.name; info.stiffness = proto.stiffness; info.generic_flag = proto.flags & 2; // TODO - shrubbery is much simpler to this, it doens't have fragments (i think) // for (int frag_idx = 0; frag_idx < proto.frag_count[GEOM_IDX]; frag_idx++) { // TieFrag frag_info; // for (int tex_idx = 0; // tex_idx < proto.geometry[GEOM_IDX].tie_fragments.at(frag_idx).tex_count / 5; tex_idx++) // { // AdgifInfo adgif; // auto& gif_data = proto.geometry[GEOM_IDX].tie_fragments[frag_idx].gif_data; // u8 ra_tex0 = gif_data.at(16 * (tex_idx * 5 + 0) + 8); // u64 ra_tex0_val; // memcpy(&ra_tex0_val, &gif_data.at(16 * (tex_idx * 5 + 0)), 8); // assert(ra_tex0 == (u8)GsRegisterAddress::TEX0_1); // assert(ra_tex0_val == 0 || ra_tex0_val == 0x800000000); // note: decal // frag_info.has_magic_tex0_bit = ra_tex0_val == 0x800000000; // memcpy(&adgif.first_w, &gif_data.at(16 * (tex_idx * 5 + 0) + 12), 4); // u8 ra_tex1 = gif_data.at(16 * (tex_idx * 5 + 1) + 8); // u64 ra_tex1_val; // memcpy(&ra_tex1_val, &gif_data.at(16 * (tex_idx * 5 + 1)), 8); // assert(ra_tex1 == (u8)GsRegisterAddress::TEX1_1); // assert(ra_tex1_val == 0x120); // some flag // u32 original_tex; // memcpy(&original_tex, &gif_data.at(16 * (tex_idx * 5 + 1) + 8), 4); // u32 new_tex = remap_texture(original_tex, map); // if (original_tex != new_tex) { // fmt::print("map from 0x{:x} to 0x{:x}\n", original_tex, new_tex); // } // u32 tpage = new_tex >> 20; // u32 tidx = (new_tex >> 8) & 0b1111'1111'1111; // u32 tex_combo = (((u32)tpage) << 16) | tidx; // auto tex = tdb.textures.find(tex_combo); // assert(tex != tdb.textures.end()); // adgif.combo_tex = tex_combo; // memcpy(&adgif.second_w, &gif_data.at(16 * (tex_idx * 5 + 1) + 12), 4); // if (ra_tex0_val == 0x800000000) { // fmt::print("texture {} in {} has weird tex setting\n", tex->second.name, proto.name); // } // u8 ra_mip = gif_data.at(16 * (tex_idx * 5 + 2) + 8); // assert(ra_mip == (u8)GsRegisterAddress::MIPTBP1_1); // memcpy(&adgif.third_w, &gif_data.at(16 * (tex_idx * 5 + 2) + 12), 4); // // who cares about the value // u8 ra_clamp = gif_data.at(16 * (tex_idx * 5 + 3) + 8); // assert(ra_clamp == (u8)GsRegisterAddress::CLAMP_1); // u64 clamp; // memcpy(&clamp, &gif_data.at(16 * (tex_idx * 5 + 3)), 8); // adgif.clamp_val = clamp; // u8 ra_alpha = gif_data.at(16 * (tex_idx * 5 + 4) + 8); // assert(ra_alpha == (u8)GsRegisterAddress::ALPHA_1); // u64 alpha; // memcpy(&alpha, &gif_data.at(16 * (tex_idx * 5 + 4)), 8); // adgif.alpha_val = alpha; // frag_info.adgifs.push_back(adgif); // } // frag_info.expected_dverts = proto.geometry[GEOM_IDX].tie_fragments[frag_idx].num_dverts; // int tex_qwc = proto.geometry[GEOM_IDX].tie_fragments.at(frag_idx).tex_count; // int other_qwc = proto.geometry[GEOM_IDX].tie_fragments.at(frag_idx).gif_count; // frag_info.other_gif_data.resize(16 * other_qwc); // memcpy(frag_info.other_gif_data.data(), // proto.geometry[GEOM_IDX].tie_fragments[frag_idx].gif_data.data() + (16 * tex_qwc), // 16 * other_qwc); // const auto& pr = proto.geometry[GEOM_IDX].tie_fragments[frag_idx].point_ref; // int in_qw = pr.size() / 16; // int out_qw = in_qw * 2; // frag_info.points_data.resize(out_qw * 16); // { // const s16* in_ptr = (const s16*)pr.data(); // s32* out_ptr = (s32*)frag_info.points_data.data(); // for (int ii = 0; ii < out_qw * 4; ii++) { // out_ptr[ii] = in_ptr[ii]; // } // } // // just for debug // for (int g = 0; g < 4; g++) { // frag_info.point_sizes.push_back(proto.geometry[g].tie_fragments[frag_idx].point_ref.size()); // } // info.frags.push_back(std::move(frag_info)); //} } } void extract_shrub(const shrub_types::DrawableTreeInstanceShrub* tree, const std::string& debug_name, const std::vector& map, const TextureDB& tex_db, const std::vector>& expected_missing_textures, tfrag3::Level& out, bool dump_level) { tfrag3::ShrubTree this_tree; // sanity check the vis tree (not a perfect check, but this is used in game and should be right) assert(tree->length == (int)tree->arrays.size()); assert(tree->length > 0); auto last_array = tree->arrays.back().get(); auto as_instance_array = dynamic_cast(last_array); assert(as_instance_array); assert(as_instance_array->length == (int)as_instance_array->instances.size()); assert(as_instance_array->length > 0); u16 idx = as_instance_array->instances.front().id; for (auto& elt : as_instance_array->instances) { assert(elt.id == idx); idx++; } bool ok = verify_node_indices(tree); assert(ok); fmt::print(" tree has {} arrays and {} instances\n", tree->length, as_instance_array->length); // extract the vis tree. Note that this extracts the tree only down to the last draw node, a // parent of between 1 and 8 instances. extract_vis_data(tree, as_instance_array->instances.front().id, this_tree); // map of instance ID to its parent. We'll need this later. std::unordered_map instance_parents; for (size_t node_idx = 0; node_idx < this_tree.bvh.vis_nodes.size(); node_idx++) { const auto& node = this_tree.bvh.vis_nodes[node_idx]; if (node.flags == 0) { for (int i = 0; i < node.num_kids; i++) { instance_parents[node.child_id + i] = node_idx; } } } auto info = collect_instance_info(as_instance_array, &tree->info.prototype_inline_array_shrub.data); update_proto_info(&info, map, tex_db, tree->info.prototype_inline_array_shrub.data); // AHHHH - likely stuck // debug_print_info(info); // emulate_tie_prototype_program(info); // emulate_tie_instance_program(info); // emulate_kicks(info); // if (dump_level) { // auto dir = file_util::get_file_path({fmt::format("debug_out/tie-{}/", debug_name)}); // file_util::create_dir_if_needed(dir); // for (auto& proto : info) { // auto data = debug_dump_proto_to_obj(proto); // file_util::write_text_file(fmt::format("{}/{}.obj", dir, proto.name), data); // // file_util::create_dir_if_needed() // } // auto full = dump_full_to_obj(info); // file_util::write_text_file(fmt::format("{}/ALL.obj", dir), full); //} // auto full_palette = make_big_palette(info); // add_vertices_and_static_draw(this_tree, out, tex_db, info); // for (auto& draw : this_tree.static_draws) { // for (auto& str : draw.vis_groups) { // auto it = instance_parents.find(str.vis_idx); // if (it == instance_parents.end()) { // str.vis_idx = UINT32_MAX; // } else { // str.vis_idx = it->second; // } // } //} // this_tree.colors = full_palette.colors; // fmt::print("TIE tree has {} draws\n", this_tree.static_draws.size()); // out.tie_trees.push_back(std::move(this_tree)); } } // namespace decompiler