mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-10 02:53:51 +00:00
refactor(editor): extract vertex weld into shared cli_weld utility
Three callers were each open-coding the same quantize-and-
bucket pass over vertex positions: --info-mesh-stats,
--info-wob-stats, and --bake-wom-collision. Move the
implementation to cli_weld.{hpp,cpp} as buildWeldMap() and
have each caller pass a flat positions array.
Identical std::map-based exact-equality keying preserved
(unbalanced-hash collisions remain absent). All three call
sites verified to produce byte-identical output:
• info-mesh-stats firepit: 240→80 verts, 0 boundary
• info-wob-stats cube: 8→8 verts, watertight YES
• bake-wom-collision tent: 18→6 verts, 8-tri WOC
About 75 lines of duplication removed; future weld-using
commands now opt in by including one header.
This commit is contained in:
parent
9031bdb620
commit
869124fa96
6 changed files with 107 additions and 76 deletions
|
|
@ -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 <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <tuple>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
|
|
@ -19,6 +19,7 @@
|
|||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
|
|
@ -952,31 +953,29 @@ int handleBakeWomCollision(int& i, int argc, char** argv) {
|
|||
std::vector<glm::vec3> positions;
|
||||
std::vector<uint32_t> 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<int64_t, int64_t, int64_t>;
|
||||
std::map<QKey, uint32_t> bucket;
|
||||
std::vector<uint32_t> 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<glm::vec3> srcPositions;
|
||||
srcPositions.reserve(wom.vertices.size());
|
||||
for (const auto& vert : wom.vertices) srcPositions.push_back(vert.position);
|
||||
std::size_t uniq = 0;
|
||||
std::vector<uint32_t> canon = buildWeldMap(srcPositions, weldEps, uniq);
|
||||
// Build canon→compactedIndex remap as we walk vertices in order.
|
||||
std::vector<uint32_t> remap(wom.vertices.size(),
|
||||
std::numeric_limits<uint32_t>::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<int64_t>(std::lround(p.x * invEps)),
|
||||
static_cast<int64_t>(std::lround(p.y * invEps)),
|
||||
static_cast<int64_t>(std::lround(p.z * invEps))};
|
||||
auto it = bucket.find(k);
|
||||
if (it == bucket.end()) {
|
||||
uint32_t newIdx = static_cast<uint32_t>(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<uint32_t>::max()) {
|
||||
remap[c] = static_cast<uint32_t>(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);
|
||||
|
|
|
|||
|
|
@ -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 <cstdio>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
|
|
@ -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<uint32_t> canon(wom.vertices.size());
|
||||
std::vector<uint32_t> 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<int64_t, int64_t, int64_t>;
|
||||
std::map<QKey, uint32_t> bucket;
|
||||
auto qkey = [&](const glm::vec3& p) -> QKey {
|
||||
return {static_cast<int64_t>(std::lround(p.x * invEps)),
|
||||
static_cast<int64_t>(std::lround(p.y * invEps)),
|
||||
static_cast<int64_t>(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<uint32_t>(v));
|
||||
canon[v] = static_cast<uint32_t>(v);
|
||||
} else {
|
||||
canon[v] = it->second;
|
||||
}
|
||||
}
|
||||
uniquePositions = bucket.size();
|
||||
std::vector<glm::vec3> 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<uint32_t>(v);
|
||||
}
|
||||
|
|
|
|||
39
tools/editor/cli_weld.cpp
Normal file
39
tools/editor/cli_weld.cpp
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
#include "cli_weld.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <map>
|
||||
#include <tuple>
|
||||
|
||||
namespace wowee {
|
||||
namespace editor {
|
||||
namespace cli {
|
||||
|
||||
std::vector<uint32_t> buildWeldMap(
|
||||
const std::vector<glm::vec3>& positions,
|
||||
float eps,
|
||||
std::size_t& uniqueOut) {
|
||||
const float invEps = 1.0f / std::max(eps, 1e-9f);
|
||||
using QKey = std::tuple<int64_t, int64_t, int64_t>;
|
||||
std::map<QKey, uint32_t> bucket;
|
||||
std::vector<uint32_t> canon(positions.size());
|
||||
for (std::size_t v = 0; v < positions.size(); ++v) {
|
||||
const auto& p = positions[v];
|
||||
QKey k{static_cast<int64_t>(std::lround(p.x * invEps)),
|
||||
static_cast<int64_t>(std::lround(p.y * invEps)),
|
||||
static_cast<int64_t>(std::lround(p.z * invEps))};
|
||||
auto it = bucket.find(k);
|
||||
if (it == bucket.end()) {
|
||||
bucket.emplace(k, static_cast<uint32_t>(v));
|
||||
canon[v] = static_cast<uint32_t>(v);
|
||||
} else {
|
||||
canon[v] = it->second;
|
||||
}
|
||||
}
|
||||
uniqueOut = bucket.size();
|
||||
return canon;
|
||||
}
|
||||
|
||||
} // namespace cli
|
||||
} // namespace editor
|
||||
} // namespace wowee
|
||||
30
tools/editor/cli_weld.hpp
Normal file
30
tools/editor/cli_weld.hpp
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#pragma once
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
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<tuple<int64,int64,int64>, 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<uint32_t> buildWeldMap(
|
||||
const std::vector<glm::vec3>& positions,
|
||||
float eps,
|
||||
std::size_t& uniqueOut);
|
||||
|
||||
} // namespace cli
|
||||
} // namespace editor
|
||||
} // namespace wowee
|
||||
|
|
@ -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 <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
|
|
@ -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<uint32_t> canon(g.vertices.size());
|
||||
// Build canon[] for this group, optionally welding via the
|
||||
// shared cli_weld utility.
|
||||
std::vector<uint32_t> 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<int64_t, int64_t, int64_t>;
|
||||
std::map<QKey, uint32_t> bucket;
|
||||
auto qkey = [&](const glm::vec3& p) -> QKey {
|
||||
return {static_cast<int64_t>(std::lround(p.x * invEps)),
|
||||
static_cast<int64_t>(std::lround(p.y * invEps)),
|
||||
static_cast<int64_t>(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<uint32_t>(v));
|
||||
canon[v] = static_cast<uint32_t>(v);
|
||||
} else {
|
||||
canon[v] = it->second;
|
||||
}
|
||||
}
|
||||
gs.uniquePositions = bucket.size();
|
||||
std::vector<glm::vec3> 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<uint32_t>(v);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue