From b3f2b81481268427ff2d72a1fb5934f0164486d1 Mon Sep 17 00:00:00 2001 From: water111 <48171810+water111@users.noreply.github.com> Date: Wed, 22 Jun 2022 22:43:42 -0400 Subject: [PATCH] [custom levels] fix various collision bugs (#1522) * [custom levels] fix various collision bugs * include --- common/custom_data/Tfrag3Data.h | 2 +- goalc/build_level/Tfrag.cpp | 2 - goalc/build_level/collide_bvh.cpp | 29 ++++---- goalc/build_level/collide_drawable.cpp | 6 -- goalc/build_level/collide_pack.cpp | 9 ++- goalc/build_level/gltf_mesh_extract.cpp | 92 +++++++++++++++++++++++-- 6 files changed, 108 insertions(+), 32 deletions(-) diff --git a/common/custom_data/Tfrag3Data.h b/common/custom_data/Tfrag3Data.h index c730190bb4..826806b1b5 100644 --- a/common/custom_data/Tfrag3Data.h +++ b/common/custom_data/Tfrag3Data.h @@ -99,7 +99,7 @@ struct PackedTfragVertices { struct Vertex { u16 xoff, yoff, zoff; u16 cluster_idx; - u16 s, t; + s16 s, t; u16 color_index; }; diff --git a/goalc/build_level/Tfrag.cpp b/goalc/build_level/Tfrag.cpp index 449cdbb22f..a15c5483f1 100644 --- a/goalc/build_level/Tfrag.cpp +++ b/goalc/build_level/Tfrag.cpp @@ -10,9 +10,7 @@ void tfrag_from_gltf(const gltf_mesh_extract::TfragOutput& mesh_extract_out, tfrag3::TfragTree& out_pc) { out_pc.kind = tfrag3::TFragmentTreeKind::NORMAL; // todo more types? out_pc.draws = std::move(mesh_extract_out.strip_draws); - fmt::print("have {} draws\n", out_pc.draws.size()); pack_tfrag_vertices(&out_pc.packed_vertices, mesh_extract_out.vertices); - fmt::print("have {} vertices\n", out_pc.packed_vertices.vertices.size()); for (auto& col : mesh_extract_out.color_palette) { tfrag3::TimeOfDayColor todc; diff --git a/goalc/build_level/collide_bvh.cpp b/goalc/build_level/collide_bvh.cpp index 8d410b2a3c..821c7e93ff 100644 --- a/goalc/build_level/collide_bvh.cpp +++ b/goalc/build_level/collide_bvh.cpp @@ -15,7 +15,6 @@ namespace collide { namespace { - constexpr int MAX_FACES_IN_FRAG = 100; /*! @@ -41,7 +40,6 @@ struct BBox { */ void add_to_bbox_recursive(const CNode& node, BBox& bbox) { if (node.faces.empty()) { - ASSERT(node.child_nodes.size() == 8); for (auto& child : node.child_nodes) { add_to_bbox_recursive(child, bbox); } @@ -68,7 +66,6 @@ BBox bbox_of_node(const CNode& node) { */ void update_bsphere_recursive(const CNode& node, const math::Vector3f& origin, float& r_squared) { if (node.faces.empty()) { - ASSERT(node.child_nodes.size() == 8); for (auto& child : node.child_nodes) { update_bsphere_recursive(child, origin, r_squared); } @@ -164,28 +161,35 @@ void split_node_to_8_children(CNode& node) { split_node_once(level1[3], &node.child_nodes[6], &node.child_nodes[7]); } +struct SplitResult { + size_t max_leaf_count = 0; + float max_bsphere_w = 0; +}; /*! * Split all leaf nodes. Returns the number of faces in the leaf with the most faces after * splitting. * This slightly unusual recursion pattern is to make sure we split everything to same depth, * which we believe might be a requirement of the collision system. */ -size_t split_all_leaves(CNode& node) { - size_t worst_leaf_face_count = 0; +SplitResult split_all_leaves(CNode& node) { + SplitResult result; if (node.child_nodes.empty()) { // we're a leaf! // split us: split_node_to_8_children(node); for (auto& child : node.child_nodes) { - worst_leaf_face_count = std::max(worst_leaf_face_count, child.faces.size()); + result.max_leaf_count = std::max(result.max_leaf_count, child.faces.size()); + result.max_bsphere_w = std::max(result.max_bsphere_w, child.bsphere.w()); } - return worst_leaf_face_count; + return result; } else { // not a leaf, recurse for (auto& child : node.child_nodes) { - worst_leaf_face_count = std::max(worst_leaf_face_count, split_all_leaves(child)); + auto cret = split_all_leaves(child); + result.max_bsphere_w = std::max(result.max_bsphere_w, cret.max_bsphere_w); + result.max_leaf_count = std::max(result.max_leaf_count, cret.max_leaf_count); } - return worst_leaf_face_count; + return result; } } @@ -197,10 +201,11 @@ void split_as_needed(CNode& root) { int num_leaves = 1; bool need_to_split = true; while (need_to_split) { - int faces_in_worst = split_all_leaves(root); + SplitResult worst = split_all_leaves(root); num_leaves *= 8; - lg::info("after splitting, the worst leaf has {} tris", faces_in_worst); - if (faces_in_worst < MAX_FACES_IN_FRAG) { + lg::info("after splitting, the worst leaf has {} tris, {} radius", worst.max_leaf_count, + worst.max_bsphere_w / 4096.f); + if (worst.max_leaf_count < MAX_FACES_IN_FRAG && worst.max_bsphere_w < (100.f * 4096.f)) { need_to_split = false; } } diff --git a/goalc/build_level/collide_drawable.cpp b/goalc/build_level/collide_drawable.cpp index 4198eadfbd..91cde30a47 100644 --- a/goalc/build_level/collide_drawable.cpp +++ b/goalc/build_level/collide_drawable.cpp @@ -147,8 +147,6 @@ size_t generate_collide_fragment_array(DataObjectGenerator& gen, gen.add_word(0); // 24 gen.add_word(0); // 28 - fmt::print("have: {}\n", meshes.size()); - ASSERT(meshes.size() == frag_mesh_locs.size()); for (size_t i = 0; i < meshes.size(); i++) { auto& mesh = meshes[i]; @@ -210,9 +208,6 @@ size_t generate_collide_draw_node_array(DataObjectGenerator& gen, } size_t DrawableTreeCollideFragment::add_to_object_file(DataObjectGenerator& gen) const { - for (auto& lev : bvh.node_arrays) { - fmt::print("lev: {}\n", lev.nodes.size()); - } // generate pat array size_t pat_array_loc = generate_pat_array(gen, packed_frags.pats); @@ -238,7 +233,6 @@ size_t DrawableTreeCollideFragment::add_to_object_file(DataObjectGenerator& gen) collide_frag_meshes, children_refs); u32 flag = 0; while (array_slot >= 0) { - fmt::print("sizes: {} {}\n", children_refs.size(), bvh.node_arrays.at(array_slot).nodes.size()); ASSERT(children_refs.size() == bvh.node_arrays.at(array_slot).nodes.size()); std::vector next_children; diff --git a/goalc/build_level/collide_pack.cpp b/goalc/build_level/collide_pack.cpp index 595ad6c89c..512167f05a 100644 --- a/goalc/build_level/collide_pack.cpp +++ b/goalc/build_level/collide_pack.cpp @@ -36,7 +36,9 @@ float u32_to_float(u32 in) { */ PackedU16Verts pack_verts_to_u16(const std::vector& input) { PackedU16Verts result; - ASSERT(!input.empty()); + if (input.empty()) { + lg::warn("Empty collide fragment"); + } // this "magic" offset is a large float where a ulp is 16.f, or 1/256th of a meter. // this means that float -> int can be done as a single addition. @@ -45,7 +47,7 @@ PackedU16Verts pack_verts_to_u16(const std::vector& input) { magic_offset.fill(u32_to_float(0x4d000000)); // we'll be treating everything as an offset from this minimum vertex: - math::Vector3f min_vtx = input[0]; + math::Vector3f min_vtx = input.empty() ? math::Vector3f::zero() : input[0]; for (auto& vtx : input) { min_vtx.min_in_place(vtx); } @@ -143,8 +145,6 @@ struct Vector3fHash { IndexedFaces dedup_frag_mesh(const collide::CollideFrag& frag, PatMap* pat_map) { IndexedFaces result; std::unordered_map vertex_map; - // avoid confusion with 0 in strip table. (todo, can probably remove) - result.vertices_float.push_back(math::Vector3f::zero()); for (auto& face_in : frag.faces) { auto& face_out = result.faces.emplace_back(); @@ -163,7 +163,6 @@ IndexedFaces dedup_frag_mesh(const collide::CollideFrag& frag, PatMap* pat_map) } } } - // fmt::print("{} -> {}\n", frag.faces.size() * 3, result.vertices_float.size()); result.vertices_u16 = pack_verts_to_u16(result.vertices_float); return result; } diff --git a/goalc/build_level/gltf_mesh_extract.cpp b/goalc/build_level/gltf_mesh_extract.cpp index a81463c052..e929052534 100644 --- a/goalc/build_level/gltf_mesh_extract.cpp +++ b/goalc/build_level/gltf_mesh_extract.cpp @@ -2,6 +2,7 @@ * Mesh extraction for GLTF meshes. */ +#include #include "gltf_mesh_extract.h" #include "goalc/build_level/color_quantization.h" #include "third-party/tiny_gltf/tiny_gltf.h" @@ -217,6 +218,10 @@ ExtractedVertices gltf_vertices(const tinygltf::Model& model, ASSERT_MSG(attrib_accessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT, "NORMAL wasn't float"); normals = extract_vec3f(data_ptr, count, byte_stride); + for (auto& nrm : normals) { + math::Vector4f nrm4(nrm.x(), nrm.y(), nrm.z(), 0.f); + nrm = (w_T_local * nrm4).xyz(); + } ASSERT(normals.size() == result.size()); } else { lg::error("No NORMAL attribute for mesh: {}", debug_name); @@ -246,8 +251,8 @@ DrawMode make_default_draw_mode() { mode.set_alpha_blend(DrawMode::AlphaBlend::DISABLED); mode.set_aref(0); mode.set_alpha_fail(GsTest::AlphaFail::KEEP); - mode.set_clamp_s_enable(true); - mode.set_clamp_t_enable(true); + mode.set_clamp_s_enable(false); + mode.set_clamp_t_enable(false); mode.disable_filt(); // for checkerboard... mode.enable_tcc(); // ? mode.disable_at(); @@ -527,6 +532,7 @@ void extract(const Input& in, if (in.get_colors) { all_vtx_colors.insert(all_vtx_colors.end(), verts.vtx_colors.begin(), verts.vtx_colors.end()); + ASSERT(all_vtx_colors.size() == out.vertices.size()); } // TODO: just putting it all in one material @@ -596,6 +602,65 @@ void extract(const Input& in, dedup_vertices(out); } +std::optional> subdivide_face_if_needed(CollideFace face_in) { + math::Vector3f v_min = face_in.v[0]; + v_min.min_in_place(face_in.v[1]); + v_min.min_in_place(face_in.v[2]); + v_min -= 16.f; + bool needs_subdiv = false; + for (auto& vert : face_in.v) { + if ((vert - v_min).squared_length() > 154.f * 154.f * 4096.f * 4096.f) { + needs_subdiv = true; + break; + } + } + + if (needs_subdiv) { + math::Vector3f a = (face_in.v[0] + face_in.v[1]) * 0.5f; + math::Vector3f b = (face_in.v[1] + face_in.v[2]) * 0.5f; + math::Vector3f c = (face_in.v[2] + face_in.v[0]) * 0.5f; + math::Vector3f v0 = face_in.v[0]; + math::Vector3f v1 = face_in.v[1]; + math::Vector3f v2 = face_in.v[2]; + CollideFace fs[4]; + fs[0].v[0] = v0; + fs[0].v[1] = a; + fs[0].v[2] = c; + fs[0].bsphere = math::bsphere_of_triangle(face_in.v); + + fs[1].v[0] = a; + fs[1].v[1] = v1; + fs[1].v[2] = b; + fs[1].bsphere = math::bsphere_of_triangle(fs[1].v); + fs[1].pat = face_in.pat; + + fs[2].v[0] = a; + fs[2].v[1] = b; + fs[2].v[2] = c; + fs[2].bsphere = math::bsphere_of_triangle(fs[2].v); + fs[2].pat = face_in.pat; + + fs[3].v[0] = b; + fs[3].v[1] = v2; + fs[3].v[2] = c; + fs[3].bsphere = math::bsphere_of_triangle(fs[3].v); + fs[3].pat = face_in.pat; + + std::vector result; + for (auto f : fs) { + auto next_faces = subdivide_face_if_needed(f); + if (next_faces) { + result.insert(result.end(), next_faces->begin(), next_faces->end()); + } else { + result.push_back(f); + } + } + return result; + } else { + return std::nullopt; + } +} + void extract(const Input& in, CollideOutput& out, const tinygltf::Model& model, @@ -606,7 +671,6 @@ void extract(const Input& in, for (const auto& n : all_nodes) { const auto& node = model.nodes[n.node_idx]; - fmt::print("node: {} {}\n", node.name, node.mesh); if (node.mesh >= 0) { const auto& mesh = model.meshes[node.mesh]; if (!mesh.extras.Has("collide")) { @@ -644,7 +708,9 @@ void extract(const Input& in, if (dots[0] > 1e-3 && dots[1] > 1e-3 && dots[2] > 1e-3) { suspicious_faces++; - std::swap(face.v[2], face.v[1]); + auto temp = face.v[2]; + face.v[2] = face.v[1]; + face.v[1] = temp; } face.bsphere = math::bsphere_of_triangle(face.v); @@ -665,8 +731,22 @@ void extract(const Input& in, } } - lg::info("{} out of {} faces were suspicious (a small number is ok)", suspicious_faces, - out.faces.size()); + std::vector fixed_faces; + int fix_count = 0; + for (auto& face : out.faces) { + auto try_fix = subdivide_face_if_needed(face); + if (try_fix) { + fix_count++; + fixed_faces.insert(fixed_faces.end(), try_fix->begin(), try_fix->end()); + } else { + fixed_faces.push_back(face); + } + } + out.faces = std::move(fixed_faces); + + lg::info("{} out of {} faces appeared to have wrong orientation and were flipped", + suspicious_faces, out.faces.size()); + lg::info("{} faces were too big and were subdivided", fix_count); // lg::info("Collision extract{} {}", mesh_count, prim_count); }