#include #include #include "utils.h" #include "voxel.h" static RcChunk **world_get_chunk_entry(World *world, int cx, int cy, int cz) { int side = RENDER_DISTANCE * 2 + 1; ivec3 min, max; glm_ivec3_subs(world->center, RENDER_DISTANCE, min); glm_ivec3_adds(world->center, RENDER_DISTANCE, max); if (cx < min[0] || cy < min[1] || cz < min[2]) return NULL; if (cx > max[0] || cy > max[1] || cz > max[2]) return NULL; int index = 0; index += ((cx % side + side) % side); index += ((cy % side + side) % side) * side; index += ((cz % side + side) % side) * side * side; return &world->chunks[index]; } static void world_mark_chunk_for_update(World *world, int cx, int cy, int cz) { RcChunk **entry, *inner; entry = world_get_chunk_entry(world, cx, cy, cz); if (entry == NULL) return; inner = *entry; if (inner == NULL) return; if (inner->chunk.state >= CHUNK_MESHED) inner->chunk.state = CHUNK_GENERATED; } static void world_reload_chunk(World *world, int cx, int cy, int cz) { ivec3 pos = { cx, cy, cz }; RcChunk **entry, *inner; entry = world_get_chunk_entry(world, cx, cy, cz); if (entry == NULL) return; inner = *entry; if (inner != NULL) { if (glm_ivec3_eqv(inner->chunk.pos, pos)) { return; } else { *entry = NULL; world_lock(world); rcchunk_drop(inner); world_unlock(world); } } inner = xalloc(sizeof(RcChunk)); inner->rc = 1; chunk_init(&inner->chunk, cx, cy, cz); chunk_generate(&inner->chunk, world->seed); *entry = inner; // mark neighboring chunks as mesh update needed world_mark_chunk_for_update(world, cx - 1, cy, cz); world_mark_chunk_for_update(world, cx + 1, cy, cz); world_mark_chunk_for_update(world, cx, cy - 1, cz); world_mark_chunk_for_update(world, cx, cy + 1, cz); world_mark_chunk_for_update(world, cx, cy, cz - 1); world_mark_chunk_for_update(world, cx, cy, cz + 1); } static void world_reload_chunks(World *world) { ivec3 min, max; glm_ivec3_subs(world->center, RENDER_DISTANCE, min); glm_ivec3_adds(world->center, RENDER_DISTANCE, max); // clang-format off for (int cx = min[0]; cx <= max[0]; cx++) for (int cy = min[1]; cy <= max[1]; cy++) for (int cz = min[2]; cz <= max[2]; cz++) { world_reload_chunk(world, cx, cy, cz); } // clang-format on } void world_init(World *world, int seed) { int side = RENDER_DISTANCE * 2 + 1; int size = side * side * side; world->seed = seed; world->loaded = false; world->chunks = xzalloc(size * sizeof(RcChunk *)); glm_ivec3_zero(world->center); mtx_init(&world->lock, mtx_plain); } void world_free(World *world) { int side = RENDER_DISTANCE * 2 + 1; int size = side * side * side; world_lock(world); for (int i = 0; i < size; i++) { RcChunk *rc_chunk = world->chunks[i]; if (rc_chunk == NULL) continue; rcchunk_drop(rc_chunk); } free(world->chunks); mtx_destroy(&world->lock); } void world_lock(World *world) { mtx_lock(&world->lock); } void world_unlock(World *world) { mtx_unlock(&world->lock); } void world_update(World *world, Camera *camera) { ivec3 cp = CHUNK_VEC_V(camera->position); if (!world->loaded || !glm_ivec3_eqv(world->center, cp)) { // camera has moved, load new chunks glm_ivec3_copy(cp, world->center); world_reload_chunks(world); } } void world_render(World *world, Renderer *renderer) { RcChunk *rc_chunk; ivec3 min, max; int world_size, mesh_quota; glm_ivec3_subs(world->center, RENDER_DISTANCE, min); glm_ivec3_adds(world->center, RENDER_DISTANCE, max); // how many chunk we want to mesh at most world_size = RENDER_DISTANCE * 2 + 1; mesh_quota = world_size * world_size / 10; // clang-format off for (int cx = min[0]; cx <= max[0]; cx++) for (int cy = min[1]; cy <= max[1]; cy++) for (int cz = min[2]; cz <= max[2]; cz++) { rc_chunk = world_get_chunk(world, cx, cy, cz); if (rc_chunk == NULL) continue; if (rc_chunk->chunk.state < CHUNK_MESHING && mesh_quota > 0) { chunk_mesh(&rc_chunk->chunk, world); mesh_quota--; } if (rc_chunk->chunk.has_mesh) renderer_draw(renderer, &rc_chunk->chunk); rcchunk_drop(rc_chunk); } // clang-format on } RcChunk *world_get_chunk(World *world, int cx, int cy, int cz) { RcChunk **entry, *inner; world_lock(world); entry = world_get_chunk_entry(world, cx, cy, cz); if (entry == NULL) goto not_found; inner = *entry; if (inner == NULL) goto not_found; inner->rc += 1; world_unlock(world); return inner; not_found: world_unlock(world); return NULL; } bool world_is_generated(World *world, int cx, int cy, int cz) { RcChunk *rc_chunk; bool generated; rc_chunk = world_get_chunk(world, cx, cy, cz); if (rc_chunk == NULL) return false; generated = rc_chunk->chunk.state >= CHUNK_GENERATED; rcchunk_drop(rc_chunk); return generated; } Block world_get_block(World *world, int gx, int gy, int gz) { RcChunk *rc_chunk; Block block; ivec3 lp = LOCAL_VEC(gx, gy, gz); ivec3 cp = CHUNK_VEC(gx, gy, gz); rc_chunk = world_get_chunk(world, cp[0], cp[1], cp[2]); if (rc_chunk == NULL) return AIR; block = chunk_at(&rc_chunk->chunk, lp[0], lp[1], lp[2]); rcchunk_drop(rc_chunk); return block; } void rcchunk_drop(RcChunk *rc_chunk) { rc_chunk->rc -= 1; if (rc_chunk->rc == 0) { chunk_free(&rc_chunk->chunk); free(rc_chunk); } }