载入中...
搜索中...
未找到
TerrainVegetationRuntime.cpp
浏览该文件的文档.
2
5
7#include "procgen/PointSet.h"
8
9#include <algorithm>
10#include <chrono>
11#include <cmath>
12#include <limits>
13#include <map>
14
15namespace eve::asset_procgen {
16namespace {
17
18std::array<float, 4> quaternionDegrees(float pitch, float yaw, float roll) {
19 constexpr float halfRadians = 0.008726646259971648f;
20 const float px = pitch * halfRadians, yy = yaw * halfRadians, rz = roll * halfRadians;
21 const float sx = std::sin(px), cx = std::cos(px);
22 const float sy = std::sin(yy), cy = std::cos(yy);
23 const float sz = std::sin(rz), cz = std::cos(rz);
24 return {sx * cy * cz - cx * sy * sz, cx * sy * cz + sx * cy * sz, cx * cy * sz - sx * sy * cz,
25 cx * cy * cz + sx * sy * sz};
26}
27
28bool finite(const TerrainVegetationInstance& instance) {
29 for (float value : instance.position)
30 if (!std::isfinite(value)) return false;
31 for (float value : instance.rotation)
32 if (!std::isfinite(value)) return false;
33 for (float value : instance.scale)
34 if (!std::isfinite(value) || value == 0.f) return false;
35 for (float value : instance.normal)
36 if (!std::isfinite(value)) return false;
37 return true;
38}
39
40void rebuildBuckets(TerrainVegetationRealization& result) {
41 result.buckets.clear();
42 std::stable_sort(result.instances.begin(), result.instances.end(),
43 [](const auto& left, const auto& right) { return left.prototype < right.prototype; });
44 for (std::size_t first = 0; first < result.instances.size();) {
45 std::size_t end = first + 1;
46 while (end < result.instances.size() && result.instances[end].prototype == result.instances[first].prototype)
47 ++end;
48 result.buckets.push_back({result.instances[first].prototype, static_cast<std::uint32_t>(first),
49 static_cast<std::uint32_t>(end - first)});
50 first = end;
51 }
52}
53
54float stableUnit(std::uint64_t value) {
55 value ^= value >> 33;
56 value *= 0xff51afd7ed558ccdULL;
57 value ^= value >> 33;
58 value *= 0xc4ceb9fe1a85ec53ULL;
59 value ^= value >> 33;
60 return float(value >> 40) * (1.f / 16777216.f);
61}
62
63float sampleControl(const TerrainAtlasImage& image, std::uint32_t channel, float u, float v) {
64 const float x = std::clamp(u, 0.f, 1.f) * float(image.width - 1);
65 const float y = std::clamp(v, 0.f, 1.f) * float(image.height - 1);
66 const auto x0 = static_cast<std::uint32_t>(std::floor(x));
67 const auto y0 = static_cast<std::uint32_t>(std::floor(y));
68 const auto x1 = std::min(x0 + 1, image.width - 1);
69 const auto y1 = std::min(y0 + 1, image.height - 1);
70 const float tx = x - float(x0), ty = y - float(y0);
71 const auto value = [&](std::uint32_t px, std::uint32_t py) {
72 return float(image.pixels[(std::size_t(py) * image.width + px) * 4 + channel]) / 255.f;
73 };
74 const float top = value(x0, y0) + (value(x1, y0) - value(x0, y0)) * tx;
75 const float bottom = value(x0, y1) + (value(x1, y1) - value(x0, y1)) * tx;
76 return top + (bottom - top) * ty;
77}
78
79} // namespace
80
82 const LoadedInstanceSet* explicitInstances,
84 if (!graph && !explicitInstances)
86 Diagnostic::error(DiagnosticCode::NotFound, "terrain has no PCG or baked vegetation provider", {}, {},
87 "asset.procgen.terrainVegetation"));
88 if (limits.maximumGeneratedInstances == 0 || limits.maximumTotalInstances == 0 || limits.maximumGraphNodes == 0)
90 Diagnostic::error(DiagnosticCode::InvalidArgument, "terrain vegetation limits must be non-zero", {}, {},
91 "asset.procgen.terrainVegetation"));
92
94 if (graph) {
95 if (!graph->graph || graph->outputNode.empty())
97 Diagnostic::error(DiagnosticCode::InvalidArgument, "loaded terrain PCG graph is incomplete", {}, {},
98 "asset.procgen.terrainVegetation"));
99 graph->graph->setExecutionNodeBudget(static_cast<int>(
100 std::min<std::uint32_t>(limits.maximumGraphNodes, std::uint32_t(std::numeric_limits<int>::max()))));
101 graph->graph->setMaxNodeOutputPoints(static_cast<int>(
102 std::min<std::uint32_t>(limits.maximumGeneratedInstances, std::uint32_t(std::numeric_limits<int>::max()))));
103 const auto began = std::chrono::steady_clock::now();
104 auto points = graph->graph->executeResult(graph->outputNode);
105 const auto ended = std::chrono::steady_clock::now();
107 if (points.value().points().size() > limits.maximumGeneratedInstances)
109 Diagnostic::error(DiagnosticCode::InvalidArgument, "generated terrain vegetation exceeds budget", {},
110 {}, "asset.procgen.terrainVegetation"));
111 result.generatedCount = static_cast<std::uint32_t>(points.value().points().size());
112 result.instances.reserve(result.generatedCount + (explicitInstances ? explicitInstances->instances.size() : 0));
113 for (std::size_t index = 0; index < points.value().points().size(); ++index) {
114 const auto& point = points.value().points()[index];
116 instance.id = point.id != 0 ? point.id : procgen::derivePointId(0x7465727261696eULL, index);
117 instance.prototype = points.value().getStringAttribute(static_cast<int>(index), "prototype", {});
118 instance.layer = points.value().getStringAttribute(static_cast<int>(index), "layer", {});
119 instance.position = {point.x, point.y, point.z};
120 instance.rotation = quaternionDegrees(point.pitch, point.yaw, point.roll);
121 instance.scale = {point.scaleX, point.scaleY, point.scaleZ};
122 instance.normal = {point.normalX, point.normalY, point.normalZ};
123 instance.seed = point.seed;
124 if (instance.prototype.empty() || !finite(instance))
126 Diagnostic::error(DiagnosticCode::ParseError, "generated vegetation instance is invalid",
127 std::to_string(index), {}, "asset.procgen.terrainVegetation"));
128 result.instances.push_back(std::move(instance));
129 }
130 result.graphNodesEvaluated = static_cast<std::uint32_t>(graph->graph->getMetricCount());
131 result.graphMilliseconds = std::chrono::duration<double, std::milli>(ended - began).count();
132 }
133
134 if (explicitInstances) {
135 result.wavingGrassAmount = explicitInstances->wavingGrassAmount;
136 result.wavingGrassSpeed = explicitInstances->wavingGrassSpeed;
137 result.wavingGrassStrength = explicitInstances->wavingGrassStrength;
138 result.wavingGrassTint = explicitInstances->wavingGrassTint;
139 result.prototypes = explicitInstances->prototypes;
140 result.explicitCount = static_cast<std::uint32_t>(explicitInstances->instances.size());
141 if (std::uint64_t(result.instances.size()) + explicitInstances->instances.size() > limits.maximumTotalInstances)
143 Diagnostic::error(DiagnosticCode::InvalidArgument, "total terrain vegetation exceeds budget", {}, {},
144 "asset.procgen.terrainVegetation"));
145 result.instances.reserve(result.instances.size() + explicitInstances->instances.size());
146 for (std::size_t index = 0; index < explicitInstances->instances.size(); ++index) {
147 const auto& source = explicitInstances->instances[index];
149 instance.id = procgen::derivePointId(0x6578706c69636974ULL, index);
150 instance.prototype = source.prototype;
151 instance.position = source.position;
152 instance.rotation = source.rotation;
153 instance.scale = source.scale;
154 if (instance.prototype.empty() || !finite(instance))
156 Diagnostic::error(DiagnosticCode::ParseError, "baked vegetation instance is invalid",
157 std::to_string(index), {}, "asset.procgen.terrainVegetation"));
158 result.instances.push_back(std::move(instance));
159 }
160 }
161 if (result.instances.size() > limits.maximumTotalInstances)
163 Diagnostic::error(DiagnosticCode::InvalidArgument, "total terrain vegetation exceeds budget", {}, {},
164 "asset.procgen.terrainVegetation"));
165
166 rebuildBuckets(result);
167 return Result<TerrainVegetationRealization>::success(std::move(result));
168}
169
172 const TerrainMaterialAtlases& atlases, const TerrainVegetationWeightDomain& domain) {
173 if (!std::isfinite(domain.minimumX) || !std::isfinite(domain.minimumZ) || !std::isfinite(domain.maximumX) ||
174 !std::isfinite(domain.maximumZ) || domain.maximumX <= domain.minimumX || domain.maximumZ <= domain.minimumZ)
176 Diagnostic::error(DiagnosticCode::InvalidArgument, "terrain vegetation weight domain is invalid", {}, {},
177 "asset.procgen.terrainVegetation"));
178 if (material.layers.empty() || atlases.groups.size() != (material.layers.size() + 3) / 4)
180 Diagnostic::error(DiagnosticCode::InvalidArgument, "terrain material control groups are incomplete", {}, {},
181 "asset.procgen.terrainVegetation"));
182 std::map<std::string, std::uint32_t, std::less<>> layerIndices;
183 for (std::uint32_t index = 0; index < material.layers.size(); ++index)
184 if (material.layers[index].name.empty() || !layerIndices.emplace(material.layers[index].name, index).second)
186 Diagnostic::error(DiagnosticCode::Conflict, "terrain layer names must be non-empty and unique", {}, {},
187 "asset.procgen.terrainVegetation"));
188 for (std::size_t index = 0; index < atlases.groups.size(); ++index) {
189 const auto& group = atlases.groups[index];
190 const std::uint32_t expected =
191 static_cast<std::uint32_t>(std::min<std::size_t>(4, material.layers.size() - index * 4));
192 if (group.firstLayer != index * 4 || group.layerCount != expected || group.control.width == 0 ||
193 group.control.height == 0 ||
194 group.control.pixels.size() != std::size_t(group.control.width) * group.control.height * 4)
196 Diagnostic::error(DiagnosticCode::InvalidArgument, "terrain control image is invalid",
197 std::to_string(index), {}, "asset.procgen.terrainVegetation"));
198 }
199
200 TerrainVegetationRealization result = realization;
201 result.instances.clear();
202 result.instances.reserve(realization.instances.size());
203 result.weightCulledCount = realization.weightCulledCount;
204 for (const auto& instance : realization.instances) {
205 if (instance.layer.empty()) {
206 result.instances.push_back(instance);
207 continue;
208 }
209 const auto found = layerIndices.find(instance.layer);
210 if (found == layerIndices.end())
212 Diagnostic::error(DiagnosticCode::NotFound, "vegetation rule references an unknown terrain layer",
213 instance.layer, {}, "asset.procgen.terrainVegetation"));
214 const std::uint32_t layer = found->second;
215 const float u = (instance.position[0] - domain.minimumX) / (domain.maximumX - domain.minimumX);
216 float v = (instance.position[2] - domain.minimumZ) / (domain.maximumZ - domain.minimumZ);
217 if (domain.flipV) v = 1.f - v;
218 const float weight = sampleControl(atlases.groups[layer / 4].control, layer % 4, u, v);
219 if (stableUnit(instance.id ^ (std::uint64_t(instance.seed) << 32) ^ layer) < weight)
220 result.instances.push_back(instance);
221 else
222 ++result.weightCulledCount;
223 }
224 if (result.weightCulledCount - realization.weightCulledCount > result.generatedCount)
226 Diagnostic::error(DiagnosticCode::Conflict, "terrain layer filtering culled non-procedural instances", {},
227 {}, "asset.procgen.terrainVegetation"));
228 result.generatedCount -= result.weightCulledCount - realization.weightCulledCount;
229 rebuildBuckets(result);
230 return Result<TerrainVegetationRealization>::success(std::move(result));
231}
232
233} // namespace eve::asset_procgen
double value
SQInteger top
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
building::EdgeCurveGroup group
float cx
Definition CardTypes.cpp:33
float cy
Definition CardTypes.cpp:34
float py
Runtime canonical terrain-layer semantics.
vk::UniqueImage image
float u
Definition Grass.cpp:233
float v
HexVec3 left
HexVec3 right
std::int32_t first
std::vector< Colorf > px
std::array< float, 4 > rotation
std::array< float, 3 > position
std::array< float, 3 > scale
bool ended
Texture * normal
bool finite
std::map< std::string, std::vector< std::string > > graph
Definition Package.cpp:59
TileLayer * layer
std::shared_ptr< const std::vector< glm::vec2 > > points
Material * material
bool found
CPU assembly of grouped terrain material atlases.
Runtime realization of terrain vegetation assets.
uint32_t index
const UnitySourceAsset & source
const AssetImportLimits & limits
glm::vec3 point
float bottom
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
Result< TerrainVegetationRealization > realizeTerrainVegetation(LoadedPointGraph *graph, const LoadedInstanceSet *explicitInstances, const TerrainVegetationLimits &limits)
Execute an optional terrain PCG graph and merge an optional baked instance set.
Result< TerrainVegetationRealization > filterTerrainVegetationByLayerWeights(const TerrainVegetationRealization &realization, const LoadedTerrainMaterial &material, const TerrainMaterialAtlases &atlases, const TerrainVegetationWeightDomain &domain)
Apply terrain RGBA layer weights to procedural instances using stable stochastic thinning.
std::uint64_t derivePointId(std::uint64_t namespaceId, std::uint64_t ordinal)
Deterministically derive a non-zero stable point identity.
Definition PointSet.cpp:469
Owning exact static-instance set selected for one runtime variant.
std::vector< RuntimeInstance > instances
std::vector< RuntimeInstancePrototype > prototypes
Owning executable graph and its selected output node.
Owning capability-selected terrain material candidate.
Complete terrain material image set; up to four groups cover sixteen layers.
std::vector< TerrainMaterialAtlasGroup > groups
One stable terrain vegetation transform and its source attributes.
Hard limits for one deterministic terrain vegetation realization.
Owning, prototype-sorted result plus scalability evidence.
std::vector< RuntimeInstancePrototype > prototypes
std::vector< TerrainVegetationInstance > instances
Explicit world-to-control-map projection for terrain layer filtering.
bool flipV
Reverse V for source maps whose rows oppose the canonical terrain Z axis.