载入中...
搜索中...
未找到
VoxelWorld.cpp
浏览该文件的文档.
1#include "voxel/VoxelWorld.h"
2
4
5#include "data/ByteData.h"
7#include "thread/Thread.h"
8
9#include <algorithm>
10#include <cmath>
11#include <cstring>
12#include <limits>
13#include <thread>
14
15namespace eve::voxel {
16
17VoxelWorld::~VoxelWorld() = default;
18
19void VoxelWorld::setTerrainParams(uint32_t seed, uint8_t top, uint8_t sub, uint8_t stone, float baseHeight,
20 float amplitude, float scale) {
21 if (!terrainSampler_) terrainSampler_ = std::make_unique<procgen::TerrainSampler>();
22 terrainSampler_->setSeed(seed);
23 terrainSampler_->setBase(0.f);
24 terrainSampler_->setAmplitude(1.f);
25 terrainSampler_->setClamp(true, 0.f, 1.f);
26 terrainSampler_->setFrequency(scale > 0.f ? scale : 1.f / 32.f);
27 terrainTop_ = top;
28 terrainSub_ = sub;
29 terrainStone_ = stone;
30 terrainBase_ = baseHeight;
31 terrainAmplitude_ = amplitude < 0.f ? 0.f : amplitude;
32 terrainEnabled_ = true;
33}
34
35int VoxelWorld::terrainHeightAt(int wx, int wz) const {
36 if (!terrainSampler_) return int(terrainBase_);
37 const float e = terrainSampler_->sample(float(wx), float(wz));
38 return int(std::floor(terrainBase_ + terrainAmplitude_ * e));
39}
40
41namespace {
42// Cap remesh worker count: enough to parallelize chunk meshing without
43// oversubscribing small devices / browser pthread pools.
44constexpr int kMaxRemeshWorkers = 4;
45} // namespace
46
48 const uint64_t k = key(cx, cy, cz);
49 auto it = chunks_.find(k);
50 if (it != chunks_.end()) return it->second.get();
51 auto chunk = std::make_unique<Chunk>(cx, cy, cz);
52 Chunk *raw = chunk.get();
53 chunks_.emplace(k, std::move(chunk));
54 return raw;
55}
56
57Chunk *VoxelWorld::getChunk(int cx, int cy, int cz) {
58 auto it = chunks_.find(key(cx, cy, cz));
59 return it == chunks_.end() ? nullptr : it->second.get();
60}
61
62const Chunk *VoxelWorld::getChunk(int cx, int cy, int cz) const {
63 auto it = chunks_.find(key(cx, cy, cz));
64 return it == chunks_.end() ? nullptr : it->second.get();
65}
66
67bool VoxelWorld::hasChunk(int cx, int cy, int cz) const {
68 return chunks_.find(key(cx, cy, cz)) != chunks_.end();
69}
70
71void VoxelWorld::removeChunk(int cx, int cy, int cz) { chunks_.erase(key(cx, cy, cz)); }
72
74 chunks_.clear();
75 visible_.clear();
76 visibleChunkKeys_.clear();
77}
78
79int VoxelWorld::unloadChunksOutside(int centerX, int centerY, int centerZ, int radiusChunks) {
80 if (radiusChunks < 0) return 0;
81 const int64_t r2 = int64_t(radiusChunks) * int64_t(radiusChunks);
82 std::vector<uint64_t> evict;
83 evict.reserve(chunks_.size() / 4);
84 for (auto &kv : chunks_) {
85 int cx, cy, cz;
86 unpackKey(kv.first, cx, cy, cz);
87 const int64_t dx = int64_t(cx) - centerX;
88 const int64_t dy = int64_t(cy) - centerY;
89 const int64_t dz = int64_t(cz) - centerZ;
90 if (dx * dx + dy * dy + dz * dz > r2) evict.push_back(kv.first);
91 }
92 for (uint64_t k : evict) chunks_.erase(k);
93 if (!evict.empty()) {
94 // Batch pointers may dangle after eviction; force re-selection.
95 visible_.clear();
96 visibleChunkKeys_.clear();
97 }
98 return int(evict.size());
99}
100
101StreamStats VoxelWorld::streamAround(int centerX, int centerY, int centerZ, int radiusChunks,
102 const std::function<void(Chunk &, int, int, int)> &generator) {
103 StreamStats stats;
104 if (radiusChunks < 0) return stats;
105
106 // Evict first so far-away dirty chunks are not remeshed below.
107 stats.evicted = unloadChunksOutside(centerX, centerY, centerZ, radiusChunks);
108
109 const int64_t r2 = int64_t(radiusChunks) * int64_t(radiusChunks);
110 for (int dz = -radiusChunks; dz <= radiusChunks; ++dz)
111 for (int dy = -radiusChunks; dy <= radiusChunks; ++dy)
112 for (int dx = -radiusChunks; dx <= radiusChunks; ++dx) {
113 const int64_t d2 = int64_t(dx) * dx + int64_t(dy) * dy + int64_t(dz) * dz;
114 if (d2 > r2) continue;
115 const int nx = centerX + dx;
116 const int ny = centerY + dy;
117 const int nz = centerZ + dz;
118 if (hasChunk(nx, ny, nz)) continue;
119 Chunk *c = getOrCreateChunk(nx, ny, nz);
120 if (generator)
121 generator(*c, nx, ny, nz);
122 else if (terrainEnabled_) {
123 // Heightmap terrain via procgen::TerrainSampler: one column
124 // per (x, z); sampling world coords keeps chunk seams flush.
125 const int wy0 = ny * kChunkSize;
126 for (int lz = 0; lz < kChunkSize; ++lz)
127 for (int lx = 0; lx < kChunkSize; ++lx) {
128 const int h = terrainHeightAt(nx * kChunkSize + lx,
129 nz * kChunkSize + lz);
130 for (int ly = 0; ly < kChunkSize; ++ly) {
131 const int wy = wy0 + ly;
132 if (wy <= h - 4)
133 c->set(lx, ly, lz, terrainStone_);
134 else if (wy <= h - 1)
135 c->set(lx, ly, lz, terrainSub_);
136 else if (wy == h)
137 c->set(lx, ly, lz, terrainTop_);
138 }
139 }
140 }
141 ++stats.created;
142 }
143
144 if (stats.created > 0) remeshDirty();
145 return stats;
146}
147
148namespace {
149
150void putU32(std::vector<uint8_t> &out, uint32_t v) {
151 out.push_back(uint8_t(v));
152 out.push_back(uint8_t(v >> 8));
153 out.push_back(uint8_t(v >> 16));
154 out.push_back(uint8_t(v >> 24));
155}
156
157void putI32(std::vector<uint8_t> &out, int32_t v) { putU32(out, uint32_t(v)); }
158
159bool getU32(const uint8_t *&p, const uint8_t *end, uint32_t &out) {
160 if (end - p < 4) return false;
161 out = uint32_t(p[0]) | (uint32_t(p[1]) << 8) | (uint32_t(p[2]) << 16) |
162 (uint32_t(p[3]) << 24);
163 p += 4;
164 return true;
165}
166
167bool getI32(const uint8_t *&p, const uint8_t *end, int32_t &out) {
168 uint32_t v;
169 if (!getU32(p, end, v)) return false;
170 out = int32_t(v);
171 return true;
172}
173
174} // namespace
175
176void VoxelWorld::serializeWorld(std::vector<uint8_t> &out) const {
177 out.clear();
178 const char magic[4] = {'E', 'V', 'V', 'X'};
179 out.insert(out.end(), magic, magic + 4);
180 out.push_back(1); // version
181 putU32(out, uint32_t(chunks_.size()));
182
183 // Deterministic output: sort chunk keys so saves are byte-stable.
184 std::vector<uint64_t> keys;
185 keys.reserve(chunks_.size());
186 for (auto &kv : chunks_) keys.push_back(kv.first);
187 std::sort(keys.begin(), keys.end());
188 for (uint64_t key : keys) {
189 const auto &chunk = chunks_.at(key);
190 int cx, cy, cz;
191 unpackKey(key, cx, cy, cz);
192 putI32(out, int32_t(cx));
193 putI32(out, int32_t(cy));
194 putI32(out, int32_t(cz));
195 const uint8_t *raw = chunk->rawVoxels();
196 out.insert(out.end(), raw, raw + kChunkSize * kChunkSize * kChunkSize);
197 }
198}
199
200bool VoxelWorld::deserializeWorld(const uint8_t *data, size_t size) {
201 const uint8_t *p = data;
202 const uint8_t *end = data + size;
203 if (size < 9 || std::memcmp(p, "EVVX", 4) != 0 || p[4] != 1) return false;
204 p += 5;
205 uint32_t count = 0;
206 if (!getU32(p, end, count)) return false;
207 const size_t voxelBytes = size_t(kChunkSize) * kChunkSize * kChunkSize;
208 if (uint64_t(count) > (uint64_t(end - p)) / (12 + voxelBytes)) return false;
209
210 clear();
211 for (uint32_t i = 0; i < count; ++i) {
212 int32_t cx = 0, cy = 0, cz = 0;
213 if (!getI32(p, end, cx) || !getI32(p, end, cy) || !getI32(p, end, cz)) return false;
214 if (size_t(end - p) < voxelBytes) return false;
215 Chunk *c = getOrCreateChunk(cx, cy, cz);
216 c->setVoxelData(p);
217 p += voxelBytes;
218 }
219 return true;
220}
221
223 std::vector<uint8_t> bytes;
224 serializeWorld(bytes);
225 return new data::ByteData(bytes.data(), bytes.size());
226}
227
229 if (!bytes) return false;
230 return deserializeWorld(static_cast<const uint8_t *>(bytes->getData()), bytes->getSize());
231}
232
233int VoxelWorld::remeshDirty(int maxThreads) {
234 std::vector<Chunk *> dirty;
235 dirty.reserve(chunks_.size());
236 for (auto &kv : chunks_) {
237 if (kv.second->isDirty()) dirty.push_back(kv.second.get());
238 }
239 const int count = int(dirty.size());
240 if (count == 0) return 0;
241
242 const auto remeshOne = [this](Chunk *c) {
243 c->remesh(types_, &VoxelWorld::chunkNeighborSampler, this);
244 };
245
246 int workers = maxThreads;
247 if (workers <= 0) {
248 workers = static_cast<int>(std::thread::hardware_concurrency());
249 if (workers < 1) workers = 1;
250 if (workers > kMaxRemeshWorkers) workers = kMaxRemeshWorkers;
251 }
252
253 if (workers <= 1 || count <= 1) {
254 for (Chunk *c : dirty) remeshOne(c);
255 return count;
256 }
257
258 // Parallel remesh through the engine JobSystem: each child task remeshes
259 // its own slice of distinct chunks. The sampler only reads the chunk map
260 // (no concurrent mutation), and each chunk is touched by exactly one task,
261 // so this is safe. wait() on the loop joins every slice.
262 auto *jobs = thread::Thread::create()->getJobSystem();
263 thread::Job *loop = nullptr;
264 try {
265 loop = jobs->parallelFor(0, count,
266 [this, &dirty, &remeshOne](int first, int last) {
267 for (int k = first; k < last; ++k) remeshOne(dirty[size_t(k)]);
268 },
269 (count + workers - 1) / workers);
270 } catch (...) {
271 // Job allocation failed (resource limits): finish everything serially.
272 // Remesh is idempotent, so any chunks already handled are done twice.
273 for (Chunk *c : dirty) remeshOne(c);
274 return count;
275 }
276 loop->wait();
277 delete loop;
278 return count;
279}
280
281void VoxelWorld::selectVisible(const float *viewProj16, float eyeX, float eyeY, float eyeZ,
282 float viewRange, bool faceCull) {
283 visible_.clear();
284 visibleChunkKeys_.clear();
285 if (!viewProj16) return;
286
288 const float rangeSq = viewRange > 0.f ? viewRange * viewRange : 0.f;
289
290 for (auto &kv : chunks_) {
291 Chunk *chunk = kv.second.get();
292
293 float minX, minY, minZ, maxX, maxY, maxZ;
294 chunk->worldAABB(minX, minY, minZ, maxX, maxY, maxZ);
295 const float cx = (minX + maxX) * 0.5f;
296 const float cy = (minY + maxY) * 0.5f;
297 const float cz = (minZ + maxZ) * 0.5f;
298
299 if (viewRange > 0.f) {
300 const float dx = cx - eyeX;
301 const float dy = cy - eyeY;
302 const float dz = cz - eyeZ;
303 if (dx * dx + dy * dy + dz * dz > rangeSq) continue;
304 }
305
306 if (!frustum.intersectsAABB(minX, minY, minZ, maxX, maxY, maxZ)) continue;
307
308 // Mesh only after range/frustum culling so edits far outside the view
309 // are not remeshed every frame.
310 chunk->ensureMeshed(types_, &VoxelWorld::chunkNeighborSampler, this);
311
312 visibleChunkKeys_.push_back(kv.first);
313
314 const float toCamX = eyeX - cx;
315 const float toCamY = eyeY - cy;
316 const float toCamZ = eyeZ - cz;
317 for (int i = 0; i < faceDirCount(); ++i) {
318 const FaceDir dir = FaceDir(i);
319 const int count = chunk->faceRectCount(dir);
320 if (count <= 0) continue;
321
322 if (faceCull) {
323 float nx, ny, nz;
324 faceNormal(dir, nx, ny, nz);
325 if (nx * toCamX + ny * toCamY + nz * toCamZ <= 0.f) continue;
326 }
327
328 DrawBatch batch;
329 batch.chunk = chunk;
330 batch.dir = dir;
331 batch.packed = chunk->facePackedData(dir);
332 batch.ao = chunk->faceAOPackedData(dir);
333 batch.count = count;
334 visible_.push_back(batch);
335 }
336 }
337}
338
339void VoxelWorld::getVisibleChunkCoord(int index, int &cx, int &cy, int &cz) const {
340 if (index < 0 || index >= int(visibleChunkKeys_.size())) {
341 cx = cy = cz = 0;
342 return;
343 }
344 unpackKey(visibleChunkKeys_[size_t(index)], cx, cy, cz);
345}
346
348 int n = 0;
349 for (const auto &b : visible_) n += b.count;
350 return n;
351}
352
354 if (!gfx) return;
355 for (const auto &b : visible_) {
356 if (!b.chunk || !b.packed || b.count <= 0) continue;
357 gfx->drawVoxelFaceInstances(b.packed, b.count, b.chunk->originX(), b.chunk->originY(),
358 b.chunk->originZ(), faceDirName(b.dir), atlas, tilesPerRow,
359 b.ao);
360 }
361}
362
363uint8_t VoxelWorld::getVoxel(int wx, int wy, int wz) const {
364 const int cx = floorDiv(wx);
365 const int cy = floorDiv(wy);
366 const int cz = floorDiv(wz);
367 const Chunk *c = getChunk(cx, cy, cz);
368 if (!c) return 0;
369 return c->get(wx - cx * kChunkSize, wy - cy * kChunkSize, wz - cz * kChunkSize);
370}
371
372void VoxelWorld::setVoxel(int wx, int wy, int wz, uint8_t texId) {
373 const int cx = floorDiv(wx);
374 const int cy = floorDiv(wy);
375 const int cz = floorDiv(wz);
376 const int lx = wx - cx * kChunkSize;
377 const int ly = wy - cy * kChunkSize;
378 const int lz = wz - cz * kChunkSize;
379
380 Chunk *c = getChunk(cx, cy, cz);
381 if (texId == 0) {
382 // Clearing an unallocated chunk is a no-op (air needs no storage).
383 if (!c) return;
384 c->set(lx, ly, lz, 0);
385 } else {
386 if (!c) c = getOrCreateChunk(cx, cy, cz);
387 c->set(lx, ly, lz, texId);
388 }
389 markNeighborChunksDirty(cx, cy, cz, lx, ly, lz);
390}
391
392void VoxelWorld::markNeighborChunksDirty(int cx, int cy, int cz, int lx, int ly, int lz) {
393 auto mark = [this](int nx, int ny, int nz) {
394 if (Chunk *n = getChunk(nx, ny, nz)) n->markDirty();
395 };
396 if (lx == 0) mark(cx - 1, cy, cz);
397 if (lx == kChunkSize - 1) mark(cx + 1, cy, cz);
398 if (ly == 0) mark(cx, cy - 1, cz);
399 if (ly == kChunkSize - 1) mark(cx, cy + 1, cz);
400 if (lz == 0) mark(cx, cy, cz - 1);
401 if (lz == kChunkSize - 1) mark(cx, cy, cz + 1);
402}
403
404uint8_t VoxelWorld::chunkNeighborSampler(void *userData, int chunkX, int chunkY, int chunkZ,
405 int localX, int localY, int localZ) {
406 const auto *self = static_cast<const VoxelWorld *>(userData);
407 const int wx = chunkX * kChunkSize + localX;
408 const int wy = chunkY * kChunkSize + localY;
409 const int wz = chunkZ * kChunkSize + localZ;
410 return self->getVoxel(wx, wy, wz);
411}
412
413bool VoxelWorld::raycast(float ox, float oy, float oz, float dx, float dy, float dz,
414 float maxDist, int &hitX, int &hitY, int &hitZ, int &prevX,
415 int &prevY, int &prevZ, int &faceX, int &faceY, int &faceZ) const {
416 const float lenSq = dx * dx + dy * dy + dz * dz;
417 if (lenSq <= 1e-12f || maxDist <= 0.f) return false;
418 const float invLen = 1.f / std::sqrt(lenSq);
419 const float rx = dx * invLen;
420 const float ry = dy * invLen;
421 const float rz = dz * invLen;
422
423 int ix = int(std::floor(ox));
424 int iy = int(std::floor(oy));
425 int iz = int(std::floor(oz));
426
427 if (getVoxel(ix, iy, iz) != 0) {
428 hitX = prevX = ix;
429 hitY = prevY = iy;
430 hitZ = prevZ = iz;
431 faceX = faceY = faceZ = 0;
432 return true;
433 }
434
435 const float inf = std::numeric_limits<float>::infinity();
436 const int stepX = rx > 0.f ? 1 : -1;
437 const int stepY = ry > 0.f ? 1 : -1;
438 const int stepZ = rz > 0.f ? 1 : -1;
439 const float absInvX = rx != 0.f ? std::fabs(1.f / rx) : inf;
440 const float absInvY = ry != 0.f ? std::fabs(1.f / ry) : inf;
441 const float absInvZ = rz != 0.f ? std::fabs(1.f / rz) : inf;
442 float tMaxX = rx != 0.f ? (rx > 0.f ? float(ix + 1) - ox : ox - float(ix)) * absInvX : inf;
443 float tMaxY = ry != 0.f ? (ry > 0.f ? float(iy + 1) - oy : oy - float(iy)) * absInvY : inf;
444 float tMaxZ = rz != 0.f ? (rz > 0.f ? float(iz + 1) - oz : oz - float(iz)) * absInvZ : inf;
445
446 prevX = ix;
447 prevY = iy;
448 prevZ = iz;
449 faceX = faceY = faceZ = 0;
450
451 constexpr int kMaxSteps = 4096;
452 for (int iter = 0; iter < kMaxSteps; ++iter) {
453 float t;
454 if (tMaxX <= tMaxY && tMaxX <= tMaxZ) {
455 ix += stepX;
456 t = tMaxX;
457 tMaxX += absInvX;
458 faceX = -stepX;
459 faceY = 0;
460 faceZ = 0;
461 } else if (tMaxY <= tMaxZ) {
462 iy += stepY;
463 t = tMaxY;
464 tMaxY += absInvY;
465 faceX = 0;
466 faceY = -stepY;
467 faceZ = 0;
468 } else {
469 iz += stepZ;
470 t = tMaxZ;
471 tMaxZ += absInvZ;
472 faceX = 0;
473 faceY = 0;
474 faceZ = -stepZ;
475 }
476 if (t > maxDist) return false;
477 if (getVoxel(ix, iy, iz) != 0) {
478 hitX = ix;
479 hitY = iy;
480 hitZ = iz;
481 return true;
482 }
483 prevX = ix;
484 prevY = iy;
485 prevZ = iz;
486 }
487 return false;
488}
489
490bool VoxelWorld::raycastScript(float ox, float oy, float oz, float dx, float dy, float dz,
491 float maxDist) {
492 raycastHit_ = raycast(ox, oy, oz, dx, dy, dz, maxDist, raycastHitX_, raycastHitY_,
493 raycastHitZ_, raycastPrevX_, raycastPrevY_, raycastPrevZ_,
494 raycastFaceX_, raycastFaceY_, raycastFaceZ_);
495 return raycastHit_;
496}
497
498void VoxelWorld::setVoxelByName(int wx, int wy, int wz, const std::string &name, int orientation) {
499 const CubeType *t = types_.find(name);
500 uint8_t id = 0;
501 if (t) id = types_.variantId(name, orientation);
502 setVoxel(wx, wy, wz, id);
503}
504
505std::string VoxelWorld::getCubeTypeName(int wx, int wy, int wz) const {
506 const CubeType *t = types_.find(getVoxel(wx, wy, wz));
507 return t ? t->name : std::string{};
508}
509
510uint8_t VoxelWorld::getCubeTypeTex(int wx, int wy, int wz, const std::string &faceDir) const {
511 FaceDir d;
512 if (!faceDirFromName(faceDir, d)) return 0;
513 const uint8_t id = getVoxel(wx, wy, wz);
514 return resolveFaceTex(types_, id, d);
515}
516
517} // namespace eve::voxel
float cx
Definition CardTypes.cpp:31
float cy
Definition CardTypes.cpp:32
uint32_t seed
glm::vec3 n
Definition Grass.cpp:64
int h
uint32_t b
uint32_t c
bool dirty
glm::vec4 p[6]
FrustumPlanes frustum
Light2D::Data * data
const char * name
Definition RockMesh.cpp:21
int d
int v
float scale
Definition TreeMesh.cpp:122
V3 dir
Definition TreeMesh.cpp:121
In-memory byte buffer implementing eve::Data (ref-counted).
Definition ByteData.h:11
void * getData() const override
Gets a pointer to the data. This pointer will obviously not be valid if the Data object is destroyed.
Definition ByteData.cpp:47
size_t getSize() const override
Gets the size of the Data in bytes.
Definition ByteData.cpp:49
3D frame / mesh / light / shadow rendering surface.
Definition IGraphics3D.h:28
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
GPU texture created via Graphics::newTexture. Owns GPU resources through an opaque backend handle.
Definition Texture.h:17
Handle to a single job inside a JobSystem.
Definition JobSystem.h:41
virtual void wait()=0
Block until this job finishes (done or failed).
One 32³ voxel chunk with six direction-sorted packed-rect instance buffers. Coordinates: chunk (cx,...
Definition Chunk.h:18
const uint32_t * faceAOPackedData(FaceDir dir) const
Definition Chunk.h:103
void ensureMeshed(const CubeTypeRegistry &types=CubeTypeRegistry::empty(), ChunkSampler sampler=nullptr, void *samplerUserData=nullptr)
Ensure mesh is up to date; remesh if dirty.
Definition Chunk.h:82
void worldAABB(float &minX, float &minY, float &minZ, float &maxX, float &maxY, float &maxZ) const
Definition Chunk.h:32
const uint32_t * facePackedData(FaceDir dir) const
Definition Chunk.h:94
int faceRectCount(FaceDir dir) const
Definition Chunk.h:92
uint8_t get(int x, int y, int z) const
Definition Chunk.h:42
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。
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.
StreamStats streamAround(int centerX, int centerY, int centerZ, int radiusChunks, const std::function< void(Chunk &, int, int, int)> &generator={})
Player-centered streaming: evict chunks outside radiusChunks, then allocate + fill + mesh missing chu...
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...
Chunk * getOrCreateChunk(int cx, int cy, int cz)
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)
void drawVisible(graphics::IGraphics3D *gfx, graphics::Texture *atlas, int tilesPerRow=16)
Issue Graphics::drawVoxelFaceInstances for every visible batch. Requires begin3DFrame + setMesh3DView...
void setVoxel(int wx, int wy, int wz, uint8_t texId)
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.
std::string getCubeTypeName(int wx, int wy, int wz) const
该体素所属方块类型名(未注册或空气返回空串)。
bool loadWorld(data::ByteData *bytes)
void selectVisible(const float *viewProj16, float eyeX, float eyeY, float eyeZ, float viewRange, bool faceCull=true)
Select chunks/faces to draw.
void removeChunk(int cx, int cy, int cz)
Chunk * getChunk(int cx, int cy, int cz)
void getVisibleChunkCoord(int index, int &cx, int &cy, int &cz) const
bool hasChunk(int cx, int cy, int cz) const
int getVisibleRectCount() const
int unloadChunksOutside(int centerX, int centerY, int centerZ, int radiusChunks)
Streaming eviction: drop chunks whose center is farther than radiusChunks chunk units from (centerX,...
int remeshDirty(int maxThreads=0)
Remesh every dirty chunk. Returns number remeshed.
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,不接触 ...
Definition Chunk.h:12
constexpr int kChunkSize
Chunk edge length in voxels (fixed).
Definition VoxelPack.h:8
void faceNormal(FaceDir d, float &nx, float &ny, float &nz)
Outward unit normal for the face.
Definition FaceDir.h:85
const char * faceDirName(FaceDir d)
Definition FaceDir.h:29
uint8_t resolveFaceTex(const CubeTypeRegistry &types, uint8_t id, FaceDir dir)
求某个体素在某面方向上的实际图集纹理 id。 未注册的 id 退化为“所有面 = 原 id”(向后兼容:体素值即纹理 id)。
constexpr int faceDirCount()
Definition FaceDir.h:27
bool faceDirFromName(const std::string &name, FaceDir &out)
Definition FaceDir.h:41
FaceDir
Six axis-aligned face directions. Each chunk keeps a separate instance buffer per direction so camera...
Definition FaceDir.h:17
std::string name
Definition CubeType.h:15
One draw batch: all packed rects of one face direction for one chunk.
Definition VoxelWorld.h:39
const uint32_t * packed
Definition VoxelWorld.h:42
const Chunk * chunk
Definition VoxelWorld.h:40
const uint32_t * ao
Definition VoxelWorld.h:43
Six frustum planes in ax+by+cz+d >= 0 form (normals point inward).
Definition Frustum.h:8
static Frustum fromViewProjColumnMajor(const float *m16)
Extract from a column-major 4x4 view-projection matrix (RH + Vulkan ZO). Layout matches glm::mat4 mem...
Definition Frustum.h:15
Result of a streaming pass.
Definition VoxelWorld.h:33