载入中...
搜索中...
未找到
RoadArtifact.cpp
浏览该文件的文档.
2
3#include "common/Diagnostic.h"
5
6#include <algorithm>
7#include <bit>
8#include <cmath>
9#include <cstdint>
10#include <iomanip>
11#include <map>
12#include <sstream>
13#include <string>
14#include <unordered_map>
15#include <utility>
16#include <vector>
17
18namespace eve::procgen {
19namespace {
20
21Bounds meshBounds(const MeshBuild& mesh) {
22 Bounds bounds;
23 for (int i = 0; i < mesh.getVertexCount(); ++i)
24 bounds.include(mesh.getPositionX(i), mesh.getPositionY(i), mesh.getPositionZ(i));
25 return bounds;
26}
27
28Bounds pointBounds(const PointSet& points) {
29 Bounds bounds;
30 for (const auto& point : points.points()) bounds.include(point.x, point.y, point.z);
31 return bounds;
32}
33
34Result<BuildKey> childKey(const BuildKey& parent, std::string_view role) {
35 auto key = BuildKey::fromCanonical(parent.format() + "/" + std::string(role));
36 if (!key)
38 Diagnostic::error(DiagnosticCode::InvalidArgument, "cannot derive road artifact child key", "role"));
39 return Result<BuildKey>::success(std::move(*key));
40}
41
42bool isStructuralColliderGroup(std::string_view group) {
43 return group != "marking" && group != "markingYellow" && group != "nav";
44}
45
46bool isDrivableColliderGroup(std::string_view group) { return group == "asphalt"; }
47
48template <class IncludeGroup>
49Collider roadCollider(const MeshBuild& mesh, IncludeGroup&& includeGroup) {
50 Collider result;
51 std::unordered_map<std::uint32_t, std::uint32_t> remap;
52 const int triangleCount = mesh.getIndexCount() / 3;
53 result.indices.reserve(mesh.indices().size());
54 for (int triangle = 0; triangle < triangleCount; ++triangle) {
55 const int group = mesh.getTriangleGroup(triangle);
56 if (group < 0 || !includeGroup(mesh.getGroupName(group))) continue;
57 for (int corner = 0; corner < 3; ++corner) {
58 const auto source = static_cast<std::uint32_t>(mesh.getIndex(triangle * 3 + corner));
59 const auto found = remap.find(source);
60 std::uint32_t destination = 0;
61 if (found == remap.end()) {
62 destination = static_cast<std::uint32_t>(result.vertices.size() / 3u);
63 remap.emplace(source, destination);
64 const int sourceIndex = static_cast<int>(source);
65 const float x = mesh.getPositionX(sourceIndex);
66 const float y = mesh.getPositionY(sourceIndex);
67 const float z = mesh.getPositionZ(sourceIndex);
68 result.vertices.insert(result.vertices.end(), {x, y, z});
69 result.bounds.include(x, y, z);
70 } else {
71 destination = found->second;
72 }
73 result.indices.push_back(destination);
74 }
75 }
76 return result;
77}
78
79struct RoadChunkCoordinate {
80 int x = 0;
81 int z = 0;
82 int lod = 0;
83 friend bool operator<(const RoadChunkCoordinate& lhs, const RoadChunkCoordinate& rhs) noexcept {
84 if (lhs.lod != rhs.lod) return lhs.lod < rhs.lod;
85 if (lhs.z != rhs.z) return lhs.z < rhs.z;
86 return lhs.x < rhs.x;
87 }
88};
89
90struct RoadChunkAccumulator {
91 MeshBuild mesh;
92 std::unordered_map<std::uint32_t, std::uint32_t> remap;
93 std::vector<float> colors;
94};
95
96std::uint64_t hashChunkMesh(const MeshBuild& mesh) {
97 std::uint64_t hash = 14695981039346656037ull;
98 auto append = [&](std::uint32_t value) {
99 for (int byte = 0; byte < 4; ++byte) {
100 hash ^= static_cast<std::uint8_t>((value >> (byte * 8)) & 0xffu);
101 hash *= 1099511628211ull;
102 }
103 };
104 for (float value : mesh.positions()) append(std::bit_cast<std::uint32_t>(value));
105 for (float value : mesh.normals()) append(std::bit_cast<std::uint32_t>(value));
106 for (float value : mesh.uvs()) append(std::bit_cast<std::uint32_t>(value));
107 for (float value : mesh.colors()) append(std::bit_cast<std::uint32_t>(value));
108 for (std::uint32_t value : mesh.indices()) append(value);
109 for (int value : mesh.triangleGroups()) append(static_cast<std::uint32_t>(value));
110 for (const auto& name : mesh.groupNames())
111 for (const unsigned char byte : name) {
112 hash ^= byte;
113 hash *= 1099511628211ull;
114 }
115 return hash;
116}
117
118Result<std::map<RoadChunkCoordinate, MeshBuild>> splitRoadMeshIntoChunks(const MeshBuild& source, float chunkSize,
119 int lod) {
120 std::map<RoadChunkCoordinate, RoadChunkAccumulator> accumulators;
121 const int triangleCount = source.getIndexCount() / 3;
122 for (int triangle = 0; triangle < triangleCount; ++triangle) {
123 const int ia = source.getIndex(triangle * 3);
124 const int ib = source.getIndex(triangle * 3 + 1);
125 const int ic = source.getIndex(triangle * 3 + 2);
126 const float centerX = (source.getPositionX(ia) + source.getPositionX(ib) + source.getPositionX(ic)) / 3.f;
127 const float centerZ = (source.getPositionZ(ia) + source.getPositionZ(ib) + source.getPositionZ(ic)) / 3.f;
128 const RoadChunkCoordinate coordinate{static_cast<int>(std::floor(centerX / chunkSize)),
129 static_cast<int>(std::floor(centerZ / chunkSize)), lod};
130 auto& accumulator = accumulators[coordinate];
131 const int sourceGroup = source.getTriangleGroup(triangle);
132 accumulator.mesh.setActiveGroup(sourceGroup >= 0 ? source.getGroupName(sourceGroup) : "default");
133 std::uint32_t destination[3]{};
134 const int sourceIndices[3] = {ia, ib, ic};
135 for (int corner = 0; corner < 3; ++corner) {
136 const auto sourceIndex = static_cast<std::uint32_t>(sourceIndices[corner]);
137 const auto found = accumulator.remap.find(sourceIndex);
138 if (found != accumulator.remap.end()) {
139 destination[corner] = found->second;
140 continue;
141 }
142 destination[corner] = static_cast<std::uint32_t>(accumulator.mesh.getVertexCount());
143 accumulator.remap.emplace(sourceIndex, destination[corner]);
144 const int vertex = static_cast<int>(sourceIndex);
145 accumulator.mesh.addVertex(source.getPositionX(vertex), source.getPositionY(vertex),
146 source.getPositionZ(vertex), source.getNormalX(vertex), source.getNormalY(vertex),
147 source.getNormalZ(vertex), source.getUvU(vertex), source.getUvV(vertex));
148 if (source.hasVertexColors())
149 for (int component = 0; component < 4; ++component)
150 accumulator.colors.push_back(source.getColor(vertex, component));
151 }
152 accumulator.mesh.addTriangle(destination[0], destination[1], destination[2]);
153 }
154
155 std::map<RoadChunkCoordinate, MeshBuild> chunks;
156 for (auto& [coordinate, accumulator] : accumulators) {
157 if (!accumulator.colors.empty()) {
158 auto colors = accumulator.mesh.setVertexColors(std::move(accumulator.colors));
159 if (!colors.ok()) return Result<std::map<RoadChunkCoordinate, MeshBuild>>::failure(colors.status());
160 }
161 chunks.emplace(coordinate, std::move(accumulator.mesh));
162 }
163 return Result<std::map<RoadChunkCoordinate, MeshBuild>>::success(std::move(chunks));
164}
165
166Result<ArtifactPart> roadChunkPart(std::string role, ArtifactId root, MeshBuild mesh, RoadChunkCoordinate coordinate) {
167 std::ostringstream canonical;
168 canonical << "eve.procgen.road.chunk.v1/" << role << '/' << std::hex << std::setfill('0') << std::setw(16)
169 << hashChunkMesh(mesh);
170 auto key = BuildKey::fromCanonical(canonical.str());
171 if (!key)
173 Diagnostic::error(DiagnosticCode::InvariantViolation, "cannot derive road chunk content key", role));
174 eve::Value::Object metadata;
175 metadata.emplace("role", eve::Value(role));
176 metadata.emplace("lod", eve::Value(static_cast<std::int64_t>(coordinate.lod)));
177 metadata.emplace("chunkX", eve::Value(static_cast<std::int64_t>(coordinate.x)));
178 metadata.emplace("chunkZ", eve::Value(static_cast<std::int64_t>(coordinate.z)));
179 const ArtifactId partId = root.child(role);
180 const Bounds bounds = meshBounds(mesh);
181 return makeArtifactPart(std::move(role), partId, ArtifactType::MeshData, eve::SchemaVersion(1), std::move(*key),
182 bounds, {root.child("mesh")}, std::move(metadata), std::move(mesh));
183}
184
185float edgeSign(float ax, float az, float bx, float bz, float px, float pz) {
186 return (px - bx) * (az - bz) - (ax - bx) * (pz - bz);
187}
188
189bool pointInTriangleXZ(float px, float pz, float ax, float az, float bx, float bz, float cx, float cz) {
190 const bool negative = edgeSign(ax, az, bx, bz, px, pz) < 0.f || edgeSign(bx, bz, cx, cz, px, pz) < 0.f ||
191 edgeSign(cx, cz, ax, az, px, pz) < 0.f;
192 const bool positive = edgeSign(ax, az, bx, bz, px, pz) > 0.f || edgeSign(bx, bz, cx, cz, px, pz) > 0.f ||
193 edgeSign(cx, cz, ax, az, px, pz) > 0.f;
194 return !(negative && positive);
195}
196
197Grid2D roadFootprint(const MeshBuild& mesh, const Bounds& bounds, float requestedCellSize, float padding = 0.f) {
198 Grid2D grid;
199 if (!bounds.isValid()) return grid;
200 const float originX = bounds.minX - padding;
201 const float originZ = bounds.minZ - padding;
202 const float extentX = std::max(requestedCellSize, bounds.maxX - bounds.minX + padding * 2.f);
203 const float extentZ = std::max(requestedCellSize, bounds.maxZ - bounds.minZ + padding * 2.f);
204 const float cellSize = std::max(requestedCellSize, std::max(extentX, extentZ) / 512.f);
205 const int width = std::clamp(static_cast<int>(std::ceil(extentX / cellSize)), 1, 512);
206 const int height = std::clamp(static_cast<int>(std::ceil(extentZ / cellSize)), 1, 512);
207 grid.resize(width, height);
208 grid.fill(0);
209 grid.setMeta("originX", std::to_string(originX));
210 grid.setMeta("originZ", std::to_string(originZ));
211 grid.setMeta("cellSize", std::to_string(cellSize));
212 grid.setMeta("semantics", "road_footprint");
213
214 const int triangleCount = mesh.getIndexCount() / 3;
215 for (int triangle = 0; triangle < triangleCount; ++triangle) {
216 const int group = mesh.getTriangleGroup(triangle);
217 if (group < 0) continue;
218 const std::string name = mesh.getGroupName(group);
219 if (name != "asphalt" && name != "sidewalk" && name != "curb") continue;
220 const int ia = mesh.getIndex(triangle * 3);
221 const int ib = mesh.getIndex(triangle * 3 + 1);
222 const int ic = mesh.getIndex(triangle * 3 + 2);
223 const float ax = mesh.getPositionX(ia), az = mesh.getPositionZ(ia);
224 const float bx = mesh.getPositionX(ib), bz = mesh.getPositionZ(ib);
225 const float cx = mesh.getPositionX(ic), cz = mesh.getPositionZ(ic);
226 const int minX =
227 std::clamp(static_cast<int>(std::floor((std::min({ax, bx, cx}) - originX) / cellSize)), 0, width - 1);
228 const int maxX =
229 std::clamp(static_cast<int>(std::floor((std::max({ax, bx, cx}) - originX) / cellSize)), 0, width - 1);
230 const int minZ =
231 std::clamp(static_cast<int>(std::floor((std::min({az, bz, cz}) - originZ) / cellSize)), 0, height - 1);
232 const int maxZ =
233 std::clamp(static_cast<int>(std::floor((std::max({az, bz, cz}) - originZ) / cellSize)), 0, height - 1);
234 for (int z = minZ; z <= maxZ; ++z) {
235 for (int x = minX; x <= maxX; ++x) {
236 const float px = originX + (static_cast<float>(x) + 0.5f) * cellSize;
237 const float pz = originZ + (static_cast<float>(z) + 0.5f) * cellSize;
238 if (pointInTriangleXZ(px, pz, ax, az, bx, bz, cx, cz)) grid.setCell(x, z, 1);
239 }
240 }
241 }
242 return grid;
243}
244
245Grid2D roadSurfaceWeights(Grid2D grid, float cellSize, float blendDistance) {
246 const int width = grid.getWidth(), height = grid.getHeight();
247 const float infinity = static_cast<float>(width + height + 1);
248 std::vector<float> distance(static_cast<std::size_t>(width * height), infinity);
249 const auto index = [width](int x, int z) { return static_cast<std::size_t>(z * width + x); };
250 for (int z = 0; z < height; ++z)
251 for (int x = 0; x < width; ++x)
252 if (grid.getCell(x, z) != 0) distance[index(x, z)] = 0.f;
253
254 constexpr float diagonal = 1.41421356f;
255 auto relax = [&](int x, int z, int nx, int nz, float cost) {
256 if (nx < 0 || nz < 0 || nx >= width || nz >= height) return;
257 distance[index(x, z)] = std::min(distance[index(x, z)], distance[index(nx, nz)] + cost);
258 };
259 for (int z = 0; z < height; ++z) {
260 for (int x = 0; x < width; ++x) {
261 relax(x, z, x - 1, z, 1.f);
262 relax(x, z, x, z - 1, 1.f);
263 relax(x, z, x - 1, z - 1, diagonal);
264 relax(x, z, x + 1, z - 1, diagonal);
265 }
266 }
267 for (int z = height; z-- > 0;) {
268 for (int x = width; x-- > 0;) {
269 relax(x, z, x + 1, z, 1.f);
270 relax(x, z, x, z + 1, 1.f);
271 relax(x, z, x + 1, z + 1, diagonal);
272 relax(x, z, x - 1, z + 1, diagonal);
273 }
274 }
275 for (int z = 0; z < height; ++z) {
276 for (int x = 0; x < width; ++x) {
277 const float metres = distance[index(x, z)] * cellSize;
278 const float weight = distance[index(x, z)] == 0.f
279 ? 1.f
280 : (blendDistance > 0.f ? std::clamp(1.f - metres / blendDistance, 0.f, 1.f)
281 : 0.f);
282 grid.setDetail(x, z, static_cast<int>(std::lround(weight * 255.f)));
283 }
284 }
285 grid.setMeta("semantics", "road_surface_weight");
286 grid.setMeta("weightChannel", "detail");
287 grid.setMeta("weightScale", "255");
288 grid.setMeta("blendDistance", std::to_string(blendDistance));
289 return grid;
290}
291
292Result<ArtifactPart> roadPart(std::string role, ArtifactId root, const BuildKey& rootKey, ArtifactType type,
293 Bounds bounds, ArtifactLeafPayload payload, std::vector<ArtifactId> dependencies = {}) {
294 auto key = childKey(rootKey, role);
295 if (!key.ok()) return Result<ArtifactPart>::failure(key.status());
296 eve::Value::Object metadata;
297 metadata.emplace("role", eve::Value(role));
298 return makeArtifactPart(role, root.child(role), type, eve::SchemaVersion(1), std::move(key).takeValue(), bounds,
299 std::move(dependencies), std::move(metadata), std::move(payload));
300}
301
302Result<void> appendNavigationParts(const road::RoadOverlay& overlay, ArtifactId root, const BuildKey& rootKey,
303 std::vector<ArtifactPart>& parts) {
304 auto append = [&](const road::RoadPolyline& line, std::string role) -> Result<void> {
305 // Fully junction-trimmed lanes legitimately have no visible samples.
306 // They carry no artifact data and must not invalidate the whole build.
307 if (line.xyz.empty()) return Result<void>::success();
308 if (line.xyz.size() % 3u != 0u)
310 DiagnosticCode::InvariantViolation, "road overlay coordinates are not packed xyz triples", role));
311 if (!std::all_of(line.xyz.begin(), line.xyz.end(), [](float value) { return std::isfinite(value); }))
313 "road overlay contains non-finite coordinates", role));
315 points.reserve(line.xyz.size() / 3);
316 for (std::size_t i = 0; i + 2 < line.xyz.size(); i += 3)
317 points.add(line.xyz[i], line.xyz[i + 1], line.xyz[i + 2]);
318 auto metadata = setPointDataIntAttribute(points, "in_edge", line.inEdge);
319 if (!metadata.ok()) return metadata;
320 metadata = setPointDataIntAttribute(points, "out_edge", line.outEdge);
321 if (!metadata.ok()) return metadata;
322 metadata = setPointDataIntAttribute(points, "in_lane", line.inLane);
323 if (!metadata.ok()) return metadata;
324 metadata = setPointDataIntAttribute(points, "out_lane", line.outLane);
325 if (!metadata.ok()) return metadata;
326 metadata = setPointDataIntAttribute(points, "in_direction", static_cast<int>(line.inDirection));
327 if (!metadata.ok()) return metadata;
328 metadata = setPointDataIntAttribute(points, "out_direction", static_cast<int>(line.outDirection));
329 if (!metadata.ok()) return metadata;
330 metadata = setPointDataIntAttribute(points, "traffic_priority", line.trafficPriority);
331 if (!metadata.ok()) return metadata;
332 auto speed = setPointDataFloatAttribute(points, "speed_limit_mps", line.speedLimitMps);
333 if (!speed.ok()) return speed;
334 // Compute before moving `points`: function argument evaluation order
335 // must not decide whether the bounds see a moved-from PointSet.
336 const Bounds bounds = pointBounds(points);
337 auto part =
338 roadPart(role, root, rootKey, ArtifactType::PointSet, bounds, std::move(points), {root.child("mesh")});
339 if (!part.ok()) return Result<void>::failure(part.status());
340 parts.push_back(std::move(part).takeValue());
341 return Result<void>::success();
342 };
343 for (std::size_t i = 0; i < overlay.lanes.size(); ++i) {
344 auto added = append(overlay.lanes[i], "navigation/lane/" + std::to_string(i));
345 if (!added.ok()) return added;
346 }
347 for (std::size_t i = 0; i < overlay.turns.size(); ++i) {
348 auto added = append(overlay.turns[i], "navigation/turn/" + std::to_string(i));
349 if (!added.ok()) return added;
350 }
351 return Result<void>::success();
352}
353
354} // namespace
355
357 if (id.isNil())
359 Diagnostic::error(DiagnosticCode::InvalidArgument, "road artifact identity must not be nil", "id"));
360 auto key = BuildKey::forRecipe("mesh.roadNetwork", params);
361 if (!key)
363 Diagnostic::error(DiagnosticCode::InvalidArgument, "road parameters cannot form a build key", "params"));
365 if (!baked.ok()) return Result<GeneratedArtifact>::failure(baked.status());
366
367 road::RoadBakeResult result = std::move(baked).takeValue();
368 const Bounds bounds = meshBounds(result.mesh);
369 Collider collider = roadCollider(result.mesh, isStructuralColliderGroup);
370 Collider drivableCollider = roadCollider(result.mesh, isDrivableColliderGroup);
371 const float chunkSize = params.getFloat("chunkSize", 64.f);
372 const int lodCount = params.getInt("lodCount", 3);
373 if (!std::isfinite(chunkSize) || chunkSize < 4.f || chunkSize > 1024.f || lodCount < 1 || lodCount > 4)
375 DiagnosticCode::InvalidArgument, "road chunkSize must be in [4,1024] and lodCount in [1,4]", "chunks"));
376 std::vector<std::map<RoadChunkCoordinate, MeshBuild>> lodChunks;
377 lodChunks.reserve(static_cast<std::size_t>(lodCount));
378 auto primaryChunks = splitRoadMeshIntoChunks(result.mesh, chunkSize, 0);
379 if (!primaryChunks.ok()) return Result<GeneratedArtifact>::failure(primaryChunks.status());
380 lodChunks.push_back(std::move(primaryChunks).takeValue());
381 const int basePathSegments = std::max(4, params.getInt("pathSegments", 48));
382 const int baseTurnSamples = std::max(4, params.getInt("turnSamples", 12));
383 for (int lod = 1; lod < lodCount; ++lod) {
384 Params lodParams = params;
385 lodParams.setInt("pathSegments", std::max(4, basePathSegments >> lod));
386 lodParams.setInt("turnSamples", std::max(4, baseTurnSamples >> lod));
387 lodParams.setFloat("junctionChordError",
388 std::min(1.f, params.getFloat("junctionChordError", 0.10f) * static_cast<float>(1 << lod)));
389 lodParams.setBool("navigation", false);
390 lodParams.setBool("placements", false);
391 auto lodBake = road::bakeRoadNetworkRecipe(lodParams);
392 if (!lodBake.ok()) return Result<GeneratedArtifact>::failure(lodBake.status());
393 auto chunks = splitRoadMeshIntoChunks(lodBake.value().mesh, chunkSize, lod);
394 if (!chunks.ok()) return Result<GeneratedArtifact>::failure(chunks.status());
395 lodChunks.push_back(std::move(chunks).takeValue());
396 }
397 const float maskCellSize = std::max(0.1f, params.getFloat("maskCellSize", 1.f));
398 const float terrainBlendDistance = params.getFloat("terrainBlendDistance", 2.f);
399 if (!std::isfinite(terrainBlendDistance) || terrainBlendDistance < 0.f || terrainBlendDistance > 64.f)
401 DiagnosticCode::InvalidArgument, "road terrain blend distance must be finite and in [0,64]",
402 "terrainBlendDistance"));
403 Grid2D footprint = roadFootprint(result.mesh, bounds, maskCellSize);
404 Grid2D surfaceWeights = roadFootprint(result.mesh, bounds, maskCellSize,
405 terrainBlendDistance + maskCellSize);
406 const float surfaceCellSize = std::stof(surfaceWeights.getMeta("cellSize", "1"));
407 surfaceWeights = roadSurfaceWeights(std::move(surfaceWeights), surfaceCellSize, terrainBlendDistance);
408 if (!bounds.isValid() || !collider.isValid() || !drivableCollider.isValid() || footprint.getWidth() <= 0 ||
409 footprint.getHeight() <= 0 ||
410 surfaceWeights.getWidth() <= 0 || surfaceWeights.getHeight() <= 0)
412 Diagnostic::error(DiagnosticCode::InvariantViolation, "road bake produced incomplete artifact data"));
413
414 std::vector<ArtifactPart> parts;
415 std::size_t chunkCount = 0;
416 for (const auto& chunks : lodChunks) chunkCount += chunks.size();
417 parts.reserve(6 + chunkCount + result.overlay.lanes.size() + result.overlay.turns.size());
418 auto meshPart = roadPart("mesh", id, *key, ArtifactType::MeshData, bounds, std::move(result.mesh));
419 if (!meshPart.ok()) return Result<GeneratedArtifact>::failure(meshPart.status());
420 parts.push_back(std::move(meshPart).takeValue());
421 auto colliderPart = roadPart("collider", id, *key, ArtifactType::Collider, collider.bounds, std::move(collider),
422 {id.child("mesh")});
423 if (!colliderPart.ok()) return Result<GeneratedArtifact>::failure(colliderPart.status());
424 parts.push_back(std::move(colliderPart).takeValue());
425 auto drivableColliderPart = roadPart("collider/drivable", id, *key, ArtifactType::Collider,
426 drivableCollider.bounds, std::move(drivableCollider), {id.child("mesh")});
427 if (!drivableColliderPart.ok()) return Result<GeneratedArtifact>::failure(drivableColliderPart.status());
428 parts.push_back(std::move(drivableColliderPart).takeValue());
429 for (auto& chunks : lodChunks) {
430 for (auto& [coordinate, chunk] : chunks) {
431 const std::string role = "render/chunk/" + std::to_string(coordinate.x) + "/" +
432 std::to_string(coordinate.z) + "/lod/" + std::to_string(coordinate.lod);
433 auto part = roadChunkPart(role, id, std::move(chunk), coordinate);
434 if (!part.ok()) return Result<GeneratedArtifact>::failure(part.status());
435 parts.push_back(std::move(part).takeValue());
436 }
437 }
438 auto maskPart =
439 roadPart("topology", id, *key, ArtifactType::Grid, bounds, std::move(footprint), {id.child("mesh")});
440 if (!maskPart.ok()) return Result<GeneratedArtifact>::failure(maskPart.status());
441 parts.push_back(std::move(maskPart).takeValue());
442 const float surfacePadding = terrainBlendDistance + surfaceCellSize;
443 Bounds surfaceBounds = bounds;
444 surfaceBounds.include(bounds.minX - surfacePadding, bounds.minY, bounds.minZ - surfacePadding);
445 surfaceBounds.include(bounds.maxX + surfacePadding, bounds.maxY, bounds.maxZ + surfacePadding);
446 auto surfacePart = roadPart("terrain/surface-weight", id, *key, ArtifactType::Grid, surfaceBounds,
447 std::move(surfaceWeights), {id.child("mesh")});
448 if (!surfacePart.ok()) return Result<GeneratedArtifact>::failure(surfacePart.status());
449 parts.push_back(std::move(surfacePart).takeValue());
450 if (!result.placements.empty()) {
451 const Bounds placementBounds = pointBounds(result.placements);
452 auto placementPart = roadPart("placements", id, *key, ArtifactType::PointSet, placementBounds,
453 std::move(result.placements), {id.child("mesh")});
454 if (!placementPart.ok()) return Result<GeneratedArtifact>::failure(placementPart.status());
455 parts.push_back(std::move(placementPart).takeValue());
456 }
457 auto navigation = appendNavigationParts(result.overlay, id, *key, parts);
458 if (!navigation.ok()) return Result<GeneratedArtifact>::failure(navigation.status());
459
460 CompositeArtifact composite;
461 composite.children = std::move(parts);
462 eve::Value::Object metadata;
463 metadata.emplace("algorithm", eve::Value("mesh.roadNetwork"));
464 metadata.emplace("determinism", eve::Value("seeded_cpu"));
465 metadata.emplace("terrainMaskRole", eve::Value("topology"));
466 metadata.emplace("terrainSurfaceWeightRole", eve::Value("terrain/surface-weight"));
467 metadata.emplace("roadChunkRolePrefix", eve::Value("render/chunk/"));
468 metadata.emplace("drivableColliderRole", eve::Value("collider/drivable"));
469 metadata.emplace("lodCount", eve::Value(static_cast<std::int64_t>(lodCount)));
470 metadata.emplace("chunkSize", eve::Value(static_cast<double>(chunkSize)));
471 return makeArtifact(id, ArtifactType::Composite, eve::SchemaVersion(2), *key, bounds, {}, std::move(metadata),
472 std::move(composite));
473}
474
475} // namespace eve::procgen
double value
Value::Object payload
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
int root
Definition AnimSmr.cpp:119
std::vector< eve::artifact::PartView > parts
building::EdgeCurveGroup group
float cx
Definition CardTypes.cpp:33
int bz
Definition CaveMesh.cpp:114
int ax
Definition CaveMesh.cpp:113
int bx
Definition CaveMesh.cpp:114
int az
Definition CaveMesh.cpp:113
float nx
float nz
float pz
Stable, structured diagnostics shared by engine modules.
eve::resource::CostSpec cost
std::array< std::uint8_t, 32 > hash
Definition Evpack.cpp:172
int triangle
Owning, backend-neutral products published by procedural generation.
std::uint32_t key
float u
Definition Grass.cpp:233
std::vector< std::uint32_t > indices
std::vector< float > normals
std::vector< float > positions
std::vector< Colorf > px
std::uint32_t height
std::uint32_t width
std::int32_t parent
std::string name
float distance
bool diagonal
std::shared_ptr< const std::vector< glm::vec2 > > points
Mesh * mesh
std::unordered_map< std::uint32_t, std::uint32_t > remap
std::vector< float > colors
int lod
bool found
uint32_t index
const UnitySourceAsset & source
glm::vec3 point
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
The canonical owning dynamic value used by data-facing protocols.
Definition Value.h:31
std::map< std::string, Value > Object
Definition Value.h:34
Id128 public API.
Definition Identity.h:112
Id128 child(std::string_view role) const noexcept
Derives a deterministic child UUID for a hierarchical identity.
Definition Identity.h:192
static std::optional< BuildKey > fromCanonical(std::string_view canonical)
Construct a build key from canonical text.
static std::optional< BuildKey > forRecipe(std::string_view recipeId, const Params &params)
Derive a compact deterministic key for a recipe and parameters.
Intermediate 2D generation result. cells store semantic ids (see Semantic.h), not tile GIDs — convert...
Definition Grid2D.h:36
int getWidth() const
Returns the width.
Definition Grid2D.cpp:20
int getHeight() const
Returns the height.
Definition Grid2D.cpp:21
std::string getMeta(const std::string &key, const std::string &defaultValue) const
Returns the meta.
Definition Grid2D.cpp:49
Owning, typed generation parameters.
Definition Params.h:27
void setInt(const std::string &key, int value)
Store an Int64 parameter, preserving its numeric Value kind.
Definition Params.cpp:149
void setBool(const std::string &key, bool value)
Stores a boolean generation parameter.
Definition Params.cpp:167
void setFloat(const std::string &key, float value)
Store a finite or non-finite floating parameter as Value::Double without text conversion.
Definition Params.cpp:165
bool empty() const
Empty.
Definition PointSet.cpp:49
std::vector< ParamSpec > params
Result< RoadBakeResult > bakeRoadNetworkRecipe(const Params &params)
Build the shared road recipe once for mesh, navigation and artifact projections.
EVENGINE_API_DOMAINS eve::Result< GeneratedArtifact > generateRoadNetworkArtifact(const Params &params, ArtifactId id)
Generate a road composite containing mesh, collider, terrain footprint, navigation and placement poin...
EVENGINE_API_DOMAINS Result< void > setPointDataFloatAttribute(PointSet &points, const std::string &attribute, float value)
Write a float into the PointSet @Data domain.
eve::Result< ArtifactPart > makeArtifactPart(std::string role, ArtifactId id, ArtifactType type, eve::SchemaVersion schemaVersion, BuildKey buildKey, Bounds bounds, std::vector< ArtifactId > dependencies, eve::Value::Object metadata, ArtifactLeafPayload payload)
Validate and construct a leaf part for a composite artifact.
Result< void > setPointDataIntAttribute(PointSet &points, const std::string &attribute, std::int64_t value)
Write an int into the PointSet @Data domain.
ArtifactType
Runtime payload kind; it is checked against the variant on publish.
eve::ArtifactId ArtifactId
Common strong UUID identity for one published procedural artifact.
std::variant< Grid2D, PointSet, MeshData, ImageData, Collider > ArtifactLeafPayload
Leaf payloads used by a CompositeArtifact.
eve::Result< GeneratedArtifact > makeArtifact(ArtifactId id, ArtifactType type, eve::SchemaVersion schemaVersion, BuildKey buildKey, Bounds bounds, std::vector< ArtifactId > dependencies, eve::Value::Object metadata, GeneratedArtifact::Payload payload)
Validate and construct one artifact record.
WidgetDesc grid(int columns, std::vector< WidgetDesc > children, std::string id)
Fixed-column grid; children are placed in source order.
Definition Widget.cpp:687
Axis-aligned bounds in the artifact's declared world coordinate space.
void include(float x, float y, float z) noexcept
Expand this box to include one point.
Backend-neutral collider product, normally a triangle mesh or hull.
bool isValid() const noexcept
Return whether the collider has complete triangle data.
A coherent set of products published from one deterministic build.
std::vector< ArtifactPart > children
Owning bake result: mesh, overlays and generic decoration placement points.
Definition RoadBake.h:62
std::vector< RoadPolyline > turns
Definition RoadTypes.h:118
std::vector< RoadPolyline > lanes
Definition RoadTypes.h:117
glm::vec4 bounds