diff --git a/CMakeLists.txt b/CMakeLists.txt index 798a2c57..c70647db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1363,6 +1363,7 @@ add_executable(wowee_editor tools/editor/cli_zone_export.cpp tools/editor/cli_arg_required.cpp tools/editor/cli_multi_arg_required.cpp + tools/editor/cli_weld.cpp tools/editor/cli_dispatch.cpp tools/editor/editor_app.cpp tools/editor/editor_camera.cpp diff --git a/tools/editor/cli_bake.cpp b/tools/editor/cli_bake.cpp index ada07592..08cffc1f 100644 --- a/tools/editor/cli_bake.cpp +++ b/tools/editor/cli_bake.cpp @@ -1,4 +1,5 @@ #include "cli_bake.hpp" +#include "cli_weld.hpp" #include "pipeline/wowee_model.hpp" #include "pipeline/wowee_building.hpp" @@ -9,7 +10,6 @@ #include #include #include -#include #include #include @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -952,31 +953,29 @@ int handleBakeWomCollision(int& i, int argc, char** argv) { std::vector positions; std::vector indices; if (useWeld) { - // Build the canon[] map first, then re-emit positions/indices - // using only the canonical (lowest-index) vertex of each - // welded equivalence class. This produces a properly indexed - // mesh so collision raycasts can share edges between faces. - const float invEps = 1.0f / std::max(weldEps, 1e-9f); - using QKey = std::tuple; - std::map bucket; - std::vector canon(wom.vertices.size()); + // Run cli_weld to map vertex i → canonical (lowest-index) + // representative of its equivalence class, then compact + // positions to one entry per unique class and renumber + // indices accordingly. The collision mesh ends up properly + // indexed so raycasts can share edges between faces. + std::vector srcPositions; + srcPositions.reserve(wom.vertices.size()); + for (const auto& vert : wom.vertices) srcPositions.push_back(vert.position); + std::size_t uniq = 0; + std::vector canon = buildWeldMap(srcPositions, weldEps, uniq); + // Build canon→compactedIndex remap as we walk vertices in order. + std::vector remap(wom.vertices.size(), + std::numeric_limits::max()); + positions.reserve(uniq); for (std::size_t v = 0; v < wom.vertices.size(); ++v) { - const auto& p = wom.vertices[v].position; - QKey k{static_cast(std::lround(p.x * invEps)), - static_cast(std::lround(p.y * invEps)), - static_cast(std::lround(p.z * invEps))}; - auto it = bucket.find(k); - if (it == bucket.end()) { - uint32_t newIdx = static_cast(positions.size()); - bucket.emplace(k, newIdx); - positions.push_back(p); - canon[v] = newIdx; - } else { - canon[v] = it->second; + uint32_t c = canon[v]; + if (remap[c] == std::numeric_limits::max()) { + remap[c] = static_cast(positions.size()); + positions.push_back(srcPositions[c]); } } indices.reserve(wom.indices.size()); - for (uint32_t orig : wom.indices) indices.push_back(canon[orig]); + for (uint32_t orig : wom.indices) indices.push_back(remap[canon[orig]]); } else { positions.reserve(wom.vertices.size()); for (const auto& vert : wom.vertices) positions.push_back(vert.position); diff --git a/tools/editor/cli_mesh_info.cpp b/tools/editor/cli_mesh_info.cpp index 5d9656b3..464e62b9 100644 --- a/tools/editor/cli_mesh_info.cpp +++ b/tools/editor/cli_mesh_info.cpp @@ -1,4 +1,5 @@ #include "cli_mesh_info.hpp" +#include "cli_weld.hpp" #include "pipeline/wowee_model.hpp" #include "pipeline/wowee_building.hpp" @@ -11,10 +12,8 @@ #include #include #include -#include #include #include -#include #include #include @@ -382,34 +381,15 @@ int handleInfoMeshStats(int& i, int argc, char** argv) { // when keying edges, so adjacent triangles whose corner // vertices happen to share a position (per-face shading // emitting duplicates) get unified. - std::vector canon(wom.vertices.size()); + std::vector canon; std::size_t uniquePositions = 0; if (useWeld) { - // Use the quantized (qx, qy, qz) tuple as the equality key — - // a hash key would risk false-positive collisions that - // incorrectly merge distinct corners (e.g. a cube's 8 corners - // collapsing to 2). std::map gives exact-match equality at - // O(log n) per op which is fast enough for any real mesh. - const float invEps = 1.0f / std::max(weldEps, 1e-9f); - using QKey = std::tuple; - std::map bucket; - auto qkey = [&](const glm::vec3& p) -> QKey { - return {static_cast(std::lround(p.x * invEps)), - static_cast(std::lround(p.y * invEps)), - static_cast(std::lround(p.z * invEps))}; - }; - for (std::size_t v = 0; v < wom.vertices.size(); ++v) { - QKey k = qkey(wom.vertices[v].position); - auto it = bucket.find(k); - if (it == bucket.end()) { - bucket.emplace(k, static_cast(v)); - canon[v] = static_cast(v); - } else { - canon[v] = it->second; - } - } - uniquePositions = bucket.size(); + std::vector positions; + positions.reserve(wom.vertices.size()); + for (const auto& v : wom.vertices) positions.push_back(v.position); + canon = buildWeldMap(positions, weldEps, uniquePositions); } else { + canon.resize(wom.vertices.size()); for (std::size_t v = 0; v < wom.vertices.size(); ++v) { canon[v] = static_cast(v); } diff --git a/tools/editor/cli_weld.cpp b/tools/editor/cli_weld.cpp new file mode 100644 index 00000000..aea6911e --- /dev/null +++ b/tools/editor/cli_weld.cpp @@ -0,0 +1,39 @@ +#include "cli_weld.hpp" + +#include +#include +#include +#include + +namespace wowee { +namespace editor { +namespace cli { + +std::vector buildWeldMap( + const std::vector& positions, + float eps, + std::size_t& uniqueOut) { + const float invEps = 1.0f / std::max(eps, 1e-9f); + using QKey = std::tuple; + std::map bucket; + std::vector canon(positions.size()); + for (std::size_t v = 0; v < positions.size(); ++v) { + const auto& p = positions[v]; + QKey k{static_cast(std::lround(p.x * invEps)), + static_cast(std::lround(p.y * invEps)), + static_cast(std::lround(p.z * invEps))}; + auto it = bucket.find(k); + if (it == bucket.end()) { + bucket.emplace(k, static_cast(v)); + canon[v] = static_cast(v); + } else { + canon[v] = it->second; + } + } + uniqueOut = bucket.size(); + return canon; +} + +} // namespace cli +} // namespace editor +} // namespace wowee diff --git a/tools/editor/cli_weld.hpp b/tools/editor/cli_weld.hpp new file mode 100644 index 00000000..a53c1ed4 --- /dev/null +++ b/tools/editor/cli_weld.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include +#include + +namespace wowee { +namespace editor { +namespace cli { + +// Vertex weld pass shared by --info-mesh-stats / --info-wob-stats / +// --bake-wom-collision. Positions are quantized onto a 1/eps grid; +// every vertex sharing a cell with a previously-seen vertex is +// remapped to that vertex's index. Returns canon[v] giving the +// canonical (lowest-index) representative of v's equivalence class +// and writes the count of distinct cells to `uniqueOut`. +// +// Implementation uses std::map, uint32_t> +// for exact equality on the quantized key — a hash-based key would +// risk false-positive collisions that incorrectly merge distinct +// corners (e.g. a unit cube's 8 corners all hashing to 2 buckets). +std::vector buildWeldMap( + const std::vector& positions, + float eps, + std::size_t& uniqueOut); + +} // namespace cli +} // namespace editor +} // namespace wowee diff --git a/tools/editor/cli_world_info.cpp b/tools/editor/cli_world_info.cpp index bf4e9203..182ffcff 100644 --- a/tools/editor/cli_world_info.cpp +++ b/tools/editor/cli_world_info.cpp @@ -1,4 +1,5 @@ #include "cli_world_info.hpp" +#include "cli_weld.hpp" #include "pipeline/wowee_building.hpp" #include "pipeline/wowee_collision.hpp" @@ -12,9 +13,7 @@ #include #include #include -#include #include -#include #include #include @@ -126,33 +125,16 @@ int handleInfoWobStats(int& i, int argc, char** argv) { return 1; } gs.tris = g.indices.size() / 3; - // Build canon[] for this group, optionally welding. - std::vector canon(g.vertices.size()); + // Build canon[] for this group, optionally welding via the + // shared cli_weld utility. + std::vector canon; if (useWeld) { - // Tuple key (qx,qy,qz) gives exact equality matching; - // a hash key would risk false-positive collisions - // collapsing distinct corners. See cli_mesh_info.cpp - // for the same pattern. - const float invEps = 1.0f / std::max(weldEps, 1e-9f); - using QKey = std::tuple; - std::map bucket; - auto qkey = [&](const glm::vec3& p) -> QKey { - return {static_cast(std::lround(p.x * invEps)), - static_cast(std::lround(p.y * invEps)), - static_cast(std::lround(p.z * invEps))}; - }; - for (std::size_t v = 0; v < g.vertices.size(); ++v) { - QKey k = qkey(g.vertices[v].position); - auto it = bucket.find(k); - if (it == bucket.end()) { - bucket.emplace(k, static_cast(v)); - canon[v] = static_cast(v); - } else { - canon[v] = it->second; - } - } - gs.uniquePositions = bucket.size(); + std::vector positions; + positions.reserve(g.vertices.size()); + for (const auto& v : g.vertices) positions.push_back(v.position); + canon = buildWeldMap(positions, weldEps, gs.uniquePositions); } else { + canon.resize(g.vertices.size()); for (std::size_t v = 0; v < g.vertices.size(); ++v) { canon[v] = static_cast(v); }