载入中...
搜索中...
未找到
GtsTerrainLod.cpp
浏览该文件的文档.
3
4#include <algorithm>
5#include <cmath>
6#include <limits>
7#include <string_view>
8
9namespace eve::procgen {
10namespace {} // namespace
11
13 float sizeX, float sizeY, float sizeZ) {
14 const int exponent = static_cast<int>(resolution);
15 const int sourceWidth = heightmap.getWidth(), sourceHeight = heightmap.getHeight();
16 if (exponent < 0 || exponent > 4 || sourceWidth < 2 || sourceHeight < 2 || !std::isfinite(sizeX) || sizeX <= 0.f ||
17 !std::isfinite(sizeY) || sizeY <= 0.f || !std::isfinite(sizeZ) || sizeZ <= 0.f)
19 "GTS terrain heightmap or export dimensions are invalid",
20 "procgen.gtsTerrainLod"));
21 const int step = 1 << exponent;
22 if ((sourceWidth - 1) % step != 0 || (sourceHeight - 1) % step != 0)
25 "GTS terrain heightmap intervals must be divisible by the save-resolution stride",
26 "procgen.gtsTerrainLod"));
27 const int columns = (sourceWidth - 1) / step + 1, rows = (sourceHeight - 1) / step + 1;
28 const std::uint64_t vertexCount = std::uint64_t(columns) * std::uint64_t(rows);
29 const std::uint64_t indexCount = std::uint64_t(columns - 1) * std::uint64_t(rows - 1) * 6u;
30 if (vertexCount > 8u * 1024u * 1024u || indexCount > 48u * 1024u * 1024u ||
31 vertexCount > std::uint64_t(std::numeric_limits<int>::max()) ||
32 indexCount > std::uint64_t(std::numeric_limits<int>::max()))
34 "GTS terrain base mesh exceeds the native export budget",
35 "procgen.gtsTerrainLod"));
36 for (float value : heightmap.data())
37 if (!std::isfinite(value))
39 "GTS terrain heightmap contains a non-finite sample",
40 "procgen.gtsTerrainLod"));
41 MeshBuild candidate;
42 candidate.reserve(static_cast<int>(vertexCount), static_cast<int>(indexCount));
43 candidate.setActiveGroup("terrain");
44 for (int z = 0; z < rows; ++z)
45 for (int x = 0; x < columns; ++x) {
46 const int gx = x * step, gz = z * step;
47 candidate.addVertex(sizeX * float(gx) / float(sourceWidth - 1), heightmap.height(gx, gz) * sizeY,
48 sizeZ * float(gz) / float(sourceHeight - 1), 0.f, 0.f, 0.f,
49 float(gx) / float(sourceWidth - 1), float(gz) / float(sourceHeight - 1));
50 }
51 for (int z = 0; z + 1 < rows; ++z)
52 for (int x = 0; x + 1 < columns; ++x) {
53 const std::uint32_t a = std::uint32_t(z * columns + x), b = a + 1, c = a + std::uint32_t(columns),
54 d = c + 1;
55 candidate.addTriangle(a, c, b);
56 candidate.addTriangle(b, c, d);
57 }
58 auto& normals = candidate.normals();
59 const auto& positions = candidate.positions();
60 const auto& indices = candidate.indices();
61 for (size_t i = 0; i < indices.size(); i += 3) {
62 const auto a = indices[i], b = indices[i + 1], c = indices[i + 2];
63 const float abx = positions[b * 3] - positions[a * 3], aby = positions[b * 3 + 1] - positions[a * 3 + 1],
64 abz = positions[b * 3 + 2] - positions[a * 3 + 2];
65 const float acx = positions[c * 3] - positions[a * 3], acy = positions[c * 3 + 1] - positions[a * 3 + 1],
66 acz = positions[c * 3 + 2] - positions[a * 3 + 2];
67 const float nx = aby * acz - abz * acy, ny = abz * acx - abx * acz, nz = abx * acy - aby * acx;
68 for (auto vertex : {a, b, c}) {
69 normals[vertex * 3] += nx;
70 normals[vertex * 3 + 1] += ny;
71 normals[vertex * 3 + 2] += nz;
72 }
73 }
74 for (size_t i = 0; i < normals.size(); i += 3) {
75 const float length =
76 std::sqrt(normals[i] * normals[i] + normals[i + 1] * normals[i + 1] + normals[i + 2] * normals[i + 2]);
77 if (!(length > 0.f) || !std::isfinite(length))
79 "GTS terrain base mesh contains a degenerate vertex normal",
80 "procgen.gtsTerrainLod"));
81 normals[i] /= length;
82 normals[i + 1] /= length;
83 normals[i + 2] /= length;
84 }
85 candidate.setMeta("kind", "gts.terrain.base");
86 candidate.setMeta("saveResolution", std::to_string(exponent));
87 candidate.setMeta("sourceWidth", std::to_string(sourceWidth));
88 candidate.setMeta("sourceHeight", std::to_string(sourceHeight));
89 output = std::move(candidate);
90 return Result<void>::success();
91}
92
94 return index >= 0 && index < static_cast<int>(tiles_.size()) ? &tiles_[index] : nullptr;
95}
96
98 return index >= 0 && index < static_cast<int>(settings_.size()) ? &settings_[index] : nullptr;
99}
100
102 if (value < 0 || value > 4)
104 "GTS save resolution must be in the range 0 through 4",
105 "procgen.gtsTerrainLod"));
106 saveResolution_ = value;
107 return Result<void>::success();
108}
110 if (value < 1 || value > 4)
112 "GTS LOD count must be in the range 1 through 4",
113 "procgen.gtsTerrainLod"));
114 lodCount_ = value;
115 return Result<void>::success();
116}
118 if (value < 0 || value > 5)
120 "GTS sub-tile split count must be in the range 0 through 5",
121 "procgen.gtsTerrainLod"));
122 subTiles_ = value;
123 return Result<void>::success();
124}
126 return index >= 0 && index < 4 ? lodQuality_[index] : -1.f;
127}
129 if (index < 0 || index >= 4 || !std::isfinite(percent) || percent < 0.f || percent > 100.f)
132 "GTS LOD quality requires a slot from 0 through 3 and percentage from 0 through 100",
133 "procgen.gtsTerrainLod"));
134 lodQuality_[index] = percent;
135 return Result<void>::success();
136}
138 if (index < 0 || index >= lodCount_) return -1.f;
139 constexpr float transitions[]{.95f, .7f, .6f, .02f};
140 return index == lodCount_ - 1 ? .02f : transitions[index];
141}
143 std::vector<GtsTerrainLodLevelSettings> levels;
144 levels.reserve(size_t(lodCount_));
145 for (int i = 0; i < lodCount_; ++i) {
147 level.quality = lodQuality_[i] / 100.f;
148 level.screenRelativeTransitionHeight = getLodTransitionHeight(i);
149 levels.push_back(level);
150 }
151 return Result<std::vector<GtsTerrainLodLevelSettings>>::success(std::move(levels));
152}
153
154int GtsTerrainLodSet::selectLevel(float relativeHeight) const {
155 if (!std::isfinite(relativeHeight) || relativeHeight < 0.0f || settings_.empty()) return -2;
156 for (int i = 0; i < static_cast<int>(settings_.size()); ++i)
157 if (relativeHeight >= settings_[i].screenRelativeTransitionHeight) return i;
158 return -1;
159}
160
161float GtsTerrainLodSet::getLevelSwitchDistance(int level, float worldDiameter, float verticalFovDegrees) const {
162 if (level < 0 || level >= static_cast<int>(settings_.size()) || !std::isfinite(worldDiameter) ||
163 worldDiameter <= 0.0f || !std::isfinite(verticalFovDegrees) || verticalFovDegrees <= 0.0f ||
164 verticalFovDegrees >= 180.0f)
165 return -1.0f;
166 constexpr float radiansPerDegree = 0.01745329251994329577f;
167 const float tangent = std::tan(verticalFovDegrees * 0.5f * radiansPerDegree);
168 return worldDiameter / (2.0f * tangent * settings_[level].screenRelativeTransitionHeight);
169}
170
171int GtsTerrainLodSet::selectLevelForCamera(float distance, float worldDiameter, float verticalFovDegrees) const {
172 if (!std::isfinite(distance) || distance <= 0.0f || !std::isfinite(worldDiameter) || worldDiameter <= 0.0f ||
173 !std::isfinite(verticalFovDegrees) || verticalFovDegrees <= 0.0f || verticalFovDegrees >= 180.0f)
174 return -2;
175 constexpr float radiansPerDegree = 0.01745329251994329577f;
176 const float relativeHeight =
177 worldDiameter / (2.0f * distance * std::tan(verticalFovDegrees * 0.5f * radiansPerDegree));
178 return selectLevel(relativeHeight);
179}
180
181std::vector<GtsTerrainLodLevelSettings> defaultGtsTerrainLodLevels() {
182 std::vector<GtsTerrainLodLevelSettings> levels(4);
183 levels[0].quality = 1.0f;
184 levels[1].quality = 0.5f;
185 levels[2].quality = 0.25f;
186 levels[3].quality = 0.125f;
187 levels[0].screenRelativeTransitionHeight = 0.95f;
188 levels[1].screenRelativeTransitionHeight = 0.7f;
189 levels[2].screenRelativeTransitionHeight = 0.6f;
190 levels[3].screenRelativeTransitionHeight = 0.02f;
191 return levels;
192}
193
195 const std::string& terrainName,
196 const std::string& meshFolder) {
197 auto safe = [](const std::string& v, bool folder) {
198 if (v.empty() || v == "." || v == ".." || v.find("..") != std::string::npos) return false;
199 constexpr std::string_view invalid = "\\:*?\"<>|";
200 if (v.find_first_of(invalid) != std::string::npos) return false;
201 return folder || v.find('/') == std::string::npos;
202 };
203 if (!safe(terrainName, false) || !safe(meshFolder, true) || meshFolder.front() == '/' || meshFolder.back() == '/')
204 return Result<std::vector<GtsTerrainLodAssetEntry>>::failure(Diagnostic::error(
205 DiagnosticCode::InvalidArgument, "terrain name or mesh folder is not a safe relative export name",
206 "procgen.gtsTerrainLod"));
207 std::vector<GtsTerrainLodAssetEntry> entries;
208 for (int tileIndex = 0; tileIndex < lods.getTileCount(); ++tileIndex) {
209 auto* tile = lods.tileAt(tileIndex);
210 if (!tile) continue;
211 for (int level = 0; level < static_cast<int>(tile->levels.size()); ++level) {
212 if (tile->levels[size_t(level)].empty()) continue;
214 e.tileIndex = tileIndex;
215 e.levelIndex = level;
216 e.objectName = terrainName + " SubTile" + std::to_string(tileIndex) + "_LOD_" + std::to_string(level);
217 e.meshName = terrainName + "_SubTile_" + std::to_string(tileIndex) + std::to_string(level);
218 e.relativePath = meshFolder + "/" + e.meshName + ".eve.mesh.json";
219 entries.push_back(std::move(e));
220 }
221 }
222 return Result<std::vector<GtsTerrainLodAssetEntry>>::success(std::move(entries));
223}
224int GtsTerrainLodAssetPlan::getEntryCount() const { return static_cast<int>(entries_.size()); }
226 return i >= 0 && i < int(entries_.size()) ? entries_[size_t(i)].tileIndex : -1;
227}
229 return i >= 0 && i < int(entries_.size()) ? entries_[size_t(i)].levelIndex : -1;
230}
232 return i >= 0 && i < int(entries_.size()) ? entries_[size_t(i)].objectName : std::string{};
233}
234std::string GtsTerrainLodAssetPlan::getMeshName(int i) const {
235 return i >= 0 && i < int(entries_.size()) ? entries_[size_t(i)].meshName : std::string{};
236}
238 return i >= 0 && i < int(entries_.size()) ? entries_[size_t(i)].relativePath : std::string{};
239}
241 const std::string& name, const std::string& folder) {
242 auto plan = planGtsTerrainLodAssets(lods, name, folder);
243 if (!plan) return Result<void>::failure(*plan.error());
244 GtsTerrainLodAssetPlan candidate;
245 candidate.entries_ = std::move(plan).takeValue();
246 output = std::move(candidate);
247 return Result<void>::success();
248}
249
251 const std::vector<GtsTerrainLodLevelSettings>& levels) {
252 if (levels.empty())
254 DiagnosticCode::InvalidArgument, "at least one terrain LOD level is required", "procgen.gtsTerrainLod"));
255 float previousTransition = 1.0f;
256 for (const auto& level : levels) {
257 if (!std::isfinite(level.quality) || level.quality < 0.0f || level.quality > 1.0f ||
258 !std::isfinite(level.screenRelativeTransitionHeight) || level.screenRelativeTransitionHeight < 0.0f ||
259 level.screenRelativeTransitionHeight > 1.0f || level.screenRelativeTransitionHeight >= previousTransition) {
261 "LOD quality or transition sequence is invalid",
262 "procgen.gtsTerrainLod"));
263 }
264 previousTransition = level.screenRelativeTransitionHeight;
265 }
266
267 auto split = splitGtsMesh(source, xSplits, zSplits, pivot);
268 if (!split) return Result<GtsTerrainLodSet>::failure(*split.error());
269
270 GtsTerrainLodSet result;
271 result.columns_ = split.value().getColumnCount();
272 result.rows_ = split.value().getRowCount();
273 result.settings_ = levels;
274 result.tiles_.reserve(split.value().getTileCount());
275 for (int tileIndex = 0; tileIndex < split.value().getTileCount(); ++tileIndex) {
276 const auto* sourceTile = split.value().tileAt(tileIndex);
278 tile.offsetX = sourceTile->offsetX;
279 tile.offsetZ = sourceTile->offsetZ;
280 tile.levels.reserve(levels.size());
281 MeshBuild previous = sourceTile->mesh;
282 for (const auto& level : levels) {
283 if (previous.empty()) {
284 tile.levels.emplace_back();
285 continue;
286 }
287 MeshBuild simplified;
288 auto simplification = simplifyGtsMesh(simplified, previous, level.quality, level.simplification);
289 if (!simplification) return Result<GtsTerrainLodSet>::failure(*simplification.error());
290 tile.levels.push_back(simplified);
291 previous = std::move(simplified);
292 }
293 result.tiles_.push_back(std::move(tile));
294 }
295 return Result<GtsTerrainLodSet>::success(std::move(result));
296}
297
299 GtsTerrainSaveResolution resolution, float sizeX, float sizeY,
300 float sizeZ, int subTileSplits, GtsMeshPivot pivot,
301 const std::vector<GtsTerrainLodLevelSettings>& levels) {
302 if (subTileSplits < 0 || subTileSplits > 5)
304 DiagnosticCode::InvalidArgument, "GTS sub-tile split count must be in the profile range 0 through 5",
305 "procgen.gtsTerrainLod"));
306 MeshBuild base;
307 auto built = buildGtsTerrainBaseMesh(base, heightmap, resolution, sizeX, sizeY, sizeZ);
308 if (!built) return Result<GtsTerrainLodSet>::failure(*built.error());
309 return buildGtsTerrainLods(base, subTileSplits, subTileSplits, pivot, levels);
310}
311
313 GtsTerrainSaveResolution resolution, float sizeX, float sizeY,
314 float sizeZ, int subTileSplits, GtsMeshPivot pivot) {
315 auto built = buildGtsTerrainLodsFromHeightmap(heightmap, resolution, sizeX, sizeY, sizeZ, subTileSplits, pivot,
317 if (!built) return Result<void>::failure(*built.error());
318 output = std::move(built).takeValue();
319 return Result<void>::success();
320}
321
323 const GtsTerrainMeshSettings& settings, float sizeX, float sizeY,
324 float sizeZ, GtsMeshPivot pivot) {
325 auto levels = settings.compileLevels();
326 if (!levels) return Result<void>::failure(*levels.error());
327 auto built =
328 buildGtsTerrainLodsFromHeightmap(heightmap, static_cast<GtsTerrainSaveResolution>(settings.getSaveResolution()),
329 sizeX, sizeY, sizeZ, settings.getSubTiles(), pivot, levels.value());
330 if (!built) return Result<void>::failure(*built.error());
331 output = std::move(built).takeValue();
332 return Result<void>::success();
333}
334
335} // namespace eve::procgen
double value
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
std::string output
std::unordered_map< std::string, QuestRuntime > entries
Vec3 tangent
Definition CaveMesh.cpp:80
float length
Definition CaveMesh.cpp:94
bool split
Definition CaveMesh.cpp:123
float nx
float nz
float ny
int rows
std::uint32_t vertexCount
std::uint32_t indexCount
std::vector< std::uint32_t > indices
std::vector< float > normals
std::vector< float > positions
float v
std::int32_t c
std::string name
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
float distance
int level
graphics::Canvas * previous
float d
TerrainThermalSettings settings
int columns
float step
Definition TreeMesh.cpp:314
uint32_t index
const UnitySourceAsset & source
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
Owned script-friendly GTS terrain LOD export manifest.
int getLevelIndex(int index) const
Return an entry LOD index, or -1.
std::string getMeshName(int index) const
Return an entry mesh name, or empty.
std::string getObjectName(int index) const
Return an entry object name, or empty.
std::string getRelativePath(int index) const
Return an entry relative path, or empty.
int getTileIndex(int index) const
Return an entry tile index, or -1.
int getEntryCount() const
Return the number of export entries.
Owned result of the GTS split-and-sequential-simplify pipeline.
float getLevelSwitchDistance(int level, float worldDiameter, float verticalFovDegrees) const
Convert one level's screen-relative threshold to a perspective-camera distance.
const GtsTerrainLodLevelSettings * levelAt(int index) const
Return one level setting or nullptr for an invalid index. @ownership The LOD set retains ownership; t...
int getTileCount() const
Return the number of tiles, including empty source cells.
const GtsTerrainLodTile * tileAt(int index) const
Return one tile or nullptr for an invalid index. @ownership The LOD set retains ownership; the caller...
int selectLevelForCamera(float distance, float worldDiameter, float verticalFovDegrees) const
Select a level directly from perspective-camera distance and bounds diameter.
int selectLevel(float relativeHeight) const
Select the active level for a projected screen-relative height.
Schema-versioned native counterpart of GTSMeshSettings.
Result< std::vector< GtsTerrainLodLevelSettings > > compileLevels() const
Compile active profile slots into sequential native LOD settings.
Result< void > setLodQuality(int index, float percent)
Set one of the four simplification percentages in the range 0 through 100.
float getLodQuality(int index) const noexcept
Return one stored simplification percentage, or -1 for an invalid slot.
Result< void > setSaveResolution(int value)
Atomically set the SaveResolution enum value.
Result< void > setLodCount(int value)
Atomically set the active LOD count.
float getLodTransitionHeight(int index) const noexcept
Return the effective fixed GTS transition height for an active LOD, or -1.
Result< void > setSubTiles(int value)
Atomically set the equal X/Z split-plane count.
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
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
void addVertex(float px, float py, float pz, float nx, float ny, float nz, float u, float v)
Adds vertex.
Definition MeshBuild.cpp:33
const std::vector< float > & normals() const
Normals.
Definition MeshBuild.h:127
void addTriangle(uint32_t i0, uint32_t i1, uint32_t i2)
Adds triangle.
Definition MeshBuild.cpp:46
void reserve(int vertexCount, int indexCount)
Reserve.
Definition MeshBuild.cpp:20
void setMeta(const std::string &key, const std::string &value)
Sets the meta.
int setActiveGroup(const std::string &name)
Select/create the named group assigned to subsequently added triangles.
Definition MeshBuild.cpp:53
const std::vector< uint32_t > & indices() const
Indices.
Definition MeshBuild.h:133
Result< int > invalid(std::string message)
Invalid.
Result< GtsMeshSplitResult > splitGtsMesh(const MeshBuild &source, int xSplits, int zSplits, GtsMeshPivot pivot)
Split every source triangle against a regular X/Z grid like GTSMeshSplitter.
Result< void > buildDefaultGtsTerrainLodsFromHeightmapInto(GtsTerrainLodSet &output, const Heightmap &heightmap, GtsTerrainSaveResolution resolution, float sizeX, float sizeY, float sizeZ, int subTileSplits, GtsMeshPivot pivot)
Atomically replace output with the complete default four-level GTS terrain conversion.
GtsTerrainSaveResolution
Pcg/GTS power-of-two sampling reduction used before terrain mesh export.
Result< void > buildGtsTerrainBaseMesh(MeshBuild &output, const Heightmap &heightmap, GtsTerrainSaveResolution resolution, float sizeX, float sizeY, float sizeZ)
Build the local-space base mesh consumed by the GTS split and LOD pipeline.
Result< int > simplifyGtsMesh(MeshBuild &output, const MeshBuild &source, float quality, const GtsMeshSimplificationOptions &o)
Simplify a mesh with deterministic quadric edge collapse.
GtsMeshPivot
Pivot translation applied independently to each GTS split mesh.
Result< GtsTerrainLodSet > buildGtsTerrainLodsFromHeightmap(const Heightmap &heightmap, GtsTerrainSaveResolution resolution, float sizeX, float sizeY, float sizeZ, int subTileSplits, GtsMeshPivot pivot, const std::vector< GtsTerrainLodLevelSettings > &levels)
Execute the complete GTS heightmap-to-split-LOD conversion as one atomic operation.
Result< void > planGtsTerrainLodAssetsInto(GtsTerrainLodAssetPlan &output, const GtsTerrainLodSet &lods, const std::string &name, const std::string &folder)
Atomically replace a script-friendly export plan.
Result< void > buildGtsTerrainLodsFromHeightmapInto(GtsTerrainLodSet &output, const Heightmap &heightmap, const GtsTerrainMeshSettings &settings, float sizeX, float sizeY, float sizeZ, GtsMeshPivot pivot)
Atomically build a complete heightmap conversion using persisted GTS mesh settings.
Result< GtsTerrainLodSet > buildGtsTerrainLods(const MeshBuild &source, int xSplits, int zSplits, GtsMeshPivot pivot, const std::vector< GtsTerrainLodLevelSettings > &levels)
Split a terrain mesh and generate each tile's sequential GTS LOD chain.
Result< std::vector< GtsTerrainLodAssetEntry > > planGtsTerrainLodAssets(const GtsTerrainLodSet &lods, const std::string &terrainName, const std::string &meshFolder)
Build the deterministic object and mesh naming plan used by GTS terrain export.
std::vector< GtsTerrainLodLevelSettings > defaultGtsTerrainLodLevels()
Return the four-level quality and transition profile used by GTSMeshSettings.
Deterministic native export identity for one GTS tile LOD mesh.
Configuration for one sequential GTS terrain mesh LOD.
One split terrain tile and all of its local-space LOD meshes.
std::vector< MeshBuild > levels