载入中...
搜索中...
未找到
TerrainProbePlacement.cpp
浏览该文件的文档.
2#include <algorithm>
3#include <cmath>
4#include <limits>
5#include <unordered_set>
6#include "procgen/PointSet.h"
11namespace eve::procgen {
12namespace {
13class PcgProbeRandom {
14public:
15 explicit PcgProbeRandom(std::int32_t seed) {
16 const auto value = seed == 0 ? 1U : static_cast<std::uint32_t>(seed);
17 a_ = UINT64_C(181353) * value; b_ = UINT64_C(7) * value;
18 }
19 float next() {
20 auto x = a_, y = b_; a_ = y; x ^= x << 23; x ^= x >> 17; x ^= y ^ (y >> 26); b_ = x;
21 return static_cast<float>(x + y) / static_cast<float>(std::numeric_limits<std::uint64_t>::max());
22 }
23 float next(float minimum, float maximum) { return minimum + next() * (maximum - minimum); }
24private:
25 std::uint64_t a_ = 0, b_ = 0;
26};
27std::uint64_t mix(std::uint64_t value) {
28 value = (value ^ (value >> 30)) * UINT64_C(0xbf58476d1ce4e5b9);
29 value = (value ^ (value >> 27)) * UINT64_C(0x94d049bb133111eb);
30 return value ^ (value >> 31);
31}
32Result<int> invalid(const char* message) {
34}
35bool supportedResolution(int value) { return value >= 16 && value <= 2048 && (value & (value - 1)) == 0; }
36} // namespace
39 using namespace raster_detail;
40 if (!validRaster(fitness) || !validRaster(heights) || s.name.empty() ||
42 !std::isfinite(s.originX) || !std::isfinite(s.originZ) || !std::isfinite(s.width) || s.width <= 0 ||
43 !std::isfinite(s.depth) || s.depth <= 0 || !std::isfinite(s.heightScale) || s.heightScale < 0 ||
44 !std::isfinite(s.spacing) || s.spacing <= 0 || !std::isfinite(s.jitterPercent) || s.jitterPercent < 0 ||
45 s.jitterPercent > 1 || !std::isfinite(s.minimumFitness) || s.minimumFitness < 0 || s.minimumFitness > 1 ||
46 !std::isfinite(s.seaLevel) || !std::isfinite(s.reflectionOffset) || !std::isfinite(s.lightOffset) ||
47 !supportedResolution(s.reflectionResolution) || !std::isfinite(s.reflectionClipDistance) ||
48 s.reflectionClipDistance <= 0 || !std::isfinite(s.reflectionShadowDistance) ||
49 s.reflectionShadowDistance < 0 || s.namespaceId == 0 || s.maxPoints < 0)
50 return invalid("terrain.probes: valid finite rasters, resource, scan and reflection settings required");
51 if (!std::all_of(fitness.data().begin(), fitness.data().end(), [](float v) { return v >= 0 && v <= 1; }))
52 return invalid("terrain.probes: normalized fitness required");
53 PointSet next;
54 PcgProbeRandom random(s.seed);
55 std::uint64_t candidate = 0;
56 int emitted = 0;
57 for (double x = 0; x <= s.width; x += s.spacing)
58 for (double z = 0; z <= s.depth; z += s.spacing, ++candidate) {
59 const double localX = x + s.spacing * random.next(-s.jitterPercent, s.jitterPercent) / 2;
60 const double localZ = z + s.spacing * random.next(-s.jitterPercent, s.jitterPercent) / 2;
61 if (localX < 0 || localZ < 0 || localX > s.width || localZ > s.depth) continue;
62 const double u = localX / s.width, v = localZ / s.depth;
63 const double strength = textureSample(fitness, u, v);
64 if (random.next(s.minimumFitness, 1) > strength) continue;
65 const double sampled = textureSample(heights, u, v) * s.heightScale;
66 double y = 0;
67 if (s.type == TerrainProbeType::Light) {
68 if (sampled <= s.seaLevel) continue;
69 y = sampled + s.lightOffset;
70 } else if (!s.seaLevelActive) y = 500 + s.seaLevel + 0.2;
71 else y = std::max(sampled, double(s.seaLevel)) + s.reflectionOffset;
72 const double worldX = s.originX + localX, worldZ = s.originZ + localZ;
73 if (emitted >= s.maxPoints || !isRepresentable(worldX) || !isRepresentable(y) ||
74 !isRepresentable(worldZ))
75 return invalid("terrain.probes: point budget or representable height exceeded");
77 point.id = mix(s.namespaceId ^ (candidate + 1));
78 if (!point.id) return invalid("terrain.probes: generated identity is reserved");
79 point.x = float(worldX); point.y = float(y); point.z = float(worldZ);
80 point.density = float(strength); point.boundsMinX = point.boundsMinZ = -s.spacing * 0.5F;
81 point.boundsMaxX = point.boundsMaxZ = s.spacing * 0.5F; point.boundsMaxY = s.heightScale;
82 point.seed = static_cast<std::uint32_t>(mix(point.id ^ static_cast<std::uint32_t>(s.seed)));
83 const int row = next.appendPoint(point);
84 auto name = next.trySetStringAttribute(row, "probeResource", s.name);
85 if (!name.ok()) return Result<int>::failure(name.status());
86 auto type = next.trySetIntAttribute(row, "probeType", static_cast<std::int64_t>(s.type));
87 if (!type.ok()) return Result<int>::failure(type.status());
88 auto source = next.trySetStringAttribute(row, "spawnNamespace", std::to_string(s.namespaceId));
89 if (!source.ok()) return Result<int>::failure(source.status());
90 auto resolution = next.trySetIntAttribute(row, "reflectionResolution", s.reflectionResolution);
91 if (!resolution.ok()) return Result<int>::failure(resolution.status());
92 auto clip = next.trySetFloatAttribute(row, "reflectionClipDistance", s.reflectionClipDistance);
93 if (!clip.ok()) return Result<int>::failure(clip.status());
94 auto shadow = next.trySetFloatAttribute(row, "reflectionShadowDistance", s.reflectionShadowDistance);
95 if (!shadow.ok()) return Result<int>::failure(shadow.status());
96 ++emitted;
97 }
98 output = std::move(next);
99 return Result<int>::success(emitted);
100}
101
103 const std::vector<TerrainProbeTile>& tiles, const Heightmap& fitness,
105 TerrainProbeOperationMode mode, bool worldMapOperation, const std::vector<std::string>& validNames) {
106 using namespace raster_detail;
107 if (tiles.empty() || mode < TerrainProbeOperationMode::Add || mode > TerrainProbeOperationMode::Remove ||
108 !validRaster(fitness) || s.name.empty() || s.type < TerrainProbeType::Reflection ||
109 s.type > TerrainProbeType::Light || !std::isfinite(s.spacing) || s.spacing <= 0 ||
110 !std::isfinite(s.jitterPercent) || s.jitterPercent < 0 || s.jitterPercent > 1 ||
111 !std::isfinite(s.minimumFitness) || s.minimumFitness < 0 || s.minimumFitness > 1 ||
112 !std::isfinite(s.heightScale) || s.heightScale < 0 || !std::isfinite(s.seaLevel) ||
113 !std::isfinite(s.reflectionOffset) || !std::isfinite(s.lightOffset) ||
114 !supportedResolution(s.reflectionResolution) || !std::isfinite(s.reflectionClipDistance) ||
115 s.reflectionClipDistance <= 0 || !std::isfinite(s.reflectionShadowDistance) ||
116 s.reflectionShadowDistance < 0 || s.namespaceId == 0 || s.maxPoints < 0)
118 Diagnostic::error(DiagnosticCode::InvalidArgument, "terrain.probes.multitile: valid tiles and settings required"));
119 if (!std::all_of(fitness.data().begin(), fitness.data().end(), [](float value) { return value >= 0 && value <= 1; }))
121 Diagnostic::error(DiagnosticCode::InvalidArgument, "terrain.probes.multitile: normalized fitness required"));
122 std::vector<TerrainOperationTile> descriptors;
123 std::unordered_set<PointSet*> owners;
124 for (const auto& tile : tiles) {
125 if (!tile.probes || !tile.heights || !owners.insert(tile.probes).second || !validRaster(*tile.heights))
127 DiagnosticCode::InvalidArgument, "terrain.probes.multitile: distinct outputs and finite heights required"));
128 descriptors.push_back({tile.name, tile.originX, tile.originZ, tile.width, tile.depth,
129 tile.resolutionX, tile.resolutionY, tile.worldMap});
130 }
132 worldMapOperation, validNames);
133 if (!mapped.ok()) return mapped;
134 auto report = std::move(mapped.value());
135 if (fitness.getWidth() != report.operationWidth || fitness.getHeight() != report.operationHeight)
137 DiagnosticCode::InvalidArgument, "terrain.probes.multitile: fitness must match operation window"));
138 struct Candidate { PointSet* owner; PointSet points; bool changed; };
139 std::vector<Candidate> candidates;
140 PcgProbeRandom random(s.seed);
141 std::uint64_t candidateIndex = 0;
142 int emitted = 0;
143 for (const auto& mapping : report.mappings) {
144 const auto tile = std::find_if(tiles.begin(), tiles.end(), [&](const auto& value) {
145 return value.name == mapping.terrainName;
146 });
147 if (tile == tiles.end())
149 Diagnostic::error(DiagnosticCode::InvariantViolation, "terrain.probes.multitile: mapped tile missing"));
150 PointSet next;
151 int changed = 0;
152 const bool clear = mode != TerrainProbeOperationMode::Add;
153 if (clear) {
154 for (int row = 0; row < tile->probes->getCount(); ++row) {
155 const auto& point = tile->probes->points()[static_cast<std::size_t>(row)];
156 const int lx = static_cast<int>(std::nearbyint((point.x - tile->originX) * tile->resolutionX / tile->width));
157 const int lz = static_cast<int>(std::nearbyint((point.z - tile->originZ) * tile->resolutionY / tile->depth));
158 bool erase = tile->probes->getStringAttribute(row, "probeResource", "") == s.name &&
159 lx >= mapping.localX && lx < mapping.localX + mapping.width &&
160 lz >= mapping.localY && lz < mapping.localY + mapping.height;
161 if (erase && mode == TerrainProbeOperationMode::Remove)
162 erase = fitness.height(mapping.operationX + lx - mapping.localX,
163 mapping.operationY + lz - mapping.localY) > s.minimumFitness;
164 if (erase) ++changed;
165 else {
166 auto copied = next.appendPointFrom(*tile->probes, static_cast<std::size_t>(row));
167 if (!copied.ok()) return Result<TerrainMultiTileReport>::failure(copied.status());
168 }
169 }
170 } else next = *tile->probes;
172 const double startX = mapping.localX * tile->width / tile->resolutionX;
173 const double startZ = mapping.localY * tile->depth / tile->resolutionY;
174 const double stopX = (mapping.localX + mapping.width - 1) * tile->width / tile->resolutionX;
175 const double stopZ = (mapping.localY + mapping.height - 1) * tile->depth / tile->resolutionY;
176 for (double x = startX; x <= stopX; x += s.spacing)
177 for (double z = startZ; z <= stopZ; z += s.spacing, ++candidateIndex) {
178 const double localX = x + s.spacing * random.next(-s.jitterPercent, s.jitterPercent) / 2;
179 const double localZ = z + s.spacing * random.next(-s.jitterPercent, s.jitterPercent) / 2;
180 const int lx = static_cast<int>(std::nearbyint(localX * tile->resolutionX / tile->width));
181 const int lz = static_cast<int>(std::nearbyint(localZ * tile->resolutionY / tile->depth));
182 if (lx < mapping.localX || lx >= mapping.localX + mapping.width ||
183 lz < mapping.localY || lz >= mapping.localY + mapping.height) continue;
184 const float strength = fitness.height(mapping.operationX + lx - mapping.localX,
185 mapping.operationY + lz - mapping.localY);
186 if (random.next(s.minimumFitness, 1) > strength) continue;
187 const double u = localX / tile->width, v = localZ / tile->depth;
188 const double sampled = textureSample(*tile->heights, u, v) * s.heightScale;
189 double y = 0;
190 if (s.type == TerrainProbeType::Light) {
191 if (sampled <= s.seaLevel) continue;
192 y = sampled + s.lightOffset;
193 } else if (!s.seaLevelActive) y = 500 + s.seaLevel + 0.2;
194 else y = std::max(sampled, double(s.seaLevel)) + s.reflectionOffset;
195 const double worldX = tile->originX + localX, worldZ = tile->originZ + localZ;
196 if (emitted >= s.maxPoints || !isRepresentable(worldX) || !isRepresentable(y) ||
197 !isRepresentable(worldZ))
199 DiagnosticCode::InvalidArgument, "terrain.probes.multitile: point budget exceeded"));
201 point.id = mix(s.namespaceId ^ (candidateIndex + 1));
202 if (!point.id)
204 DiagnosticCode::InvalidArgument, "terrain.probes.multitile: generated identity is reserved"));
205 point.seed = static_cast<std::uint32_t>(mix(point.id ^ static_cast<std::uint32_t>(s.seed)));
206 point.x = float(worldX); point.y = float(y); point.z = float(worldZ);
207 point.density = strength; point.boundsMinX = point.boundsMinZ = -s.spacing * 0.5F;
208 point.boundsMaxX = point.boundsMaxZ = s.spacing * 0.5F; point.boundsMaxY = s.heightScale;
209 const int row = next.appendPoint(point);
210 auto resource = next.trySetStringAttribute(row, "probeResource", s.name);
212 auto type = next.trySetIntAttribute(row, "probeType", static_cast<std::int64_t>(s.type));
213 if (!type.ok()) return Result<TerrainMultiTileReport>::failure(type.status());
214 auto source = next.trySetStringAttribute(row, "spawnNamespace", std::to_string(s.namespaceId));
215 if (!source.ok()) return Result<TerrainMultiTileReport>::failure(source.status());
216 auto resolution = next.trySetIntAttribute(row, "reflectionResolution", s.reflectionResolution);
217 if (!resolution.ok()) return Result<TerrainMultiTileReport>::failure(resolution.status());
218 auto clip = next.trySetFloatAttribute(row, "reflectionClipDistance", s.reflectionClipDistance);
219 if (!clip.ok()) return Result<TerrainMultiTileReport>::failure(clip.status());
220 auto shadow = next.trySetFloatAttribute(row, "reflectionShadowDistance", s.reflectionShadowDistance);
221 if (!shadow.ok()) return Result<TerrainMultiTileReport>::failure(shadow.status());
222 ++emitted; ++changed;
223 }
224 }
225 report.changedSamples += changed;
226 candidates.push_back({tile->probes, std::move(next), changed > 0});
227 }
228 report.affectedTiles = static_cast<int>(std::count_if(candidates.begin(), candidates.end(),
229 [](const auto& value) { return value.changed; }));
230 for (auto& candidate : candidates) *candidate.owner = std::move(candidate.points);
231 return Result<TerrainMultiTileReport>::success(std::move(report));
232}
233} // namespace eve::procgen
ActionParameterOperation operation
double value
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
std::string output
const std::string & s
std::string message
float maximum[3]
float minimum[3]
glm::vec4 clip
float u
Definition Grass.cpp:233
float v
std::string name
std::uint32_t seed
Definition PointSet.cpp:807
std::shared_ptr< const std::vector< glm::vec2 > > points
std::string resource
Battle::Random random
const UnitySourceAsset & source
glm::vec3 point
std::vector< WfcTile > tiles
Definition WfcSimple.cpp:22
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
int getHeight() const
Returns the height.
Definition Heightmap.cpp:17
const std::vector< float > & data() const
Data.
Definition Heightmap.h:48
float height(int x, int y) const
Height.
Definition Heightmap.cpp:28
int getWidth() const
Returns the width.
Definition Heightmap.cpp:16
Script-friendly collection of attributed 3D samples.
Definition PointSet.h:59
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).
Definition HexMetrics.h:76
double textureSample(const Heightmap &map, double u, double v)
Texture sample.
bool validRaster(const Heightmap &map)
Valid raster.
Result< int > invalid(std::string message)
Invalid.
bool isRepresentable(double value)
True when representable.
TerrainProbeOperationMode
Mutation performed by a multi-terrain probe rule.
Result< int > exportTerrainProbePoints(PointSet &output, const Heightmap &fitness, const Heightmap &heights, const TerrainProbePlacementSettings &s)
Export Pcg ReflectionProbe or LightProbe placements into an owned attributed PointSet.
Result< TerrainMultiTileReport > mapTerrainOperationMultiTile(const std::vector< TerrainOperationTile > &tiles, const TerrainStampSettings &settings, TerrainOperationDomain domain, bool worldMapOperation, const std::vector< std::string > &validTerrainNames)
Calculate Pcg-compatible local and shared pixel rectangles for any multi-terrain raster domain.
Result< TerrainMultiTileReport > applyTerrainProbesMultiTile(const std::vector< TerrainProbeTile > &tiles, const Heightmap &fitness, const TerrainProbePlacementSettings &s, const TerrainStampSettings &operation, TerrainProbeOperationMode mode, bool worldMapOperation, const std::vector< std::string > &validNames)
Apply one Pcg probe rule over mapped terrain tiles as one atomic transaction.
One deterministic sample used by script-first procedural pipelines.
Definition PointSet.h:17
std::uint64_t id
Stable non-zero identity; zero marks legacy or not-yet-assigned data.
Definition PointSet.h:19
Deterministic value settings for Pcg SetProbes terrain scanning.
Value configuration for a rectangular stamp in world X/Z coordinates.