10uint64_t TerrainStreamingCache::key(
int chunkX,
int chunkY) {
11 return uint64_t(uint32_t(chunkX)) | (uint64_t(uint32_t(chunkY)) << 32);
16 if (!next.open(data,
size,
error))
return false;
17 asset_ = std::move(next);
23 std::shared_ptr<const ITerrainArchiveSource>
source) {
25 auto opened = next.openSource(std::move(
source));
26 if (!opened)
return opened;
27 asset_ = std::move(next);
35 int maxLoads, std::string *
error) {
39 const int centerX = worldX >= 0 ? worldX / chunkSize : -((-worldX - 1) / chunkSize) - 1;
40 const int centerY = worldY >= 0 ? worldY / chunkSize : -((-worldY - 1) / chunkSize) - 1;
43 for (
auto it = resident_.begin(); it != resident_.end();) {
44 const int cx = int32_t(uint32_t(it->first));
45 const int cy = int32_t(uint32_t(it->first >> 32));
46 const int64_t
dx = int64_t(
cx) - centerX,
dy = int64_t(
cy) - centerY;
47 if (
dx *
dx +
dy *
dy > radiusSq) { it = resident_.erase(it); ++
stats.evicted; }
51 struct Request {
int x,
y; int64_t distanceSq; };
52 std::vector<Request> requests;
55 const int64_t distanceSq = int64_t(
dx) *
dx + int64_t(
dy) *
dy;
56 if (distanceSq > radiusSq)
continue;
57 const int chunkX = centerX +
dx, chunkY = centerY +
dy;
59 requests.push_back({chunkX, chunkY, distanceSq});
62 std::sort(requests.begin(), requests.end(), [](
const Request &
a,
const Request &
b) {
63 return std::tie(a.distanceSq, a.y, a.x) < std::tie(b.distanceSq, b.y, b.x);
65 const size_t loadCount = maxLoads > 0 ? std::min(requests.size(),
size_t(maxLoads)) : requests.size();
66 for (
size_t i = 0; i < loadCount; ++i) {
68 std::string chunkError;
69 if (!asset_.
loadChunk(requests[i].x, requests[i].y, chunk, &chunkError)) {
74 resident_.emplace(
key(requests[i].
x, requests[i].
y), std::move(chunk));
77 stats.pending = int(requests.size() - loadCount);
83 const auto it = resident_.find(
key(chunkX, chunkY));
84 return it == resident_.end() ? nullptr : &it->second;
88 if (worldX < 0 || worldY < 0 || worldX >= asset_.
getWidth() || worldY >= asset_.
getHeight())
return false;
91 if (!chunk)
return false;
92 const int lx = worldX -
cx * cs, ly = worldY -
cy * cs;
93 if (lx >= chunk->
width || ly >= chunk->
height)
return false;
94 const size_t i = size_t(ly) * size_t(chunk->
width) + size_t(lx);
106 int &receiverY)
const {
107 static constexpr int dx[8] = {-1, 0, 1, -1, 1, -1, 0, 1};
108 static constexpr int dy[8] = {-1, -1, -1, 0, 0, 1, 1, 1};
111 source.flowDirection >= 8)
return false;
112 receiverX = worldX +
dx[
source.flowDirection];
113 receiverY = worldY +
dy[
source.flowDirection];
115 return sampleCell(receiverX, receiverY, receiver);
119 std::vector<std::pair<int, int>> &out)
const {
123 out.emplace_back(worldX, worldY);
127 if (
current.flowDirection < 0)
return true;
128 int nextX = 0, nextY = 0;
129 if (!
getReceiver(worldX, worldY, nextX, nextY))
return false;
130 if (std::find(out.begin(), out.end(), std::pair<int, int>{nextX, nextY}) != out.end())
132 out.emplace_back(nextX, nextY);
133 worldX = nextX; worldY = nextY;
141 if (
width <= 0 ||
height <= 0 || originX < 0 || originY < 0 ||
145 next.originX = originX; next.originY = originY;
147 next.hydrology.width =
width; next.hydrology.height =
height;
148 next.climate.width =
width; next.climate.height =
height;
150 next.hydrology.flowDirection.resize(
count);
151 next.hydrology.flowVectorX.resize(
count); next.hydrology.flowVectorY.resize(
count);
152 next.hydrology.flowAccumulation.resize(
count); next.hydrology.lakeDepth.resize(
count);
153 next.hydrology.rivers.resize(
count); next.hydrology.streamOrder.resize(
count);
154 next.climate.temperature.resize(
count); next.climate.moisture.resize(
count);
155 next.climate.biomes.resize(
count);
159 const size_t i = size_t(
y) * size_t(
width) + size_t(
x);
160 next.heights.setHeight(
x,
y,
sample.height);
161 next.hydrology.flowDirection[i] = int8_t(
sample.flowDirection);
162 next.hydrology.flowVectorX[i] =
sample.flowVectorX;
163 next.hydrology.flowVectorY[i] =
sample.flowVectorY;
164 next.hydrology.flowAccumulation[i] =
sample.flowAccumulation;
165 next.hydrology.lakeDepth[i] =
sample.lakeDepth;
166 next.hydrology.rivers[i] = uint8_t(
sample.river);
167 next.hydrology.streamOrder[i] = uint8_t(
sample.streamOrder);
168 next.climate.temperature[i] =
sample.temperature;
169 next.climate.moisture[i] =
sample.moisture;
170 next.climate.biomes[i] =
sample.biome;
172 out = std::move(next);
177 if (!std::isfinite(worldX) || !std::isfinite(worldY) || worldX < 0.f || worldY < 0.f ||
178 worldX >
float(asset_.
getWidth() - 1) || worldY >
float(asset_.
getHeight() - 1))
return false;
179 const int x0 = int(std::floor(worldX)), y0 = int(std::floor(worldY));
180 const int x1 = std::min(x0 + 1, asset_.
getWidth() - 1), y1 = std::min(y0 + 1, asset_.
getHeight() - 1);
183 const float tx = worldX - float(x0), ty = worldY - float(y0);
184 const float top =
a.height + (
b.height -
a.height) * tx;
185 const float bottom =
c.height + (
d.height -
c.height) * tx;
const UnitySourceAsset & source
Move-only operation result carrying either a value or Status.
static Result success(T value)
Construct a successful result owning value.
In-memory terrain heightmap: a dense float grid (row-major, index = y * width + x) materialized from ...
const std::vector< float > & data() const
Data.
Versioned, random-access terrain archive.
const TerrainChunkEntry * findChunk(int chunkX, int chunkY) const noexcept
Find one chunk directory entry without scanning the full directory.
bool loadChunk(int chunkX, int chunkY, TerrainChunkData &out, std::string *error=nullptr) const
Compatibility operation that loads and verifies one chunk.
int getChunkSize() const
Returns the chunk size.
int getHeight() const
Returns the height.
int getWidth() const
Returns the width.
const TerrainChunkData * getChunk(int chunkX, int chunkY) const
Borrow a resident chunk. @lifetime Pointer remains valid until cache mutation.
bool getReceiver(int worldX, int worldY, int &receiverX, int &receiverY) const
Resolve a cell's D8 receiver in global coordinates, including an adjacent chunk.
int getResidentCount() const
Returns the resident count.
bool buildWindow(int originX, int originY, int width, int height, TerrainStreamingWindow &out) const
Compatibility operation that assembles a dense hydrology window.
bool open(const uint8_t *data, size_t size, std::string *error=nullptr)
Compatibility operation that opens EVTR and clears decoded chunks.
bool traceFlow(int worldX, int worldY, int maxSteps, std::vector< std::pair< int, int > > &out) const
Compatibility operation that follows D8 receivers across chunks.
bool sampleHeight(float worldX, float worldY, float &out) const
Compatibility operation for cross-chunk bilinear height sampling.
Result< void > openSource(std::shared_ptr< const ITerrainArchiveSource > source)
Open EVTR through a retained random-access source without loading chunk payloads.
TerrainStreamStats streamAround(int worldX, int worldY, int radiusChunks, int maxLoads=0, std::string *error=nullptr)
Stream chunks around a world-space sample coordinate.
bool sampleCell(int worldX, int worldY, TerrainSample &out) const
Compatibility operation that reads a resident integer world cell.
double sample(const Heightmap &map, double u, double v)
Sample.
Decoded terrain chunk containing all baked runtime layers.
std::vector< float > flowAccumulation
std::vector< float > moisture
std::vector< float > lakeDepth
std::vector< float > flowVectorX
std::vector< uint8_t > rivers
std::vector< Biome > biomes
std::vector< uint8_t > streamOrder
std::vector< int8_t > flowDirection
std::vector< float > flowVectorY
std::vector< float > temperature
TerrainSample public API.
TerrainStreamStats public API.
A rectangular, globally addressed terrain window assembled from resident chunks.