23 float amplitude,
float scale) {
25 if (!terrainSampler_) terrainSampler_ = std::make_unique<procgen::TerrainSampler>();
26 terrainSampler_->setSeed(
seed);
27 terrainSampler_->setBase(0.f);
28 terrainSampler_->setAmplitude(1.f);
29 terrainSampler_->setClamp(
true, 0.f, 1.f);
30 terrainSampler_->setFrequency(
scale > 0.f ?
scale : 1.f / 32.f);
33 terrainStone_ = stone;
34 terrainBase_ = baseHeight;
35 terrainAmplitude_ = amplitude < 0.f ? 0.f : amplitude;
36 terrainEnabled_ =
true;
41 if (!terrainSampler_) terrainSampler_ = std::make_unique<procgen::TerrainSampler>();
43 terrainSampler_->setSeed(uint32_t(
value));
44 }
else if (
key ==
"top") {
45 terrainTop_ = uint8_t(
value);
46 }
else if (
key ==
"sub") {
47 terrainSub_ = uint8_t(
value);
48 }
else if (
key ==
"stone") {
49 terrainStone_ = uint8_t(
value);
50 }
else if (
key ==
"sand") {
51 terrainSand_ = uint8_t(
value);
52 }
else if (
key ==
"base") {
54 }
else if (
key ==
"amplitude") {
56 }
else if (
key ==
"scale" ||
key ==
"frequency") {
57 terrainSampler_->setFrequency(
value > 0.f ?
value : 1.f / 32.f);
58 }
else if (
key ==
"octaves") {
59 terrainSampler_->setOctaves(
int(
value));
60 }
else if (
key ==
"lacunarity") {
61 terrainSampler_->setLacunarity(
value);
62 }
else if (
key ==
"gain") {
63 terrainSampler_->setGain(
value);
64 }
else if (
key ==
"ridge") {
65 terrainSampler_->setRidge(
value);
66 }
else if (
key ==
"warp") {
67 terrainSampler_->setWarp(
value);
68 }
else if (
key ==
"exponent") {
69 terrainSampler_->setExponent(
value);
70 }
else if (
key ==
"continent") {
71 terrainSampler_->setContinent(
value);
72 }
else if (
key ==
"island") {
73 terrainSampler_->setIsland(
value);
74 }
else if (
key ==
"coast") {
75 terrainSampler_->setCoastSoftness(
value);
76 }
else if (
key ==
"worldWidth") {
77 terrainSampler_->setWorldSize(
int(
value), terrainSampler_->getWorldHeight());
78 }
else if (
key ==
"worldHeight") {
79 terrainSampler_->setWorldSize(terrainSampler_->getWorldWidth(), int(
value));
80 }
else if (
key ==
"sandLevel") {
82 }
else if (
key ==
"enable") {
83 terrainEnabled_ =
value != 0.f;
89 terrainEnabled_ =
false;
90 terrainAssetEnabled_ =
false;
91 terrainAsset_.
clear();
98 uint8_t sandTex,
float sandLevel,
int wx,
int wz,
int &
height, uint8_t &topTex) {
101 topTex = topTexDefault;
104 const float e =
sampler->sample(
float(
wx),
float(
wz));
105 height = int(std::floor(base + amp * e));
106 topTex = (sandLevel > 0.f && sandTex != 0 && e <= sandLevel) ? sandTex : topTexDefault;
114constexpr int kMaxRemeshWorkers = 4;
115constexpr int kMaxStreamJobs = 8;
119 const uint64_t k =
key(
cx,
cy, cz);
120 auto it = chunks_.find(k);
121 if (it != chunks_.end())
return it->second.get();
122 auto chunk = std::make_unique<Chunk>(
cx,
cy, cz);
124 chunks_.emplace(k, std::move(chunk));
129 auto it = chunks_.find(
key(
cx,
cy, cz));
130 return it == chunks_.end() ? nullptr : it->second.get();
134 auto it = chunks_.find(
key(
cx,
cy, cz));
135 return it == chunks_.end() ? nullptr : it->second.get();
139 return chunks_.find(
key(
cx,
cy, cz)) != chunks_.end();
143 if (chunks_.erase(
key(
cx,
cy, cz)) > 0) ++revision_;
148 const bool changed = !chunks_.empty();
151 visibleChunkKeys_.clear();
152 if (changed) ++revision_;
157 if (!
bytes || !std::isfinite(heightOffset) || !std::isfinite(heightScale))
return false;
159 if (!terrainAsset_.
open(
static_cast<const uint8_t *
>(
bytes->getData()),
bytes->getSize(),
160 &
error))
return false;
161 terrainAssetOffset_ = heightOffset;
162 terrainAssetScale_ = heightScale;
163 terrainAssetEnabled_ =
true;
170 if (!terrainAssetEnabled_)
return {};
171 return terrainAsset_.
streamAround(worldX, worldZ, radiusChunks, maxLoads);
175 uint8_t alpine, uint8_t riverbed) {
176 terrainVegetation_ = vegetation;
177 terrainAssetSand_ = sand;
179 terrainAlpine_ = alpine;
180 terrainRiverbed_ = riverbed;
184 if (terrainAssetEnabled_) {
185 float storedHeight = 0.f;
187 return int(std::floor(terrainAssetOffset_ + terrainAssetScale_ * storedHeight));
189 if (terrainEnabled_) {
190 if (!terrainSampler_)
return int(std::floor(terrainBase_));
191 const float e = terrainSampler_->sample(
float(
wx),
float(
wz));
192 return int(std::floor(terrainBase_ + terrainAmplitude_ * e));
194 return int(std::floor(terrainAssetOffset_));
197uint8_t VoxelWorld::terrainSurfaceAt(
int wx,
int wz)
const {
198 if (!terrainAssetEnabled_)
return terrainTop_;
200 if (!terrainAsset_.
sampleCell(
wx,
wz, sample))
return terrainTop_;
201 switch (sample.biome) {
205 return terrainAssetSand_;
210 return terrainAlpine_;
212 return terrainRiverbed_;
214 return terrainVegetation_;
219 streamCacheChunks_ = extraChunks < 0 ? 0 : extraChunks;
223 if (radiusChunks < 0)
return 0;
224 const int64_t r2 = int64_t(radiusChunks) * int64_t(radiusChunks);
225 std::vector<uint64_t> evict;
226 evict.reserve(chunks_.size() / 4);
227 for (
auto &kv : chunks_) {
229 unpackKey(kv.first,
cx,
cy, cz);
230 const int64_t
dx = int64_t(
cx) - centerX;
231 const int64_t
dy = int64_t(
cy) - centerY;
232 const int64_t
dz = int64_t(cz) - centerZ;
233 if (
dx *
dx +
dy *
dy +
dz *
dz > r2) evict.push_back(kv.first);
235 for (uint64_t k : evict) chunks_.erase(k);
236 if (!evict.empty()) {
239 visibleChunkKeys_.clear();
242 return int(evict.size());
245void VoxelWorld::fillChunkTerrain(
Chunk &
c,
int nx,
int ny,
int nz)
const {
250 uint8_t surface = terrainTop_;
253 if (terrainAssetEnabled_) {
255 surface = terrainSurfaceAt(
wx,
wz);
257 sampleTerrainColumn(terrainSampler_.get(), terrainBase_, terrainAmplitude_,
258 terrainTop_, terrainSand_, sandLevel_,
wx,
wz,
h, surface);
261 const int wy = wy0 + ly;
263 c.set(lx, ly, lz, terrainStone_);
264 else if (
wy <=
h - 1)
265 c.set(lx, ly, lz, terrainSub_);
267 c.set(lx, ly, lz, surface);
273 const std::function<
void(
Chunk &,
int,
int,
int)> &generator,
276 if (radiusChunks < 0)
return stats;
277 if (maxCreates < 0) maxCreates = 0;
279 const int keepRadius = radiusChunks + streamCacheChunks_;
281 if (!generator && terrainAssetEnabled_) {
283 const int worldRadius = (keepRadius + 1) *
kChunkSize;
284 const int assetRadius = assetChunk > 0
285 ? int(std::ceil(
float(worldRadius) * 1.41421356f /
float(assetChunk))) + 1 : 0;
286 const int assetLoads = maxCreates > 0 ? maxCreates : 0;
293 reapRetiredStreamJobs();
294 restoreRetiredInside(centerX, centerY, centerZ, keepRadius);
295 dropStreamJobsOutside(centerX, centerY, centerZ, keepRadius);
296 const bool asyncTerrain = maxCreates > 0 && !generator && terrainEnabled_ && !terrainAssetEnabled_;
298 stats.created = harvestStreamJobs(centerX, centerY, centerZ, keepRadius, maxCreates);
306 std::vector<Missing> missing;
307 const int64_t r2 = int64_t(radiusChunks) * int64_t(radiusChunks);
308 for (
int dz = -radiusChunks;
dz <= radiusChunks; ++
dz)
309 for (
int dy = -radiusChunks;
dy <= radiusChunks; ++
dy)
310 for (
int dx = -radiusChunks;
dx <= radiusChunks; ++
dx) {
311 const int64_t d2 = int64_t(
dx) *
dx + int64_t(
dy) *
dy + int64_t(
dz) *
dz;
312 if (d2 > r2)
continue;
313 const int nx = centerX +
dx;
314 const int ny = centerY +
dy;
315 const int nz = centerZ +
dz;
317 missing.push_back({int(d2),
nx,
ny,
nz});
319 std::sort(missing.begin(), missing.end(), [](
const Missing &
a,
const Missing &
b) {
320 if (a.d2 != b.d2) return a.d2 < b.d2;
321 if (a.nx != b.nx) return a.nx < b.nx;
322 if (a.ny != b.ny) return a.ny < b.ny;
329 jobs = thread::Thread::create()->getJobSystem();
335 for (
const Missing &
m : missing) {
336 if (
int(inflight_.size()) >= kMaxStreamJobs)
break;
337 if (submitted >= maxCreates)
break;
342 job.chunk = std::make_unique<Chunk>(
m.nx,
m.ny,
m.nz);
347 typesCopy = std::move(typesCopy)]() {
348 fillChunkTerrain(*raw, nx, ny, nz);
349 raw->remesh(typesCopy, nullptr, nullptr);
355 fillChunkTerrain(*raw,
m.nx,
m.ny,
m.nz);
356 raw->
remesh(types_,
nullptr,
nullptr);
357 chunks_.emplace(
key(
m.nx,
m.ny,
m.nz), std::move(job.chunk));
362 inflight_.push_back(std::move(job));
365 stats.pending = int(missing.size() -
size_t(submitted)) + int(inflight_.size());
370 const size_t budget = maxCreates > 0 ? size_t(maxCreates) : missing.size();
371 const size_t createCount = std::min(budget, missing.size());
374 for (
size_t i = 0; i < createCount; ++i) {
375 const Missing &
m = missing[i];
378 generator(*
c,
m.nx,
m.ny,
m.nz);
379 else if (terrainEnabled_ || terrainAssetEnabled_)
380 fillChunkTerrain(*
c,
m.nx,
m.ny,
m.nz);
386 if (
stats.created > 0) {
389 if (maxCreates > 0) {
390 static const int nbs[6][3] = {{1, 0, 0}, {-1, 0, 0}, {0, 1, 0},
391 {0, -1, 0}, {0, 0, 1}, {0, 0, -1}};
393 for (
const auto &
d : nbs) {
406void putU32(std::vector<uint8_t> &out, uint32_t
v) {
407 out.push_back(uint8_t(
v));
408 out.push_back(uint8_t(
v >> 8));
409 out.push_back(uint8_t(
v >> 16));
410 out.push_back(uint8_t(
v >> 24));
413void putI32(std::vector<uint8_t> &out, int32_t
v) { putU32(out, uint32_t(
v)); }
415bool getU32(
const uint8_t *&
p,
const uint8_t *
end, uint32_t &out) {
416 if (
end -
p < 4)
return false;
417 out = uint32_t(
p[0]) | (uint32_t(
p[1]) << 8) | (uint32_t(
p[2]) << 16) |
418 (uint32_t(
p[3]) << 24);
423bool getI32(
const uint8_t *&
p,
const uint8_t *
end, int32_t &out) {
425 if (!getU32(
p,
end,
v))
return false;
434 const char magic[4] = {
'E',
'V',
'V',
'X'};
435 out.insert(out.end(), magic, magic + 4);
437 putU32(out, uint32_t(chunks_.size()));
440 std::vector<uint64_t> keys;
441 keys.reserve(chunks_.size());
442 for (
auto &kv : chunks_) keys.push_back(kv.first);
443 std::sort(keys.begin(), keys.end());
444 for (uint64_t
key : keys) {
445 const auto &chunk = chunks_.at(
key);
448 putI32(out, int32_t(
cx));
449 putI32(out, int32_t(
cy));
450 putI32(out, int32_t(cz));
451 const uint8_t *raw = chunk->rawVoxels();
457 const uint8_t *
p = data;
458 const uint8_t *
end = data +
size;
459 if (
size < 9 || std::memcmp(
p,
"EVVX", 4) != 0 ||
p[4] != 1)
return false;
462 if (!getU32(
p,
end,
count))
return false;
464 if (uint64_t(
count) > (uint64_t(
end -
p)) / (12 + voxelBytes))
return false;
467 for (uint32_t i = 0; i <
count; ++i) {
468 int32_t
cx = 0,
cy = 0, cz = 0;
469 if (!getI32(
p,
end,
cx) || !getI32(
p,
end,
cy) || !getI32(
p,
end, cz))
return false;
470 if (
size_t(
end -
p) < voxelBytes)
return false;
480 std::vector<uint8_t>
bytes;
486 if (!
bytes)
return false;
492 for (
auto &kv : chunks_) {
493 if (kv.second->isDirty()) ++
n;
499 return int(inflight_.size() + retired_.size());
502void VoxelWorld::waitStreamJobs() {
503 auto waitAll = [](std::vector<StreamJob> &jobs) {
504 for (StreamJob &job : jobs) {
505 if (!job.job)
continue;
516void VoxelWorld::reapRetiredStreamJobs() {
517 std::vector<StreamJob> keep;
518 keep.reserve(retired_.size());
519 for (StreamJob &job : retired_) {
520 if (job.job && !job.job->isDone()) {
521 keep.push_back(std::move(job));
529 retired_ = std::move(keep);
532bool VoxelWorld::isStreamJob(
int cx,
int cy,
int cz)
const {
533 auto match = [
cx,
cy, cz](
const StreamJob &job) {
534 return job.cx ==
cx && job.cy ==
cy && job.cz == cz;
536 for (
const StreamJob &job : inflight_) {
537 if (match(job))
return true;
539 for (
const StreamJob &job : retired_) {
540 if (match(job))
return true;
545void VoxelWorld::restoreRetiredInside(
int centerX,
int centerY,
int centerZ,
int radiusChunks) {
546 const int64_t r2 = int64_t(radiusChunks) * int64_t(radiusChunks);
547 std::vector<StreamJob> keep;
548 keep.reserve(retired_.size());
549 for (StreamJob &job : retired_) {
550 const int64_t
dx = int64_t(job.cx) - centerX;
551 const int64_t
dy = int64_t(job.cy) - centerY;
552 const int64_t
dz = int64_t(job.cz) - centerZ;
554 inflight_.push_back(std::move(job));
556 keep.push_back(std::move(job));
558 retired_ = std::move(keep);
561void VoxelWorld::dropStreamJobsOutside(
int centerX,
int centerY,
int centerZ,
int radiusChunks) {
562 const int64_t r2 = int64_t(radiusChunks) * int64_t(radiusChunks);
563 std::vector<StreamJob> keep;
564 keep.reserve(inflight_.size());
565 for (StreamJob &job : inflight_) {
566 const int64_t
dx = int64_t(job.cx) - centerX;
567 const int64_t
dy = int64_t(job.cy) - centerY;
568 const int64_t
dz = int64_t(job.cz) - centerZ;
570 keep.push_back(std::move(job));
573 retired_.push_back(std::move(job));
575 inflight_ = std::move(keep);
578int VoxelWorld::harvestStreamJobs(
int centerX,
int centerY,
int centerZ,
int radiusChunks,
580 const int64_t r2 = int64_t(radiusChunks) * int64_t(radiusChunks);
582 std::vector<StreamJob> keep;
583 keep.reserve(inflight_.size());
584 for (StreamJob &job : inflight_) {
585 if (!job.job || !job.job->isDone() || (maxHarvest > 0 && harvested >= maxHarvest)) {
586 keep.push_back(std::move(job));
592 const int64_t
dx = int64_t(job.cx) - centerX;
593 const int64_t
dy = int64_t(job.cy) - centerY;
594 const int64_t
dz = int64_t(job.cz) - centerZ;
596 if (!
inside ||
hasChunk(job.cx, job.cy, job.cz) || !job.chunk)
continue;
597 chunks_.emplace(
key(job.cx, job.cy, job.cz), std::move(job.chunk));
600 inflight_ = std::move(keep);
601 if (harvested > 0) ++revision_;
605void VoxelWorld::remeshChunks(
const std::vector<Chunk *> &chunks,
int maxThreads) {
606 const int count = int(chunks.size());
607 if (
count == 0)
return;
609 const auto remeshOne = [
this](Chunk *
c) {
611 c->remesh(types_, &VoxelWorld::chunkNeighborSampler,
this);
614 int workers = maxThreads;
616 workers =
static_cast<int>(std::thread::hardware_concurrency());
617 if (workers < 1) workers = 1;
618 if (workers > kMaxRemeshWorkers) workers = kMaxRemeshWorkers;
621 if (workers <= 1 ||
count <= 1) {
622 for (Chunk *
c : chunks) remeshOne(
c);
626 auto *jobs = thread::Thread::create()->getJobSystem();
627 thread::Job *
loop =
nullptr;
630 [
this, &chunks, &remeshOne](
int first,
int last) {
631 for (
int k =
first; k < last; ++k) remeshOne(chunks[
size_t(k)]);
633 (
count + workers - 1) / workers);
635 for (Chunk *
c : chunks) remeshOne(
c);
643 std::vector<Chunk *>
dirty;
644 dirty.reserve(chunks_.size());
645 for (
auto &kv : chunks_) {
646 if (!kv.second->isDirty())
continue;
647 dirty.push_back(kv.second.get());
648 if (maxChunks > 0 &&
int(
dirty.size()) >= maxChunks)
break;
650 remeshChunks(
dirty, maxThreads);
651 return int(
dirty.size());
655 float viewRange,
bool faceCull) {
657 visibleChunkKeys_.clear();
658 if (!viewProj16)
return;
661 const float rangeSq = viewRange > 0.f ? viewRange * viewRange : 0.f;
663 for (
auto &kv : chunks_) {
666 float minX, minY, minZ, maxX, maxY, maxZ;
667 chunk->
worldAABB(minX, minY, minZ, maxX, maxY, maxZ);
668 const float cx = (minX + maxX) * 0.5f;
669 const float cy = (minY + maxY) * 0.5f;
670 const float cz = (minZ + maxZ) * 0.5f;
672 if (viewRange > 0.f) {
673 const float dx =
cx - eyeX;
674 const float dy =
cy - eyeY;
675 const float dz = cz - eyeZ;
679 if (!
frustum.intersectsAABB(minX, minY, minZ, maxX, maxY, maxZ))
continue;
683 chunk->
ensureMeshed(types_, &VoxelWorld::chunkNeighborSampler,
this);
685 visibleChunkKeys_.push_back(kv.first);
690 if (
count <= 0)
continue;
698 bool allBackFacing =
false;
708 if (allBackFacing)
continue;
717 visible_.push_back(batch);
723 if (index < 0 || index >=
int(visibleChunkKeys_.size())) {
727 unpackKey(visibleChunkKeys_[
size_t(
index)],
cx,
cy, cz);
732 for (
const auto &
b : visible_)
n +=
b.count;
738 for (
const auto &
b : visible_) {
739 if (!
b.chunk || !
b.packed ||
b.count <= 0)
continue;
747 const int cx = floorDiv(
wx);
748 const int cy = floorDiv(
wy);
749 const int cz = floorDiv(
wz);
756 const int cx = floorDiv(
wx);
757 const int cy = floorDiv(
wy);
758 const int cz = floorDiv(
wz);
764 if (
c &&
c->get(lx, ly, lz) == texId)
return;
768 c->set(lx, ly, lz, 0);
771 c->set(lx, ly, lz, texId);
774 markNeighborChunksDirty(
cx,
cy, cz, lx, ly, lz);
777void VoxelWorld::markNeighborChunksDirty(
int cx,
int cy,
int cz,
int lx,
int ly,
int lz) {
778 auto mark = [
this](
int nx,
int ny,
int nz) {
781 if (lx == 0) mark(
cx - 1,
cy, cz);
783 if (ly == 0) mark(
cx,
cy - 1, cz);
785 if (lz == 0) mark(
cx,
cy, cz - 1);
789uint8_t VoxelWorld::chunkNeighborSampler(
void *userData,
int chunkX,
int chunkY,
int chunkZ,
790 int localX,
int localY,
int localZ) {
791 const auto *self =
static_cast<const VoxelWorld *
>(userData);
795 return self->getVoxel(
wx,
wy,
wz);
799 float maxDist,
int &hitX,
int &hitY,
int &hitZ,
int &prevX,
800 int &prevY,
int &prevZ,
int &faceX,
int &faceY,
int &faceZ)
const {
802 if (lenSq <= 1e-12f || maxDist <= 0.f)
return false;
803 const float invLen = 1.f / std::sqrt(lenSq);
804 const float rx =
dx * invLen;
805 const float ry =
dy * invLen;
806 const float rz =
dz * invLen;
808 int ix = int(std::floor(
ox));
809 int iy = int(std::floor(
oy));
810 int iz = int(std::floor(oz));
816 faceX = faceY = faceZ = 0;
820 const float inf = std::numeric_limits<float>::infinity();
821 const int stepX = rx > 0.f ? 1 : -1;
822 const int stepY = ry > 0.f ? 1 : -1;
823 const int stepZ = rz > 0.f ? 1 : -1;
824 const float absInvX = rx != 0.f ? std::fabs(1.f / rx) : inf;
825 const float absInvY = ry != 0.f ? std::fabs(1.f / ry) : inf;
826 const float absInvZ = rz != 0.f ? std::fabs(1.f / rz) : inf;
827 float tMaxX = rx != 0.f ? (rx > 0.f ? float(ix + 1) -
ox :
ox - float(ix)) * absInvX : inf;
828 float tMaxY = ry != 0.f ? (ry > 0.f ? float(iy + 1) -
oy :
oy - float(iy)) * absInvY : inf;
829 float tMaxZ = rz != 0.f ? (rz > 0.f ? float(iz + 1) - oz : oz - float(iz)) * absInvZ : inf;
834 faceX = faceY = faceZ = 0;
836 constexpr int kMaxSteps = 4096;
837 for (
int iter = 0; iter < kMaxSteps; ++iter) {
839 if (tMaxX <= tMaxY && tMaxX <= tMaxZ) {
846 }
else if (tMaxY <= tMaxZ) {
861 if (
t > maxDist)
return false;
877 raycastHit_ =
raycast(
ox,
oy, oz,
dx,
dy,
dz, maxDist, raycastHitX_, raycastHitY_,
878 raycastHitZ_, raycastPrevX_, raycastPrevY_, raycastPrevZ_,
879 raycastFaceX_, raycastFaceY_, raycastFaceZ_);
892 return t ?
t->
name : std::string{};
vk::UniqueSampler sampler
std::array< float, 3 > scale
std::vector< char > inside
In-memory byte buffer implementing eve::Data (ref-counted).
virtual void drawVoxelFaceInstances(const uint32_t *packed, int count, float originX, float originY, float originZ, const std::string &faceDir, Texture *atlas, int tilesPerRow=16, const uint32_t *ao=nullptr)=0
Instanced voxel face rectangles (32-bit packed instances). ao: optional per-instance ambient-occlusio...
GPU texture created via Graphics::newTexture. Owns GPU resources through an opaque backend handle.
int getChunkSize() const
Returns the chunk size.
Deterministic terrain height sampling (Red Blob Games / Perlin fBm recipe).
bool open(const uint8_t *data, size_t size, std::string *error=nullptr)
Compatibility operation that opens EVTR and clears decoded chunks.
bool sampleHeight(float worldX, float worldY, float &out) const
Compatibility operation for cross-chunk bilinear height sampling.
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.
const TerrainAsset & asset() const
Asset.
Abstract job scheduler: dependencies, parallel_for, task_group, arena.
virtual Job * submit(JobFunc body)=0
Create a job and schedule it immediately.
virtual bool isRunning() const =0
Whether the system is still accepting new jobs.
One 32³ voxel chunk with six direction-sorted packed-rect instance buffers. Coordinates: chunk (cx,...
const uint32_t * faceAOPackedData(FaceDir dir) const
Face ao packed data.
void ensureMeshed(const CubeTypeRegistry &types=CubeTypeRegistry::empty(), ChunkSampler sampler=nullptr, void *samplerUserData=nullptr)
Ensure mesh is up to date; remesh if dirty.
void worldAABB(float &minX, float &minY, float &minZ, float &maxX, float &maxY, float &maxZ) const
World aabb.
const uint32_t * facePackedData(FaceDir dir) const
Face packed data.
int faceRectCount(FaceDir dir) const
Face rect count.
uint8_t get(int x, int y, int z) const
Returns the value.
void remesh(const CubeTypeRegistry &types=CubeTypeRegistry::empty(), ChunkSampler sampler=nullptr, void *samplerUserData=nullptr)
Rebuild six face instance buffers via greedy meshing.
EVENGINE_API_DOMAINS public API.
const CubeType * find(const std::string &name) const
按名字返回 0 度变体;未找到返回 nullptr。
uint8_t variantId(const std::string &name, int orientation) const
名字 + orientation(0..3) 对应的具体类型 id;非方向性类型忽略 orientation。
int remeshDirty(int maxThreads=0, int maxChunks=0)
Remesh dirty chunks.
bool loadTerrainAsset(data::ByteData *bytes, float heightOffset=0.f, float heightScale=1.f)
Compatibility operation that opens baked EVTR terrain data.
data::ByteData * saveWorld() const
Script-facing wrappers around serialize/deserialize.
int terrainHeightAt(int wx, int wz) const
Terrain height (world blocks) at a column for the configured seed.
void setVoxelByName(int wx, int wy, int wz, const std::string &name, int orientation=0)
按方块名 + orientation(0..3) 设置体素;内部解析为具体类型 id 后写 Chunk。 未注册的名字按空气(0)处理。
void setTerrainParams(uint32_t seed, uint8_t top, uint8_t sub, uint8_t stone, float baseHeight, float amplitude, float scale)
Configure the built-in terrain generator used by streamAround(). The sampling itself lives in procgen...
uint8_t getCubeTypeTex(int wx, int wy, int wz, const std::string &faceDir) const
该体素在某面方向上的纹理 id(faceDir 如 "posX"/"+y"/"negZ")。
uint8_t getVoxel(int wx, int wy, int wz) const
World-space voxel get/set. Air (0) never allocates a chunk; a border edit also invalidates the adjace...
void drawVisible(graphics::Graphics *gfx, graphics::Texture *atlas, int tilesPerRow=16)
Issue Graphics::drawVoxelFaceInstances for every visible batch. Requires begin3DFrame + setMesh3DView...
~VoxelWorld()
Voxel world.
Chunk * getOrCreateChunk(int cx, int cy, int cz)
Returns the or create chunk.
procgen::TerrainStreamStats streamTerrainAssetAround(int worldX, int worldZ, int radiusChunks, int maxLoads=0)
Decode/evict EVTR chunks around a world-space column with an optional load budget.
bool raycast(float ox, float oy, float oz, float dx, float dy, float dz, float maxDist, int &hitX, int &hitY, int &hitZ, int &prevX, int &prevY, int &prevZ, int &faceX, int &faceY, int &faceZ) const
Voxel raycast (Amanatides & Woo DDA). Returns true when a solid voxel is found within maxDist world u...
bool deserializeWorld(const uint8_t *data, size_t size)
Deserialize world.
void setVoxel(int wx, int wy, int wz, uint8_t texId)
Sets the voxel.
bool raycastScript(float ox, float oy, float oz, float dx, float dy, float dz, float maxDist)
Script-facing raycast: stores the last result, returns hit/miss.
int getDirtyCount() const
Number of chunks whose mesh is stale.
std::string getCubeTypeName(int wx, int wy, int wz) const
该体素所属方块类型名(未注册或空气返回空串)。
bool loadWorld(data::ByteData *bytes)
Loads world.
void disableTerrain()
Disables terrain.
void selectVisible(const float *viewProj16, float eyeX, float eyeY, float eyeZ, float viewRange, bool faceCull=true)
Select chunks/faces to draw.
int getInflightStreamCount() const
Terrain jobs still running (not yet inserted into the world).
void removeChunk(int cx, int cy, int cz)
Removes chunk.
Chunk * getChunk(int cx, int cy, int cz)
Returns the chunk.
void getVisibleChunkCoord(int index, int &cx, int &cy, int &cz) const
Returns the visible chunk coord.
bool hasChunk(int cx, int cy, int cz) const
True when chunk.
StreamStats streamAround(int centerX, int centerY, int centerZ, int radiusChunks, const std::function< void(Chunk &, int, int, int)> &generator={}, int maxCreates=0)
Player-centered streaming: fill missing chunks inside radiusChunks, unload only past radius + getStre...
int getVisibleRectCount() const
Returns the visible rect count.
void setStreamCacheChunks(int extraChunks)
Extra chunk radius kept after leaving the stream sphere. streamAround still creates only inside radiu...
void setTerrainAssetMaterials(uint8_t vegetation, uint8_t sand, uint8_t snow, uint8_t alpine, uint8_t riverbed)
Configure biome surface ids: vegetation, sand, snow, alpine rock, river bed.
int unloadChunksOutside(int centerX, int centerY, int centerZ, int radiusChunks)
Streaming eviction: drop chunks whose center is farther than radiusChunks chunk units from (centerX,...
void setTerrainParam(const std::string &key, float value)
细粒度地形参数配置(脚本用)。key 支持: seed/top/sub/stone/sand/base/amplitude/scale(=frequency)/ octaves/lacunarity/g...
void serializeWorld(std::vector< uint8_t > &out) const
Persistence: serialize every chunk (coords + raw voxels) into out. Format is portable little-endian: ...
方块类型定义:名字、各面图集纹理、方向性、组合声明。 方向性方块在注册时按 orientation 绕 Y 轴展开成多个"具体类型"变体, 每个变体持有旋转后的各面纹理;渲染端只消费纹理 id,不接触 ...
constexpr int kChunkSize
Chunk edge length in voxels (fixed).
const char * faceDirName(FaceDir d)
Face dir name.
uint8_t resolveFaceTex(const CubeTypeRegistry &types, uint8_t id, FaceDir dir)
求某个体素在某面方向上的实际图集纹理 id。 未注册的 id 退化为“所有面 = 原 id”(向后兼容:体素值即纹理 id)。
constexpr int faceDirCount()
Face dir count.
bool faceDirFromName(const std::string &name, FaceDir &out)
Face dir from name.
FaceDir
Six axis-aligned face directions. Each chunk keeps a separate instance buffer per direction so camera...
TerrainSample public API.
TerrainStreamStats public API.
One draw batch: all packed rects of one face direction for one chunk.
Six frustum planes in ax+by+cz+d >= 0 form (normals point inward).
static Frustum fromViewProjColumnMajor(const float *m16)
Extract from a column-major 4x4 view-projection matrix (RH + Vulkan ZO). Layout matches glm::mat4 mem...
Result of a streaming pass.