载入中...
搜索中...
未找到
RoadTerrain.cpp
浏览该文件的文档.
2
3#include "common/Diagnostic.h"
5
6#include <algorithm>
7#include <cmath>
8#include <cstdint>
9#include <limits>
10#include <string>
11#include <utility>
12#include <vector>
13
14namespace eve::procgen::road {
15namespace {
16
17template <class T>
18Result<T> terrainFail(DiagnosticCode code, std::string message, std::string path = {}) {
19 return Result<T>::failure(Diagnostic::error(code, std::move(message), std::move(path), {}, "procgen.road"));
20}
21
22} // namespace
23
26 if (terrain.getWidth() <= 0 || terrain.getHeight() <= 0 || roadMesh.getIndexCount() % 3 != 0 ||
27 !std::isfinite(options.originX) || !std::isfinite(options.originZ) || !std::isfinite(options.cellSize) ||
28 options.cellSize <= 0.f || !std::isfinite(options.heightScale) || options.heightScale <= 0.f ||
29 !std::isfinite(options.blendDistance) || options.blendDistance < 0.f ||
30 !std::isfinite(options.maxVerticalDelta) || options.maxVerticalDelta < 0.f || options.maximumWork == 0u)
31 return terrainFail<RoadTerrainConformReceipt>(DiagnosticCode::InvalidArgument,
32 "road terrain conformance options are invalid", "terrain");
33 if (options.blendDistance / options.cellSize > 256.f)
34 return terrainFail<RoadTerrainConformReceipt>(DiagnosticCode::InvalidArgument,
35 "road terrain blend radius exceeds 256 cells",
36 "terrain.blendDistance");
37
38 const int width = terrain.getWidth(), height = terrain.getHeight();
39 const auto sampleCount = static_cast<std::size_t>(width) * static_cast<std::size_t>(height);
40 std::vector<float> target(sampleCount, std::numeric_limits<float>::quiet_NaN());
41 std::vector<float> targetDelta(sampleCount, std::numeric_limits<float>::max());
42 std::vector<std::uint8_t> skippedBridge(sampleCount, 0u), skippedTunnel(sampleCount, 0u);
43 std::size_t work = 0u;
44 const int triangleCount = roadMesh.getIndexCount() / 3;
45 for (int triangle = 0; triangle < triangleCount; ++triangle) {
46 const int group = roadMesh.getTriangleGroup(triangle);
47 if (group < 0 || roadMesh.getGroupName(group) != "asphalt") continue;
48 const int ia = roadMesh.getIndex(triangle * 3), ib = roadMesh.getIndex(triangle * 3 + 1);
49 const int ic = roadMesh.getIndex(triangle * 3 + 2);
50 if (ia < 0 || ib < 0 || ic < 0 || ia >= roadMesh.getVertexCount() || ib >= roadMesh.getVertexCount() ||
51 ic >= roadMesh.getVertexCount())
52 return terrainFail<RoadTerrainConformReceipt>(DiagnosticCode::InvariantViolation,
53 "road mesh contains an invalid triangle index", "roadMesh");
54 const float ax = roadMesh.getPositionX(ia), ay = roadMesh.getPositionY(ia), az = roadMesh.getPositionZ(ia);
55 const float bx = roadMesh.getPositionX(ib), by = roadMesh.getPositionY(ib), bz = roadMesh.getPositionZ(ib);
56 const float cx = roadMesh.getPositionX(ic), cy = roadMesh.getPositionY(ic), cz = roadMesh.getPositionZ(ic);
57 if (!std::isfinite(ax) || !std::isfinite(ay) || !std::isfinite(az) || !std::isfinite(bx) ||
58 !std::isfinite(by) || !std::isfinite(bz) || !std::isfinite(cx) || !std::isfinite(cy) ||
59 !std::isfinite(cz))
60 return terrainFail<RoadTerrainConformReceipt>(DiagnosticCode::InvariantViolation,
61 "road mesh contains non-finite positions", "roadMesh");
62 const float denominator = (bz - cz) * (ax - cx) + (cx - bx) * (az - cz);
63 if (std::fabs(denominator) <= 1e-7f) continue;
64 const int minX = std::max(0, static_cast<int>(std::floor((std::min({ax, bx, cx}) - options.originX) /
65 options.cellSize)));
66 const int maxX = std::min(width - 1, static_cast<int>(std::ceil((std::max({ax, bx, cx}) - options.originX) /
67 options.cellSize)));
68 const int minZ = std::max(0, static_cast<int>(std::floor((std::min({az, bz, cz}) - options.originZ) /
69 options.cellSize)));
70 const int maxZ = std::min(height - 1, static_cast<int>(std::ceil((std::max({az, bz, cz}) - options.originZ) /
71 options.cellSize)));
72 if (minX > maxX || minZ > maxZ) continue;
73 for (int z = minZ; z <= maxZ; ++z) {
74 for (int x = minX; x <= maxX; ++x) {
75 if (++work > options.maximumWork)
76 return terrainFail<RoadTerrainConformReceipt>(DiagnosticCode::PreconditionViolation,
77 "road terrain rasterization exceeds the work budget",
78 "terrain");
79 const float worldX = options.originX + static_cast<float>(x) * options.cellSize;
80 const float worldZ = options.originZ + static_cast<float>(z) * options.cellSize;
81 const float wa = ((bz - cz) * (worldX - cx) + (cx - bx) * (worldZ - cz)) / denominator;
82 const float wb = ((cz - az) * (worldX - cx) + (ax - cx) * (worldZ - cz)) / denominator;
83 const float wc = 1.f - wa - wb;
84 if (wa < -1e-4f || wb < -1e-4f || wc < -1e-4f) continue;
85 const float roadY = wa * ay + wb * by + wc * cy;
86 const auto index = static_cast<std::size_t>(z) * static_cast<std::size_t>(width) +
87 static_cast<std::size_t>(x);
88 const float terrainY = terrain.height(x, z) * options.heightScale;
89 const float delta = std::fabs(roadY - terrainY);
90 if (delta <= options.maxVerticalDelta && delta < targetDelta[index]) {
91 target[index] = roadY;
92 targetDelta[index] = delta;
93 } else if (delta > options.maxVerticalDelta) {
94 if (roadY > terrainY)
95 skippedBridge[index] = 1u;
96 else
97 skippedTunnel[index] = 1u;
98 }
99 }
100 }
101 }
102
103 std::vector<std::size_t> covered;
104 for (std::size_t i = 0; i < target.size(); ++i)
105 if (std::isfinite(target[i])) covered.push_back(i);
107 receipt.width = width;
108 receipt.height = height;
109 for (std::size_t index = 0; index < sampleCount; ++index) {
110 if (std::isfinite(target[index])) continue;
111 receipt.skippedBridgeSamples += skippedBridge[index] != 0u;
112 receipt.skippedTunnelSamples += skippedTunnel[index] != 0u;
113 }
114 if (covered.empty()) return Result<RoadTerrainConformReceipt>::success(std::move(receipt));
115 const int blendCells = static_cast<int>(std::ceil(options.blendDistance / options.cellSize));
116 const auto diameter = static_cast<std::size_t>(blendCells * 2 + 1);
117 if (covered.size() > options.maximumWork / (diameter * diameter))
118 return terrainFail<RoadTerrainConformReceipt>(DiagnosticCode::PreconditionViolation,
119 "road terrain blending exceeds the work budget", "terrain");
120
121 std::vector<float> weights(sampleCount, 0.f), blendedTarget(sampleCount, 0.f);
122 for (const auto sourceIndex : covered) {
123 const int sourceX = static_cast<int>(sourceIndex % static_cast<std::size_t>(width));
124 const int sourceZ = static_cast<int>(sourceIndex / static_cast<std::size_t>(width));
125 for (int dz = -blendCells; dz <= blendCells; ++dz) {
126 for (int dx = -blendCells; dx <= blendCells; ++dx) {
127 const int x = sourceX + dx, z = sourceZ + dz;
128 if (x < 0 || z < 0 || x >= width || z >= height) continue;
129 const float distance = std::sqrt(static_cast<float>(dx * dx + dz * dz));
130 if (distance > static_cast<float>(blendCells)) continue;
131 const float weight = blendCells == 0 ? 1.f : 1.f - distance / (static_cast<float>(blendCells) + 0.5f);
132 const auto index = static_cast<std::size_t>(z) * static_cast<std::size_t>(width) +
133 static_cast<std::size_t>(x);
134 if (weight > weights[index]) {
136 blendedTarget[index] = target[sourceIndex];
137 }
138 }
139 }
140 }
141
142 Heightmap candidate = terrain;
143 receipt.sampleIndices.reserve(covered.size());
144 receipt.before.reserve(covered.size());
145 receipt.after.reserve(covered.size());
146 for (int z = 0; z < height; ++z) {
147 for (int x = 0; x < width; ++x) {
148 const auto index = static_cast<std::size_t>(z) * static_cast<std::size_t>(width) +
149 static_cast<std::size_t>(x);
150 if (weights[index] <= 0.f) continue;
151 const float oldHeight = terrain.height(x, z);
152 const float newHeight =
153 oldHeight + (blendedTarget[index] / options.heightScale - oldHeight) * weights[index];
154 if (newHeight != oldHeight) {
155 candidate.setHeight(x, z, newHeight);
156 receipt.sampleIndices.push_back(index);
157 receipt.before.push_back(oldHeight);
158 receipt.after.push_back(newHeight);
159 }
160 }
161 }
162 terrain = std::move(candidate);
163 return Result<RoadTerrainConformReceipt>::success(std::move(receipt));
164}
165
168 auto receipt = conformHeightmapToRoadWithReceipt(terrain, roadMesh, options);
169 if (!receipt.ok()) return Result<int>::failure(receipt.status());
170 return Result<int>::success(static_cast<int>(receipt.value().sampleIndices.size()));
171}
172
174 if (terrain.getWidth() != receipt.width || terrain.getHeight() != receipt.height || receipt.width <= 0 ||
175 receipt.height <= 0 || receipt.sampleIndices.size() != receipt.before.size() ||
176 receipt.sampleIndices.size() != receipt.after.size())
177 return terrainFail<int>(DiagnosticCode::InvalidArgument, "road terrain receipt shape is invalid", "receipt");
178 const auto sampleCount = static_cast<std::size_t>(receipt.width) * static_cast<std::size_t>(receipt.height);
179 std::size_t previous = 0;
180 for (std::size_t i = 0; i < receipt.sampleIndices.size(); ++i) {
181 const auto index = receipt.sampleIndices[i];
182 if (index >= sampleCount || (i > 0 && index <= previous) || !std::isfinite(receipt.before[i]) ||
183 !std::isfinite(receipt.after[i]))
184 return terrainFail<int>(DiagnosticCode::InvalidArgument, "road terrain receipt samples are invalid",
185 "receipt.samples");
186 previous = index;
187 const int x = static_cast<int>(index % static_cast<std::size_t>(receipt.width));
188 const int z = static_cast<int>(index / static_cast<std::size_t>(receipt.width));
189 if (terrain.height(x, z) != receipt.after[i])
190 return terrainFail<int>(DiagnosticCode::Conflict,
191 "road terrain receipt is stale and would overwrite a newer edit", "terrain");
192 }
193 Heightmap candidate = terrain;
194 for (std::size_t i = 0; i < receipt.sampleIndices.size(); ++i) {
195 const auto index = receipt.sampleIndices[i];
196 const int x = static_cast<int>(index % static_cast<std::size_t>(receipt.width));
197 const int z = static_cast<int>(index / static_cast<std::size_t>(receipt.width));
198 candidate.setHeight(x, z, receipt.before[i]);
199 }
200 terrain = std::move(candidate);
201 return Result<int>::success(static_cast<int>(receipt.sampleIndices.size()));
202}
203
204} // namespace eve::procgen::road
LogicalId target
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
std::string terrain
building::EdgeCurveGroup group
float cx
Definition CardTypes.cpp:33
float cy
Definition CardTypes.cpp:34
int bz
Definition CaveMesh.cpp:114
int ax
Definition CaveMesh.cpp:113
int ay
Definition CaveMesh.cpp:113
int bx
Definition CaveMesh.cpp:114
int az
Definition CaveMesh.cpp:113
int by
Definition CaveMesh.cpp:114
Stable, structured diagnostics shared by engine modules.
int triangle
std::string message
DiagnosticCode code
float u
Definition Grass.cpp:233
std::uint32_t height
std::uint32_t width
float distance
graphics::Canvas * previous
std::string path
Definition PlayHost.cpp:110
float dz
float dx
const SquirrelValueOptions & options
float sourceX
float weights[3]
uint32_t index
int covered
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
In-memory terrain heightmap: a dense float grid (row-major, index = y * width + x) materialized from ...
Definition Heightmap.h:21
void setHeight(int x, int y, float h)
Sets the height.
Definition Heightmap.cpp:23
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.
int getIndexCount() const
Returns the index count.
int getTriangleGroup(int triangleIndex) const
Group index for a triangle (not an index-buffer element), or -1.
Definition MeshBuild.cpp:70
int getIndex(int i) const
std::string getGroupName(int groupIndex) const
Group name, or an empty string for an invalid index.
Definition MeshBuild.cpp:65
float getPositionX(int i) const
Returns the position x.
EVENGINE_API_DOMAINS Result< int > conformHeightmapToRoad(Heightmap &terrain, const MeshBuild &roadMesh, const RoadTerrainConformOptions &options={})
Atomically conform a heightmap to nearby baked road surface triangles.
EVENGINE_API_DOMAINS Result< int > restoreHeightmapFromRoad(Heightmap &terrain, const RoadTerrainConformReceipt &receipt)
Atomically restore a conformance receipt if none of its samples became stale.
EVENGINE_API_DOMAINS Result< RoadTerrainConformReceipt > conformHeightmapToRoadWithReceipt(Heightmap &terrain, const MeshBuild &roadMesh, const RoadTerrainConformOptions &options={})
Atomically conform terrain and return the exact reversible sample delta.
DiagnosticCode
Stable machine-readable diagnostic codes.
Definition Diagnostic.h:47
World/grid mapping and bounded blending settings for road-to-terrain conformance.
Definition RoadBake.h:18
Exact changed samples needed to safely restore one road terrain conformance. @cost Storage,...
Definition RoadBake.h:32
int skippedTunnelSamples
Cells whose terrain roof was kept above tunnel surfaces.
Definition RoadBake.h:36
int skippedBridgeSamples
Cells kept below elevated road surfaces.
Definition RoadBake.h:35
std::vector< std::size_t > sampleIndices
Definition RoadBake.h:37