载入中...
搜索中...
未找到
MeshDeformationSession.cpp
浏览该文件的文档.
2
3#include "common/Capability.h"
5
6#include <algorithm>
7#include <array>
8#include <cmath>
9#include <unordered_set>
10
11namespace eve::procgen {
12namespace {
13
14bool validMesh(const MeshBuild& mesh) {
15 if (mesh.empty() || mesh.positions().size() % 3u != 0u || mesh.normals().size() != mesh.positions().size() ||
16 mesh.uvs().size() != mesh.positions().size() / 3u * 2u || mesh.indices().size() % 3u != 0u)
17 return false;
18 return std::all_of(mesh.indices().begin(), mesh.indices().end(),
19 [&](std::uint32_t index) { return index < static_cast<std::uint32_t>(mesh.getVertexCount()); });
20}
21
22void recalculateNormals(MeshBuild& mesh) {
23 auto& normals = mesh.normals();
24 std::fill(normals.begin(), normals.end(), 0.f);
25 const auto& positions = mesh.positions();
26 for (std::size_t i = 0; i + 2 < mesh.indices().size(); i += 3u) {
27 const std::array<std::size_t, 3> v{std::size_t(mesh.indices()[i]) * 3u, std::size_t(mesh.indices()[i + 1]) * 3u,
28 std::size_t(mesh.indices()[i + 2]) * 3u};
29 const std::array<float, 3> ab{positions[v[1]] - positions[v[0]], positions[v[1] + 1] - positions[v[0] + 1],
30 positions[v[1] + 2] - positions[v[0] + 2]};
31 const std::array<float, 3> ac{positions[v[2]] - positions[v[0]], positions[v[2] + 1] - positions[v[0] + 1],
32 positions[v[2] + 2] - positions[v[0] + 2]};
33 const std::array<float, 3> cross{ab[1] * ac[2] - ab[2] * ac[1], ab[2] * ac[0] - ab[0] * ac[2],
34 ab[0] * ac[1] - ab[1] * ac[0]};
35 for (const auto base : v)
36 for (int component = 0; component < 3; ++component)
37 normals[base + static_cast<std::size_t>(component)] += cross[static_cast<std::size_t>(component)];
38 }
39 for (std::size_t i = 0; i < normals.size(); i += 3u) {
40 const float length =
41 std::sqrt(normals[i] * normals[i] + normals[i + 1] * normals[i + 1] + normals[i + 2] * normals[i + 2]);
42 if (length > 1e-7f) {
43 normals[i] /= length;
44 normals[i + 1] /= length;
45 normals[i + 2] /= length;
46 }
47 }
48}
49
50} // namespace
51
53 if (!validMesh(mesh))
55 Diagnostic::error(DiagnosticCode::InvalidArgument, "sculpting source mesh is empty or invalid", "mesh", {}, "procgen.meshDeformationSession"));
56 original_ = mesh;
57 current_ = mesh;
58 surfaceEquilibrium_ = mesh.positions();
59 surfaceVelocity_.assign(mesh.positions().size(), 0.f);
60 selectedVertices_.assign(static_cast<std::size_t>(mesh.getVertexCount()), 0u);
61 invalidateImpactVertexBlocks();
62 undo_.clear();
63 colliderRefreshMode_ = "manual";
64 colliderRefreshInterval_ = 0.f;
65 colliderRefreshAccumulator_ = 0.f;
66 colliderOffsetX_ = colliderOffsetY_ = colliderOffsetZ_ = 0.f;
67 colliderRefreshConfigured_ = false;
68 colliderRefreshPending_ = false;
69 initialized_ = true;
70 ++revision_;
71 return Result<void>::success();
72}
73
74void MeshDeformationSession::invalidateImpactVertexBlocks() noexcept {
75 impactVertexBlocks_.clear();
76 impactBlockDivisions_ = 0;
77}
78
80 if (!initialized_)
82 Diagnostic::error(DiagnosticCode::PreconditionViolation, "damage session is not initialized", "mesh", {}, "procgen.meshDeformationSession"));
83 if (divisionsPerAxis < 1 || divisionsPerAxis > 128)
85 Diagnostic::error(DiagnosticCode::InvalidArgument, "impact block divisions must be in [1, 128]", "divisionsPerAxis", {}, "procgen.meshDeformationSession"));
86 const auto& positions = current_.positions();
87 impactBlockMinX_ = impactBlockMaxX_ = positions[0];
88 impactBlockMinY_ = impactBlockMaxY_ = positions[1];
89 impactBlockMinZ_ = impactBlockMaxZ_ = positions[2];
90 for (std::size_t index = 3; index < positions.size(); index += 3) {
91 impactBlockMinX_ = std::min(impactBlockMinX_, positions[index]);
92 impactBlockMaxX_ = std::max(impactBlockMaxX_, positions[index]);
93 impactBlockMinY_ = std::min(impactBlockMinY_, positions[index + 1]);
94 impactBlockMaxY_ = std::max(impactBlockMaxY_, positions[index + 1]);
95 impactBlockMinZ_ = std::min(impactBlockMinZ_, positions[index + 2]);
96 impactBlockMaxZ_ = std::max(impactBlockMaxZ_, positions[index + 2]);
97 }
98 const std::size_t blockCount = static_cast<std::size_t>(divisionsPerAxis) * divisionsPerAxis * divisionsPerAxis;
99 std::vector<std::vector<std::uint32_t>> blocks(blockCount);
100 const auto coordinate = [divisionsPerAxis](float value, float minimum, float maximum) {
101 if (maximum - minimum <= 1e-7f) return 0;
102 return std::clamp(static_cast<int>((value - minimum) / (maximum - minimum) * divisionsPerAxis), 0,
103 divisionsPerAxis - 1);
104 };
105 for (int vertex = 0; vertex < current_.getVertexCount(); ++vertex) {
106 const auto base = static_cast<std::size_t>(vertex) * 3u;
107 const int ix = coordinate(positions[base], impactBlockMinX_, impactBlockMaxX_);
108 const int iy = coordinate(positions[base + 1], impactBlockMinY_, impactBlockMaxY_);
109 const int iz = coordinate(positions[base + 2], impactBlockMinZ_, impactBlockMaxZ_);
110 blocks[static_cast<std::size_t>((iz * divisionsPerAxis + iy) * divisionsPerAxis + ix)].push_back(
111 static_cast<std::uint32_t>(vertex));
112 }
113 impactVertexBlocks_ = std::move(blocks);
114 impactBlockDivisions_ = divisionsPerAxis;
115 ++revision_;
116 return Result<void>::success();
117}
118
120 return static_cast<int>(std::count_if(impactVertexBlocks_.begin(), impactVertexBlocks_.end(),
121 [](const auto& block) { return !block.empty(); }));
122}
123
124std::vector<std::uint32_t> MeshDeformationSession::impactCandidates(float x, float y, float z, float radius) const {
125 if (impactVertexBlocks_.empty()) {
126 std::vector<std::uint32_t> all(static_cast<std::size_t>(current_.getVertexCount()));
127 for (std::uint32_t index = 0; index < all.size(); ++index) all[index] = index;
128 return all;
129 }
130 const auto coordinate = [this](float value, float minimum, float maximum) {
131 if (maximum - minimum <= 1e-7f) return 0;
132 return std::clamp(static_cast<int>((value - minimum) / (maximum - minimum) * impactBlockDivisions_), 0,
133 impactBlockDivisions_ - 1);
134 };
135 const int minX = coordinate(x - radius, impactBlockMinX_, impactBlockMaxX_);
136 const int maxX = coordinate(x + radius, impactBlockMinX_, impactBlockMaxX_);
137 const int minY = coordinate(y - radius, impactBlockMinY_, impactBlockMaxY_);
138 const int maxY = coordinate(y + radius, impactBlockMinY_, impactBlockMaxY_);
139 const int minZ = coordinate(z - radius, impactBlockMinZ_, impactBlockMaxZ_);
140 const int maxZ = coordinate(z + radius, impactBlockMinZ_, impactBlockMaxZ_);
141 std::vector<std::uint32_t> candidates;
142 for (int iz = minZ; iz <= maxZ; ++iz)
143 for (int iy = minY; iy <= maxY; ++iy)
144 for (int ix = minX; ix <= maxX; ++ix) {
145 const auto& block = impactVertexBlocks_[static_cast<std::size_t>(
146 (iz * impactBlockDivisions_ + iy) * impactBlockDivisions_ + ix)];
147 candidates.insert(candidates.end(), block.begin(), block.end());
148 }
149 return candidates;
150}
151
152Result<void> MeshDeformationSession::applyBrushResult(std::string_view mode, float x, float y, float z, float radius,
153 float strength, float falloff, float directionX, float directionY,
154 float directionZ) {
155 if (!initialized_)
157 Diagnostic::error(DiagnosticCode::PreconditionViolation, "sculpting session is not initialized", "mesh", {}, "procgen.meshDeformationSession"));
158 if ((mode != "inflate" && mode != "dent" && mode != "flatten" && mode != "smooth" && mode != "directional") ||
159 !std::isfinite(x) || !std::isfinite(y) || !std::isfinite(z) || !std::isfinite(radius) ||
160 !std::isfinite(strength) || !std::isfinite(falloff) || radius <= 0.f || falloff <= 0.f)
162 Diagnostic::error(DiagnosticCode::InvalidArgument, "invalid sculpting brush parameters", "brush", {}, "procgen.meshDeformationSession"));
163 MeshBuild candidate = current_;
164 auto& positions = candidate.positions();
165 const auto sourcePositions = current_.positions();
166 const auto sourceNormals = current_.normals();
167 std::vector<std::unordered_set<std::uint32_t>> neighbors(static_cast<std::size_t>(candidate.getVertexCount()));
168 if (mode == "smooth") {
169 for (std::size_t i = 0; i + 2 < candidate.indices().size(); i += 3u) {
170 const std::array<std::uint32_t, 3> triangle{candidate.indices()[i], candidate.indices()[i + 1],
171 candidate.indices()[i + 2]};
172 for (int corner = 0; corner < 3; ++corner) {
173 neighbors[triangle[corner]].insert(triangle[(corner + 1) % 3]);
174 neighbors[triangle[corner]].insert(triangle[(corner + 2) % 3]);
175 }
176 }
177 }
178 float directionLength = std::sqrt(directionX * directionX + directionY * directionY + directionZ * directionZ);
179 if (mode == "directional" && directionLength < 1e-7f)
181 Diagnostic::error(DiagnosticCode::InvalidArgument, "directional brush requires a direction", "direction", {}, "procgen.meshDeformationSession"));
182 directionLength = std::max(directionLength, 1e-7f);
183 for (int vertex = 0; vertex < candidate.getVertexCount(); ++vertex) {
184 const std::size_t base = static_cast<std::size_t>(vertex) * 3u;
185 const float dx = sourcePositions[base] - x, dy = sourcePositions[base + 1] - y,
186 dz = sourcePositions[base + 2] - z;
187 const float distance = std::sqrt(dx * dx + dy * dy + dz * dz);
188 if (distance >= radius) continue;
189 const float weight = std::pow(1.f - distance / radius, falloff) * strength;
190 if (mode == "smooth") {
191 const auto& adjacent = neighbors[static_cast<std::size_t>(vertex)];
192 if (adjacent.empty()) continue;
193 std::array<float, 3> average{};
194 for (const auto neighbor : adjacent)
195 for (int component = 0; component < 3; ++component)
196 average[static_cast<std::size_t>(component)] +=
197 sourcePositions[static_cast<std::size_t>(neighbor) * 3u + static_cast<std::size_t>(component)];
198 for (int component = 0; component < 3; ++component)
199 positions[base + static_cast<std::size_t>(component)] =
200 std::lerp(sourcePositions[base + static_cast<std::size_t>(component)],
201 average[static_cast<std::size_t>(component)] / static_cast<float>(adjacent.size()),
202 std::clamp(std::abs(weight), 0.f, 1.f));
203 } else if (mode == "flatten") {
204 positions[base + 1] = std::lerp(sourcePositions[base + 1], y, std::clamp(std::abs(weight), 0.f, 1.f));
205 } else {
206 const float sign = mode == "dent" ? -1.f : 1.f;
207 const float vx = mode == "directional" ? directionX / directionLength : sourceNormals[base];
208 const float vy = mode == "directional" ? directionY / directionLength : sourceNormals[base + 1];
209 const float vz = mode == "directional" ? directionZ / directionLength : sourceNormals[base + 2];
210 positions[base] += vx * weight * sign;
211 positions[base + 1] += vy * weight * sign;
212 positions[base + 2] += vz * weight * sign;
213 }
214 }
215 recalculateNormals(candidate);
216 pushUndo();
217 surfaceEquilibrium_ = candidate.positions();
218 current_ = std::move(candidate);
219 invalidateImpactVertexBlocks();
220 ++revision_;
221 return Result<void>::success();
222}
223
224Result<void> MeshDeformationSession::applyBrushGpuResult(std::string_view mode, float x, float y, float z, float radius,
225 float strength, float falloff, float directionX,
226 float directionY, float directionZ) {
227 if (!initialized_)
229 Diagnostic::error(DiagnosticCode::PreconditionViolation, "sculpting session is not initialized", "mesh", {}, "procgen.meshDeformationSession"));
230 if ((mode != "inflate" && mode != "dent" && mode != "flatten" && mode != "smooth" && mode != "directional") ||
231 !std::isfinite(x) || !std::isfinite(y) || !std::isfinite(z) || !std::isfinite(radius) ||
232 !std::isfinite(strength) || !std::isfinite(falloff) || radius <= 0.f || falloff <= 0.f)
234 Diagnostic::error(DiagnosticCode::InvalidArgument, "invalid GPU sculpting brush parameters", "brush", {}, "procgen.meshDeformationSession"));
235 const float directionLength = std::sqrt(directionX * directionX + directionY * directionY + directionZ * directionZ);
236 if (mode == "directional" && directionLength < 1e-7f)
238 Diagnostic::error(DiagnosticCode::InvalidArgument, "directional brush requires a direction", "direction", {}, "procgen.meshDeformationSession"));
239 auto* provider = eve::cap::query<IMeshDeformationCompute>();
240 if (!provider)
242 Diagnostic::error(DiagnosticCode::Unsupported, "GPU mesh deformation provider is not installed", "backend", {}, "procgen.meshDeformationSession"));
243
245 request.positions = current_.positions();
246 request.normals = current_.normals();
247 request.centerX = x; request.centerY = y; request.centerZ = z; request.radius = radius;
248 request.strength = strength; request.falloff = falloff;
249 request.directionX = directionX; request.directionY = directionY; request.directionZ = directionZ;
250 if (mode == "inflate") request.operation = MeshDeformationComputeOperation::Inflate;
251 else if (mode == "dent") request.operation = MeshDeformationComputeOperation::Dent;
252 else if (mode == "flatten") request.operation = MeshDeformationComputeOperation::Flatten;
253 else if (mode == "directional") request.operation = MeshDeformationComputeOperation::Directional;
254 else {
256 request.targets = request.positions;
257 std::vector<std::unordered_set<std::uint32_t>> neighbors(static_cast<std::size_t>(current_.getVertexCount()));
258 for (std::size_t i = 0; i + 2 < current_.indices().size(); i += 3u) {
259 const std::array<std::uint32_t, 3> triangle{current_.indices()[i], current_.indices()[i + 1], current_.indices()[i + 2]};
260 for (int corner = 0; corner < 3; ++corner) {
261 neighbors[triangle[corner]].insert(triangle[(corner + 1) % 3]);
262 neighbors[triangle[corner]].insert(triangle[(corner + 2) % 3]);
263 }
264 }
265 for (std::size_t vertex = 0; vertex < neighbors.size(); ++vertex) {
266 if (neighbors[vertex].empty()) continue;
267 for (std::uint32_t neighbor : neighbors[vertex])
268 for (std::size_t component = 0; component < 3u; ++component)
269 request.targets[vertex * 3u + component] += current_.positions()[neighbor * 3u + component];
270 for (std::size_t component = 0; component < 3u; ++component)
271 request.targets[vertex * 3u + component] =
272 (request.targets[vertex * 3u + component] - current_.positions()[vertex * 3u + component]) /
273 static_cast<float>(neighbors[vertex].size());
274 }
275 }
276 auto deformed = provider->deform(std::move(request));
277 if (!deformed.ok()) return Result<void>::failure(deformed.status());
278 if (deformed.value().size() != current_.positions().size() ||
279 !std::all_of(deformed.value().begin(), deformed.value().end(), [](float value) { return std::isfinite(value); }))
281 Diagnostic::error(DiagnosticCode::Failed, "GPU returned an invalid mesh deformation", "backend", {}, "procgen.meshDeformationSession"));
282 MeshBuild candidate = current_;
283 candidate.positions() = std::move(deformed).takeValue();
284 recalculateNormals(candidate);
285 pushUndo();
286 surfaceEquilibrium_ = candidate.positions();
287 current_ = std::move(candidate);
288 invalidateImpactVertexBlocks();
289 ++revision_;
290 return Result<void>::success();
291}
292
293Result<void> MeshDeformationSession::applyImpactResult(float x, float y, float z, float impulseX, float impulseY,
294 float impulseZ, float radius, float plasticity, float hardness,
295 float maxDisplacement) {
296 if (!initialized_)
298 Diagnostic::error(DiagnosticCode::PreconditionViolation, "damage session is not initialized", "mesh", {}, "procgen.meshDeformationSession"));
299 const float impulseLength = std::sqrt(impulseX * impulseX + impulseY * impulseY + impulseZ * impulseZ);
300 if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(z) || !std::isfinite(impulseLength) ||
301 !std::isfinite(radius) || !std::isfinite(plasticity) || !std::isfinite(hardness) ||
302 !std::isfinite(maxDisplacement) || impulseLength <= 1e-7f || radius <= 0.f || plasticity < 0.f ||
303 hardness <= 0.f || maxDisplacement <= 0.f)
305 Diagnostic::error(DiagnosticCode::InvalidArgument, "invalid mesh impact parameters", "impact", {}, "procgen.meshDeformationSession"));
306
307 MeshBuild candidate = current_;
308 auto& positions = candidate.positions();
309 const auto sourcePositions = current_.positions();
310 const auto& baseline = original_.positions();
311 const float directionX = impulseX / impulseLength;
312 const float directionY = impulseY / impulseLength;
313 const float directionZ = impulseZ / impulseLength;
314 const auto candidates = impactCandidates(x, y, z, radius + maxDisplacement * 2.f);
315 for (const std::uint32_t vertex : candidates) {
316 const std::size_t base = static_cast<std::size_t>(vertex) * 3u;
317 const float dx = sourcePositions[base] - x;
318 const float dy = sourcePositions[base + 1] - y;
319 const float dz = sourcePositions[base + 2] - z;
320 const float distance = std::sqrt(dx * dx + dy * dy + dz * dz);
321 if (distance >= radius) continue;
322 const float amount = impulseLength * plasticity * std::pow(1.f - distance / radius, hardness);
323 float displacedX = sourcePositions[base] + directionX * amount - baseline[base];
324 float displacedY = sourcePositions[base + 1] + directionY * amount - baseline[base + 1];
325 float displacedZ = sourcePositions[base + 2] + directionZ * amount - baseline[base + 2];
326 const float displacement =
327 std::sqrt(displacedX * displacedX + displacedY * displacedY + displacedZ * displacedZ);
328 if (displacement > maxDisplacement) {
329 const float scale = maxDisplacement / displacement;
330 displacedX *= scale;
331 displacedY *= scale;
332 displacedZ *= scale;
333 }
334 positions[base] = baseline[base] + displacedX;
335 positions[base + 1] = baseline[base + 1] + displacedY;
336 positions[base + 2] = baseline[base + 2] + displacedZ;
337 }
338 recalculateNormals(candidate);
339 pushUndo();
340 surfaceEquilibrium_ = candidate.positions();
341 current_ = std::move(candidate);
342 ++revision_;
343 return Result<void>::success();
344}
345
346Result<void> MeshDeformationSession::applyImpactGpuResult(float x, float y, float z, float impulseX, float impulseY,
347 float impulseZ, float radius, float plasticity,
348 float hardness, float maxDisplacement) {
349 if (!initialized_)
351 Diagnostic::error(DiagnosticCode::PreconditionViolation, "damage session is not initialized", "mesh", {}, "procgen.meshDeformationSession"));
352 const float impulseLength = std::sqrt(impulseX * impulseX + impulseY * impulseY + impulseZ * impulseZ);
353 if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(z) || !std::isfinite(impulseLength) ||
354 !std::isfinite(radius) || !std::isfinite(plasticity) || !std::isfinite(hardness) ||
355 !std::isfinite(maxDisplacement) || impulseLength <= 1e-7f || radius <= 0.f || plasticity < 0.f ||
356 hardness <= 0.f || maxDisplacement <= 0.f)
358 Diagnostic::error(DiagnosticCode::InvalidArgument, "invalid GPU mesh impact parameters", "impact", {}, "procgen.meshDeformationSession"));
359 auto* provider = eve::cap::query<IMeshDeformationCompute>();
360 if (!provider)
362 Diagnostic::error(DiagnosticCode::Unsupported, "GPU mesh deformation provider is not installed", "backend", {}, "procgen.meshDeformationSession"));
365 request.positions = current_.positions(); request.normals = current_.normals(); request.baseline = original_.positions();
366 request.centerX = x; request.centerY = y; request.centerZ = z; request.radius = radius;
367 request.strength = impulseLength * plasticity; request.falloff = hardness;
368 request.directionX = impulseX / impulseLength; request.directionY = impulseY / impulseLength;
369 request.directionZ = impulseZ / impulseLength; request.maxDisplacement = maxDisplacement;
370 auto deformed = provider->deform(std::move(request));
371 if (!deformed.ok()) return Result<void>::failure(deformed.status());
372 if (deformed.value().size() != current_.positions().size() ||
373 !std::all_of(deformed.value().begin(), deformed.value().end(), [](float value) { return std::isfinite(value); }))
375 Diagnostic::error(DiagnosticCode::Failed, "GPU returned invalid impact positions", "backend", {}, "procgen.meshDeformationSession"));
376 MeshBuild candidate = current_;
377 candidate.positions() = std::move(deformed).takeValue();
378 recalculateNormals(candidate);
379 pushUndo(); surfaceEquilibrium_ = candidate.positions(); current_ = std::move(candidate);
380 invalidateImpactVertexBlocks(); ++revision_;
381 return Result<void>::success();
382}
383
384Result<void> MeshDeformationSession::applySurfaceContactResult(float x, float y, float z, float normalX, float normalY,
385 float normalZ, float velocityX, float velocityY,
386 float velocityZ, float radius, float depth, float drag,
387 float falloff, float plasticity) {
388 if (!initialized_)
390 Diagnostic::error(DiagnosticCode::PreconditionViolation, "interactive surface is not initialized", "mesh", {}, "procgen.meshDeformationSession"));
391 const float normalLength = std::sqrt(normalX * normalX + normalY * normalY + normalZ * normalZ);
392 if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(z) || !std::isfinite(normalLength) ||
393 !std::isfinite(velocityX) || !std::isfinite(velocityY) || !std::isfinite(velocityZ) || !std::isfinite(radius) ||
394 !std::isfinite(depth) || !std::isfinite(drag) || !std::isfinite(falloff) || !std::isfinite(plasticity) ||
395 normalLength <= 1e-7f || radius <= 0.f || depth < 0.f || drag < 0.f || falloff <= 0.f || plasticity < 0.f ||
396 plasticity > 1.f)
398 Diagnostic::error(DiagnosticCode::InvalidArgument, "invalid interactive surface contact", "contact", {}, "procgen.meshDeformationSession"));
399 normalX /= normalLength;
400 normalY /= normalLength;
401 normalZ /= normalLength;
402 const float normalVelocity = velocityX * normalX + velocityY * normalY + velocityZ * normalZ;
403 const float tangentX = velocityX - normalX * normalVelocity;
404 const float tangentY = velocityY - normalY * normalVelocity;
405 const float tangentZ = velocityZ - normalZ * normalVelocity;
406 MeshBuild candidate = current_;
407 auto equilibrium = surfaceEquilibrium_;
408 auto& positions = candidate.positions();
409 const auto source = current_.positions();
410 for (int vertex = 0; vertex < candidate.getVertexCount(); ++vertex) {
411 const std::size_t base = static_cast<std::size_t>(vertex) * 3u;
412 const float dx = source[base] - x, dy = source[base + 1] - y, dz = source[base + 2] - z;
413 const float distance = std::sqrt(dx * dx + dy * dy + dz * dz);
414 if (distance >= radius) continue;
415 const float weight = std::pow(1.f - distance / radius, falloff);
416 const float moveX = (-normalX * depth + tangentX * drag) * weight;
417 const float moveY = (-normalY * depth + tangentY * drag) * weight;
418 const float moveZ = (-normalZ * depth + tangentZ * drag) * weight;
419 positions[base] += moveX;
420 positions[base + 1] += moveY;
421 positions[base + 2] += moveZ;
422 equilibrium[base] += moveX * plasticity;
423 equilibrium[base + 1] += moveY * plasticity;
424 equilibrium[base + 2] += moveZ * plasticity;
425 }
426 recalculateNormals(candidate);
427 pushUndo();
428 current_ = std::move(candidate);
429 surfaceEquilibrium_ = std::move(equilibrium);
430 invalidateImpactVertexBlocks();
431 ++revision_;
432 return Result<void>::success();
433}
434
436 if (!initialized_)
438 Diagnostic::error(DiagnosticCode::PreconditionViolation, "interactive surface is not initialized", "mesh", {}, "procgen.meshDeformationSession"));
439 if (!std::isfinite(dt) || !std::isfinite(recoveryRate) || dt < 0.f || recoveryRate <= 0.f)
441 Diagnostic::error(DiagnosticCode::InvalidArgument, "invalid interactive surface recovery step", "recovery", {}, "procgen.meshDeformationSession"));
442 if (dt == 0.f) return Result<void>::success();
443 MeshBuild candidate = current_;
444 auto& positions = candidate.positions();
445 const float blend = 1.f - std::exp(-recoveryRate * dt);
446 bool changed = false;
447 for (std::size_t i = 0; i < positions.size(); ++i) {
448 const float next = std::lerp(positions[i], surfaceEquilibrium_[i], blend);
449 changed = changed || std::abs(next - positions[i]) > 1e-7f;
450 positions[i] = next;
451 }
452 if (!changed) return Result<void>::success();
453 recalculateNormals(candidate);
454 current_ = std::move(candidate);
455 invalidateImpactVertexBlocks();
456 ++revision_;
457 return Result<void>::success();
458}
459
460Result<void> MeshDeformationSession::applySlimeImpulseResult(float x, float y, float z, float impulseX, float impulseY,
461 float impulseZ, float radius, float falloff) {
462 if (!initialized_)
464 Diagnostic::error(DiagnosticCode::PreconditionViolation, "mesh slime is not initialized", "mesh", {}, "procgen.meshDeformationSession"));
465 if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(z) || !std::isfinite(impulseX) ||
466 !std::isfinite(impulseY) || !std::isfinite(impulseZ) || !std::isfinite(radius) || !std::isfinite(falloff) ||
467 radius <= 0.f || falloff <= 0.f)
469 Diagnostic::error(DiagnosticCode::InvalidArgument, "invalid mesh slime impulse", "slime.impulse", {}, "procgen.meshDeformationSession"));
470 auto candidateVelocity = surfaceVelocity_;
471 const auto& positions = current_.positions();
472 for (int vertex = 0; vertex < current_.getVertexCount(); ++vertex) {
473 const std::size_t base = static_cast<std::size_t>(vertex) * 3u;
474 const float dx = positions[base] - x, dy = positions[base + 1] - y, dz = positions[base + 2] - z;
475 const float distance = std::sqrt(dx * dx + dy * dy + dz * dz);
476 if (distance >= radius) continue;
477 const float weight = std::pow(1.f - distance / radius, falloff);
478 candidateVelocity[base] += impulseX * weight;
479 candidateVelocity[base + 1] += impulseY * weight;
480 candidateVelocity[base + 2] += impulseZ * weight;
481 }
482 pushUndo();
483 surfaceVelocity_ = std::move(candidateVelocity);
484 ++revision_;
485 return Result<void>::success();
486}
487
488Result<void> MeshDeformationSession::stepSlimeResult(float dt, float stiffness, float damping, float maxSpeed) {
489 if (!initialized_)
491 Diagnostic::error(DiagnosticCode::PreconditionViolation, "mesh slime is not initialized", "mesh", {}, "procgen.meshDeformationSession"));
492 if (!std::isfinite(dt) || !std::isfinite(stiffness) || !std::isfinite(damping) || !std::isfinite(maxSpeed) ||
493 dt <= 0.f || stiffness < 0.f || damping < 0.f || maxSpeed <= 0.f)
495 Diagnostic::error(DiagnosticCode::InvalidArgument, "invalid mesh slime simulation step", "slime.step", {}, "procgen.meshDeformationSession"));
496 MeshBuild candidate = current_;
497 auto& positions = candidate.positions();
498 auto velocity = surfaceVelocity_;
499 const float dampingFactor = std::exp(-damping * dt);
500 for (int vertex = 0; vertex < candidate.getVertexCount(); ++vertex) {
501 const std::size_t base = static_cast<std::size_t>(vertex) * 3u;
502 for (int component = 0; component < 3; ++component) {
503 const std::size_t index = base + static_cast<std::size_t>(component);
504 velocity[index] += (surfaceEquilibrium_[index] - positions[index]) * stiffness * dt;
505 velocity[index] *= dampingFactor;
506 }
507 const float speed = std::sqrt(velocity[base] * velocity[base] + velocity[base + 1] * velocity[base + 1] +
508 velocity[base + 2] * velocity[base + 2]);
509 if (speed > maxSpeed) {
510 const float scale = maxSpeed / speed;
511 velocity[base] *= scale;
512 velocity[base + 1] *= scale;
513 velocity[base + 2] *= scale;
514 }
515 positions[base] += velocity[base] * dt;
516 positions[base + 1] += velocity[base + 1] * dt;
517 positions[base + 2] += velocity[base + 2] * dt;
518 }
519 recalculateNormals(candidate);
520 current_ = std::move(candidate);
521 surfaceVelocity_ = std::move(velocity);
522 invalidateImpactVertexBlocks();
523 ++revision_;
524 return Result<void>::success();
525}
526
527Result<void> MeshDeformationSession::configureColliderRefreshResult(std::string_view mode, float intervalSeconds,
528 float offsetX, float offsetY, float offsetZ) {
529 if (!initialized_)
531 Diagnostic::error(DiagnosticCode::PreconditionViolation, "collider refresh session is not initialized", "mesh", {}, "procgen.meshDeformationSession"));
532 if ((mode != "once" && mode != "everyFrame" && mode != "interval" && mode != "manual") ||
533 !std::isfinite(intervalSeconds) || !std::isfinite(offsetX) || !std::isfinite(offsetY) ||
534 !std::isfinite(offsetZ) || (mode == "interval" && intervalSeconds <= 0.f) || intervalSeconds < 0.f)
536 Diagnostic::error(DiagnosticCode::InvalidArgument, "invalid collider refresh configuration", "colliderRefresh", {}, "procgen.meshDeformationSession"));
537 colliderRefreshMode_ = mode;
538 colliderRefreshInterval_ = intervalSeconds;
539 colliderRefreshAccumulator_ = 0.f;
540 colliderOffsetX_ = offsetX;
541 colliderOffsetY_ = offsetY;
542 colliderOffsetZ_ = offsetZ;
543 colliderRefreshConfigured_ = true;
544 colliderRefreshPending_ = mode == "once";
545 ++revision_;
546 return Result<void>::success();
547}
548
550 if (!initialized_ || !colliderRefreshConfigured_)
552 Diagnostic::error(DiagnosticCode::PreconditionViolation, "collider refresh is not configured", "colliderRefresh", {}, "procgen.meshDeformationSession"));
553 colliderRefreshPending_ = true;
554 return Result<void>::success();
555}
556
558 if (!initialized_ || !colliderRefreshConfigured_)
560 "collider refresh is not configured", "colliderRefresh", {},
561 "procgen.meshDeformationSession"));
562 if (!std::isfinite(dt) || dt < 0.f)
564 "collider refresh dt must be non-negative", "colliderRefresh.dt",
565 {}, "procgen.meshDeformationSession"));
566 if (colliderRefreshMode_ == "everyFrame") return Result<bool>::success(true);
567 if (colliderRefreshMode_ == "interval") {
568 colliderRefreshAccumulator_ += dt;
569 const float tolerance = std::max(1e-6f, colliderRefreshInterval_ * 1e-6f);
570 if (colliderRefreshAccumulator_ + tolerance < colliderRefreshInterval_) return Result<bool>::success(false);
571 colliderRefreshAccumulator_ = std::fmod(colliderRefreshAccumulator_, colliderRefreshInterval_);
572 if (colliderRefreshAccumulator_ + tolerance >= colliderRefreshInterval_) colliderRefreshAccumulator_ = 0.f;
573 return Result<bool>::success(true);
574 }
575 const bool refresh = colliderRefreshPending_;
576 colliderRefreshPending_ = false;
577 return Result<bool>::success(refresh);
578}
579
581 if (!initialized_ || !colliderRefreshConfigured_)
583 "collider refresh is not configured", "colliderRefresh", {},
584 "procgen.meshDeformationSession"));
585 MeshBuild collider = current_;
586 auto& positions = collider.positions();
587 for (std::size_t i = 0; i + 2u < positions.size(); i += 3u) {
588 positions[i] += colliderOffsetX_;
589 positions[i + 1u] += colliderOffsetY_;
590 positions[i + 2u] += colliderOffsetZ_;
591 }
592 collider.setMeta("purpose", "dynamicCollider");
593 collider.setMeta("refreshMode", colliderRefreshMode_);
594 return Result<MeshBuild>::success(std::move(collider));
595}
596
598 if (!initialized_)
600 "vertex editor is not initialized", "mesh", {},
601 "procgen.meshDeformationSession"));
602 if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(z) || !std::isfinite(radius) || radius <= 0.f)
604 "invalid spherical vertex selection", "selection", {},
605 "procgen.meshDeformationSession"));
606 auto candidate = replace ? std::vector<std::uint8_t>(selectedVertices_.size(), 0u) : selectedVertices_;
607 const auto& positions = current_.positions();
608 const float radiusSquared = radius * radius;
609 for (int vertex = 0; vertex < current_.getVertexCount(); ++vertex) {
610 const std::size_t base = static_cast<std::size_t>(vertex) * 3u;
611 const float dx = positions[base] - x, dy = positions[base + 1] - y, dz = positions[base + 2] - z;
612 if (dx * dx + dy * dy + dz * dz <= radiusSquared) candidate[static_cast<std::size_t>(vertex)] = 1u;
613 }
614 selectedVertices_ = std::move(candidate);
616}
617
618Result<int> MeshDeformationSession::selectVerticesBoxResult(float minX, float minY, float minZ, float maxX, float maxY,
619 float maxZ, bool replace) {
620 if (!initialized_)
622 "vertex editor is not initialized", "mesh", {},
623 "procgen.meshDeformationSession"));
624 if (!std::isfinite(minX) || !std::isfinite(minY) || !std::isfinite(minZ) || !std::isfinite(maxX) ||
625 !std::isfinite(maxY) || !std::isfinite(maxZ) || minX > maxX || minY > maxY || minZ > maxZ)
626 return Result<int>::failure(Diagnostic::error(DiagnosticCode::InvalidArgument, "invalid box vertex selection",
627 "selection", {}, "procgen.meshDeformationSession"));
628 auto candidate = replace ? std::vector<std::uint8_t>(selectedVertices_.size(), 0u) : selectedVertices_;
629 const auto& positions = current_.positions();
630 for (int vertex = 0; vertex < current_.getVertexCount(); ++vertex) {
631 const std::size_t base = static_cast<std::size_t>(vertex) * 3u;
632 if (positions[base] >= minX && positions[base] <= maxX && positions[base + 1] >= minY &&
633 positions[base + 1] <= maxY && positions[base + 2] >= minZ && positions[base + 2] <= maxZ)
634 candidate[static_cast<std::size_t>(vertex)] = 1u;
635 }
636 selectedVertices_ = std::move(candidate);
638}
639
641 std::fill(selectedVertices_.begin(), selectedVertices_.end(), 0u);
642}
643
645 return static_cast<int>(std::count(selectedVertices_.begin(), selectedVertices_.end(), std::uint8_t{1}));
646}
647
649 if (!initialized_ || selectedVertexCount() == 0)
651 Diagnostic::error(DiagnosticCode::PreconditionViolation, "vertex center requires a selection", "selection",
652 {}, "procgen.meshDeformationSession"));
654 const auto& positions = current_.positions();
655 int count = 0;
656 for (int vertex = 0; vertex < current_.getVertexCount(); ++vertex) {
657 if (!selectedVertices_[static_cast<std::size_t>(vertex)]) continue;
658 const auto base = static_cast<std::size_t>(vertex) * 3u;
659 center.x += positions[base];
660 center.y += positions[base + 1];
661 center.z += positions[base + 2];
662 ++count;
663 }
664 center.x /= static_cast<float>(count);
665 center.y /= static_cast<float>(count);
666 center.z /= static_cast<float>(count);
668}
669
671 if (!initialized_)
673 Diagnostic::error(DiagnosticCode::PreconditionViolation, "vertex editor is not initialized", "mesh", {}, "procgen.meshDeformationSession"));
674 if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(z))
676 Diagnostic::error(DiagnosticCode::InvalidArgument, "vertex move delta must be finite", "move", {}, "procgen.meshDeformationSession"));
677 if (selectedVertexCount() == 0)
679 Diagnostic::error(DiagnosticCode::PreconditionViolation, "vertex move requires a selection", "selection", {}, "procgen.meshDeformationSession"));
680 MeshBuild candidate = current_;
681 auto& positions = candidate.positions();
682 for (int vertex = 0; vertex < candidate.getVertexCount(); ++vertex) {
683 if (!selectedVertices_[static_cast<std::size_t>(vertex)]) continue;
684 const std::size_t base = static_cast<std::size_t>(vertex) * 3u;
685 positions[base] += x;
686 positions[base + 1] += y;
687 positions[base + 2] += z;
688 }
689 recalculateNormals(candidate);
690 pushUndo();
691 surfaceEquilibrium_ = candidate.positions();
692 current_ = std::move(candidate);
693 invalidateImpactVertexBlocks();
694 ++revision_;
695 return Result<void>::success();
696}
697
699 float originY, float originZ, float directionX,
700 float directionY, float directionZ,
701 float distance) {
702 if (mode != "pull" && mode != "push" && mode != "grab")
704 Diagnostic::error(DiagnosticCode::InvalidArgument, "vertex manipulation mode must be pull, push, or grab", "mode", {}, "procgen.meshDeformationSession"));
705 if (!std::isfinite(originX) || !std::isfinite(originY) || !std::isfinite(originZ) || !std::isfinite(directionX) ||
706 !std::isfinite(directionY) || !std::isfinite(directionZ) || !std::isfinite(distance))
708 Diagnostic::error(DiagnosticCode::InvalidArgument, "vertex manipulation values must be finite", "input", {}, "procgen.meshDeformationSession"));
709 if (mode == "grab")
710 return moveSelectedVerticesResult(directionX * distance, directionY * distance, directionZ * distance);
711 if (!initialized_ || selectedVertexCount() == 0)
713 Diagnostic::error(DiagnosticCode::PreconditionViolation, "vertex manipulation requires a selection", "selection", {}, "procgen.meshDeformationSession"));
714 MeshBuild candidate = current_;
715 auto& positions = candidate.positions();
716 const float sign = mode == "push" ? -1.f : 1.f;
717 for (int vertex = 0; vertex < candidate.getVertexCount(); ++vertex) {
718 if (!selectedVertices_[static_cast<std::size_t>(vertex)]) continue;
719 const std::size_t base = static_cast<std::size_t>(vertex) * 3u;
720 float dx = positions[base] - originX, dy = positions[base + 1] - originY, dz = positions[base + 2] - originZ;
721 const float length = std::sqrt(dx * dx + dy * dy + dz * dz);
722 if (length < 1e-7f) continue;
723 positions[base] += dx / length * distance * sign;
724 positions[base + 1] += dy / length * distance * sign;
725 positions[base + 2] += dz / length * distance * sign;
726 }
727 recalculateNormals(candidate);
728 pushUndo();
729 surfaceEquilibrium_ = candidate.positions();
730 current_ = std::move(candidate);
731 invalidateImpactVertexBlocks();
732 ++revision_;
733 return Result<void>::success();
734}
735
737 if (!initialized_)
739 Diagnostic::error(DiagnosticCode::PreconditionViolation, "sculpting session is not initialized", "mesh", {}, "procgen.meshDeformationSession"));
740 current_ = original_;
741 surfaceEquilibrium_ = original_.positions();
742 surfaceVelocity_.assign(surfaceEquilibrium_.size(), 0.f);
743 undo_.clear();
744 invalidateImpactVertexBlocks();
745 ++revision_;
746 return Result<void>::success();
747}
748
750 if (!initialized_)
752 Diagnostic::error(DiagnosticCode::PreconditionViolation, "sculpting session is not initialized", "mesh", {}, "procgen.meshDeformationSession"));
753 original_ = current_;
754 surfaceEquilibrium_ = current_.positions();
755 surfaceVelocity_.assign(surfaceEquilibrium_.size(), 0.f);
756 undo_.clear();
757 invalidateImpactVertexBlocks();
758 ++revision_;
759 return Result<void>::success();
760}
761
763 if (undo_.empty()) return Result<void>::failure(
764 Diagnostic::error(DiagnosticCode::NotFound, "sculpting undo history is empty", "undo", {}, "procgen.meshDeformationSession"));
765 current_ = std::move(undo_.back().mesh);
766 surfaceEquilibrium_ = std::move(undo_.back().equilibrium);
767 surfaceVelocity_ = std::move(undo_.back().velocity);
768 undo_.pop_back();
769 invalidateImpactVertexBlocks();
770 ++revision_;
771 return Result<void>::success();
772}
773
775 if (!initialized_)
777 "sculpting session is not initialized", "mesh", {},
778 "procgen.meshDeformationSession"));
779 return Result<MeshBuild>::success(current_);
780}
781
782void MeshDeformationSession::pushUndo() {
783 constexpr std::size_t historyLimit = 32;
784 if (undo_.size() == historyLimit) undo_.erase(undo_.begin());
785 undo_.push_back({current_, surfaceEquilibrium_, surfaceVelocity_});
786}
787
788} // namespace eve::procgen
double value
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
float length
Definition CaveMesh.cpp:94
int triangle
float maximum[3]
float minimum[3]
const GltfImportRequest & request
float u
Definition Grass.cpp:233
std::uint32_t ab
std::uint32_t ac
std::vector< float > normals
std::vector< float > positions
float v
float blend
std::array< float, 3 > scale
float distance
float radius
Mesh * mesh
float dz
float dy
float dx
std::uint32_t count
float offsetX
float offsetY
float size
Definition TreeMesh.cpp:156
uint32_t index
const UnitySourceAsset & source
std::uint32_t depth
float vz
float vy
float vx
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
const std::vector< float > & positions() const
Positions.
Definition MeshBuild.h:125
const std::vector< float > & normals() const
Normals.
Definition MeshBuild.h:127
void setMeta(const std::string &key, const std::string &value)
Sets the meta.
const std::vector< uint32_t > & indices() const
Indices.
Definition MeshBuild.h:133
int selectedVertexCount() const noexcept
Return selected vertex count.
Result< void > initializeResult(const MeshBuild &mesh)
Replace the session with an owning copy of a valid source mesh.
Result< int > selectVerticesSphereResult(float x, float y, float z, float radius, bool replace=true)
Replace or extend vertex selection using a world/local-space sphere.
Result< void > applyImpactGpuResult(float x, float y, float z, float impulseX, float impulseY, float impulseZ, float radius, float plasticity, float hardness, float maxDisplacement)
Apply plastic damage through the registered GPU provider; absence returns Unsupported.
Result< void > moveSelectedVerticesResult(float x, float y, float z)
Translate selected vertices by a finite axis-gizmo delta as one undoable edit.
Result< void > undoResult()
Restore the most recent pre-edit snapshot.
void clearVertexSelection() noexcept
Clear runtime vertex selection.
Result< void > prepareImpactVertexBlocksResult(int divisionsPerAxis)
Build a bounded uniform vertex-block index for repeated high-density impact queries.
Result< void > stepSlimeResult(float dt, float stiffness, float damping, float maxSpeed)
Advance deterministic spring-damper Mesh Slime dynamics using injected time.
Result< bool > updateColliderRefreshResult(float dt)
Advance the refresh scheduler and consume at most one pending refresh.
Result< int > selectVerticesBoxResult(float minX, float minY, float minZ, float maxX, float maxY, float maxZ, bool replace=true)
Replace or extend vertex selection using an axis-aligned world/local-space box.
Result< void > applyBrushGpuResult(std::string_view mode, float x, float y, float z, float radius, float strength, float falloff, float directionX=0.f, float directionY=1.f, float directionZ=0.f)
Apply a sculpt brush through the registered GPU provider; absence returns Unsupported.
Result< void > applyImpactResult(float x, float y, float z, float impulseX, float impulseY, float impulseZ, float radius, float plasticity, float hardness, float maxDisplacement)
Apply one collision-neutral plastic impact to the current mesh.
Result< void > bakeResult()
Make the current mesh the new restore point and clear undo history.
Result< void > manipulateSelectedVerticesResult(std::string_view mode, float originX, float originY, float originZ, float directionX, float directionY, float directionZ, float distance)
Apply pull, push, or grab to selected vertices as one undoable edit.
Result< void > requestColliderRefreshResult()
Mark a manual or once collider refresh as pending.
Result< MeshVertexSelectionCenter > selectedVertexCenterResult() const
Return the arithmetic center of selected vertices for gizmo adapters.
Result< void > applySurfaceContactResult(float x, float y, float z, float normalX, float normalY, float normalZ, float velocityX, float velocityY, float velocityZ, float radius, float depth, float drag, float falloff, float plasticity=0.f)
Apply one Interactive Surface contact using caller-supplied local-space values.
Result< void > configureColliderRefreshResult(std::string_view mode, float intervalSeconds, float offsetX=0.f, float offsetY=0.f, float offsetZ=0.f)
Configure collider refresh scheduling and local position offset.
Result< void > restoreResult()
Restore the initial mesh and clear undo history.
Result< MeshBuild > currentMeshResult() const
Return an independent owning snapshot of the current mesh.
Result< void > applySlimeImpulseResult(float x, float y, float z, float impulseX, float impulseY, float impulseZ, float radius, float falloff)
Inject one localized velocity impulse into the Mesh Slime simulation state.
Result< void > applyBrushResult(std::string_view mode, float x, float y, float z, float radius, float strength, float falloff, float directionX=0.f, float directionY=1.f, float directionZ=0.f)
Apply a localized brush to the current mesh.
Result< MeshBuild > colliderMeshResult() const
Return an owning current triangle-mesh collider snapshot with configured offset applied.
int impactVertexBlockCount() const noexcept
Return the number of non-empty prepared impact blocks.
Result< void > recoverSurfaceResult(float dt, float recoveryRate)
Advance elastic recovery using explicit simulation time.
double cross(const Vec2 &a, const Vec2 &b)
Cross.
Definition UrbanTypes.h:36
Owning, backend-neutral input for one synchronous GPU mesh deformation.
MeshDeformationComputeOperation operation
Value-only center of the current runtime vertex selection.