载入中...
搜索中...
未找到
MeshMerge.cpp
浏览该文件的文档.
2
4
5#include <algorithm>
6#include <cmath>
7#include <cstdint>
8#include <unordered_map>
9#include <utility>
10
11namespace eve::procgen {
12namespace {
13
14Result<MeshBuild> weldMeshLocal(const MeshBuild& input, float tolerance,
15 std::vector<std::int32_t>& vertexSourceIds) {
16 if (!std::isfinite(tolerance) || tolerance <= 0.f)
18 "merge weld tolerance must be finite and positive",
19 "weldTolerance", {}, "procgen.mesh.merge"));
20 struct Key {
21 std::int64_t x, y, z;
22 bool operator==(const Key&) const = default;
23 };
24 struct Hash {
25 std::size_t operator()(const Key& key) const noexcept {
26 std::size_t value = std::hash<std::int64_t>{}(key.x);
27 value ^= std::hash<std::int64_t>{}(key.y) + 0x9e3779b9u + (value << 6u) + (value >> 2u);
28 value ^= std::hash<std::int64_t>{}(key.z) + 0x9e3779b9u + (value << 6u) + (value >> 2u);
29 return value;
30 }
31 };
32 MeshBuild output;
33 output.reserve(input.getVertexCount(), input.getIndexCount());
34 std::unordered_map<Key, std::uint32_t, Hash> vertices;
35 std::vector<std::uint32_t> remap(static_cast<std::size_t>(input.getVertexCount()));
36 std::vector<std::int32_t> outSources;
37 outSources.reserve(static_cast<std::size_t>(input.getVertexCount()));
38 for (int i = 0; i < input.getVertexCount(); ++i) {
39 const Key key{std::llround(input.getPositionX(i) / tolerance),
40 std::llround(input.getPositionY(i) / tolerance),
41 std::llround(input.getPositionZ(i) / tolerance)};
42 const auto found = vertices.find(key);
43 if (found != vertices.end()) {
44 remap[static_cast<std::size_t>(i)] = found->second;
45 continue;
46 }
47 const auto next = static_cast<std::uint32_t>(output.getVertexCount());
48 vertices.emplace(key, next);
49 remap[static_cast<std::size_t>(i)] = next;
50 output.addVertex(input.getPositionX(i), input.getPositionY(i), input.getPositionZ(i), input.getNormalX(i),
51 input.getNormalY(i), input.getNormalZ(i), input.getUvU(i), input.getUvV(i));
52 outSources.push_back(vertexSourceIds[static_cast<std::size_t>(i)]);
53 }
54 std::vector<int> assignments;
55 for (int i = 0; i + 2 < input.getIndexCount(); i += 3) {
56 const auto a = remap[static_cast<std::size_t>(input.getIndex(i))];
57 const auto b = remap[static_cast<std::size_t>(input.getIndex(i + 1))];
58 const auto c = remap[static_cast<std::size_t>(input.getIndex(i + 2))];
59 if (a == b || b == c || a == c) continue;
60 output.addTriangle(a, b, c);
61 assignments.push_back(input.getTriangleGroup(i / 3));
62 }
63 std::vector<std::string> names;
64 for (int i = 0; i < input.getGroupCount(); ++i) names.push_back(input.getGroupName(i));
65 auto restored = output.restoreGroupData(std::move(names), std::move(assignments), -1);
66 if (!restored.ok()) return Result<MeshBuild>::failure(restored.status());
67 if (input.hasVertexColors()) {
68 std::vector<float> colors(static_cast<std::size_t>(output.getVertexCount()) * 4u, 1.f);
69 for (int i = 0; i < input.getVertexCount(); ++i) {
70 const auto dst = remap[static_cast<std::size_t>(i)];
71 for (int c = 0; c < 4; ++c)
72 colors[static_cast<std::size_t>(dst) * 4u + static_cast<std::size_t>(c)] = input.getColor(i, c);
73 }
74 auto set = output.setVertexColors(std::move(colors));
75 if (!set.ok()) return Result<MeshBuild>::failure(set.status());
76 }
77 // Shared verts need geometry normals; keep the first copy's normal would crease the weld.
78 {
79 auto& normals = output.normals();
80 const auto& positions = output.positions();
81 const auto& indices = output.indices();
82 std::fill(normals.begin(), normals.end(), 0.f);
83 for (std::size_t i = 0; i + 2 < indices.size(); i += 3) {
84 const std::size_t a = static_cast<std::size_t>(indices[i]) * 3u;
85 const std::size_t b = static_cast<std::size_t>(indices[i + 1u]) * 3u;
86 const std::size_t c = static_cast<std::size_t>(indices[i + 2u]) * 3u;
87 const float abx = positions[b] - positions[a];
88 const float aby = positions[b + 1u] - positions[a + 1u];
89 const float abz = positions[b + 2u] - positions[a + 2u];
90 const float acx = positions[c] - positions[a];
91 const float acy = positions[c + 1u] - positions[a + 1u];
92 const float acz = positions[c + 2u] - positions[a + 2u];
93 const float fx = aby * acz - abz * acy;
94 const float fy = abz * acx - abx * acz;
95 const float fz = abx * acy - aby * acx;
96 for (const auto vertex : {a, b, c}) {
97 normals[vertex] += fx;
98 normals[vertex + 1u] += fy;
99 normals[vertex + 2u] += fz;
100 }
101 }
102 for (std::size_t i = 0; i + 2 < normals.size(); i += 3) {
103 const float length =
104 std::sqrt(normals[i] * normals[i] + normals[i + 1u] * normals[i + 1u] + normals[i + 2u] * normals[i + 2u]);
105 if (length > 1e-7f) {
106 normals[i] /= length;
107 normals[i + 1u] /= length;
108 normals[i + 2u] /= length;
109 }
110 }
111 }
112 for (const auto& [key, value] : input.metadata()) output.setMeta(key, value);
113 vertexSourceIds = std::move(outSources);
114 return Result<MeshBuild>::success(std::move(output));
115}
116
117void translateMesh(MeshBuild& mesh, float dx, float dy, float dz) {
118 auto& positions = mesh.positions();
119 for (std::size_t i = 0; i + 2 < positions.size(); i += 3u) {
120 positions[i] += dx;
121 positions[i + 1u] += dy;
122 positions[i + 2u] += dz;
123 }
124}
125
126} // namespace
127
128Result<void> MeshMergePlan::appendSource(const MeshBuild& mesh, float tx, float ty, float tz, float yawDegrees,
129 float sx, float sy, float sz, std::string defaultMaterialId) {
130 if (mesh.empty())
132 "source", {}, "procgen.mesh.merge"));
133 if (!std::isfinite(tx) || !std::isfinite(ty) || !std::isfinite(tz) || !std::isfinite(yawDegrees) ||
134 !std::isfinite(sx) || !std::isfinite(sy) || !std::isfinite(sz) || sx == 0.f || sy == 0.f || sz == 0.f)
136 "merge source transform is invalid", "transform", {},
137 "procgen.mesh.merge"));
138 if (defaultMaterialId.empty())
140 "merge default material id is empty", "defaultMaterialId", {},
141 "procgen.mesh.merge"));
142 MeshBuild transformed;
143 if (!transformed.appendTransformed(&mesh, tx, ty, tz, yawDegrees, sx, sy, sz))
145 "merge failed to transform source mesh", "source", {},
146 "procgen.mesh.merge"));
147 if (transformed.getGroupCount() == 0) {
148 // Stamp default material onto every triangle.
149 MeshBuild stamped;
150 stamped.reserve(transformed.getVertexCount(), transformed.getIndexCount());
151 stamped.setActiveGroup(defaultMaterialId);
152 for (int v = 0; v < transformed.getVertexCount(); ++v)
153 stamped.addVertex(transformed.getPositionX(v), transformed.getPositionY(v), transformed.getPositionZ(v),
154 transformed.getNormalX(v), transformed.getNormalY(v), transformed.getNormalZ(v),
155 transformed.getUvU(v), transformed.getUvV(v));
156 for (int i = 0; i + 2 < transformed.getIndexCount(); i += 3)
157 stamped.addTriangle(static_cast<std::uint32_t>(transformed.getIndex(i)),
158 static_cast<std::uint32_t>(transformed.getIndex(i + 1)),
159 static_cast<std::uint32_t>(transformed.getIndex(i + 2)));
160 if (transformed.hasVertexColors()) {
161 auto set = stamped.setVertexColors(transformed.colors());
162 if (!set.ok()) return Result<void>::failure(set.status());
163 }
164 transformed = std::move(stamped);
165 }
166 Source source;
167 source.mesh = std::move(transformed);
168 source.defaultMaterialId = std::move(defaultMaterialId);
169 sources_.push_back(std::move(source));
170 return Result<void>::success();
171}
172
173void MeshMergePlan::clear() noexcept { sources_.clear(); }
174int MeshMergePlan::getSourceCount() const noexcept { return static_cast<int>(sources_.size()); }
175
177 enableContactBlend_ = enabled;
178 return Result<void>::success();
179}
180
182 if (params.falloff != "smooth" && params.falloff != "linear" && params.falloff != "sharp" &&
183 params.falloff != "sphere")
185 "merge contact blend falloff is invalid", "falloff", {},
186 "procgen.mesh.merge"));
187 falloffStorage_ = std::string(params.falloff);
188 blendParams_ = params;
189 blendParams_.falloff = falloffStorage_;
190 return Result<void>::success();
191}
192
194 if (!std::isfinite(tolerance))
196 "merge weld tolerance must be finite", "weldTolerance", {},
197 "procgen.mesh.merge"));
198 weldTolerance_ = tolerance;
199 return Result<void>::success();
200}
201
203 if (!std::isfinite(quality) || quality > 1.f)
205 "merge simplify quality must be finite and <= 1",
206 "simplifyQuality", {}, "procgen.mesh.merge"));
207 simplifyQuality_ = quality;
208 return Result<void>::success();
209}
210
212 pivotMode_ = mode;
213 return Result<void>::success();
214}
215
217 mergeVertexColors_ = enabled;
218 return Result<void>::success();
219}
220
222 if (plan.sources_.empty())
224 DiagnosticCode::InvalidArgument, "merge plan has no sources", "plan", {}, "procgen.mesh.merge"));
225
226 MeshBuild combined;
227 std::vector<std::int32_t> vertexSourceIds;
228 float firstX = 0.f, firstY = 0.f, firstZ = 0.f;
229 bool haveFirst = false;
230
231 for (std::size_t sourceIndex = 0; sourceIndex < plan.sources_.size(); ++sourceIndex) {
232 const auto& source = plan.sources_[sourceIndex];
233 if (!haveFirst && source.mesh.getVertexCount() > 0) {
234 firstX = source.mesh.getPositionX(0);
235 firstY = source.mesh.getPositionY(0);
236 firstZ = source.mesh.getPositionZ(0);
237 haveFirst = true;
238 }
239 if (!combined.appendTransformed(&source.mesh, 0.f, 0.f, 0.f, 0.f, 1.f, 1.f, 1.f))
241 DiagnosticCode::InvalidArgument, "merge append failed", "source", {}, "procgen.mesh.merge"));
242 for (int v = 0; v < source.mesh.getVertexCount(); ++v)
243 vertexSourceIds.push_back(static_cast<std::int32_t>(sourceIndex));
244 }
245
246 if (!plan.mergeVertexColors_ && combined.hasVertexColors()) {
247 auto cleared = combined.setVertexColors({});
248 if (!cleared.ok()) return Result<MeshBuild>::failure(cleared.status());
249 }
250
251 // Contact blend first so soft-snap can close gaps; weld afterward so snapped
252 // contact verts can become a continuous shell with shared normals.
253 if (plan.enableContactBlend_) {
254 auto blended = meshContactBlendResult(combined, vertexSourceIds, plan.blendParams_);
255 if (!blended.ok()) return blended;
256 combined = std::move(blended).takeValue();
257 }
258
259 if (plan.weldTolerance_ > 0.f) {
260 auto welded = weldMeshLocal(combined, plan.weldTolerance_, vertexSourceIds);
261 if (!welded.ok()) return welded;
262 combined = std::move(welded).takeValue();
263 // Weld rebuilds geometry normals; re-apply soft-snap neighborhood smooth for continuous lighting.
264 if (plan.enableContactBlend_ && plan.blendParams_.softSnapPositions)
266 }
267
268 if (plan.simplifyQuality_ > 0.f && plan.simplifyQuality_ < 1.f) {
269 MeshBuild simplified;
270 auto simp = simplifyGtsMesh(simplified, combined, plan.simplifyQuality_, {});
271 if (!simp.ok()) return Result<MeshBuild>::failure(simp.status());
272 combined = std::move(simplified);
273 }
274
275 if (plan.pivotMode_ == MeshMergePlan::PivotMode::FirstSource && haveFirst)
276 translateMesh(combined, -firstX, -firstY, -firstZ);
277
278 combined.setMeta("kind", "procgen.mesh.merged");
279 combined.setMeta("contactBlend.enabled", plan.enableContactBlend_ ? "1" : "0");
280 return Result<MeshBuild>::success(std::move(combined));
281}
282
283} // namespace eve::procgen
double value
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
std::string output
float length
Definition CaveMesh.cpp:94
EvpackChunkInput input
Definition Evpack.cpp:170
std::uint32_t key
std::vector< std::uint32_t > indices
std::vector< float > normals
std::vector< float > positions
float v
std::int32_t c
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
std::vector< Point > vertices
Mesh * mesh
std::unordered_map< std::uint32_t, std::uint32_t > remap
std::vector< float > colors
bool found
float dz
float dy
float dx
const UnitySourceAsset & source
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
CPU triangle mesh from procedural mesh recipes (e.g. marching cubes). Positions/normals are xyz-packe...
Definition MeshBuild.h:19
float getPositionZ(int i) const
Returns the position z.
float getPositionY(int i) const
Returns the position y.
void addVertex(float px, float py, float pz, float nx, float ny, float nz, float u, float v)
Adds vertex.
Definition MeshBuild.cpp:33
void addTriangle(uint32_t i0, uint32_t i1, uint32_t i2)
Adds triangle.
Definition MeshBuild.cpp:46
float getNormalZ(int i) const
Returns the normal z.
int getGroupCount() const
Number of named triangle groups.
Definition MeshBuild.cpp:63
void reserve(int vertexCount, int indexCount)
Reserve.
Definition MeshBuild.cpp:20
void setMeta(const std::string &key, const std::string &value)
Sets the meta.
int setActiveGroup(const std::string &name)
Select/create the named group assigned to subsequently added triangles.
Definition MeshBuild.cpp:53
const std::vector< float > & colors() const
Colors.
Definition MeshBuild.h:131
float getNormalY(int i) const
Returns the normal y.
int getIndexCount() const
Returns the index count.
eve::Result< void > setVertexColors(std::vector< float > colors)
Atomically replace the optional packed RGBA stream; empty removes it.
float getUvU(int i) const
Returns the uv u.
bool appendTransformed(const MeshBuild *other, float tx, float ty, float tz, float yawDegrees, float sx, float sy, float sz)
Append another procedural mesh after an affine transform.
float getUvV(int i) const
Returns the uv v.
float getNormalX(int i) const
Returns the normal x.
int getIndex(int i) const
bool hasVertexColors() const noexcept
Return whether this mesh owns one packed RGBA color per vertex.
float getPositionX(int i) const
Returns the position x.
Authoring plan for commit-style static mesh merge.
Definition MeshMerge.h:21
int getSourceCount() const noexcept
PivotMode
Pivot placement for the merged mesh.
Definition MeshMerge.h:24
Result< void > setContactBlendParams(const MeshContactBlendParams &params)
Result< void > setMergeVertexColors(bool enabled) noexcept
Result< void > setEnableContactBlend(bool enabled) noexcept
Result< void > setPivotMode(PivotMode mode) noexcept
Result< void > appendSource(const MeshBuild &mesh, float tx, float ty, float tz, float yawDegrees, float sx, float sy, float sz, std::string defaultMaterialId="default")
Copy one mesh after yaw/scale/translation into the plan.
Result< void > setWeldTolerance(float tolerance) noexcept
Positive weld tolerance enables hard weld after contact blend; non-positive disables (default)....
Result< void > setSimplifyQuality(float quality) noexcept
Optional GTS quality in (0,1); non-positive disables simplify (default). When set,...
std::vector< ParamSpec > params
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).
Definition HexMetrics.h:76
Result< int > simplifyGtsMesh(MeshBuild &output, const MeshBuild &source, float quality, const GtsMeshSimplificationOptions &o)
Simplify a mesh with deterministic quadric edge collapse.
Result< MeshBuild > meshContactBlendResult(const MeshBuild &mesh, const std::vector< std::int32_t > &vertexSourceIds, const MeshContactBlendParams &params)
Fuse edge normals / optional positions and material blend weights across source slices.
void rebuildSoftSnapContactNormals(MeshBuild &mesh)
Rebuild triangle normals then 1-ring-average them (soft-snap / post-weld lighting).
Result< MeshBuild > mergeStaticMeshes(const MeshMergePlan &plan)
Merge planned static meshes into one owning MeshBuild.
bool enabled
Parameters for contact-band edge and material fusion.
std::string_view falloff
smooth|linear|sharp|sphere