CavEX/source/chunk.c
xtreme8000 97d6b8c041 Codebase ported from old project
also added many new things
2022-11-22 22:03:12 +01:00

574 lines
16 KiB
C

#include <assert.h>
#include <gccore.h>
#include <malloc.h>
#include <math.h>
#include <stddef.h>
#include <string.h>
#include "block/blocks.h"
#include "chunk.h"
#include "stack.h"
#define CHUNK_INDEX(x, y, z) ((x) + ((z) + (y)*CHUNK_SIZE) * CHUNK_SIZE)
#define CHUNK_LIGHT_INDEX(x, y, z) \
((x) + ((z) + (y) * (CHUNK_SIZE + 2)) * (CHUNK_SIZE + 2))
extern void log_num(int x, int offset, bool flip);
void chunk_init(struct chunk* c, struct world* world, w_coord_t x, w_coord_t y,
w_coord_t z) {
assert(c && world);
c->blocks = malloc(CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE * 3);
assert(c->blocks);
memset(c->blocks, BLOCK_AIR, CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE * 3);
c->x = x;
c->y = y;
c->z = z;
for(int k = 0; k < 13; k++)
c->has_displist[k] = false;
c->rebuild_displist = false;
c->world = world;
}
void chunk_destroy(struct chunk* c) {
assert(c);
free(c->blocks);
for(int k = 0; k < 13; k++) {
if(c->has_displist[k])
displaylist_destroy(c->mesh + k);
}
}
struct block_data chunk_get_block(struct chunk* c, c_coord_t x, c_coord_t y,
c_coord_t z) {
assert(c && x < CHUNK_SIZE && y < CHUNK_SIZE && z < CHUNK_SIZE);
return (struct block_data) {
.type = c->blocks[CHUNK_INDEX(x, y, z) * 3 + 0],
.metadata = c->blocks[CHUNK_INDEX(x, y, z) * 3 + 1],
.sky_light = c->blocks[CHUNK_INDEX(x, y, z) * 3 + 2] & 0xF,
.torch_light = c->blocks[CHUNK_INDEX(x, y, z) * 3 + 2] >> 4,
};
}
static struct block_data chunk_lookup_block(struct chunk* c, w_coord_t x,
w_coord_t y, w_coord_t z) {
assert(c);
struct chunk* other = c;
if(x < c->x || y < c->y || z < c->z || x >= c->x + CHUNK_SIZE
|| y >= c->y + CHUNK_SIZE || z >= c->z + CHUNK_SIZE)
other = world_find_chunk(c->world, x, y, z);
return other ? chunk_get_block(other, x & CHUNK_SIZE_BITS,
y & CHUNK_SIZE_BITS, z & CHUNK_SIZE_BITS) :
(struct block_data) {
.type = (y < WORLD_HEIGHT) ? 1 : 0,
.metadata = 0,
.sky_light = 0,
.torch_light = 0,
};
}
void chunk_set_block(struct chunk* c, c_coord_t x, c_coord_t y, c_coord_t z,
struct block_data blk) {
assert(c && x < CHUNK_SIZE && y < CHUNK_SIZE && z < CHUNK_SIZE);
c->blocks[CHUNK_INDEX(x, y, z) * 3 + 0] = blk.type;
c->blocks[CHUNK_INDEX(x, y, z) * 3 + 1] = blk.metadata;
c->blocks[CHUNK_INDEX(x, y, z) * 3 + 2]
= (blk.torch_light << 4) | blk.sky_light;
c->rebuild_displist = true;
// TODO: diagonal chunks, just sharing edge or single point
if(x == 0) {
struct chunk* other = world_find_chunk(c->world, c->x - 1, c->y, c->z);
if(other)
other->rebuild_displist = true;
}
if(x == CHUNK_SIZE - 1) {
struct chunk* other
= world_find_chunk(c->world, c->x + CHUNK_SIZE, c->y, c->z);
if(other)
other->rebuild_displist = true;
}
if(y == 0) {
struct chunk* other = world_find_chunk(c->world, c->x, c->y - 1, c->z);
if(other)
other->rebuild_displist = true;
}
if(y == CHUNK_SIZE - 1) {
struct chunk* other
= world_find_chunk(c->world, c->x, c->y + CHUNK_SIZE, c->z);
if(other)
other->rebuild_displist = true;
}
if(z == 0) {
struct chunk* other = world_find_chunk(c->world, c->x, c->y, c->z - 1);
if(other)
other->rebuild_displist = true;
}
if(z == CHUNK_SIZE - 1) {
struct chunk* other
= world_find_chunk(c->world, c->x, c->y, c->z + CHUNK_SIZE);
if(other)
other->rebuild_displist = true;
}
}
static int chunk_test_side(enum side* on_sides, c_coord_t x, c_coord_t y,
c_coord_t z) {
assert(on_sides);
int count = 0;
if(x == 0)
on_sides[count++] = SIDE_LEFT;
if(x == CHUNK_SIZE - 1)
on_sides[count++] = SIDE_RIGHT;
if(y == 0)
on_sides[count++] = SIDE_BOTTOM;
if(y == CHUNK_SIZE - 1)
on_sides[count++] = SIDE_TOP;
if(z == 0)
on_sides[count++] = SIDE_FRONT;
if(z == CHUNK_SIZE - 1)
on_sides[count++] = SIDE_BACK;
return count;
}
static void chunk_test2(struct chunk* c, struct stack* queue, bool* visited,
uint8_t* reachable, c_coord_t x, c_coord_t y,
c_coord_t z) {
assert(queue && visited && reachable);
if(visited[CHUNK_INDEX(x, y, z)]
|| (blocks[c->blocks[CHUNK_INDEX(x, y, z) * 3 + 0]]
&& !blocks[c->blocks[CHUNK_INDEX(x, y, z) * 3 + 0]]
->can_see_through))
return;
stack_clear(queue);
stack_push(queue, (uint8_t[]) {x, y, z});
visited[CHUNK_INDEX(x, y, z)] = true;
uint8_t reached_sides = 0;
while(!stack_empty(queue)) {
uint8_t block[3];
stack_pop(queue, block);
enum side on_sides[3];
size_t on_sides_len
= chunk_test_side(on_sides, block[0], block[1], block[2]);
assert(on_sides_len <= 3);
for(size_t k = 0; k < on_sides_len; k++)
reached_sides |= (1 << on_sides[k]);
for(int s = 0; s < 6; s++) {
int nx, ny, nz;
blocks_side_offset(s, &nx, &ny, &nz);
nx += block[0];
ny += block[1];
nz += block[2];
if(nx >= 0 && ny >= 0 && nz >= 0 && nx < CHUNK_SIZE
&& ny < CHUNK_SIZE && nz < CHUNK_SIZE
&& !visited[CHUNK_INDEX(nx, ny, nz)]
&& (!blocks[c->blocks[CHUNK_INDEX(nx, ny, nz) * 3 + 0]]
|| blocks[c->blocks[CHUNK_INDEX(nx, ny, nz) * 3 + 0]]
->can_see_through)) {
stack_push(queue, (uint8_t[]) {nx, ny, nz});
visited[CHUNK_INDEX(nx, ny, nz)] = true;
}
}
}
for(int s = 0; s < 6; s++) {
if(reached_sides & (1 << s))
reachable[s] |= reached_sides;
}
}
void chunk_test(struct chunk* c) {
assert(c);
memset(c->reachable, 0, sizeof(c->reachable));
bool* visited = malloc(CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE);
assert(visited);
memset(visited, false, CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE);
struct stack queue;
stack_create(&queue, CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE / 4,
sizeof(uint8_t[3]));
for(int y = 0; y < CHUNK_SIZE; y++) {
for(int x = 0; x < CHUNK_SIZE; x++) {
chunk_test2(c, &queue, visited, c->reachable, x, y, 0);
chunk_test2(c, &queue, visited, c->reachable, x, 0, y);
chunk_test2(c, &queue, visited, c->reachable, 0, x, y);
chunk_test2(c, &queue, visited, c->reachable, x, y, CHUNK_SIZE - 1);
chunk_test2(c, &queue, visited, c->reachable, x, CHUNK_SIZE - 1, y);
chunk_test2(c, &queue, visited, c->reachable, CHUNK_SIZE - 1, x, y);
}
}
stack_destroy(&queue);
free(visited);
}
static void chunk_vertex_light(struct chunk* c, uint8_t* light_data) {
assert(c && light_data);
for(c_coord_t y = 0; y < CHUNK_SIZE + 2; y++) {
for(c_coord_t z = 0; z < CHUNK_SIZE + 2; z++) {
for(c_coord_t x = 0; x < CHUNK_SIZE + 2; x++) {
struct block_data b1[4] = {
chunk_lookup_block(c, c->x + x + 0, c->y + y - 1,
c->z + z + 0),
chunk_lookup_block(c, c->x + x - 1, c->y + y - 1,
c->z + z + 0),
chunk_lookup_block(c, c->x + x + 0, c->y + y - 1,
c->z + z - 1),
chunk_lookup_block(c, c->x + x - 1, c->y + y - 1,
c->z + z - 1),
};
struct block_data b2[4] = {
chunk_lookup_block(c, c->x + x - 1, c->y + y + 0,
c->z + z + 0),
chunk_lookup_block(c, c->x + x - 1, c->y + y - 1,
c->z + z + 0),
chunk_lookup_block(c, c->x + x - 1, c->y + y + 0,
c->z + z - 1),
chunk_lookup_block(c, c->x + x - 1, c->y + y - 1,
c->z + z - 1),
};
struct block_data b3[4] = {
chunk_lookup_block(c, c->x + x + 0, c->y + y + 0,
c->z + z - 1),
chunk_lookup_block(c, c->x + x - 1, c->y + y + 0,
c->z + z - 1),
chunk_lookup_block(c, c->x + x + 0, c->y + y - 1,
c->z + z - 1),
chunk_lookup_block(c, c->x + x - 1, c->y + y - 1,
c->z + z - 1),
};
int shade_table[5] = {0, 1, 3, 5, 0};
int sum_sky, sum_torch, count;
sum_sky = sum_torch = count = 0;
for(int k = 0; k < 4; k++) {
if(!blocks[b1[k].type]
|| (blocks[b1[k].type]->can_see_through
&& !blocks[b1[k].type]->ignore_lighting)) {
sum_sky += b1[k].sky_light;
sum_torch += b1[k].torch_light;
count++;
}
}
sum_torch = count > 0 ? (sum_torch + count - 1) / count : 0;
sum_sky = count > 0 ? (sum_sky + count - 1) / count : 0;
sum_torch = MAX(sum_torch - shade_table[4 - count], 0);
sum_sky = MAX(sum_sky - shade_table[4 - count], 0);
light_data[CHUNK_LIGHT_INDEX(x, y, z) * 3 + 0]
= (sum_torch << 4) | sum_sky;
sum_sky = sum_torch = count = 0;
for(int k = 0; k < 4; k++) {
if(!blocks[b2[k].type]
|| (blocks[b2[k].type]->can_see_through
&& !blocks[b2[k].type]->ignore_lighting)) {
sum_sky += b2[k].sky_light;
sum_torch += b2[k].torch_light;
count++;
}
}
sum_torch = count > 0 ? (sum_torch + count - 1) / count : 0;
sum_sky = count > 0 ? (sum_sky + count - 1) / count : 0;
sum_torch = MAX(sum_torch - shade_table[4 - count], 0);
sum_sky = MAX(sum_sky - shade_table[4 - count], 0);
light_data[CHUNK_LIGHT_INDEX(x, y, z) * 3 + 1]
= (sum_torch << 4) | sum_sky;
sum_sky = sum_torch = count = 0;
for(int k = 0; k < 4; k++) {
if(!blocks[b3[k].type]
|| (blocks[b3[k].type]->can_see_through
&& !blocks[b3[k].type]->ignore_lighting)) {
sum_sky += b3[k].sky_light;
sum_torch += b3[k].torch_light;
count++;
}
}
sum_torch = count > 0 ? (sum_torch + count - 1) / count : 0;
sum_sky = count > 0 ? (sum_sky + count - 1) / count : 0;
sum_torch = MAX(sum_torch - shade_table[4 - count], 0);
sum_sky = MAX(sum_sky - shade_table[4 - count], 0);
light_data[CHUNK_LIGHT_INDEX(x, y, z) * 3 + 2]
= (sum_torch << 4) | sum_sky;
}
}
}
}
static size_t chunk_rebuild(struct chunk* c, struct displaylist* d,
uint8_t* light_data, bool count_only,
bool transparent, enum side side_only,
bool double_sided_only) {
assert(c && d && light_data);
size_t visible_faces = 0;
for(c_coord_t y = 0; y < CHUNK_SIZE; y++) {
for(c_coord_t z = 0; z < CHUNK_SIZE; z++) {
for(c_coord_t x = 0; x < CHUNK_SIZE; x++) {
struct block_data local = chunk_get_block(c, x, y, z);
if(blocks[local.type]
&& transparent == blocks[local.type]->transparent
&& double_sided_only == blocks[local.type]->double_sided) {
struct block_info local_info = (struct block_info) {
.block = &local,
.world = c->world,
.x = c->x + x,
.y = c->y + y,
.z = c->z + z,
};
uint8_t vertex_light[24] = {
light_data[CHUNK_LIGHT_INDEX(x + 0, y + 0, z + 0) * 3
+ 0],
light_data[CHUNK_LIGHT_INDEX(x + 1, y + 0, z + 0) * 3
+ 0],
light_data[CHUNK_LIGHT_INDEX(x + 1, y + 0, z + 1) * 3
+ 0],
light_data[CHUNK_LIGHT_INDEX(x + 0, y + 0, z + 1) * 3
+ 0],
light_data[CHUNK_LIGHT_INDEX(x + 0, y + 2, z + 0) * 3
+ 0],
light_data[CHUNK_LIGHT_INDEX(x + 1, y + 2, z + 0) * 3
+ 0],
light_data[CHUNK_LIGHT_INDEX(x + 1, y + 2, z + 1) * 3
+ 0],
light_data[CHUNK_LIGHT_INDEX(x + 0, y + 2, z + 1) * 3
+ 0],
light_data[CHUNK_LIGHT_INDEX(x + 0, y + 0, z + 0) * 3
+ 1],
light_data[CHUNK_LIGHT_INDEX(x + 0, y + 1, z + 0) * 3
+ 1],
light_data[CHUNK_LIGHT_INDEX(x + 0, y + 1, z + 1) * 3
+ 1],
light_data[CHUNK_LIGHT_INDEX(x + 0, y + 0, z + 1) * 3
+ 1],
light_data[CHUNK_LIGHT_INDEX(x + 2, y + 0, z + 0) * 3
+ 1],
light_data[CHUNK_LIGHT_INDEX(x + 2, y + 1, z + 0) * 3
+ 1],
light_data[CHUNK_LIGHT_INDEX(x + 2, y + 1, z + 1) * 3
+ 1],
light_data[CHUNK_LIGHT_INDEX(x + 2, y + 0, z + 1) * 3
+ 1],
light_data[CHUNK_LIGHT_INDEX(x + 0, y + 0, z + 0) * 3
+ 2],
light_data[CHUNK_LIGHT_INDEX(x + 1, y + 0, z + 0) * 3
+ 2],
light_data[CHUNK_LIGHT_INDEX(x + 1, y + 1, z + 0) * 3
+ 2],
light_data[CHUNK_LIGHT_INDEX(x + 0, y + 1, z + 0) * 3
+ 2],
light_data[CHUNK_LIGHT_INDEX(x + 0, y + 0, z + 2) * 3
+ 2],
light_data[CHUNK_LIGHT_INDEX(x + 1, y + 0, z + 2) * 3
+ 2],
light_data[CHUNK_LIGHT_INDEX(x + 1, y + 1, z + 2) * 3
+ 2],
light_data[CHUNK_LIGHT_INDEX(x + 0, y + 1, z + 2) * 3
+ 2],
};
for(int k = 0; k < ((side_only == SIDE_MAX) ? 6 : 1); k++) {
enum side s = (side_only == SIDE_MAX) ? (enum side)k :
side_only;
int ox, oy, oz;
blocks_side_offset(s, &ox, &oy, &oz);
struct block_data neighbours = chunk_lookup_block(
c, c->x + x + ox, c->y + y + oy, c->z + z + oz);
struct block_info neighbours_info
= (struct block_info) {
.block = &neighbours,
.world = c->world,
.x = c->x + x + ox,
.y = c->y + y + oy,
.z = c->z + z + oz,
};
bool face_visible = true;
if(blocks[neighbours.type]
&& ((!transparent
&& !blocks[neighbours.type]->transparent)
|| transparent)) {
struct face_occlusion* a
= blocks[local.type]->getSideMask(
&local_info, s, &neighbours_info);
struct face_occlusion* b
= blocks[neighbours.type]->getSideMask(
&neighbours_info, blocks_side_opposite(s),
&local_info);
face_visible = face_occlusion_test(a, b);
}
if(face_visible)
visible_faces += blocks[local.type]->renderBlock(
d, &local_info, s, &neighbours_info,
vertex_light, count_only);
}
}
}
}
}
return visible_faces;
}
void chunk_check_built(struct chunk* c) {
assert(c);
if(c->rebuild_displist) {
uint8_t* light_data = malloc((CHUNK_SIZE + 2) * (CHUNK_SIZE + 2)
* (CHUNK_SIZE + 2) * 3);
assert(light_data);
chunk_vertex_light(c, light_data);
for(int k = 0; k < 13; k++) {
if(c->has_displist[k])
displaylist_destroy(c->mesh + k);
size_t vertices
= chunk_rebuild(c, c->mesh + k, light_data, true,
k >= 6 && k != 12,
(k == 12) ? SIDE_MAX : (k % 6), k == 12)
* 4;
if(vertices > 0 && vertices <= 0xFFFF * 4) {
displaylist_init(c->mesh + k, vertices, 3 * 2 + 2 * 1 + 1);
displaylist_begin(c->mesh + k);
GX_Begin(GX_QUADS, GX_VTXFMT0, vertices);
chunk_rebuild(c, c->mesh + k, light_data, false,
k >= 6 && k != 12, (k == 12) ? SIDE_MAX : (k % 6),
k == 12);
GX_End();
displaylist_end(c->mesh + k);
c->has_displist[k] = true;
} else {
c->has_displist[k] = false;
}
}
free(light_data);
chunk_test(c);
c->rebuild_displist = false;
}
}
void chunk_pre_render(struct chunk* c, Mtx view) {
assert(c && view);
Mtx model;
guMtxTrans(model, c->x, c->y, c->z);
guMtxConcat(view, model, c->model_view);
}
static void check_matrix_set(struct chunk* c, bool* needs_matrix) {
if(*needs_matrix) {
GX_LoadPosMtxImm(c->model_view, GX_PNMTX0);
*needs_matrix = false;
}
}
void chunk_render(struct chunk* c, bool pass, float x, float y, float z) {
assert(c);
chunk_check_built(c);
bool needs_matrix = true;
int offset = pass ? 6 : 0;
if(y < c->y + CHUNK_SIZE && c->has_displist[SIDE_BOTTOM + offset]) {
check_matrix_set(c, &needs_matrix);
displaylist_render(c->mesh + SIDE_BOTTOM + offset);
}
if(y > c->y && c->has_displist[SIDE_TOP + offset]) {
check_matrix_set(c, &needs_matrix);
displaylist_render(c->mesh + SIDE_TOP + offset);
}
if(x < c->x + CHUNK_SIZE && c->has_displist[SIDE_LEFT + offset]) {
check_matrix_set(c, &needs_matrix);
displaylist_render(c->mesh + SIDE_LEFT + offset);
}
if(x > c->x && c->has_displist[SIDE_RIGHT + offset]) {
check_matrix_set(c, &needs_matrix);
displaylist_render(c->mesh + SIDE_RIGHT + offset);
}
if(z < c->z + CHUNK_SIZE && c->has_displist[SIDE_FRONT + offset]) {
check_matrix_set(c, &needs_matrix);
displaylist_render(c->mesh + SIDE_FRONT + offset);
}
if(z > c->z && c->has_displist[SIDE_BACK + offset]) {
check_matrix_set(c, &needs_matrix);
displaylist_render(c->mesh + SIDE_BACK + offset);
}
if(!pass && c->has_displist[12]) {
check_matrix_set(c, &needs_matrix);
GX_SetCullMode(GX_CULL_NONE);
displaylist_render(c->mesh + 12);
GX_SetCullMode(GX_CULL_BACK);
}
}