CavEX/source/chunk_mesher.c
2023-04-14 19:32:51 +02:00

573 lines
15 KiB
C

/*
Copyright (c) 2022 ByteBit/xtreme8000
This file is part of CavEX.
CavEX is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
CavEX is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with CavEX. If not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include <stdint.h>
#include "chunk_mesher.h"
#include "platform/displaylist.h"
#include "platform/thread.h"
#include "stack.h"
#include "world.h"
#define BLK_INDEX(x, y, z) \
((x) + ((z) + (y) * (CHUNK_SIZE + 2)) * (CHUNK_SIZE + 2))
#define BLK_INDEX2(x, y, z) ((x) + ((z) + (y)*CHUNK_SIZE) * CHUNK_SIZE)
#define BLK_DATA(b, x, y, z) ((b)[BLK_INDEX((x) + 1, (y) + 1, (z) + 1)])
#ifndef MAX
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#endif
struct chunk_mesher_rpc {
struct chunk* chunk;
// ingoing
struct {
struct block_data* blocks;
} request;
// outgoing
struct {
struct displaylist mesh[13];
bool has_displist[13];
uint8_t reachable[6];
} result;
};
static struct chunk_mesher_rpc rpc_msg[CHUNK_MESHER_QLENGTH];
static struct thread_channel mesher_requests;
static struct thread_channel mesher_results;
static struct thread_channel mesher_empty_msg;
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_test(struct block_data* bd, struct stack* queue,
bool* visited, uint8_t* reachable, c_coord_t x,
c_coord_t y, c_coord_t z) {
assert(bd && queue && visited && reachable);
if(visited[BLK_INDEX2(x, y, z)]
|| (blocks[BLK_DATA(bd, x, y, z).type]
&& !blocks[BLK_DATA(bd, x, y, z).type]->can_see_through))
return;
stack_clear(queue);
stack_push(queue, (uint8_t[]) {x, y, z});
visited[BLK_INDEX2(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[BLK_INDEX2(nx, ny, nz)]
&& (!blocks[BLK_DATA(bd, nx, ny, nz).type]
|| blocks[BLK_DATA(bd, nx, ny, nz).type]->can_see_through)) {
stack_push(queue, (uint8_t[]) {nx, ny, nz});
visited[BLK_INDEX2(nx, ny, nz)] = true;
}
}
}
for(int s = 0; s < 6; s++) {
if(reached_sides & (1 << s))
reachable[s] |= reached_sides;
}
}
static void chunk_test_init(struct block_data* bd, uint8_t* reachable) {
assert(bd && reachable);
memset(reachable, 0, 6 * sizeof(uint8_t));
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_test(bd, &queue, visited, reachable, x, y, 0);
chunk_test(bd, &queue, visited, reachable, x, 0, y);
chunk_test(bd, &queue, visited, reachable, 0, x, y);
chunk_test(bd, &queue, visited, reachable, x, y, CHUNK_SIZE - 1);
chunk_test(bd, &queue, visited, reachable, x, CHUNK_SIZE - 1, y);
chunk_test(bd, &queue, visited, reachable, CHUNK_SIZE - 1, x, y);
}
}
stack_destroy(&queue);
free(visited);
}
static void chunk_mesher_vertex_light(struct block_data* bd,
uint8_t* light_data) {
assert(bd && 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] = {
BLK_DATA(bd, x + 0, y - 1, z + 0),
BLK_DATA(bd, x - 1, y - 1, z + 0),
BLK_DATA(bd, x + 0, y - 1, z - 1),
BLK_DATA(bd, x - 1, y - 1, z - 1),
};
struct block_data b2[4] = {
BLK_DATA(bd, x - 1, y + 0, z + 0),
b1[1],
BLK_DATA(bd, x - 1, y + 0, z - 1),
b1[3],
};
struct block_data b3[4] = {
BLK_DATA(bd, x + 0, y + 0, z - 1),
b2[2],
b1[2],
b1[3],
};
// TODO: clean up horrible code
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[BLK_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[BLK_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[BLK_INDEX(x, y, z) * 3 + 2]
= (sum_torch << 4) | sum_sky;
}
}
}
}
static void chunk_mesher_rebuild(struct block_data* bd, w_coord_t cx,
w_coord_t cy, w_coord_t cz,
struct displaylist* d, bool count_only,
size_t* vertices) {
assert(bd && d && vertices);
uint8_t* light_data = NULL;
for(int k = 0; k < 13; k++)
vertices[k] = 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 = BLK_DATA(bd, x, y, z);
if(blocks[local.type]) {
struct block_data neighbours[6];
struct block_info neighbours_info[6];
for(int k = 0; k < SIDE_MAX; k++) {
enum side s = (enum side)k;
int ox, oy, oz;
blocks_side_offset(s, &ox, &oy, &oz);
neighbours[k] = BLK_DATA(bd, x + ox, y + oy, z + oz);
neighbours_info[k] = (struct block_info) {
.block = neighbours + k,
.neighbours = NULL,
.x = cx + x + ox,
.y = cy + y + oy,
.z = cz + z + oz,
};
}
struct block_info local_info = (struct block_info) {
.block = &local,
.neighbours = neighbours,
.x = cx + x,
.y = cy + y,
.z = cz + z,
};
uint8_t vertex_light[24];
bool light_loaded = count_only;
for(int k = 0; k < SIDE_MAX; k++) {
enum side s = (enum side)k;
bool face_visible = true;
if(blocks[neighbours[k].type]
&& ((!blocks[local.type]->transparent
&& !blocks[neighbours[k].type]->transparent)
|| blocks[local.type]->transparent)) {
struct face_occlusion* a
= blocks[local.type]->getSideMask(
&local_info, s, neighbours_info + k);
struct face_occlusion* b
= blocks[neighbours[k].type]->getSideMask(
neighbours_info + k,
blocks_side_opposite(s), &local_info);
face_visible = face_occlusion_test(a, b);
}
int dp_index = k;
if(blocks[local.type]->transparent)
dp_index += 6;
if(blocks[local.type]->double_sided)
dp_index = 12;
if(face_visible
|| blocks[local.type]->renderBlockAlways) {
if(!light_data) {
light_data
= malloc((CHUNK_SIZE + 2) * (CHUNK_SIZE + 2)
* (CHUNK_SIZE + 2) * 3);
assert(light_data);
chunk_mesher_vertex_light(bd, light_data);
}
if(!light_loaded) {
light_loaded = true;
vertex_light[0]
= light_data[BLK_INDEX(x + 0, y + 0, z + 0)
* 3
+ 0];
vertex_light[1]
= light_data[BLK_INDEX(x + 1, y + 0, z + 0)
* 3
+ 0];
vertex_light[2]
= light_data[BLK_INDEX(x + 1, y + 0, z + 1)
* 3
+ 0];
vertex_light[3]
= light_data[BLK_INDEX(x + 0, y + 0, z + 1)
* 3
+ 0];
vertex_light[4]
= light_data[BLK_INDEX(x + 0, y + 2, z + 0)
* 3
+ 0];
vertex_light[5]
= light_data[BLK_INDEX(x + 1, y + 2, z + 0)
* 3
+ 0];
vertex_light[6]
= light_data[BLK_INDEX(x + 1, y + 2, z + 1)
* 3
+ 0];
vertex_light[7]
= light_data[BLK_INDEX(x + 0, y + 2, z + 1)
* 3
+ 0];
vertex_light[8]
= light_data[BLK_INDEX(x + 0, y + 0, z + 0)
* 3
+ 1];
vertex_light[9]
= light_data[BLK_INDEX(x + 0, y + 1, z + 0)
* 3
+ 1];
vertex_light[10]
= light_data[BLK_INDEX(x + 0, y + 1, z + 1)
* 3
+ 1];
vertex_light[11]
= light_data[BLK_INDEX(x + 0, y + 0, z + 1)
* 3
+ 1];
vertex_light[12]
= light_data[BLK_INDEX(x + 2, y + 0, z + 0)
* 3
+ 1];
vertex_light[13]
= light_data[BLK_INDEX(x + 2, y + 1, z + 0)
* 3
+ 1];
vertex_light[14]
= light_data[BLK_INDEX(x + 2, y + 1, z + 1)
* 3
+ 1];
vertex_light[15]
= light_data[BLK_INDEX(x + 2, y + 0, z + 1)
* 3
+ 1];
vertex_light[16]
= light_data[BLK_INDEX(x + 0, y + 0, z + 0)
* 3
+ 2];
vertex_light[17]
= light_data[BLK_INDEX(x + 1, y + 0, z + 0)
* 3
+ 2];
vertex_light[18]
= light_data[BLK_INDEX(x + 1, y + 1, z + 0)
* 3
+ 2];
vertex_light[19]
= light_data[BLK_INDEX(x + 0, y + 1, z + 0)
* 3
+ 2];
vertex_light[20]
= light_data[BLK_INDEX(x + 0, y + 0, z + 2)
* 3
+ 2];
vertex_light[21]
= light_data[BLK_INDEX(x + 1, y + 0, z + 2)
* 3
+ 2];
vertex_light[22]
= light_data[BLK_INDEX(x + 1, y + 1, z + 2)
* 3
+ 2];
vertex_light[23]
= light_data[BLK_INDEX(x + 0, y + 1, z + 2)
* 3
+ 2];
}
}
if(face_visible)
vertices[dp_index]
+= blocks[local.type]->renderBlock(
d + dp_index, &local_info, s,
neighbours_info + k, vertex_light,
count_only)
* 4;
if(blocks[local.type]->renderBlockAlways)
vertices[dp_index]
+= blocks[local.type]->renderBlockAlways(
d + dp_index, &local_info, s,
neighbours_info + k, vertex_light,
count_only)
* 4;
}
}
}
}
}
if(light_data)
free(light_data);
}
static void chunk_mesher_build(struct chunk_mesher_rpc* req) {
for(int k = 0; k < 13; k++) {
req->result.has_displist[k] = false;
displaylist_init(req->result.mesh + k, 64, 3 * 2 + 2 * 1 + 1);
}
size_t vertices[13];
chunk_mesher_rebuild(req->request.blocks, req->chunk->x, req->chunk->y,
req->chunk->z, req->result.mesh, false, vertices);
for(int k = 0; k < 13; k++) {
if(vertices[k] > 0 && vertices[k] <= 0xFFFF * 4) {
displaylist_finalize(req->result.mesh + k, vertices[k]);
req->result.has_displist[k] = true;
} else {
displaylist_destroy(req->result.mesh + k);
}
}
chunk_test_init(req->request.blocks, req->result.reachable);
free(req->request.blocks);
}
static void* chunk_mesher_local_thread(void* user) {
while(1) {
struct chunk_mesher_rpc* request;
tchannel_receive(&mesher_requests, (void**)&request, true);
chunk_mesher_build(request);
tchannel_send(&mesher_results, request, true);
}
return NULL;
}
void chunk_mesher_init() {
tchannel_init(&mesher_requests, CHUNK_MESHER_QLENGTH);
tchannel_init(&mesher_results, CHUNK_MESHER_QLENGTH);
tchannel_init(&mesher_empty_msg, CHUNK_MESHER_QLENGTH);
for(int k = 0; k < CHUNK_MESHER_QLENGTH; k++)
tchannel_send(&mesher_empty_msg, rpc_msg + k, true);
struct thread t;
thread_create(&t, chunk_mesher_local_thread, NULL, 4);
}
void chunk_mesher_receive() {
struct chunk_mesher_rpc* result;
while(tchannel_receive(&mesher_results, (void**)&result, false)) {
for(int k = 0; k < 13; k++) {
if(result->chunk->has_displist[k])
displaylist_destroy(result->chunk->mesh + k);
result->chunk->mesh[k] = result->result.mesh[k];
result->chunk->has_displist[k] = result->result.has_displist[k];
}
for(int k = 0; k < 6; k++)
result->chunk->reachable[k] = result->result.reachable[k];
chunk_unref(result->chunk);
tchannel_send(&mesher_empty_msg, result, true);
}
}
bool chunk_mesher_send(struct chunk* c) {
assert(c);
struct chunk_mesher_rpc* request;
if(!tchannel_receive(&mesher_empty_msg, (void**)&request, false))
return false;
struct block_data* bd
= malloc((CHUNK_SIZE + 2) * (CHUNK_SIZE + 2) * (CHUNK_SIZE + 2)
* sizeof(struct block_data));
if(!bd) {
tchannel_send(&mesher_empty_msg, request, true);
return false;
}
chunk_ref(c);
request->chunk = c;
request->request.blocks = bd;
for(w_coord_t y = -1; y < CHUNK_SIZE + 1; y++) {
for(w_coord_t z = -1; z < CHUNK_SIZE + 1; z++) {
for(w_coord_t x = -1; x < CHUNK_SIZE + 1; x++) {
BLK_DATA(bd, x, y, z) = chunk_lookup_block(c, x, y, z);
}
}
}
tchannel_send(&mesher_requests, request, true);
return true;
}