refactor separate framework from game code, add demo3d
This commit is contained in:
parent
19ae869769
commit
40f5cdcaa5
92 changed files with 2665 additions and 6547 deletions
3
demo3d/cart.fnl
Normal file
3
demo3d/cart.fnl
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{:title "pxl8 3d demo"
|
||||
:resolution "640x360"
|
||||
:window-size [1280 720]}
|
||||
594
demo3d/client/bsp/demo3d_bsp.c
Normal file
594
demo3d/client/bsp/demo3d_bsp.c
Normal file
|
|
@ -0,0 +1,594 @@
|
|||
#include "demo3d_bsp.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_color.h"
|
||||
#include "pxl8_io.h"
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_mem.h"
|
||||
|
||||
#define BSP_VERSION 29
|
||||
|
||||
typedef enum {
|
||||
CHUNK_ENTITIES = 0,
|
||||
CHUNK_PLANES = 1,
|
||||
CHUNK_TEXTURES = 2,
|
||||
CHUNK_VERTICES = 3,
|
||||
CHUNK_VISIBILITY = 4,
|
||||
CHUNK_NODES = 5,
|
||||
CHUNK_TEXINFO = 6,
|
||||
CHUNK_FACES = 7,
|
||||
CHUNK_LIGHTING = 8,
|
||||
CHUNK_CLIPNODES = 9,
|
||||
CHUNK_LEAFS = 10,
|
||||
CHUNK_MARKSURFACES = 11,
|
||||
CHUNK_EDGES = 12,
|
||||
CHUNK_SURFEDGES = 13,
|
||||
CHUNK_MODELS = 14,
|
||||
CHUNK_COUNT = 15
|
||||
} demo3d_bsp_chunk_type;
|
||||
|
||||
typedef struct {
|
||||
u32 offset;
|
||||
u32 size;
|
||||
} demo3d_bsp_chunk;
|
||||
|
||||
typedef struct {
|
||||
u32 version;
|
||||
demo3d_bsp_chunk chunks[CHUNK_COUNT];
|
||||
} demo3d_bsp_header;
|
||||
|
||||
static inline pxl8_vec3 read_vec3(pxl8_stream* stream) {
|
||||
f32 x = pxl8_read_f32(stream);
|
||||
f32 y = pxl8_read_f32(stream);
|
||||
f32 z = pxl8_read_f32(stream);
|
||||
return (pxl8_vec3){x, z, y};
|
||||
}
|
||||
|
||||
static bool validate_chunk(const demo3d_bsp_chunk* chunk, u32 element_size, usize file_size) {
|
||||
if (chunk->size == 0) return true;
|
||||
if (chunk->offset >= file_size) return false;
|
||||
if (chunk->offset + chunk->size > file_size) return false;
|
||||
if (chunk->size % element_size != 0) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool demo3d_bsp_get_edge_vertex(const demo3d_bsp* bsp, i32 surfedge_idx, u32* out_vert_idx) {
|
||||
if (surfedge_idx >= (i32)bsp->num_surfedges) return false;
|
||||
|
||||
i32 edge_idx = bsp->surfedges[surfedge_idx];
|
||||
u32 vertex_index;
|
||||
|
||||
if (edge_idx >= 0) {
|
||||
if ((u32)edge_idx >= bsp->num_edges) return false;
|
||||
vertex_index = 0;
|
||||
} else {
|
||||
edge_idx = -edge_idx;
|
||||
if ((u32)edge_idx >= bsp->num_edges) return false;
|
||||
vertex_index = 1;
|
||||
}
|
||||
|
||||
*out_vert_idx = bsp->edges[edge_idx].vertex[vertex_index];
|
||||
return *out_vert_idx < bsp->num_vertices;
|
||||
}
|
||||
|
||||
pxl8_result demo3d_bsp_load(const char* path, demo3d_bsp* bsp) {
|
||||
if (!path || !bsp) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
memset(bsp, 0, sizeof(*bsp));
|
||||
|
||||
u8* file_data = NULL;
|
||||
usize file_size = 0;
|
||||
pxl8_result result = pxl8_io_read_binary_file(path, &file_data, &file_size);
|
||||
if (result != PXL8_OK) {
|
||||
pxl8_error("Failed to load BSP file: %s", path);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (file_size < sizeof(demo3d_bsp_header)) {
|
||||
pxl8_error("BSP file too small: %s", path);
|
||||
pxl8_free(file_data);
|
||||
return PXL8_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
pxl8_stream stream = pxl8_stream_create(file_data, (u32)file_size);
|
||||
|
||||
demo3d_bsp_header header;
|
||||
header.version = pxl8_read_u32(&stream);
|
||||
|
||||
if (header.version != BSP_VERSION) {
|
||||
pxl8_error("Invalid BSP version: %u (expected %d)", header.version, BSP_VERSION);
|
||||
pxl8_free(file_data);
|
||||
return PXL8_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
for (i32 i = 0; i < CHUNK_COUNT; i++) {
|
||||
header.chunks[i].offset = pxl8_read_u32(&stream);
|
||||
header.chunks[i].size = pxl8_read_u32(&stream);
|
||||
}
|
||||
|
||||
demo3d_bsp_chunk* chunk = &header.chunks[CHUNK_VERTICES];
|
||||
if (!validate_chunk(chunk, 12, file_size)) goto error_cleanup;
|
||||
bsp->num_vertices = chunk->size / 12;
|
||||
if (bsp->num_vertices > 0) {
|
||||
bsp->vertices = pxl8_calloc(bsp->num_vertices, sizeof(demo3d_bsp_vertex));
|
||||
if (!bsp->vertices) goto error_cleanup;
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_vertices; i++) {
|
||||
bsp->vertices[i].position = read_vec3(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_EDGES];
|
||||
if (!validate_chunk(chunk, 4, file_size)) goto error_cleanup;
|
||||
bsp->num_edges = chunk->size / 4;
|
||||
if (bsp->num_edges > 0) {
|
||||
bsp->edges = pxl8_calloc(bsp->num_edges, sizeof(demo3d_bsp_edge));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_edges; i++) {
|
||||
bsp->edges[i].vertex[0] = pxl8_read_u16(&stream);
|
||||
bsp->edges[i].vertex[1] = pxl8_read_u16(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_SURFEDGES];
|
||||
if (!validate_chunk(chunk, 4, file_size)) goto error_cleanup;
|
||||
bsp->num_surfedges = chunk->size / 4;
|
||||
if (bsp->num_surfedges > 0) {
|
||||
bsp->surfedges = pxl8_calloc(bsp->num_surfedges, sizeof(i32));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_surfedges; i++) {
|
||||
bsp->surfedges[i] = pxl8_read_i32(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_PLANES];
|
||||
if (!validate_chunk(chunk, 20, file_size)) goto error_cleanup;
|
||||
bsp->num_planes = chunk->size / 20;
|
||||
if (bsp->num_planes > 0) {
|
||||
bsp->planes = pxl8_calloc(bsp->num_planes, sizeof(demo3d_bsp_plane));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_planes; i++) {
|
||||
bsp->planes[i].normal = read_vec3(&stream);
|
||||
bsp->planes[i].dist = pxl8_read_f32(&stream);
|
||||
bsp->planes[i].type = pxl8_read_i32(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_FACES];
|
||||
if (!validate_chunk(chunk, 20, file_size)) goto error_cleanup;
|
||||
bsp->num_faces = chunk->size / 20;
|
||||
if (bsp->num_faces > 0) {
|
||||
bsp->faces = pxl8_calloc(bsp->num_faces, sizeof(demo3d_bsp_face));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_faces; i++) {
|
||||
bsp->faces[i].plane_id = pxl8_read_u16(&stream);
|
||||
bsp->faces[i].side = pxl8_read_u16(&stream);
|
||||
bsp->faces[i].first_edge = pxl8_read_u32(&stream);
|
||||
bsp->faces[i].num_edges = pxl8_read_u16(&stream);
|
||||
bsp->faces[i].material_id = pxl8_read_u16(&stream);
|
||||
bsp->faces[i].styles[0] = pxl8_read_u8(&stream);
|
||||
bsp->faces[i].styles[1] = pxl8_read_u8(&stream);
|
||||
bsp->faces[i].styles[2] = pxl8_read_u8(&stream);
|
||||
bsp->faces[i].styles[3] = pxl8_read_u8(&stream);
|
||||
bsp->faces[i].lightmap_offset = pxl8_read_u32(&stream);
|
||||
|
||||
bsp->faces[i].aabb_min = (pxl8_vec3){1e30f, 1e30f, 1e30f};
|
||||
bsp->faces[i].aabb_max = (pxl8_vec3){-1e30f, -1e30f, -1e30f};
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_NODES];
|
||||
if (!validate_chunk(chunk, 24, file_size)) goto error_cleanup;
|
||||
bsp->num_nodes = chunk->size / 24;
|
||||
if (bsp->num_nodes > 0) {
|
||||
bsp->nodes = pxl8_calloc(bsp->num_nodes, sizeof(demo3d_bsp_node));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_nodes; i++) {
|
||||
bsp->nodes[i].plane_id = pxl8_read_u32(&stream);
|
||||
bsp->nodes[i].children[0] = pxl8_read_i16(&stream);
|
||||
bsp->nodes[i].children[1] = pxl8_read_i16(&stream);
|
||||
i16 nx = pxl8_read_i16(&stream);
|
||||
i16 ny = pxl8_read_i16(&stream);
|
||||
i16 nz = pxl8_read_i16(&stream);
|
||||
bsp->nodes[i].mins[0] = nx;
|
||||
bsp->nodes[i].mins[1] = nz;
|
||||
bsp->nodes[i].mins[2] = ny;
|
||||
i16 mx = pxl8_read_i16(&stream);
|
||||
i16 my = pxl8_read_i16(&stream);
|
||||
i16 mz = pxl8_read_i16(&stream);
|
||||
bsp->nodes[i].maxs[0] = mx;
|
||||
bsp->nodes[i].maxs[1] = mz;
|
||||
bsp->nodes[i].maxs[2] = my;
|
||||
bsp->nodes[i].first_face = pxl8_read_u16(&stream);
|
||||
bsp->nodes[i].num_faces = pxl8_read_u16(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_LEAFS];
|
||||
if (!validate_chunk(chunk, 28, file_size)) goto error_cleanup;
|
||||
bsp->num_leafs = chunk->size / 28;
|
||||
if (bsp->num_leafs > 0) {
|
||||
bsp->leafs = pxl8_calloc(bsp->num_leafs, sizeof(demo3d_bsp_leaf));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_leafs; i++) {
|
||||
bsp->leafs[i].contents = pxl8_read_i32(&stream);
|
||||
bsp->leafs[i].visofs = pxl8_read_i32(&stream);
|
||||
i16 nx = pxl8_read_i16(&stream);
|
||||
i16 ny = pxl8_read_i16(&stream);
|
||||
i16 nz = pxl8_read_i16(&stream);
|
||||
bsp->leafs[i].mins[0] = nx;
|
||||
bsp->leafs[i].mins[1] = nz;
|
||||
bsp->leafs[i].mins[2] = ny;
|
||||
i16 mx = pxl8_read_i16(&stream);
|
||||
i16 my = pxl8_read_i16(&stream);
|
||||
i16 mz = pxl8_read_i16(&stream);
|
||||
bsp->leafs[i].maxs[0] = mx;
|
||||
bsp->leafs[i].maxs[1] = mz;
|
||||
bsp->leafs[i].maxs[2] = my;
|
||||
bsp->leafs[i].first_marksurface = pxl8_read_u16(&stream);
|
||||
bsp->leafs[i].num_marksurfaces = pxl8_read_u16(&stream);
|
||||
for (u32 j = 0; j < 4; j++) bsp->leafs[i].ambient_level[j] = pxl8_read_u8(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_MARKSURFACES];
|
||||
if (!validate_chunk(chunk, 2, file_size)) goto error_cleanup;
|
||||
bsp->num_marksurfaces = chunk->size / 2;
|
||||
if (bsp->num_marksurfaces > 0) {
|
||||
bsp->marksurfaces = pxl8_calloc(bsp->num_marksurfaces, sizeof(u16));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_marksurfaces; i++) {
|
||||
bsp->marksurfaces[i] = pxl8_read_u16(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_MODELS];
|
||||
if (!validate_chunk(chunk, 64, file_size)) goto error_cleanup;
|
||||
bsp->num_models = chunk->size / 64;
|
||||
if (bsp->num_models > 0) {
|
||||
bsp->models = pxl8_calloc(bsp->num_models, sizeof(demo3d_bsp_model));
|
||||
pxl8_stream_seek(&stream, chunk->offset);
|
||||
for (u32 i = 0; i < bsp->num_models; i++) {
|
||||
f32 minx = pxl8_read_f32(&stream);
|
||||
f32 miny = pxl8_read_f32(&stream);
|
||||
f32 minz = pxl8_read_f32(&stream);
|
||||
bsp->models[i].mins[0] = minx;
|
||||
bsp->models[i].mins[1] = minz;
|
||||
bsp->models[i].mins[2] = miny;
|
||||
f32 maxx = pxl8_read_f32(&stream);
|
||||
f32 maxy = pxl8_read_f32(&stream);
|
||||
f32 maxz = pxl8_read_f32(&stream);
|
||||
bsp->models[i].maxs[0] = maxx;
|
||||
bsp->models[i].maxs[1] = maxz;
|
||||
bsp->models[i].maxs[2] = maxy;
|
||||
bsp->models[i].origin = read_vec3(&stream);
|
||||
for (u32 j = 0; j < 4; j++) bsp->models[i].headnode[j] = pxl8_read_i32(&stream);
|
||||
bsp->models[i].visleafs = pxl8_read_i32(&stream);
|
||||
bsp->models[i].first_face = pxl8_read_i32(&stream);
|
||||
bsp->models[i].num_faces = pxl8_read_i32(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_VISIBILITY];
|
||||
if (!validate_chunk(chunk, 1, file_size)) goto error_cleanup;
|
||||
bsp->visdata_size = chunk->size;
|
||||
if (bsp->visdata_size > 0) {
|
||||
bsp->visdata = pxl8_malloc(bsp->visdata_size);
|
||||
memcpy(bsp->visdata, file_data + chunk->offset, bsp->visdata_size);
|
||||
}
|
||||
|
||||
chunk = &header.chunks[CHUNK_LIGHTING];
|
||||
if (!validate_chunk(chunk, 1, file_size)) goto error_cleanup;
|
||||
bsp->lightdata_size = chunk->size;
|
||||
if (bsp->lightdata_size > 0) {
|
||||
bsp->lightdata = pxl8_malloc(bsp->lightdata_size);
|
||||
memcpy(bsp->lightdata, file_data + chunk->offset, bsp->lightdata_size);
|
||||
}
|
||||
|
||||
pxl8_free(file_data);
|
||||
|
||||
for (u32 i = 0; i < bsp->num_faces; i++) {
|
||||
demo3d_bsp_face* face = &bsp->faces[i];
|
||||
f32 min_x = 1e30f, min_y = 1e30f, min_z = 1e30f;
|
||||
f32 max_x = -1e30f, max_y = -1e30f, max_z = -1e30f;
|
||||
|
||||
for (u32 j = 0; j < face->num_edges; j++) {
|
||||
i32 surfedge_idx = face->first_edge + j;
|
||||
u32 vert_idx;
|
||||
|
||||
if (!demo3d_bsp_get_edge_vertex(bsp, surfedge_idx, &vert_idx)) continue;
|
||||
|
||||
pxl8_vec3 v = bsp->vertices[vert_idx].position;
|
||||
|
||||
if (v.x < min_x) min_x = v.x;
|
||||
if (v.x > max_x) max_x = v.x;
|
||||
if (v.y < min_y) min_y = v.y;
|
||||
if (v.y > max_y) max_y = v.y;
|
||||
if (v.z < min_z) min_z = v.z;
|
||||
if (v.z > max_z) max_z = v.z;
|
||||
}
|
||||
|
||||
face->aabb_min = (pxl8_vec3){min_x, min_y, min_z};
|
||||
face->aabb_max = (pxl8_vec3){max_x, max_y, max_z};
|
||||
}
|
||||
|
||||
pxl8_debug("Loaded BSP: %u verts, %u faces, %u nodes, %u leafs",
|
||||
bsp->num_vertices, bsp->num_faces, bsp->num_nodes, bsp->num_leafs);
|
||||
|
||||
return PXL8_OK;
|
||||
|
||||
error_cleanup:
|
||||
pxl8_error("BSP chunk validation failed: %s", path);
|
||||
pxl8_free(file_data);
|
||||
demo3d_bsp_destroy(bsp);
|
||||
return PXL8_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
void demo3d_bsp_destroy(demo3d_bsp* bsp) {
|
||||
if (!bsp) return;
|
||||
|
||||
pxl8_free(bsp->cell_portals);
|
||||
pxl8_free(bsp->edges);
|
||||
pxl8_free(bsp->faces);
|
||||
pxl8_free(bsp->leafs);
|
||||
pxl8_free(bsp->lightdata);
|
||||
pxl8_free(bsp->lightmaps);
|
||||
pxl8_free(bsp->marksurfaces);
|
||||
pxl8_free(bsp->models);
|
||||
pxl8_free(bsp->nodes);
|
||||
pxl8_free(bsp->planes);
|
||||
pxl8_free(bsp->surfedges);
|
||||
pxl8_free(bsp->vertex_lights);
|
||||
pxl8_free(bsp->vertices);
|
||||
pxl8_free(bsp->visdata);
|
||||
pxl8_free(bsp->heightfield);
|
||||
|
||||
memset(bsp, 0, sizeof(*bsp));
|
||||
}
|
||||
|
||||
i32 demo3d_bsp_find_leaf(const demo3d_bsp* bsp, pxl8_vec3 pos) {
|
||||
if (!bsp || bsp->num_nodes == 0) return -1;
|
||||
|
||||
i32 node_id = 0;
|
||||
|
||||
while (node_id >= 0) {
|
||||
const demo3d_bsp_node* node = &bsp->nodes[node_id];
|
||||
const demo3d_bsp_plane* plane = &bsp->planes[node->plane_id];
|
||||
|
||||
f32 dist = pxl8_vec3_dot(pos, plane->normal) - plane->dist;
|
||||
node_id = node->children[dist < 0 ? 1 : 0];
|
||||
}
|
||||
|
||||
return -(node_id + 1);
|
||||
}
|
||||
|
||||
|
||||
bool demo3d_bsp_is_leaf_visible(const demo3d_bsp* bsp, i32 leaf_from, i32 leaf_to) {
|
||||
if (!bsp || !bsp->visdata || bsp->visdata_size == 0) return true;
|
||||
if (leaf_from < 0 || leaf_to < 0) return true;
|
||||
if ((u32)leaf_from >= bsp->num_leafs || (u32)leaf_to >= bsp->num_leafs) return true;
|
||||
|
||||
i32 visofs = bsp->leafs[leaf_from].visofs;
|
||||
if (visofs < 0) return true;
|
||||
|
||||
u32 row_size = (bsp->num_leafs + 7) >> 3;
|
||||
u32 byte_idx = (u32)leaf_to >> 3;
|
||||
u32 bit_idx = (u32)leaf_to & 7;
|
||||
|
||||
u8* vis = bsp->visdata + visofs;
|
||||
u8* vis_end = bsp->visdata + bsp->visdata_size;
|
||||
u32 out = 0;
|
||||
|
||||
while (out < row_size && vis < vis_end) {
|
||||
if (*vis) {
|
||||
if (out == byte_idx) {
|
||||
return (*vis & (1 << bit_idx)) != 0;
|
||||
}
|
||||
out++;
|
||||
vis++;
|
||||
} else {
|
||||
vis++;
|
||||
if (vis >= vis_end) break;
|
||||
u32 count = *vis++;
|
||||
if (out + count > byte_idx && byte_idx >= out) {
|
||||
return false;
|
||||
}
|
||||
out += count;
|
||||
}
|
||||
}
|
||||
|
||||
return out > byte_idx ? false : true;
|
||||
}
|
||||
|
||||
demo3d_bsp_pvs demo3d_bsp_decompress_pvs(const demo3d_bsp* bsp, i32 leaf) {
|
||||
demo3d_bsp_pvs pvs = {0};
|
||||
|
||||
u32 row = (bsp->num_leafs + 7) >> 3;
|
||||
pvs.data = pxl8_malloc(row);
|
||||
pvs.size = row;
|
||||
|
||||
if (!pvs.data) return pvs;
|
||||
|
||||
if (!bsp || leaf < 0 || (u32)leaf >= bsp->num_leafs) {
|
||||
memset(pvs.data, 0xFF, row);
|
||||
return pvs;
|
||||
}
|
||||
|
||||
i32 visofs = bsp->leafs[leaf].visofs;
|
||||
if (visofs < 0 || !bsp->visdata || bsp->visdata_size == 0) {
|
||||
memset(pvs.data, 0xFF, row);
|
||||
return pvs;
|
||||
}
|
||||
|
||||
u8* in = bsp->visdata + visofs;
|
||||
u8* out = pvs.data;
|
||||
u8* out_end = pvs.data + row;
|
||||
|
||||
do {
|
||||
if (*in) {
|
||||
*out++ = *in++;
|
||||
} else {
|
||||
in++;
|
||||
i32 c = *in++;
|
||||
while (c > 0 && out < out_end) {
|
||||
*out++ = 0;
|
||||
c--;
|
||||
}
|
||||
}
|
||||
} while (out < out_end);
|
||||
|
||||
return pvs;
|
||||
}
|
||||
|
||||
void demo3d_bsp_pvs_destroy(demo3d_bsp_pvs* pvs) {
|
||||
if (pvs) {
|
||||
pxl8_free(pvs->data);
|
||||
pvs->data = NULL;
|
||||
pvs->size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool demo3d_bsp_pvs_is_visible(const demo3d_bsp_pvs* pvs, i32 leaf) {
|
||||
if (!pvs || !pvs->data || leaf < 0) return true;
|
||||
u32 byte_idx = (u32)leaf >> 3;
|
||||
u32 bit_idx = (u32)leaf & 7;
|
||||
if (byte_idx >= pvs->size) return true;
|
||||
return (pvs->data[byte_idx] & (1 << bit_idx)) != 0;
|
||||
}
|
||||
|
||||
demo3d_bsp_lightmap demo3d_bsp_lightmap_uniform(u8 r, u8 g, u8 b) {
|
||||
return (demo3d_bsp_lightmap){
|
||||
.color = {r, g, b},
|
||||
.height = 0,
|
||||
.offset = 0,
|
||||
.width = 0,
|
||||
};
|
||||
}
|
||||
|
||||
demo3d_bsp_lightmap demo3d_bsp_lightmap_mapped(u8 width, u8 height, u32 offset) {
|
||||
return (demo3d_bsp_lightmap){
|
||||
.color = {0, 0, 0},
|
||||
.height = height,
|
||||
.offset = offset,
|
||||
.width = width,
|
||||
};
|
||||
}
|
||||
|
||||
u8 demo3d_bsp_light_at(const demo3d_bsp* bsp, f32 x, f32 y, f32 z, u8 ambient) {
|
||||
if (!bsp || !bsp->vertices || !bsp->vertex_lights) return 255;
|
||||
|
||||
f32 best_dist = FLT_MAX;
|
||||
u32 best_idx = 0;
|
||||
|
||||
for (u32 i = 0; i < bsp->num_vertices; i++) {
|
||||
f32 dx = bsp->vertices[i].position.x - x;
|
||||
f32 dy = bsp->vertices[i].position.y - y;
|
||||
f32 dz = bsp->vertices[i].position.z - z;
|
||||
f32 dist = dx * dx + dy * dy + dz * dz;
|
||||
if (dist < best_dist) {
|
||||
best_dist = dist;
|
||||
best_idx = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (best_idx >= bsp->num_vertex_lights) return 255;
|
||||
|
||||
u32 packed = bsp->vertex_lights[best_idx];
|
||||
u8 direct = (packed >> 24) & 0xFF;
|
||||
u8 ao = (packed >> 16) & 0xFF;
|
||||
f32 combined = (f32)direct + ((f32)ambient / 255.0f) * (f32)ao;
|
||||
return (u8)(combined > 255.0f ? 255.0f : combined);
|
||||
}
|
||||
|
||||
demo3d_bsp_lightmap_sample demo3d_bsp_sample_lightmap(const demo3d_bsp* bsp, u32 face_idx, f32 u, f32 v) {
|
||||
demo3d_bsp_lightmap_sample white = {255, 255, 255};
|
||||
|
||||
if (!bsp || !bsp->lightmaps || face_idx >= bsp->num_lightmaps) {
|
||||
return white;
|
||||
}
|
||||
|
||||
const demo3d_bsp_lightmap* lm = &bsp->lightmaps[face_idx];
|
||||
|
||||
if (lm->width == 0) {
|
||||
return (demo3d_bsp_lightmap_sample){lm->color[2], lm->color[1], lm->color[0]};
|
||||
}
|
||||
|
||||
if (!bsp->lightdata || bsp->lightdata_size == 0) {
|
||||
return white;
|
||||
}
|
||||
|
||||
f32 w = (f32)lm->width;
|
||||
f32 h = (f32)lm->height;
|
||||
f32 fx = u * w;
|
||||
f32 fy = v * h;
|
||||
|
||||
if (fx < 0) fx = 0;
|
||||
if (fx > w - 1.001f) fx = w - 1.001f;
|
||||
if (fy < 0) fy = 0;
|
||||
if (fy > h - 1.001f) fy = h - 1.001f;
|
||||
|
||||
u32 x0 = (u32)fx;
|
||||
u32 y0 = (u32)fy;
|
||||
u32 x1 = x0 + 1;
|
||||
u32 y1 = y0 + 1;
|
||||
if (x1 >= lm->width) x1 = lm->width - 1;
|
||||
if (y1 >= lm->height) y1 = lm->height - 1;
|
||||
|
||||
f32 frac_x = fx - (f32)x0;
|
||||
f32 frac_y = fy - (f32)y0;
|
||||
|
||||
u32 stride = lm->width;
|
||||
u32 base = lm->offset;
|
||||
|
||||
u32 idx00 = base + y0 * stride + x0;
|
||||
u32 idx10 = base + y0 * stride + x1;
|
||||
u32 idx01 = base + y1 * stride + x0;
|
||||
u32 idx11 = base + y1 * stride + x1;
|
||||
|
||||
u8 r00, g00, b00, r10, g10, b10, r01, g01, b01, r11, g11, b11;
|
||||
|
||||
if (idx00 < bsp->lightdata_size) pxl8_rgb332_unpack(bsp->lightdata[idx00], &r00, &g00, &b00);
|
||||
else { r00 = g00 = b00 = 255; }
|
||||
if (idx10 < bsp->lightdata_size) pxl8_rgb332_unpack(bsp->lightdata[idx10], &r10, &g10, &b10);
|
||||
else { r10 = g10 = b10 = 255; }
|
||||
if (idx01 < bsp->lightdata_size) pxl8_rgb332_unpack(bsp->lightdata[idx01], &r01, &g01, &b01);
|
||||
else { r01 = g01 = b01 = 255; }
|
||||
if (idx11 < bsp->lightdata_size) pxl8_rgb332_unpack(bsp->lightdata[idx11], &r11, &g11, &b11);
|
||||
else { r11 = g11 = b11 = 255; }
|
||||
|
||||
f32 inv_x = 1.0f - frac_x;
|
||||
f32 inv_y = 1.0f - frac_y;
|
||||
|
||||
u8 r = (u8)(r00 * inv_x * inv_y + r10 * frac_x * inv_y + r01 * inv_x * frac_y + r11 * frac_x * frac_y);
|
||||
u8 g = (u8)(g00 * inv_x * inv_y + g10 * frac_x * inv_y + g01 * inv_x * frac_y + g11 * frac_x * frac_y);
|
||||
u8 b = (u8)(b00 * inv_x * inv_y + b10 * frac_x * inv_y + b01 * inv_x * frac_y + b11 * frac_x * frac_y);
|
||||
|
||||
return (demo3d_bsp_lightmap_sample){b, g, r};
|
||||
}
|
||||
|
||||
u32 demo3d_bsp_face_count(const demo3d_bsp* bsp) {
|
||||
if (!bsp) return 0;
|
||||
return bsp->num_faces;
|
||||
}
|
||||
|
||||
pxl8_vec3 demo3d_bsp_face_normal(const demo3d_bsp* bsp, u32 face_id) {
|
||||
pxl8_vec3 up = {0, 1, 0};
|
||||
if (!bsp || face_id >= bsp->num_faces) return up;
|
||||
|
||||
const demo3d_bsp_face* face = &bsp->faces[face_id];
|
||||
if (face->plane_id >= bsp->num_planes) return up;
|
||||
|
||||
pxl8_vec3 normal = bsp->planes[face->plane_id].normal;
|
||||
if (face->side) {
|
||||
normal.x = -normal.x;
|
||||
normal.y = -normal.y;
|
||||
normal.z = -normal.z;
|
||||
}
|
||||
return normal;
|
||||
}
|
||||
|
||||
void demo3d_bsp_face_set_material(demo3d_bsp* bsp, u32 face_id, u16 material_id) {
|
||||
if (!bsp || face_id >= bsp->num_faces) return;
|
||||
bsp->faces[face_id].material_id = material_id;
|
||||
}
|
||||
162
demo3d/client/bsp/demo3d_bsp.h
Normal file
162
demo3d/client/bsp/demo3d_bsp.h
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_math.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
typedef struct demo3d_bsp_edge {
|
||||
u16 vertex[2];
|
||||
} demo3d_bsp_edge;
|
||||
|
||||
typedef struct demo3d_bsp_face {
|
||||
u32 first_edge;
|
||||
u32 lightmap_offset;
|
||||
u16 num_edges;
|
||||
u16 plane_id;
|
||||
u16 side;
|
||||
|
||||
u8 styles[4];
|
||||
u16 material_id;
|
||||
|
||||
pxl8_vec3 aabb_min;
|
||||
pxl8_vec3 aabb_max;
|
||||
} demo3d_bsp_face;
|
||||
|
||||
typedef struct demo3d_bsp_leaf {
|
||||
u8 ambient_level[4];
|
||||
i32 contents;
|
||||
|
||||
u16 first_marksurface;
|
||||
i16 maxs[3];
|
||||
i16 mins[3];
|
||||
u16 num_marksurfaces;
|
||||
|
||||
i32 visofs;
|
||||
} demo3d_bsp_leaf;
|
||||
|
||||
typedef struct demo3d_bsp_model {
|
||||
i32 first_face;
|
||||
i32 headnode[4];
|
||||
f32 maxs[3];
|
||||
f32 mins[3];
|
||||
i32 num_faces;
|
||||
|
||||
pxl8_vec3 origin;
|
||||
i32 visleafs;
|
||||
} demo3d_bsp_model;
|
||||
|
||||
typedef struct demo3d_bsp_node {
|
||||
i32 children[2];
|
||||
|
||||
u16 first_face;
|
||||
i16 maxs[3];
|
||||
i16 mins[3];
|
||||
u16 num_faces;
|
||||
|
||||
u32 plane_id;
|
||||
} demo3d_bsp_node;
|
||||
|
||||
typedef struct demo3d_bsp_plane {
|
||||
f32 dist;
|
||||
pxl8_vec3 normal;
|
||||
i32 type;
|
||||
} demo3d_bsp_plane;
|
||||
|
||||
|
||||
typedef struct demo3d_bsp_vertex {
|
||||
pxl8_vec3 position;
|
||||
} demo3d_bsp_vertex;
|
||||
|
||||
typedef struct demo3d_bsp_lightmap {
|
||||
u8 color[3];
|
||||
u8 height;
|
||||
u32 offset;
|
||||
u8 width;
|
||||
} demo3d_bsp_lightmap;
|
||||
|
||||
typedef struct demo3d_bsp_lightmap_sample {
|
||||
u8 b;
|
||||
u8 g;
|
||||
u8 r;
|
||||
} demo3d_bsp_lightmap_sample;
|
||||
|
||||
typedef struct demo3d_bsp_pvs {
|
||||
u8* data;
|
||||
u32 size;
|
||||
} demo3d_bsp_pvs;
|
||||
|
||||
typedef struct demo3d_bsp_portal {
|
||||
f32 x0, z0;
|
||||
f32 x1, z1;
|
||||
u32 target_leaf;
|
||||
} demo3d_bsp_portal;
|
||||
|
||||
typedef struct demo3d_bsp_cell_portals {
|
||||
demo3d_bsp_portal portals[4];
|
||||
u8 num_portals;
|
||||
} demo3d_bsp_cell_portals;
|
||||
|
||||
typedef struct demo3d_bsp {
|
||||
demo3d_bsp_cell_portals* cell_portals;
|
||||
demo3d_bsp_edge* edges;
|
||||
demo3d_bsp_face* faces;
|
||||
demo3d_bsp_leaf* leafs;
|
||||
u8* lightdata;
|
||||
demo3d_bsp_lightmap* lightmaps;
|
||||
u16* marksurfaces;
|
||||
demo3d_bsp_model* models;
|
||||
demo3d_bsp_node* nodes;
|
||||
demo3d_bsp_plane* planes;
|
||||
i32* surfedges;
|
||||
u32* vertex_lights;
|
||||
demo3d_bsp_vertex* vertices;
|
||||
u8* visdata;
|
||||
f32* heightfield;
|
||||
|
||||
u32 lightdata_size;
|
||||
u32 num_cell_portals;
|
||||
u32 num_edges;
|
||||
u32 num_faces;
|
||||
u32 num_leafs;
|
||||
u32 num_lightmaps;
|
||||
u32 num_marksurfaces;
|
||||
u32 num_models;
|
||||
u32 num_nodes;
|
||||
u32 num_planes;
|
||||
u32 num_surfedges;
|
||||
u32 num_vertex_lights;
|
||||
u32 num_vertices;
|
||||
u32 num_heightfield;
|
||||
f32 heightfield_ox;
|
||||
f32 heightfield_oz;
|
||||
f32 heightfield_cell_size;
|
||||
u16 heightfield_w;
|
||||
u16 heightfield_h;
|
||||
u32 visdata_size;
|
||||
f32 bounds_min_x;
|
||||
f32 bounds_min_z;
|
||||
f32 bounds_max_x;
|
||||
f32 bounds_max_z;
|
||||
} demo3d_bsp;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
demo3d_bsp_pvs demo3d_bsp_decompress_pvs(const demo3d_bsp* bsp, i32 leaf);
|
||||
void demo3d_bsp_destroy(demo3d_bsp* bsp);
|
||||
u32 demo3d_bsp_face_count(const demo3d_bsp* bsp);
|
||||
pxl8_vec3 demo3d_bsp_face_normal(const demo3d_bsp* bsp, u32 face_id);
|
||||
void demo3d_bsp_face_set_material(demo3d_bsp* bsp, u32 face_id, u16 material_id);
|
||||
i32 demo3d_bsp_find_leaf(const demo3d_bsp* bsp, pxl8_vec3 pos);
|
||||
bool demo3d_bsp_is_leaf_visible(const demo3d_bsp* bsp, i32 leaf_from, i32 leaf_to);
|
||||
demo3d_bsp_lightmap demo3d_bsp_lightmap_mapped(u8 width, u8 height, u32 offset);
|
||||
demo3d_bsp_lightmap demo3d_bsp_lightmap_uniform(u8 r, u8 g, u8 b);
|
||||
pxl8_result demo3d_bsp_load(const char* path, demo3d_bsp* bsp);
|
||||
void demo3d_bsp_pvs_destroy(demo3d_bsp_pvs* pvs);
|
||||
bool demo3d_bsp_pvs_is_visible(const demo3d_bsp_pvs* pvs, i32 leaf);
|
||||
u8 demo3d_bsp_light_at(const demo3d_bsp* bsp, f32 x, f32 y, f32 z, u8 ambient);
|
||||
demo3d_bsp_lightmap_sample demo3d_bsp_sample_lightmap(const demo3d_bsp* bsp, u32 face_idx, f32 u, f32 v);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
213
demo3d/client/bsp/demo3d_bsp_render.c
Normal file
213
demo3d/client/bsp/demo3d_bsp_render.c
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
#include "demo3d_bsp_render.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_gfx3d.h"
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_mem.h"
|
||||
#include "pxl8_mesh.h"
|
||||
|
||||
static inline bool demo3d_bsp_get_edge_vertex(const demo3d_bsp* bsp, i32 surfedge_idx, u32* out_vert_idx) {
|
||||
if (surfedge_idx >= (i32)bsp->num_surfedges) return false;
|
||||
|
||||
i32 edge_idx = bsp->surfedges[surfedge_idx];
|
||||
u32 vertex_index;
|
||||
|
||||
if (edge_idx >= 0) {
|
||||
if ((u32)edge_idx >= bsp->num_edges) return false;
|
||||
vertex_index = 0;
|
||||
} else {
|
||||
edge_idx = -edge_idx;
|
||||
if ((u32)edge_idx >= bsp->num_edges) return false;
|
||||
vertex_index = 1;
|
||||
}
|
||||
|
||||
*out_vert_idx = bsp->edges[edge_idx].vertex[vertex_index];
|
||||
return *out_vert_idx < bsp->num_vertices;
|
||||
}
|
||||
|
||||
static inline bool face_in_frustum(const demo3d_bsp* bsp, u32 face_id, const pxl8_frustum* frustum) {
|
||||
const demo3d_bsp_face* face = &bsp->faces[face_id];
|
||||
return pxl8_frustum_test_aabb(frustum, face->aabb_min, face->aabb_max);
|
||||
}
|
||||
|
||||
static void collect_face_to_mesh(const demo3d_bsp* bsp, const demo3d_bsp_render_state* state,
|
||||
u32 face_id, pxl8_mesh* mesh, u8 ambient) {
|
||||
const demo3d_bsp_face* face = &bsp->faces[face_id];
|
||||
if (face->num_edges < 3) return;
|
||||
|
||||
pxl8_vec3 normal = {0, 1, 0};
|
||||
if (face->plane_id < bsp->num_planes) {
|
||||
normal = bsp->planes[face->plane_id].normal;
|
||||
if (face->side) {
|
||||
normal.x = -normal.x;
|
||||
normal.y = -normal.y;
|
||||
normal.z = -normal.z;
|
||||
}
|
||||
}
|
||||
|
||||
const pxl8_gfx_material* material = NULL;
|
||||
f32 tex_scale = 64.0f;
|
||||
if (state && face->material_id < state->num_materials) {
|
||||
material = &state->materials[face->material_id];
|
||||
}
|
||||
|
||||
pxl8_vec3 u_axis = {1.0f, 0.0f, 0.0f};
|
||||
pxl8_vec3 v_axis = {0.0f, 0.0f, 1.0f};
|
||||
f32 u_offset = 0.0f, v_offset = 0.0f;
|
||||
if (material) {
|
||||
u_offset = material->u_offset;
|
||||
v_offset = material->v_offset;
|
||||
}
|
||||
f32 abs_ny = normal.y < 0 ? -normal.y : normal.y;
|
||||
if (abs_ny > 0.7f) {
|
||||
u_axis = (pxl8_vec3){1.0f, 0.0f, 0.0f};
|
||||
v_axis = (pxl8_vec3){0.0f, 0.0f, 1.0f};
|
||||
} else {
|
||||
f32 abs_nx = normal.x < 0 ? -normal.x : normal.x;
|
||||
f32 abs_nz = normal.z < 0 ? -normal.z : normal.z;
|
||||
if (abs_nx > abs_nz) {
|
||||
u_axis = (pxl8_vec3){0.0f, 0.0f, 1.0f};
|
||||
} else {
|
||||
u_axis = (pxl8_vec3){1.0f, 0.0f, 0.0f};
|
||||
}
|
||||
v_axis = (pxl8_vec3){0.0f, 1.0f, 0.0f};
|
||||
}
|
||||
|
||||
u16 base_idx = (u16)mesh->vertex_count;
|
||||
u32 num_verts = 0;
|
||||
|
||||
for (u32 i = 0; i < face->num_edges && num_verts < 64; i++) {
|
||||
u32 edge_i = face->side ? (face->num_edges - 1 - i) : i;
|
||||
i32 surfedge_idx = face->first_edge + edge_i;
|
||||
u32 vert_idx;
|
||||
|
||||
if (!demo3d_bsp_get_edge_vertex(bsp, surfedge_idx, &vert_idx)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pxl8_vec3 pos = bsp->vertices[vert_idx].position;
|
||||
|
||||
f32 u = (pxl8_vec3_dot(pos, u_axis) + u_offset) / tex_scale;
|
||||
f32 v = (pxl8_vec3_dot(pos, v_axis) + v_offset) / tex_scale;
|
||||
|
||||
u8 light = 255;
|
||||
if (bsp->vertex_lights && vert_idx < bsp->num_vertex_lights) {
|
||||
u32 packed = bsp->vertex_lights[vert_idx];
|
||||
u8 direct = (packed >> 24) & 0xFF;
|
||||
u8 ao = (packed >> 16) & 0xFF;
|
||||
f32 combined = (f32)direct + ((f32)ambient / 255.0f) * (f32)ao;
|
||||
light = (u8)(combined > 255.0f ? 255.0f : combined);
|
||||
}
|
||||
|
||||
pxl8_vertex vtx = {
|
||||
.position = pos,
|
||||
.normal = normal,
|
||||
.u = u,
|
||||
.v = v,
|
||||
.color = 15,
|
||||
.light = light,
|
||||
};
|
||||
pxl8_mesh_push_vertex(mesh, vtx);
|
||||
num_verts++;
|
||||
}
|
||||
|
||||
if (num_verts < 3) return;
|
||||
|
||||
for (u32 i = 1; i < num_verts - 1; i++) {
|
||||
pxl8_mesh_push_triangle(mesh, base_idx, base_idx + i, base_idx + i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
demo3d_bsp_render_state* demo3d_bsp_render_state_create(u32 num_faces) {
|
||||
demo3d_bsp_render_state* state = pxl8_calloc(1, sizeof(demo3d_bsp_render_state));
|
||||
if (!state) return NULL;
|
||||
|
||||
state->num_faces = num_faces;
|
||||
if (num_faces > 0) {
|
||||
state->render_face_flags = pxl8_calloc(num_faces, 1);
|
||||
if (!state->render_face_flags) {
|
||||
pxl8_free(state);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
void demo3d_bsp_render_state_destroy(demo3d_bsp_render_state* state) {
|
||||
if (!state) return;
|
||||
pxl8_free(state->materials);
|
||||
pxl8_free(state->render_face_flags);
|
||||
pxl8_free(state);
|
||||
}
|
||||
|
||||
void demo3d_bsp_render(pxl8_gfx* gfx, const demo3d_bsp* bsp,
|
||||
demo3d_bsp_render_state* state,
|
||||
const pxl8_gfx_draw_opts* opts) {
|
||||
if (!gfx || !bsp || !state || bsp->num_faces == 0) return;
|
||||
if (!state->materials || state->num_materials == 0) return;
|
||||
if (!state->render_face_flags) return;
|
||||
|
||||
const pxl8_frustum* frustum = pxl8_3d_get_frustum(gfx);
|
||||
if (!frustum) return;
|
||||
|
||||
pxl8_mesh* mesh = pxl8_mesh_create(8192, 16384);
|
||||
if (!mesh) return;
|
||||
|
||||
u8 ambient = pxl8_gfx_get_ambient(gfx);
|
||||
pxl8_mat4 identity = pxl8_mat4_identity();
|
||||
|
||||
for (u32 mat = 0; mat < state->num_materials; mat++) {
|
||||
for (u32 face_id = 0; face_id < bsp->num_faces; face_id++) {
|
||||
if (!state->render_face_flags[face_id]) continue;
|
||||
const demo3d_bsp_face* face = &bsp->faces[face_id];
|
||||
if (face->material_id != mat) continue;
|
||||
if (!face_in_frustum(bsp, face_id, frustum)) continue;
|
||||
collect_face_to_mesh(bsp, state, face_id, mesh, ambient);
|
||||
}
|
||||
if (mesh->index_count > 0) {
|
||||
pxl8_gfx_material mat_copy = state->materials[mat];
|
||||
if (state->exterior) mat_copy.double_sided = true;
|
||||
pxl8_3d_draw_mesh(gfx, mesh, &identity, &mat_copy, opts);
|
||||
pxl8_mesh_clear(mesh);
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_mesh_destroy(mesh);
|
||||
}
|
||||
|
||||
void demo3d_bsp_set_material(demo3d_bsp_render_state* state, u16 material_id, const pxl8_gfx_material* material) {
|
||||
if (!state || !material) return;
|
||||
|
||||
if (material_id >= state->num_materials) {
|
||||
u32 new_count = material_id + 1;
|
||||
pxl8_gfx_material* new_materials = pxl8_realloc(state->materials, new_count * sizeof(pxl8_gfx_material));
|
||||
if (!new_materials) return;
|
||||
|
||||
for (u32 i = state->num_materials; i < new_count; i++) {
|
||||
memset(&new_materials[i], 0, sizeof(pxl8_gfx_material));
|
||||
new_materials[i].u_axis = (pxl8_vec3){1.0f, 0.0f, 0.0f};
|
||||
if (i == 0 || i == 2) {
|
||||
new_materials[i].v_axis = (pxl8_vec3){0.0f, 0.0f, 1.0f};
|
||||
} else {
|
||||
new_materials[i].v_axis = (pxl8_vec3){0.0f, 1.0f, 0.0f};
|
||||
}
|
||||
}
|
||||
|
||||
state->materials = new_materials;
|
||||
state->num_materials = new_count;
|
||||
}
|
||||
|
||||
pxl8_vec3 u_axis = state->materials[material_id].u_axis;
|
||||
pxl8_vec3 v_axis = state->materials[material_id].v_axis;
|
||||
f32 u_offset = state->materials[material_id].u_offset;
|
||||
f32 v_offset = state->materials[material_id].v_offset;
|
||||
|
||||
state->materials[material_id] = *material;
|
||||
|
||||
state->materials[material_id].u_axis = u_axis;
|
||||
state->materials[material_id].v_axis = v_axis;
|
||||
state->materials[material_id].u_offset = u_offset;
|
||||
state->materials[material_id].v_offset = v_offset;
|
||||
}
|
||||
29
demo3d/client/bsp/demo3d_bsp_render.h
Normal file
29
demo3d/client/bsp/demo3d_bsp_render.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
|
||||
#include "demo3d_bsp.h"
|
||||
#include "pxl8_gfx.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct demo3d_bsp_render_state {
|
||||
pxl8_gfx_material* materials;
|
||||
u8* render_face_flags;
|
||||
u32 num_materials;
|
||||
u32 num_faces;
|
||||
bool exterior;
|
||||
} demo3d_bsp_render_state;
|
||||
|
||||
demo3d_bsp_render_state* demo3d_bsp_render_state_create(u32 num_faces);
|
||||
void demo3d_bsp_render_state_destroy(demo3d_bsp_render_state* state);
|
||||
|
||||
void demo3d_bsp_render(pxl8_gfx* gfx, const demo3d_bsp* bsp,
|
||||
demo3d_bsp_render_state* state,
|
||||
const pxl8_gfx_draw_opts* opts);
|
||||
void demo3d_bsp_set_material(demo3d_bsp_render_state* state, u16 material_id,
|
||||
const pxl8_gfx_material* material);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
218
demo3d/client/demo3d.c
Normal file
218
demo3d/client/demo3d.c
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
#include "pxl8_sys.h"
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_mem.h"
|
||||
#include "pxl8_script.h"
|
||||
|
||||
#include "demo3d_net.h"
|
||||
#include "demo3d_world.h"
|
||||
|
||||
static const char* demo3d_ffi_cdefs =
|
||||
"typedef struct demo3d_bsp demo3d_bsp;\n"
|
||||
"\n"
|
||||
"u32 demo3d_bsp_face_count(const demo3d_bsp* bsp);\n"
|
||||
"pxl8_vec3 demo3d_bsp_face_normal(const demo3d_bsp* bsp, u32 face_id);\n"
|
||||
"void demo3d_bsp_face_set_material(demo3d_bsp* bsp, u32 face_id, u16 material_id);\n"
|
||||
"u8 demo3d_bsp_light_at(const demo3d_bsp* bsp, f32 x, f32 y, f32 z, u8 ambient);\n"
|
||||
"\n"
|
||||
"typedef struct demo3d_chunk {\n"
|
||||
" u32 id;\n"
|
||||
" u32 version;\n"
|
||||
" demo3d_bsp* bsp;\n"
|
||||
"} demo3d_chunk;\n"
|
||||
"\n"
|
||||
"typedef struct demo3d_world demo3d_world;\n"
|
||||
"\n"
|
||||
"typedef struct pxl8_ray {\n"
|
||||
" pxl8_vec3 normal;\n"
|
||||
" pxl8_vec3 point;\n"
|
||||
" float fraction;\n"
|
||||
" bool hit;\n"
|
||||
"} pxl8_ray;\n"
|
||||
"\n"
|
||||
"demo3d_world* demo3d_get_world(pxl8* sys);\n"
|
||||
"demo3d_chunk* demo3d_world_active_chunk(demo3d_world* world);\n"
|
||||
"bool demo3d_world_point_solid(const demo3d_world* world, float x, float y, float z);\n"
|
||||
"pxl8_ray demo3d_world_ray(const demo3d_world* world, pxl8_vec3 from, pxl8_vec3 to);\n"
|
||||
"pxl8_ray demo3d_world_sweep(const demo3d_world* world, pxl8_vec3 from, pxl8_vec3 to, float radius);\n"
|
||||
"void demo3d_world_render(demo3d_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);\n"
|
||||
"void demo3d_world_set_bsp_material(demo3d_world* world, u16 material_id, const pxl8_gfx_material* material);\n"
|
||||
"\n"
|
||||
"typedef struct demo3d_sim_entity {\n"
|
||||
" pxl8_vec3 pos;\n"
|
||||
" pxl8_vec3 vel;\n"
|
||||
" f32 yaw;\n"
|
||||
" f32 pitch;\n"
|
||||
" u32 flags;\n"
|
||||
" u16 kind;\n"
|
||||
" u16 _pad;\n"
|
||||
"} demo3d_sim_entity;\n"
|
||||
"\n"
|
||||
"void demo3d_world_init_local_player(demo3d_world* world, f32 x, f32 y, f32 z);\n"
|
||||
"void demo3d_world_set_look(demo3d_world* world, f32 yaw, f32 pitch);\n"
|
||||
"demo3d_sim_entity* demo3d_world_local_player(demo3d_world* world);\n"
|
||||
"\n"
|
||||
"typedef struct demo3d_input_msg {\n"
|
||||
" u32 buttons;\n"
|
||||
" f32 look_dx;\n"
|
||||
" f32 look_dy;\n"
|
||||
" f32 move_x;\n"
|
||||
" f32 move_y;\n"
|
||||
" f32 yaw;\n"
|
||||
" u64 tick;\n"
|
||||
" u64 timestamp;\n"
|
||||
"} demo3d_input_msg;\n"
|
||||
"\n"
|
||||
"typedef struct demo3d_sim_config {\n"
|
||||
" f32 move_speed;\n"
|
||||
" f32 ground_accel;\n"
|
||||
" f32 air_accel;\n"
|
||||
" f32 stop_speed;\n"
|
||||
" f32 friction;\n"
|
||||
" f32 gravity;\n"
|
||||
" f32 jump_velocity;\n"
|
||||
" f32 player_radius;\n"
|
||||
" f32 player_height;\n"
|
||||
" f32 max_pitch;\n"
|
||||
"} demo3d_sim_config;\n"
|
||||
"\n"
|
||||
"typedef struct demo3d_sim_world {\n"
|
||||
" const demo3d_bsp* chunks[9];\n"
|
||||
" i32 center_cx;\n"
|
||||
" i32 center_cz;\n"
|
||||
" f32 chunk_size;\n"
|
||||
"} demo3d_sim_world;\n"
|
||||
"\n"
|
||||
"void demo3d_sim_move_player(demo3d_sim_entity* ent, const demo3d_input_msg* input, const demo3d_sim_world* world, const demo3d_sim_config* cfg, f32 dt);\n"
|
||||
"void demo3d_sim_integrate(demo3d_sim_entity* ent, const demo3d_sim_world* world, const demo3d_sim_config* cfg, f32 dt);\n"
|
||||
"pxl8_vec3 demo3d_sim_trace(const demo3d_sim_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius, f32 height);\n"
|
||||
"bool demo3d_sim_check_ground(const demo3d_sim_world* world, pxl8_vec3 pos, f32 radius);\n"
|
||||
"\n"
|
||||
"demo3d_sim_world demo3d_world_sim_world(const demo3d_world* world, pxl8_vec3 pos);\n"
|
||||
"void demo3d_world_set_sim_config(demo3d_world* world, const demo3d_sim_config* config);\n"
|
||||
"void demo3d_world_push_input(demo3d_world* world, const demo3d_input_msg* input);\n"
|
||||
"\n"
|
||||
"typedef struct demo3d_net demo3d_net;\n"
|
||||
"\n"
|
||||
"typedef struct demo3d_entity_state {\n"
|
||||
" u64 entity_id;\n"
|
||||
" u8 userdata[56];\n"
|
||||
"} demo3d_entity_state;\n"
|
||||
"\n"
|
||||
"typedef struct demo3d_snapshot_header {\n"
|
||||
" u16 entity_count;\n"
|
||||
" u16 event_count;\n"
|
||||
" u64 player_id;\n"
|
||||
" u64 tick;\n"
|
||||
" f32 time;\n"
|
||||
"} demo3d_snapshot_header;\n"
|
||||
"\n"
|
||||
"demo3d_net* demo3d_get_net(pxl8* sys);\n"
|
||||
"bool demo3d_net_connected(const demo3d_net* ng);\n"
|
||||
"i32 demo3d_net_spawn(demo3d_net* ng, f32 x, f32 y, f32 z, f32 yaw, f32 pitch);\n"
|
||||
"bool demo3d_net_has_chunk(const demo3d_net* ng);\n"
|
||||
"i32 demo3d_net_chunk_cx(const demo3d_net* ng);\n"
|
||||
"i32 demo3d_net_chunk_cz(const demo3d_net* ng);\n"
|
||||
"const demo3d_entity_state* demo3d_net_entities(const demo3d_net* ng);\n"
|
||||
"u64 demo3d_net_player_id(const demo3d_net* ng);\n"
|
||||
"const u8* demo3d_net_entity_userdata(const demo3d_net* ng, u64 entity_id);\n"
|
||||
"const u8* demo3d_net_entity_prev_userdata(const demo3d_net* ng, u64 entity_id);\n"
|
||||
"f32 demo3d_net_lerp_alpha(const demo3d_net* ng);\n"
|
||||
"const demo3d_snapshot_header* demo3d_net_snapshot(const demo3d_net* ng);\n"
|
||||
;
|
||||
|
||||
typedef struct demo3d_state {
|
||||
demo3d_net* net;
|
||||
demo3d_world* world;
|
||||
} demo3d_state;
|
||||
|
||||
static pxl8_result demo3d_init(pxl8_game* game, void* userdata) {
|
||||
(void)userdata;
|
||||
demo3d_state* state = pxl8_calloc(1, sizeof(demo3d_state));
|
||||
if (!state) return PXL8_ERROR_INITIALIZATION_FAILED;
|
||||
game->userdata = state;
|
||||
|
||||
pxl8_script_add_ffi(game->script, demo3d_ffi_cdefs);
|
||||
|
||||
state->world = demo3d_world_create();
|
||||
if (!state->world) {
|
||||
pxl8_error("failed to create world");
|
||||
return PXL8_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
|
||||
pxl8_net_config net_cfg = { .address = "127.0.0.1", .port = 7777 };
|
||||
state->net = demo3d_net_create(&net_cfg);
|
||||
if (state->net) {
|
||||
demo3d_net_set_chunk_cache(state->net, demo3d_world_get_chunk_cache(state->world));
|
||||
demo3d_net_set_world(state->net, state->world);
|
||||
demo3d_net_connect(state->net);
|
||||
}
|
||||
|
||||
#ifdef PXL8_ASYNC_THREADS
|
||||
if (state->net) {
|
||||
demo3d_net_start_thread(state->net);
|
||||
}
|
||||
if (state->world) {
|
||||
demo3d_world_start_sim_thread(state->world, state->net);
|
||||
}
|
||||
#endif
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static void demo3d_update(pxl8_game* game, f32 dt, void* userdata) {
|
||||
(void)userdata;
|
||||
demo3d_state* state = (demo3d_state*)game->userdata;
|
||||
|
||||
#ifdef PXL8_ASYNC_THREADS
|
||||
demo3d_net_update(state->net, dt);
|
||||
#else
|
||||
if (state->net) {
|
||||
while (demo3d_net_poll(state->net)) {}
|
||||
demo3d_net_update(state->net, dt);
|
||||
demo3d_world_sync(state->world, state->net);
|
||||
}
|
||||
demo3d_world_update(state->world, dt);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void demo3d_quit(pxl8_game* game, void* userdata) {
|
||||
(void)userdata;
|
||||
demo3d_state* state = (demo3d_state*)game->userdata;
|
||||
if (!state) return;
|
||||
|
||||
#ifdef PXL8_ASYNC_THREADS
|
||||
if (state->world) {
|
||||
demo3d_world_stop_sim_thread(state->world);
|
||||
}
|
||||
if (state->net) {
|
||||
demo3d_net_stop_thread(state->net);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (state->net) demo3d_net_destroy(state->net);
|
||||
if (state->world) demo3d_world_destroy(state->world);
|
||||
|
||||
pxl8_free(state);
|
||||
game->userdata = NULL;
|
||||
}
|
||||
|
||||
demo3d_net* demo3d_get_net(pxl8* sys) {
|
||||
pxl8_game* game = pxl8_get_game(sys);
|
||||
if (!game || !game->userdata) return NULL;
|
||||
return ((demo3d_state*)game->userdata)->net;
|
||||
}
|
||||
|
||||
demo3d_world* demo3d_get_world(pxl8* sys) {
|
||||
pxl8_game* game = pxl8_get_game(sys);
|
||||
if (!game || !game->userdata) return NULL;
|
||||
return ((demo3d_state*)game->userdata)->world;
|
||||
}
|
||||
|
||||
void pxl8_register_game(pxl8* sys) {
|
||||
pxl8_game_hooks hooks = {
|
||||
.init = demo3d_init,
|
||||
.update = demo3d_update,
|
||||
.quit = demo3d_quit,
|
||||
};
|
||||
pxl8_set_game_hooks(sys, hooks);
|
||||
}
|
||||
297
demo3d/client/net/demo3d_net.c
Normal file
297
demo3d/client/net/demo3d_net.c
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
#include "pxl8_platform.h"
|
||||
#include "demo3d_net.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_bytes.h"
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_mem.h"
|
||||
#include "demo3d_world.h"
|
||||
#include "demo3d_chunk_cache.h"
|
||||
|
||||
static const demo3d_entity_state* find_entity(const demo3d_entity_state* entities, u16 count, u64 id) {
|
||||
for (u16 i = 0; i < count; i++) {
|
||||
if (entities[i].entity_id == id) return &entities[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool dispatch_message(demo3d_net* ng, const u8* data, usize len) {
|
||||
if (len < sizeof(demo3d_msg_header)) return false;
|
||||
|
||||
demo3d_msg_header hdr;
|
||||
usize offset = demo3d_protocol_deserialize_header(data, len, &hdr);
|
||||
|
||||
if (hdr.type == DEMO3D_MSG_CHUNK) {
|
||||
if (!ng->chunk_cache) return false;
|
||||
demo3d_chunk_msg_header chunk_hdr;
|
||||
offset += demo3d_protocol_deserialize_chunk_msg_header(data + offset, len - offset, &chunk_hdr);
|
||||
const u8* payload = data + offset;
|
||||
usize payload_len = chunk_hdr.payload_size;
|
||||
if (payload_len > len - offset) payload_len = len - offset;
|
||||
demo3d_chunk_cache_receive(ng->chunk_cache, &chunk_hdr, payload, payload_len);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hdr.type == DEMO3D_MSG_CHUNK_ENTER) {
|
||||
demo3d_chunk_enter_msg chunk_msg;
|
||||
demo3d_protocol_deserialize_chunk_enter(data + offset, len - offset, &chunk_msg);
|
||||
ng->chunk_cx = chunk_msg.cx;
|
||||
ng->chunk_cz = chunk_msg.cz;
|
||||
ng->has_chunk = true;
|
||||
pxl8_debug("Received CHUNK_ENTER cx=%d cz=%d", chunk_msg.cx, chunk_msg.cz);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hdr.type == DEMO3D_MSG_CHUNK_EXIT) {
|
||||
ng->has_chunk = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hdr.type != DEMO3D_MSG_SNAPSHOT) return false;
|
||||
|
||||
demo3d_snapshot_header snap;
|
||||
offset += demo3d_protocol_deserialize_snapshot_header(data + offset, len - offset, &snap);
|
||||
if (snap.tick <= ng->highest_tick) return false;
|
||||
|
||||
memcpy(ng->prev_entities, ng->entities, sizeof(ng->entities));
|
||||
ng->prev_snapshot = ng->snapshot;
|
||||
ng->highest_tick = snap.tick;
|
||||
ng->snapshot = snap;
|
||||
ng->interp_time = 0.0f;
|
||||
|
||||
u16 count = snap.entity_count;
|
||||
if (count > DEMO3D_MAX_SNAPSHOT_ENTITIES) count = DEMO3D_MAX_SNAPSHOT_ENTITIES;
|
||||
|
||||
for (u16 i = 0; i < count; i++) {
|
||||
offset += demo3d_protocol_deserialize_entity_state(data + offset, len - offset, &ng->entities[i]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
demo3d_net* demo3d_net_create(const pxl8_net_config* config) {
|
||||
demo3d_net* ng = pxl8_calloc(1, sizeof(demo3d_net));
|
||||
if (!ng) return NULL;
|
||||
ng->transport = pxl8_net_create(config);
|
||||
if (!ng->transport) {
|
||||
pxl8_free(ng);
|
||||
return NULL;
|
||||
}
|
||||
return ng;
|
||||
}
|
||||
|
||||
void demo3d_net_destroy(demo3d_net* ng) {
|
||||
if (!ng) return;
|
||||
pxl8_net_destroy(ng->transport);
|
||||
pxl8_free(ng);
|
||||
}
|
||||
|
||||
pxl8_result demo3d_net_connect(demo3d_net* ng) {
|
||||
if (!ng) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
return pxl8_net_connect(ng->transport);
|
||||
}
|
||||
|
||||
bool demo3d_net_connected(const demo3d_net* ng) {
|
||||
return ng && pxl8_net_connected(ng->transport);
|
||||
}
|
||||
|
||||
void demo3d_net_disconnect(demo3d_net* ng) {
|
||||
if (!ng) return;
|
||||
pxl8_net_disconnect(ng->transport);
|
||||
}
|
||||
|
||||
const demo3d_entity_state* demo3d_net_entities(const demo3d_net* ng) {
|
||||
if (!ng) return NULL;
|
||||
return ng->entities;
|
||||
}
|
||||
|
||||
const u8* demo3d_net_entity_prev_userdata(const demo3d_net* ng, u64 entity_id) {
|
||||
if (!ng) return NULL;
|
||||
const demo3d_entity_state* e = find_entity(ng->prev_entities, ng->prev_snapshot.entity_count, entity_id);
|
||||
return e ? e->userdata : NULL;
|
||||
}
|
||||
|
||||
const u8* demo3d_net_entity_userdata(const demo3d_net* ng, u64 entity_id) {
|
||||
if (!ng) return NULL;
|
||||
const demo3d_entity_state* e = find_entity(ng->entities, ng->snapshot.entity_count, entity_id);
|
||||
return e ? e->userdata : NULL;
|
||||
}
|
||||
|
||||
const demo3d_input_msg* demo3d_net_input_at(const demo3d_net* ng, u64 tick) {
|
||||
if (!ng) return NULL;
|
||||
if (tick < ng->input_oldest_tick) return NULL;
|
||||
u64 age = tick - ng->input_oldest_tick;
|
||||
if (age >= DEMO3D_NET_INPUT_HISTORY_SIZE) return NULL;
|
||||
u64 idx = tick % DEMO3D_NET_INPUT_HISTORY_SIZE;
|
||||
const demo3d_input_msg* msg = &ng->input_history[idx];
|
||||
if (msg->tick != tick) return NULL;
|
||||
return msg;
|
||||
}
|
||||
|
||||
u64 demo3d_net_input_oldest_tick(const demo3d_net* ng) {
|
||||
if (!ng) return 0;
|
||||
return ng->input_oldest_tick;
|
||||
}
|
||||
|
||||
void demo3d_net_input_push(demo3d_net* ng, const demo3d_input_msg* input) {
|
||||
if (!ng || !input) return;
|
||||
u64 idx = input->tick % DEMO3D_NET_INPUT_HISTORY_SIZE;
|
||||
ng->input_history[idx] = *input;
|
||||
ng->input_head = input->tick;
|
||||
if (input->tick >= DEMO3D_NET_INPUT_HISTORY_SIZE) {
|
||||
ng->input_oldest_tick = input->tick - DEMO3D_NET_INPUT_HISTORY_SIZE + 1;
|
||||
}
|
||||
}
|
||||
|
||||
f32 demo3d_net_lerp_alpha(const demo3d_net* ng) {
|
||||
if (!ng) return 1.0f;
|
||||
return ng->interp_time / (1.0f / DEMO3D_NET_TICK_RATE);
|
||||
}
|
||||
|
||||
bool demo3d_net_needs_correction(const demo3d_net* ng) {
|
||||
if (!ng) return false;
|
||||
if (ng->snapshot.tick == 0) return false;
|
||||
if (ng->predicted_tick == 0) return false;
|
||||
|
||||
u64 player_id = ng->snapshot.player_id;
|
||||
const demo3d_entity_state* server = find_entity(ng->entities, ng->snapshot.entity_count, player_id);
|
||||
if (!server) return false;
|
||||
|
||||
return memcmp(server->userdata, ng->predicted_state, DEMO3D_NET_USERDATA_SIZE) != 0;
|
||||
}
|
||||
|
||||
u64 demo3d_net_player_id(const demo3d_net* ng) {
|
||||
if (!ng) return 0;
|
||||
return ng->snapshot.player_id;
|
||||
}
|
||||
|
||||
bool demo3d_net_poll(demo3d_net* ng) {
|
||||
if (!ng || !pxl8_net_connected(ng->transport)) return false;
|
||||
|
||||
u8 recv_buf[4096];
|
||||
usize len = pxl8_net_recv(ng->transport, recv_buf, sizeof(recv_buf));
|
||||
u64 prev_tick = ng->highest_tick;
|
||||
if (!dispatch_message(ng, recv_buf, len)) return false;
|
||||
|
||||
if (ng->highest_tick > prev_tick && ng->world) {
|
||||
demo3d_world_reconcile(ng->world, ng, 1.0f / 30.0f);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
u8* demo3d_net_predicted_state(demo3d_net* ng) {
|
||||
if (!ng) return NULL;
|
||||
return ng->predicted_state;
|
||||
}
|
||||
|
||||
void demo3d_net_predicted_tick_set(demo3d_net* ng, u64 tick) {
|
||||
if (!ng) return;
|
||||
ng->predicted_tick = tick;
|
||||
}
|
||||
|
||||
pxl8_result demo3d_net_send_command(demo3d_net* ng, const demo3d_command_msg* cmd) {
|
||||
if (!ng || !cmd) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
if (!pxl8_net_connected(ng->transport)) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
u8 buf[512];
|
||||
demo3d_msg_header hdr = {.type = DEMO3D_MSG_COMMAND, .version = DEMO3D_PROTOCOL_VERSION};
|
||||
usize offset = demo3d_protocol_serialize_header(&hdr, buf, sizeof(buf));
|
||||
offset += demo3d_protocol_serialize_command(cmd, buf + offset, sizeof(buf) - offset);
|
||||
|
||||
return pxl8_net_send(ng->transport, buf, offset);
|
||||
}
|
||||
|
||||
pxl8_result demo3d_net_send_input(demo3d_net* ng, const demo3d_input_msg* input) {
|
||||
if (!ng || !input) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
if (!pxl8_net_connected(ng->transport)) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
demo3d_net_input_push(ng, input);
|
||||
|
||||
u8 buf[512];
|
||||
demo3d_msg_header hdr = {.type = DEMO3D_MSG_INPUT, .version = DEMO3D_PROTOCOL_VERSION};
|
||||
usize offset = demo3d_protocol_serialize_header(&hdr, buf, sizeof(buf));
|
||||
offset += demo3d_protocol_serialize_input(input, buf + offset, sizeof(buf) - offset);
|
||||
|
||||
return pxl8_net_send(ng->transport, buf, offset);
|
||||
}
|
||||
|
||||
const demo3d_snapshot_header* demo3d_net_snapshot(const demo3d_net* ng) {
|
||||
if (!ng) return NULL;
|
||||
return &ng->snapshot;
|
||||
}
|
||||
|
||||
u64 demo3d_net_tick(const demo3d_net* ng) {
|
||||
if (!ng) return 0;
|
||||
return ng->snapshot.tick;
|
||||
}
|
||||
|
||||
void demo3d_net_update(demo3d_net* ng, f32 dt) {
|
||||
if (!ng) return;
|
||||
ng->interp_time += dt;
|
||||
}
|
||||
|
||||
void demo3d_net_set_chunk_cache(demo3d_net* ng, demo3d_chunk_cache* cache) {
|
||||
if (!ng) return;
|
||||
ng->chunk_cache = cache;
|
||||
}
|
||||
|
||||
demo3d_chunk_cache* demo3d_net_chunk_cache(demo3d_net* ng) {
|
||||
if (!ng) return NULL;
|
||||
return ng->chunk_cache;
|
||||
}
|
||||
|
||||
void demo3d_net_set_world(demo3d_net* ng, demo3d_world* world) {
|
||||
if (!ng) return;
|
||||
ng->world = world;
|
||||
}
|
||||
|
||||
i32 demo3d_net_chunk_cx(const demo3d_net* ng) {
|
||||
if (!ng) return 0;
|
||||
return ng->chunk_cx;
|
||||
}
|
||||
|
||||
i32 demo3d_net_chunk_cz(const demo3d_net* ng) {
|
||||
if (!ng) return 0;
|
||||
return ng->chunk_cz;
|
||||
}
|
||||
|
||||
bool demo3d_net_has_chunk(const demo3d_net* ng) {
|
||||
if (!ng) return false;
|
||||
return ng->has_chunk;
|
||||
}
|
||||
|
||||
pxl8_result demo3d_net_spawn(demo3d_net* ng, f32 x, f32 y, f32 z, f32 yaw, f32 pitch) {
|
||||
if (!ng) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
demo3d_command_msg cmd = {0};
|
||||
cmd.cmd_type = DEMO3D_CMD_SPAWN_ENTITY;
|
||||
|
||||
u8* p = cmd.payload;
|
||||
memcpy(p, &x, 4); p += 4;
|
||||
memcpy(p, &y, 4); p += 4;
|
||||
memcpy(p, &z, 4); p += 4;
|
||||
memcpy(p, &yaw, 4); p += 4;
|
||||
memcpy(p, &pitch, 4);
|
||||
cmd.payload_size = 20;
|
||||
|
||||
return demo3d_net_send_command(ng, &cmd);
|
||||
}
|
||||
|
||||
#ifdef PXL8_ASYNC_THREADS
|
||||
void demo3d_net_start_thread(demo3d_net* ng) {
|
||||
if (!ng) return;
|
||||
pxl8_net_start_thread(ng->transport);
|
||||
}
|
||||
|
||||
void demo3d_net_stop_thread(demo3d_net* ng) {
|
||||
if (!ng) return;
|
||||
pxl8_net_stop_thread(ng->transport);
|
||||
}
|
||||
|
||||
bool demo3d_net_dispatch_packet(demo3d_net* ng, const pxl8_packet* pkt) {
|
||||
if (!ng || !pkt) return false;
|
||||
return dispatch_message(ng, pkt->data, pkt->len);
|
||||
}
|
||||
#endif
|
||||
74
demo3d/client/net/demo3d_net.h
Normal file
74
demo3d/client/net/demo3d_net.h
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_net.h"
|
||||
#include "demo3d_protocol.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#define DEMO3D_NET_INPUT_HISTORY_SIZE 64
|
||||
#define DEMO3D_NET_USERDATA_SIZE 56
|
||||
#define DEMO3D_NET_TICK_RATE 30.0f
|
||||
|
||||
typedef struct demo3d_world demo3d_world;
|
||||
typedef struct demo3d_chunk_cache demo3d_chunk_cache;
|
||||
|
||||
typedef struct demo3d_net {
|
||||
pxl8_net* transport;
|
||||
|
||||
demo3d_chunk_cache* chunk_cache;
|
||||
i32 chunk_cx;
|
||||
i32 chunk_cz;
|
||||
bool has_chunk;
|
||||
demo3d_world* world;
|
||||
|
||||
u64 highest_tick;
|
||||
f32 interp_time;
|
||||
|
||||
demo3d_entity_state entities[DEMO3D_MAX_SNAPSHOT_ENTITIES];
|
||||
demo3d_entity_state prev_entities[DEMO3D_MAX_SNAPSHOT_ENTITIES];
|
||||
demo3d_snapshot_header prev_snapshot;
|
||||
demo3d_snapshot_header snapshot;
|
||||
|
||||
u64 input_head;
|
||||
demo3d_input_msg input_history[DEMO3D_NET_INPUT_HISTORY_SIZE];
|
||||
u64 input_oldest_tick;
|
||||
|
||||
u8 predicted_state[DEMO3D_NET_USERDATA_SIZE];
|
||||
u64 predicted_tick;
|
||||
} demo3d_net;
|
||||
|
||||
demo3d_net* demo3d_net_create(const pxl8_net_config* config);
|
||||
void demo3d_net_destroy(demo3d_net* ng);
|
||||
pxl8_result demo3d_net_connect(demo3d_net* ng);
|
||||
bool demo3d_net_connected(const demo3d_net* ng);
|
||||
void demo3d_net_disconnect(demo3d_net* ng);
|
||||
|
||||
const demo3d_entity_state* demo3d_net_entities(const demo3d_net* ng);
|
||||
const u8* demo3d_net_entity_prev_userdata(const demo3d_net* ng, u64 entity_id);
|
||||
const u8* demo3d_net_entity_userdata(const demo3d_net* ng, u64 entity_id);
|
||||
const demo3d_input_msg* demo3d_net_input_at(const demo3d_net* ng, u64 tick);
|
||||
u64 demo3d_net_input_oldest_tick(const demo3d_net* ng);
|
||||
void demo3d_net_input_push(demo3d_net* ng, const demo3d_input_msg* input);
|
||||
f32 demo3d_net_lerp_alpha(const demo3d_net* ng);
|
||||
bool demo3d_net_needs_correction(const demo3d_net* ng);
|
||||
u64 demo3d_net_player_id(const demo3d_net* ng);
|
||||
bool demo3d_net_poll(demo3d_net* ng);
|
||||
u8* demo3d_net_predicted_state(demo3d_net* ng);
|
||||
void demo3d_net_predicted_tick_set(demo3d_net* ng, u64 tick);
|
||||
pxl8_result demo3d_net_send_command(demo3d_net* ng, const demo3d_command_msg* cmd);
|
||||
pxl8_result demo3d_net_send_input(demo3d_net* ng, const demo3d_input_msg* input);
|
||||
const demo3d_snapshot_header* demo3d_net_snapshot(const demo3d_net* ng);
|
||||
u64 demo3d_net_tick(const demo3d_net* ng);
|
||||
void demo3d_net_update(demo3d_net* ng, f32 dt);
|
||||
void demo3d_net_set_chunk_cache(demo3d_net* ng, demo3d_chunk_cache* cache);
|
||||
demo3d_chunk_cache* demo3d_net_chunk_cache(demo3d_net* ng);
|
||||
void demo3d_net_set_world(demo3d_net* ng, demo3d_world* world);
|
||||
i32 demo3d_net_chunk_cx(const demo3d_net* ng);
|
||||
i32 demo3d_net_chunk_cz(const demo3d_net* ng);
|
||||
bool demo3d_net_has_chunk(const demo3d_net* ng);
|
||||
pxl8_result demo3d_net_spawn(demo3d_net* ng, f32 x, f32 y, f32 z, f32 yaw, f32 pitch);
|
||||
|
||||
#ifdef PXL8_ASYNC_THREADS
|
||||
void demo3d_net_start_thread(demo3d_net* ng);
|
||||
void demo3d_net_stop_thread(demo3d_net* ng);
|
||||
bool demo3d_net_dispatch_packet(demo3d_net* ng, const pxl8_packet* pkt);
|
||||
#endif
|
||||
197
demo3d/client/net/demo3d_protocol.c
Normal file
197
demo3d/client/net/demo3d_protocol.c
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
#include "demo3d_protocol.h"
|
||||
#include "pxl8_bytes.h"
|
||||
|
||||
usize demo3d_protocol_serialize_header(const demo3d_msg_header* msg, u8* buf, usize len) {
|
||||
if (len < sizeof(demo3d_msg_header)) return 0;
|
||||
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
|
||||
pxl8_write_u32_be(&s, msg->sequence);
|
||||
pxl8_write_u16_be(&s, msg->size);
|
||||
pxl8_write_u8(&s, msg->type);
|
||||
pxl8_write_u8(&s, msg->version);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
usize demo3d_protocol_deserialize_header(const u8* buf, usize len, demo3d_msg_header* msg) {
|
||||
if (len < sizeof(demo3d_msg_header)) return 0;
|
||||
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
|
||||
msg->sequence = pxl8_read_u32_be(&s);
|
||||
msg->size = pxl8_read_u16_be(&s);
|
||||
msg->type = pxl8_read_u8(&s);
|
||||
msg->version = pxl8_read_u8(&s);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
usize demo3d_protocol_serialize_input(const demo3d_input_msg* msg, u8* buf, usize len) {
|
||||
if (len < sizeof(demo3d_input_msg)) return 0;
|
||||
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
|
||||
pxl8_write_u32_be(&s, msg->buttons);
|
||||
pxl8_write_f32_be(&s, msg->look_dx);
|
||||
pxl8_write_f32_be(&s, msg->look_dy);
|
||||
pxl8_write_f32_be(&s, msg->move_x);
|
||||
pxl8_write_f32_be(&s, msg->move_y);
|
||||
pxl8_write_f32_be(&s, msg->yaw);
|
||||
pxl8_write_u64_be(&s, msg->tick);
|
||||
pxl8_write_u64_be(&s, msg->timestamp);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
usize demo3d_protocol_deserialize_input(const u8* buf, usize len, demo3d_input_msg* msg) {
|
||||
if (len < sizeof(demo3d_input_msg)) return 0;
|
||||
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
|
||||
msg->buttons = pxl8_read_u32_be(&s);
|
||||
msg->look_dx = pxl8_read_f32_be(&s);
|
||||
msg->look_dy = pxl8_read_f32_be(&s);
|
||||
msg->move_x = pxl8_read_f32_be(&s);
|
||||
msg->move_y = pxl8_read_f32_be(&s);
|
||||
msg->yaw = pxl8_read_f32_be(&s);
|
||||
msg->tick = pxl8_read_u64_be(&s);
|
||||
msg->timestamp = pxl8_read_u64_be(&s);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
usize demo3d_protocol_serialize_command(const demo3d_command_msg* msg, u8* buf, usize len) {
|
||||
if (len < sizeof(demo3d_command_msg)) return 0;
|
||||
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
|
||||
pxl8_write_u16_be(&s, msg->cmd_type);
|
||||
pxl8_write_bytes(&s, msg->payload, DEMO3D_COMMAND_PAYLOAD_SIZE);
|
||||
pxl8_write_u16_be(&s, msg->payload_size);
|
||||
pxl8_write_u64_be(&s, msg->tick);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
usize demo3d_protocol_deserialize_command(const u8* buf, usize len, demo3d_command_msg* msg) {
|
||||
if (len < sizeof(demo3d_command_msg)) return 0;
|
||||
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
|
||||
msg->cmd_type = pxl8_read_u16_be(&s);
|
||||
pxl8_read_bytes(&s, msg->payload, DEMO3D_COMMAND_PAYLOAD_SIZE);
|
||||
msg->payload_size = pxl8_read_u16_be(&s);
|
||||
msg->tick = pxl8_read_u64_be(&s);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
usize demo3d_protocol_serialize_entity_state(const demo3d_entity_state* state, u8* buf, usize len) {
|
||||
if (len < sizeof(demo3d_entity_state)) return 0;
|
||||
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
|
||||
pxl8_write_u64_be(&s, state->entity_id);
|
||||
pxl8_write_bytes(&s, state->userdata, 56);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
usize demo3d_protocol_deserialize_entity_state(const u8* buf, usize len, demo3d_entity_state* state) {
|
||||
if (len < sizeof(demo3d_entity_state)) return 0;
|
||||
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
|
||||
state->entity_id = pxl8_read_u64_be(&s);
|
||||
pxl8_read_bytes(&s, state->userdata, 56);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
usize demo3d_protocol_serialize_event(const demo3d_event_msg* msg, u8* buf, usize len) {
|
||||
if (len < sizeof(demo3d_event_msg)) return 0;
|
||||
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
|
||||
pxl8_write_u8(&s, msg->event_type);
|
||||
pxl8_write_bytes(&s, msg->payload, DEMO3D_EVENT_PAYLOAD_SIZE);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
usize demo3d_protocol_deserialize_event(const u8* buf, usize len, demo3d_event_msg* msg) {
|
||||
if (len < sizeof(demo3d_event_msg)) return 0;
|
||||
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
|
||||
msg->event_type = pxl8_read_u8(&s);
|
||||
pxl8_read_bytes(&s, msg->payload, DEMO3D_EVENT_PAYLOAD_SIZE);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
usize demo3d_protocol_serialize_snapshot_header(const demo3d_snapshot_header* hdr, u8* buf, usize len) {
|
||||
if (len < sizeof(demo3d_snapshot_header)) return 0;
|
||||
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
|
||||
pxl8_write_u16_be(&s, hdr->entity_count);
|
||||
pxl8_write_u16_be(&s, hdr->event_count);
|
||||
pxl8_write_u64_be(&s, hdr->player_id);
|
||||
pxl8_write_u64_be(&s, hdr->tick);
|
||||
pxl8_write_f32_be(&s, hdr->time);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
usize demo3d_protocol_deserialize_snapshot_header(const u8* buf, usize len, demo3d_snapshot_header* hdr) {
|
||||
if (len < sizeof(demo3d_snapshot_header)) return 0;
|
||||
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
|
||||
hdr->entity_count = pxl8_read_u16_be(&s);
|
||||
hdr->event_count = pxl8_read_u16_be(&s);
|
||||
hdr->player_id = pxl8_read_u64_be(&s);
|
||||
hdr->tick = pxl8_read_u64_be(&s);
|
||||
hdr->time = pxl8_read_f32_be(&s);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
usize demo3d_protocol_serialize_chunk_msg_header(const demo3d_chunk_msg_header* hdr, u8* buf, usize len) {
|
||||
if (len < 24) return 0;
|
||||
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
|
||||
pxl8_write_u8(&s, hdr->chunk_type);
|
||||
pxl8_write_u8(&s, hdr->flags);
|
||||
pxl8_write_u8(&s, hdr->fragment_idx);
|
||||
pxl8_write_u8(&s, hdr->fragment_count);
|
||||
pxl8_write_u32_be(&s, hdr->id);
|
||||
pxl8_write_u32_be(&s, (u32)hdr->cx);
|
||||
pxl8_write_u32_be(&s, (u32)hdr->cy);
|
||||
pxl8_write_u32_be(&s, (u32)hdr->cz);
|
||||
pxl8_write_u32_be(&s, hdr->version);
|
||||
pxl8_write_u16_be(&s, hdr->payload_size);
|
||||
pxl8_write_u16_be(&s, hdr->reserved);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
usize demo3d_protocol_deserialize_chunk_msg_header(const u8* buf, usize len, demo3d_chunk_msg_header* hdr) {
|
||||
if (len < 24) return 0;
|
||||
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
|
||||
hdr->chunk_type = pxl8_read_u8(&s);
|
||||
hdr->flags = pxl8_read_u8(&s);
|
||||
hdr->fragment_idx = pxl8_read_u8(&s);
|
||||
hdr->fragment_count = pxl8_read_u8(&s);
|
||||
hdr->id = pxl8_read_u32_be(&s);
|
||||
hdr->cx = (i32)pxl8_read_u32_be(&s);
|
||||
hdr->cy = (i32)pxl8_read_u32_be(&s);
|
||||
hdr->cz = (i32)pxl8_read_u32_be(&s);
|
||||
hdr->version = pxl8_read_u32_be(&s);
|
||||
hdr->payload_size = pxl8_read_u16_be(&s);
|
||||
hdr->reserved = pxl8_read_u16_be(&s);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
usize demo3d_protocol_deserialize_bsp_wire_header(const u8* buf, usize len, demo3d_bsp_wire_header* hdr) {
|
||||
if (len < 48) return 0;
|
||||
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
|
||||
hdr->num_vertices = pxl8_read_u32_be(&s);
|
||||
hdr->num_edges = pxl8_read_u32_be(&s);
|
||||
hdr->num_faces = pxl8_read_u32_be(&s);
|
||||
hdr->num_planes = pxl8_read_u32_be(&s);
|
||||
hdr->num_nodes = pxl8_read_u32_be(&s);
|
||||
hdr->num_leafs = pxl8_read_u32_be(&s);
|
||||
hdr->num_surfedges = pxl8_read_u32_be(&s);
|
||||
hdr->num_marksurfaces = pxl8_read_u32_be(&s);
|
||||
hdr->num_cell_portals = pxl8_read_u32_be(&s);
|
||||
hdr->visdata_size = pxl8_read_u32_be(&s);
|
||||
hdr->num_vertex_lights = pxl8_read_u32_be(&s);
|
||||
hdr->num_heightfield = pxl8_read_u32_be(&s);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
usize demo3d_protocol_serialize_chunk_enter(const demo3d_chunk_enter_msg* msg, u8* buf, usize len) {
|
||||
if (len < 8) return 0;
|
||||
pxl8_write_stream s = pxl8_write_stream_create(buf, (u32)len);
|
||||
pxl8_write_u32_be(&s, (u32)msg->cx);
|
||||
pxl8_write_u32_be(&s, (u32)msg->cz);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
usize demo3d_protocol_deserialize_chunk_enter(const u8* buf, usize len, demo3d_chunk_enter_msg* msg) {
|
||||
if (len < 8) return 0;
|
||||
pxl8_stream s = pxl8_stream_create(buf, (u32)len);
|
||||
msg->cx = (i32)pxl8_read_u32_be(&s);
|
||||
msg->cz = (i32)pxl8_read_u32_be(&s);
|
||||
return s.offset;
|
||||
}
|
||||
|
||||
u32 demo3d_chunk_hash(i32 cx, i32 cz) {
|
||||
u32 h = (u32)cx * 374761393u + (u32)cz * 668265263u;
|
||||
return h ^ (h >> 16);
|
||||
}
|
||||
143
demo3d/client/net/demo3d_protocol.h
Normal file
143
demo3d/client/net/demo3d_protocol.h
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define DEMO3D_PROTOCOL_VERSION 1
|
||||
#define DEMO3D_MAX_SNAPSHOT_ENTITIES 256
|
||||
#define DEMO3D_MAX_SNAPSHOT_EVENTS 32
|
||||
#define DEMO3D_COMMAND_PAYLOAD_SIZE 64
|
||||
#define DEMO3D_EVENT_PAYLOAD_SIZE 15
|
||||
|
||||
typedef enum demo3d_msg_type {
|
||||
DEMO3D_MSG_NONE = 0,
|
||||
DEMO3D_MSG_CHUNK,
|
||||
DEMO3D_MSG_CHUNK_ENTER,
|
||||
DEMO3D_MSG_CHUNK_EXIT,
|
||||
DEMO3D_MSG_COMMAND,
|
||||
DEMO3D_MSG_CONNECT,
|
||||
DEMO3D_MSG_DISCONNECT,
|
||||
DEMO3D_MSG_EVENT,
|
||||
DEMO3D_MSG_INPUT,
|
||||
DEMO3D_MSG_SNAPSHOT
|
||||
} demo3d_msg_type;
|
||||
|
||||
typedef struct demo3d_msg_header {
|
||||
u32 sequence;
|
||||
u16 size;
|
||||
u8 type;
|
||||
u8 version;
|
||||
} demo3d_msg_header;
|
||||
|
||||
typedef enum demo3d_cmd_type {
|
||||
DEMO3D_CMD_NONE = 0,
|
||||
DEMO3D_CMD_SPAWN_ENTITY,
|
||||
} demo3d_cmd_type;
|
||||
|
||||
typedef struct demo3d_input_msg {
|
||||
u32 buttons;
|
||||
f32 look_dx;
|
||||
f32 look_dy;
|
||||
f32 move_x;
|
||||
f32 move_y;
|
||||
f32 yaw;
|
||||
u64 tick;
|
||||
u64 timestamp;
|
||||
} demo3d_input_msg;
|
||||
|
||||
typedef struct demo3d_command_msg {
|
||||
u16 cmd_type;
|
||||
u8 payload[DEMO3D_COMMAND_PAYLOAD_SIZE];
|
||||
u16 payload_size;
|
||||
u64 tick;
|
||||
} demo3d_command_msg;
|
||||
|
||||
typedef struct demo3d_entity_state {
|
||||
u64 entity_id;
|
||||
u8 userdata[56];
|
||||
} demo3d_entity_state;
|
||||
|
||||
typedef struct demo3d_event_msg {
|
||||
u8 event_type;
|
||||
u8 payload[DEMO3D_EVENT_PAYLOAD_SIZE];
|
||||
} demo3d_event_msg;
|
||||
|
||||
typedef struct demo3d_snapshot_header {
|
||||
u16 entity_count;
|
||||
u16 event_count;
|
||||
u64 player_id;
|
||||
u64 tick;
|
||||
f32 time;
|
||||
} demo3d_snapshot_header;
|
||||
|
||||
#define DEMO3D_CHUNK_TYPE_BSP 1
|
||||
|
||||
#define DEMO3D_CHUNK_FLAG_FINAL 0x04
|
||||
#define DEMO3D_CHUNK_MAX_PAYLOAD 1400
|
||||
|
||||
typedef struct demo3d_chunk_msg_header {
|
||||
u8 chunk_type;
|
||||
u8 flags;
|
||||
u8 fragment_idx;
|
||||
u8 fragment_count;
|
||||
u32 id;
|
||||
i32 cx, cy, cz;
|
||||
u32 version;
|
||||
u16 payload_size;
|
||||
u16 reserved;
|
||||
} demo3d_chunk_msg_header;
|
||||
|
||||
typedef struct demo3d_bsp_wire_header {
|
||||
u32 num_vertices;
|
||||
u32 num_edges;
|
||||
u32 num_faces;
|
||||
u32 num_planes;
|
||||
u32 num_nodes;
|
||||
u32 num_leafs;
|
||||
u32 num_surfedges;
|
||||
u32 num_marksurfaces;
|
||||
u32 num_cell_portals;
|
||||
u32 visdata_size;
|
||||
u32 num_vertex_lights;
|
||||
u32 num_heightfield;
|
||||
} demo3d_bsp_wire_header;
|
||||
|
||||
usize demo3d_protocol_serialize_header(const demo3d_msg_header* msg, u8* buf, usize len);
|
||||
usize demo3d_protocol_deserialize_header(const u8* buf, usize len, demo3d_msg_header* msg);
|
||||
|
||||
usize demo3d_protocol_serialize_input(const demo3d_input_msg* msg, u8* buf, usize len);
|
||||
usize demo3d_protocol_deserialize_input(const u8* buf, usize len, demo3d_input_msg* msg);
|
||||
|
||||
usize demo3d_protocol_serialize_command(const demo3d_command_msg* msg, u8* buf, usize len);
|
||||
usize demo3d_protocol_deserialize_command(const u8* buf, usize len, demo3d_command_msg* msg);
|
||||
|
||||
usize demo3d_protocol_serialize_entity_state(const demo3d_entity_state* state, u8* buf, usize len);
|
||||
usize demo3d_protocol_deserialize_entity_state(const u8* buf, usize len, demo3d_entity_state* state);
|
||||
|
||||
usize demo3d_protocol_serialize_event(const demo3d_event_msg* msg, u8* buf, usize len);
|
||||
usize demo3d_protocol_deserialize_event(const u8* buf, usize len, demo3d_event_msg* msg);
|
||||
|
||||
usize demo3d_protocol_serialize_snapshot_header(const demo3d_snapshot_header* hdr, u8* buf, usize len);
|
||||
usize demo3d_protocol_deserialize_snapshot_header(const u8* buf, usize len, demo3d_snapshot_header* hdr);
|
||||
|
||||
usize demo3d_protocol_serialize_chunk_msg_header(const demo3d_chunk_msg_header* hdr, u8* buf, usize len);
|
||||
usize demo3d_protocol_deserialize_chunk_msg_header(const u8* buf, usize len, demo3d_chunk_msg_header* hdr);
|
||||
|
||||
usize demo3d_protocol_deserialize_bsp_wire_header(const u8* buf, usize len, demo3d_bsp_wire_header* hdr);
|
||||
|
||||
typedef struct demo3d_chunk_enter_msg {
|
||||
i32 cx;
|
||||
i32 cz;
|
||||
} demo3d_chunk_enter_msg;
|
||||
|
||||
usize demo3d_protocol_serialize_chunk_enter(const demo3d_chunk_enter_msg* msg, u8* buf, usize len);
|
||||
usize demo3d_protocol_deserialize_chunk_enter(const u8* buf, usize len, demo3d_chunk_enter_msg* msg);
|
||||
|
||||
u32 demo3d_chunk_hash(i32 cx, i32 cz);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
448
demo3d/client/sim/demo3d_sim.c
Normal file
448
demo3d/client/sim/demo3d_sim.c
Normal file
|
|
@ -0,0 +1,448 @@
|
|||
#include "demo3d_sim.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#define DIST_EPSILON 0.03125f
|
||||
|
||||
typedef struct {
|
||||
f32 fraction;
|
||||
pxl8_vec3 normal;
|
||||
bool all_solid;
|
||||
bool start_solid;
|
||||
} trace_result;
|
||||
|
||||
static i32 bsp_find_leaf(const demo3d_bsp* bsp, pxl8_vec3 pos) {
|
||||
if (!bsp || bsp->num_nodes == 0) return -1;
|
||||
|
||||
i32 node_id = 0;
|
||||
while (node_id >= 0) {
|
||||
const demo3d_bsp_node* node = &bsp->nodes[node_id];
|
||||
const demo3d_bsp_plane* plane = &bsp->planes[node->plane_id];
|
||||
f32 dist = pxl8_vec3_dot(pos, plane->normal) - plane->dist;
|
||||
node_id = node->children[dist < 0 ? 1 : 0];
|
||||
}
|
||||
|
||||
return -(node_id + 1);
|
||||
}
|
||||
|
||||
static i32 bsp_contents_from(const demo3d_bsp* bsp, i32 node_id, pxl8_vec3 pos) {
|
||||
while (node_id >= 0) {
|
||||
const demo3d_bsp_node* node = &bsp->nodes[node_id];
|
||||
const demo3d_bsp_plane* plane = &bsp->planes[node->plane_id];
|
||||
f32 d = pxl8_vec3_dot(pos, plane->normal) - plane->dist;
|
||||
node_id = node->children[d < 0 ? 1 : 0];
|
||||
}
|
||||
i32 leaf_idx = -(node_id + 1);
|
||||
if (leaf_idx < 0 || (u32)leaf_idx >= bsp->num_leafs) return -1;
|
||||
return bsp->leafs[leaf_idx].contents;
|
||||
}
|
||||
|
||||
static bool bsp_recursive_trace(const demo3d_bsp* bsp, i32 node_id,
|
||||
f32 p1f, f32 p2f,
|
||||
pxl8_vec3 p1, pxl8_vec3 p2,
|
||||
trace_result* tr) {
|
||||
if (node_id < 0) {
|
||||
i32 leaf_idx = -(node_id + 1);
|
||||
if (leaf_idx >= 0 && (u32)leaf_idx < bsp->num_leafs &&
|
||||
bsp->leafs[leaf_idx].contents == -1) {
|
||||
tr->start_solid = true;
|
||||
} else {
|
||||
tr->all_solid = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const demo3d_bsp_node* node = &bsp->nodes[node_id];
|
||||
const demo3d_bsp_plane* plane = &bsp->planes[node->plane_id];
|
||||
|
||||
f32 t1 = pxl8_vec3_dot(p1, plane->normal) - plane->dist;
|
||||
f32 t2 = pxl8_vec3_dot(p2, plane->normal) - plane->dist;
|
||||
|
||||
if (t1 >= 0 && t2 >= 0)
|
||||
return bsp_recursive_trace(bsp, node->children[0], p1f, p2f, p1, p2, tr);
|
||||
if (t1 < 0 && t2 < 0)
|
||||
return bsp_recursive_trace(bsp, node->children[1], p1f, p2f, p1, p2, tr);
|
||||
|
||||
i32 side;
|
||||
f32 frac;
|
||||
if (t1 < 0) {
|
||||
frac = (t1 + DIST_EPSILON) / (t1 - t2);
|
||||
side = 1;
|
||||
} else {
|
||||
frac = (t1 - DIST_EPSILON) / (t1 - t2);
|
||||
side = 0;
|
||||
}
|
||||
if (frac < 0) frac = 0;
|
||||
if (frac > 1) frac = 1;
|
||||
|
||||
f32 midf = p1f + (p2f - p1f) * frac;
|
||||
pxl8_vec3 mid = {
|
||||
p1.x + frac * (p2.x - p1.x),
|
||||
p1.y + frac * (p2.y - p1.y),
|
||||
p1.z + frac * (p2.z - p1.z),
|
||||
};
|
||||
|
||||
if (!bsp_recursive_trace(bsp, node->children[side], p1f, midf, p1, mid, tr))
|
||||
return false;
|
||||
|
||||
if (bsp_contents_from(bsp, node->children[side ^ 1], mid) != -1)
|
||||
return bsp_recursive_trace(bsp, node->children[side ^ 1], midf, p2f, mid, p2, tr);
|
||||
|
||||
if (tr->all_solid)
|
||||
return false;
|
||||
|
||||
if (midf < tr->fraction) {
|
||||
tr->fraction = midf;
|
||||
if (side == 0) {
|
||||
tr->normal = plane->normal;
|
||||
} else {
|
||||
tr->normal.x = -plane->normal.x;
|
||||
tr->normal.y = -plane->normal.y;
|
||||
tr->normal.z = -plane->normal.z;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static trace_result bsp_trace_line(const demo3d_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to) {
|
||||
trace_result tr = { .fraction = 1.0f, .all_solid = true };
|
||||
if (!bsp || bsp->num_nodes == 0) {
|
||||
tr.all_solid = false;
|
||||
return tr;
|
||||
}
|
||||
bsp_recursive_trace(bsp, 0, 0.0f, 1.0f, from, to, &tr);
|
||||
if (tr.all_solid) {
|
||||
tr.fraction = 0.0f;
|
||||
tr.start_solid = true;
|
||||
}
|
||||
return tr;
|
||||
}
|
||||
|
||||
bool demo3d_bsp_point_solid(const demo3d_bsp* bsp, pxl8_vec3 pos) {
|
||||
if (!bsp) return false;
|
||||
if (bsp->bounds_max_x > bsp->bounds_min_x &&
|
||||
(pos.x < bsp->bounds_min_x || pos.x >= bsp->bounds_max_x ||
|
||||
pos.z < bsp->bounds_min_z || pos.z >= bsp->bounds_max_z))
|
||||
return false;
|
||||
i32 leaf = bsp_find_leaf(bsp, pos);
|
||||
if (leaf < 0 || (u32)leaf >= bsp->num_leafs) return true;
|
||||
return bsp->leafs[leaf].contents == -1;
|
||||
}
|
||||
|
||||
static void trace_offsets(const demo3d_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to,
|
||||
f32 radius, f32* out_frac, pxl8_vec3* out_normal) {
|
||||
f32 d = radius * 0.7071f;
|
||||
pxl8_vec3 offsets[9] = {
|
||||
{0, 0, 0},
|
||||
{radius, 0, 0}, {-radius, 0, 0},
|
||||
{0, 0, radius}, {0, 0, -radius},
|
||||
{d, 0, d}, {d, 0, -d},
|
||||
{-d, 0, d}, {-d, 0, -d},
|
||||
};
|
||||
|
||||
*out_frac = 1.0f;
|
||||
*out_normal = (pxl8_vec3){0, 0, 0};
|
||||
|
||||
for (i32 i = 0; i < 9; i++) {
|
||||
pxl8_vec3 s = { from.x + offsets[i].x, from.y + offsets[i].y, from.z + offsets[i].z };
|
||||
pxl8_vec3 e = { to.x + offsets[i].x, to.y + offsets[i].y, to.z + offsets[i].z };
|
||||
trace_result tr = bsp_trace_line(bsp, s, e);
|
||||
if (tr.fraction < *out_frac && !tr.start_solid) {
|
||||
*out_frac = tr.fraction;
|
||||
*out_normal = tr.normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_vec3 demo3d_bsp_trace(const demo3d_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to, f32 radius) {
|
||||
if (!bsp || bsp->num_nodes == 0) return to;
|
||||
|
||||
f32 frac;
|
||||
pxl8_vec3 normal;
|
||||
trace_offsets(bsp, from, to, radius, &frac, &normal);
|
||||
if (frac >= 1.0f) return to;
|
||||
|
||||
pxl8_vec3 delta = { to.x - from.x, to.y - from.y, to.z - from.z };
|
||||
pxl8_vec3 hit = {
|
||||
from.x + delta.x * frac,
|
||||
from.y + delta.y * frac,
|
||||
from.z + delta.z * frac,
|
||||
};
|
||||
|
||||
f32 remaining = 1.0f - frac;
|
||||
if (remaining <= 0.0f) return hit;
|
||||
|
||||
f32 backoff = pxl8_vec3_dot(delta, normal) * remaining;
|
||||
pxl8_vec3 slide_target = {
|
||||
hit.x + delta.x * remaining - normal.x * backoff,
|
||||
hit.y + delta.y * remaining - normal.y * backoff,
|
||||
hit.z + delta.z * remaining - normal.z * backoff,
|
||||
};
|
||||
|
||||
f32 slide_frac;
|
||||
pxl8_vec3 slide_normal;
|
||||
trace_offsets(bsp, hit, slide_target, radius, &slide_frac, &slide_normal);
|
||||
if (slide_frac >= 1.0f) return slide_target;
|
||||
|
||||
pxl8_vec3 slide_delta = {
|
||||
slide_target.x - hit.x,
|
||||
slide_target.y - hit.y,
|
||||
slide_target.z - hit.z,
|
||||
};
|
||||
return (pxl8_vec3){
|
||||
hit.x + slide_delta.x * slide_frac,
|
||||
hit.y + slide_delta.y * slide_frac,
|
||||
hit.z + slide_delta.z * slide_frac,
|
||||
};
|
||||
}
|
||||
|
||||
static const demo3d_bsp* sim_bsp_at(const demo3d_sim_world* world, f32 x, f32 z) {
|
||||
i32 cx = (i32)floorf(x / world->chunk_size);
|
||||
i32 cz = (i32)floorf(z / world->chunk_size);
|
||||
i32 dx = cx - world->center_cx + 1;
|
||||
i32 dz = cz - world->center_cz + 1;
|
||||
if (dx < 0 || dx > 2 || dz < 0 || dz > 2) return NULL;
|
||||
return world->chunks[dz * 3 + dx];
|
||||
}
|
||||
|
||||
static f32 bsp_terrain_height(const demo3d_bsp* bsp, f32 x, f32 z) {
|
||||
if (!bsp || !bsp->heightfield || bsp->heightfield_cell_size <= 0) return -1e9f;
|
||||
f32 lx = (x - bsp->heightfield_ox) / bsp->heightfield_cell_size;
|
||||
f32 lz = (z - bsp->heightfield_oz) / bsp->heightfield_cell_size;
|
||||
i32 ix = (i32)floorf(lx);
|
||||
i32 iz = (i32)floorf(lz);
|
||||
if (ix < 0 || ix >= bsp->heightfield_w - 1 || iz < 0 || iz >= bsp->heightfield_h - 1) return -1e9f;
|
||||
f32 fx = lx - ix;
|
||||
f32 fz = lz - iz;
|
||||
i32 w = bsp->heightfield_w;
|
||||
f32 h00 = bsp->heightfield[iz * w + ix];
|
||||
f32 h10 = bsp->heightfield[iz * w + ix + 1];
|
||||
f32 h01 = bsp->heightfield[(iz + 1) * w + ix];
|
||||
f32 h11 = bsp->heightfield[(iz + 1) * w + ix + 1];
|
||||
f32 h0 = h00 + (h10 - h00) * fx;
|
||||
f32 h1 = h01 + (h11 - h01) * fx;
|
||||
return h0 + (h1 - h0) * fz;
|
||||
}
|
||||
|
||||
static f32 sim_terrain_height(const demo3d_sim_world* world, f32 x, f32 z) {
|
||||
const demo3d_bsp* bsp = sim_bsp_at(world, x, z);
|
||||
return bsp_terrain_height(bsp, x, z);
|
||||
}
|
||||
|
||||
static void sim_trace_offsets(const demo3d_sim_world* world, pxl8_vec3 from, pxl8_vec3 to,
|
||||
f32 radius, f32* out_frac, pxl8_vec3* out_normal) {
|
||||
f32 d = radius * 0.7071f;
|
||||
pxl8_vec3 offsets[9] = {
|
||||
{0, 0, 0},
|
||||
{radius, 0, 0}, {-radius, 0, 0},
|
||||
{0, 0, radius}, {0, 0, -radius},
|
||||
{d, 0, d}, {d, 0, -d},
|
||||
{-d, 0, d}, {-d, 0, -d},
|
||||
};
|
||||
|
||||
*out_frac = 1.0f;
|
||||
*out_normal = (pxl8_vec3){0, 0, 0};
|
||||
|
||||
for (i32 i = 0; i < 9; i++) {
|
||||
pxl8_vec3 s = { from.x + offsets[i].x, from.y + offsets[i].y, from.z + offsets[i].z };
|
||||
pxl8_vec3 e = { to.x + offsets[i].x, to.y + offsets[i].y, to.z + offsets[i].z };
|
||||
const demo3d_bsp* bsp_s = sim_bsp_at(world, s.x, s.z);
|
||||
const demo3d_bsp* bsp_e = sim_bsp_at(world, e.x, e.z);
|
||||
if (bsp_s) {
|
||||
trace_result tr = bsp_trace_line(bsp_s, s, e);
|
||||
if (tr.fraction < *out_frac && !tr.start_solid) {
|
||||
*out_frac = tr.fraction;
|
||||
*out_normal = tr.normal;
|
||||
}
|
||||
}
|
||||
if (bsp_e && bsp_e != bsp_s) {
|
||||
bool inside_bounds = !(bsp_e->bounds_max_x > bsp_e->bounds_min_x) ||
|
||||
(s.x >= bsp_e->bounds_min_x && s.x < bsp_e->bounds_max_x &&
|
||||
s.z >= bsp_e->bounds_min_z && s.z < bsp_e->bounds_max_z);
|
||||
if (inside_bounds) {
|
||||
trace_result tr = bsp_trace_line(bsp_e, s, e);
|
||||
if (tr.fraction < *out_frac && !tr.start_solid) {
|
||||
*out_frac = tr.fraction;
|
||||
*out_normal = tr.normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_vec3 demo3d_sim_trace(const demo3d_sim_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius, f32 height) {
|
||||
(void)height;
|
||||
if (!world || world->chunk_size <= 0) return to;
|
||||
|
||||
f32 frac;
|
||||
pxl8_vec3 normal;
|
||||
sim_trace_offsets(world, from, to, radius, &frac, &normal);
|
||||
if (frac >= 1.0f) return to;
|
||||
|
||||
pxl8_vec3 delta = { to.x - from.x, to.y - from.y, to.z - from.z };
|
||||
pxl8_vec3 hit = {
|
||||
from.x + delta.x * frac,
|
||||
from.y + delta.y * frac,
|
||||
from.z + delta.z * frac,
|
||||
};
|
||||
|
||||
f32 remaining = 1.0f - frac;
|
||||
if (remaining <= 0.0f) return hit;
|
||||
|
||||
f32 backoff = pxl8_vec3_dot(delta, normal) * remaining;
|
||||
pxl8_vec3 slide_target = {
|
||||
hit.x + delta.x * remaining - normal.x * backoff,
|
||||
hit.y + delta.y * remaining - normal.y * backoff,
|
||||
hit.z + delta.z * remaining - normal.z * backoff,
|
||||
};
|
||||
|
||||
f32 slide_frac;
|
||||
pxl8_vec3 slide_normal;
|
||||
sim_trace_offsets(world, hit, slide_target, radius, &slide_frac, &slide_normal);
|
||||
if (slide_frac >= 1.0f) return slide_target;
|
||||
|
||||
pxl8_vec3 slide_delta = {
|
||||
slide_target.x - hit.x,
|
||||
slide_target.y - hit.y,
|
||||
slide_target.z - hit.z,
|
||||
};
|
||||
return (pxl8_vec3){
|
||||
hit.x + slide_delta.x * slide_frac,
|
||||
hit.y + slide_delta.y * slide_frac,
|
||||
hit.z + slide_delta.z * slide_frac,
|
||||
};
|
||||
}
|
||||
|
||||
bool demo3d_sim_check_ground(const demo3d_sim_world* world, pxl8_vec3 pos, f32 radius) {
|
||||
if (!world || world->chunk_size <= 0) return true;
|
||||
|
||||
f32 th = sim_terrain_height(world, pos.x, pos.z);
|
||||
if (th > -1e8f && pos.y - th < 2.0f) return true;
|
||||
|
||||
pxl8_vec3 down = {pos.x, pos.y - 2.0f, pos.z};
|
||||
pxl8_vec3 result = demo3d_sim_trace(world, pos, down, radius, 0.0f);
|
||||
|
||||
return result.y > down.y;
|
||||
}
|
||||
|
||||
void demo3d_sim_move_player(demo3d_sim_entity* ent, const demo3d_input_msg* input,
|
||||
const demo3d_sim_world* world, const demo3d_sim_config* cfg, f32 dt) {
|
||||
if (!ent || !input || !cfg) return;
|
||||
if (!(ent->flags & DEMO3D_SIM_FLAG_ALIVE)) return;
|
||||
|
||||
ent->yaw -= input->look_dx * 0.008f;
|
||||
f32 new_pitch = ent->pitch - input->look_dy * 0.008f;
|
||||
if (new_pitch > cfg->max_pitch) new_pitch = cfg->max_pitch;
|
||||
if (new_pitch < -cfg->max_pitch) new_pitch = -cfg->max_pitch;
|
||||
ent->pitch = new_pitch;
|
||||
|
||||
f32 sin_yaw = sinf(ent->yaw);
|
||||
f32 cos_yaw = cosf(ent->yaw);
|
||||
|
||||
f32 input_len = sqrtf(input->move_x * input->move_x + input->move_y * input->move_y);
|
||||
|
||||
pxl8_vec3 move_dir = {0, 0, 0};
|
||||
f32 target_speed = 0.0f;
|
||||
|
||||
if (input_len > 0.0f) {
|
||||
f32 nx = input->move_x / input_len;
|
||||
f32 ny = input->move_y / input_len;
|
||||
move_dir.x = cos_yaw * nx - sin_yaw * ny;
|
||||
move_dir.z = -sin_yaw * nx - cos_yaw * ny;
|
||||
target_speed = cfg->move_speed;
|
||||
}
|
||||
|
||||
ent->vel.x = move_dir.x * target_speed;
|
||||
ent->vel.z = move_dir.z * target_speed;
|
||||
|
||||
bool grounded = (ent->flags & DEMO3D_SIM_FLAG_GROUNDED) != 0;
|
||||
|
||||
if (grounded && (input->buttons & 1)) {
|
||||
ent->vel.y = cfg->jump_velocity;
|
||||
ent->flags &= ~DEMO3D_SIM_FLAG_GROUNDED;
|
||||
grounded = false;
|
||||
}
|
||||
|
||||
if (!grounded) {
|
||||
ent->vel.y -= cfg->gravity * dt;
|
||||
}
|
||||
|
||||
pxl8_vec3 target = {
|
||||
ent->pos.x + ent->vel.x * dt,
|
||||
ent->pos.y + ent->vel.y * dt,
|
||||
ent->pos.z + ent->vel.z * dt
|
||||
};
|
||||
|
||||
if (grounded) {
|
||||
f32 th_dest = sim_terrain_height(world, target.x, target.z);
|
||||
if (th_dest > -1e8f) target.y = th_dest;
|
||||
}
|
||||
|
||||
pxl8_vec3 new_pos = demo3d_sim_trace(world, ent->pos, target, cfg->player_radius, cfg->player_height);
|
||||
|
||||
f32 th = sim_terrain_height(world, new_pos.x, new_pos.z);
|
||||
if (th > -1e8f && new_pos.y < th) {
|
||||
new_pos.y = th;
|
||||
if (ent->vel.y < 0) ent->vel.y = 0;
|
||||
}
|
||||
|
||||
ent->pos = new_pos;
|
||||
|
||||
if (demo3d_sim_check_ground(world, ent->pos, cfg->player_radius)) {
|
||||
ent->flags |= DEMO3D_SIM_FLAG_GROUNDED;
|
||||
if (ent->vel.y < 0) ent->vel.y = 0;
|
||||
} else {
|
||||
ent->flags &= ~DEMO3D_SIM_FLAG_GROUNDED;
|
||||
}
|
||||
}
|
||||
|
||||
void demo3d_sim_integrate(demo3d_sim_entity* ent, const demo3d_sim_world* world,
|
||||
const demo3d_sim_config* cfg, f32 dt) {
|
||||
if (!ent || !cfg) return;
|
||||
if (!(ent->flags & DEMO3D_SIM_FLAG_ALIVE)) return;
|
||||
if (ent->flags & DEMO3D_SIM_FLAG_PLAYER) return;
|
||||
|
||||
bool grounded = (ent->flags & DEMO3D_SIM_FLAG_GROUNDED) != 0;
|
||||
|
||||
ent->vel.y -= cfg->gravity * dt;
|
||||
|
||||
if (grounded) {
|
||||
f32 speed = sqrtf(ent->vel.x * ent->vel.x + ent->vel.z * ent->vel.z);
|
||||
if (speed > 0.0f) {
|
||||
f32 drop = speed * cfg->friction * dt;
|
||||
f32 new_speed = speed - drop;
|
||||
if (new_speed < 0.0f) new_speed = 0.0f;
|
||||
f32 scale = new_speed / speed;
|
||||
ent->vel.x *= scale;
|
||||
ent->vel.z *= scale;
|
||||
}
|
||||
}
|
||||
|
||||
pxl8_vec3 target = {
|
||||
ent->pos.x + ent->vel.x * dt,
|
||||
ent->pos.y + ent->vel.y * dt,
|
||||
ent->pos.z + ent->vel.z * dt
|
||||
};
|
||||
|
||||
if (grounded) {
|
||||
f32 th_dest = sim_terrain_height(world, target.x, target.z);
|
||||
if (th_dest > -1e8f) target.y = th_dest;
|
||||
}
|
||||
|
||||
pxl8_vec3 new_pos = demo3d_sim_trace(world, ent->pos, target, cfg->player_radius, cfg->player_height);
|
||||
|
||||
f32 th2 = sim_terrain_height(world, new_pos.x, new_pos.z);
|
||||
if (th2 > -1e8f && new_pos.y < th2) {
|
||||
new_pos.y = th2;
|
||||
if (ent->vel.y < 0.0f) ent->vel.y = 0.0f;
|
||||
}
|
||||
|
||||
ent->pos = new_pos;
|
||||
|
||||
if (demo3d_sim_check_ground(world, ent->pos, cfg->player_radius)) {
|
||||
ent->flags |= DEMO3D_SIM_FLAG_GROUNDED;
|
||||
if (ent->vel.y < 0.0f) ent->vel.y = 0.0f;
|
||||
} else {
|
||||
ent->flags &= ~DEMO3D_SIM_FLAG_GROUNDED;
|
||||
}
|
||||
}
|
||||
56
demo3d/client/sim/demo3d_sim.h
Normal file
56
demo3d/client/sim/demo3d_sim.h
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
#pragma once
|
||||
|
||||
#include "demo3d_bsp.h"
|
||||
#include "pxl8_math.h"
|
||||
#include "demo3d_protocol.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define DEMO3D_SIM_FLAG_ALIVE (1 << 0)
|
||||
#define DEMO3D_SIM_FLAG_PLAYER (1 << 1)
|
||||
#define DEMO3D_SIM_FLAG_GROUNDED (1 << 2)
|
||||
|
||||
typedef struct demo3d_sim_config {
|
||||
f32 move_speed;
|
||||
f32 ground_accel;
|
||||
f32 air_accel;
|
||||
f32 stop_speed;
|
||||
f32 friction;
|
||||
f32 gravity;
|
||||
f32 jump_velocity;
|
||||
f32 player_radius;
|
||||
f32 player_height;
|
||||
f32 max_pitch;
|
||||
} demo3d_sim_config;
|
||||
|
||||
typedef struct demo3d_sim_entity {
|
||||
pxl8_vec3 pos;
|
||||
pxl8_vec3 vel;
|
||||
f32 yaw;
|
||||
f32 pitch;
|
||||
u32 flags;
|
||||
u16 kind;
|
||||
u16 _pad;
|
||||
} demo3d_sim_entity;
|
||||
|
||||
typedef struct demo3d_sim_world {
|
||||
const demo3d_bsp* chunks[9];
|
||||
i32 center_cx;
|
||||
i32 center_cz;
|
||||
f32 chunk_size;
|
||||
} demo3d_sim_world;
|
||||
|
||||
bool demo3d_bsp_point_solid(const demo3d_bsp* bsp, pxl8_vec3 pos);
|
||||
pxl8_vec3 demo3d_bsp_trace(const demo3d_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to, f32 radius);
|
||||
|
||||
pxl8_vec3 demo3d_sim_trace(const demo3d_sim_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius, f32 height);
|
||||
bool demo3d_sim_check_ground(const demo3d_sim_world* world, pxl8_vec3 pos, f32 radius);
|
||||
void demo3d_sim_move_player(demo3d_sim_entity* ent, const demo3d_input_msg* input, const demo3d_sim_world* world, const demo3d_sim_config* cfg, f32 dt);
|
||||
void demo3d_sim_integrate(demo3d_sim_entity* ent, const demo3d_sim_world* world, const demo3d_sim_config* cfg, f32 dt);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
17
demo3d/client/world/demo3d_chunk.c
Normal file
17
demo3d/client/world/demo3d_chunk.c
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#include "demo3d_chunk.h"
|
||||
|
||||
#include "demo3d_bsp.h"
|
||||
#include "pxl8_mem.h"
|
||||
|
||||
demo3d_chunk* demo3d_chunk_create_bsp(u32 id) {
|
||||
demo3d_chunk* chunk = pxl8_calloc(1, sizeof(demo3d_chunk));
|
||||
if (!chunk) return NULL;
|
||||
chunk->id = id;
|
||||
return chunk;
|
||||
}
|
||||
|
||||
void demo3d_chunk_destroy(demo3d_chunk* chunk) {
|
||||
if (!chunk) return;
|
||||
if (chunk->bsp) demo3d_bsp_destroy(chunk->bsp);
|
||||
pxl8_free(chunk);
|
||||
}
|
||||
21
demo3d/client/world/demo3d_chunk.h
Normal file
21
demo3d/client/world/demo3d_chunk.h
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#include "demo3d_bsp.h"
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct demo3d_chunk {
|
||||
u32 id;
|
||||
u32 version;
|
||||
demo3d_bsp* bsp;
|
||||
} demo3d_chunk;
|
||||
|
||||
demo3d_chunk* demo3d_chunk_create_bsp(u32 id);
|
||||
void demo3d_chunk_destroy(demo3d_chunk* chunk);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
401
demo3d/client/world/demo3d_chunk_cache.c
Normal file
401
demo3d/client/world/demo3d_chunk_cache.c
Normal file
|
|
@ -0,0 +1,401 @@
|
|||
#include "demo3d_chunk_cache.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_bytes.h"
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_mem.h"
|
||||
|
||||
static demo3d_chunk_cache_entry* find_entry_bsp(demo3d_chunk_cache* cache, u32 id) {
|
||||
for (u32 i = 0; i < cache->entry_count; i++) {
|
||||
demo3d_chunk_cache_entry* e = &cache->entries[i];
|
||||
if (e->valid && e->chunk && e->chunk->id == id) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static demo3d_chunk_cache_entry* alloc_entry(demo3d_chunk_cache* cache) {
|
||||
if (cache->entry_count < DEMO3D_CHUNK_CACHE_SIZE) {
|
||||
demo3d_chunk_cache_entry* e = &cache->entries[cache->entry_count++];
|
||||
memset(e, 0, sizeof(*e));
|
||||
return e;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < DEMO3D_CHUNK_CACHE_SIZE; i++) {
|
||||
if (!cache->entries[i].valid) {
|
||||
demo3d_chunk_cache_entry* e = &cache->entries[i];
|
||||
memset(e, 0, sizeof(*e));
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
u64 oldest = cache->entries[0].last_used;
|
||||
u32 slot = 0;
|
||||
for (u32 i = 1; i < DEMO3D_CHUNK_CACHE_SIZE; i++) {
|
||||
if (cache->entries[i].last_used < oldest) {
|
||||
oldest = cache->entries[i].last_used;
|
||||
slot = i;
|
||||
}
|
||||
}
|
||||
|
||||
demo3d_chunk_cache_entry* e = &cache->entries[slot];
|
||||
if (e->chunk) {
|
||||
demo3d_chunk_destroy(e->chunk);
|
||||
}
|
||||
memset(e, 0, sizeof(*e));
|
||||
return e;
|
||||
}
|
||||
|
||||
static void assembly_reset(demo3d_chunk_assembly* a) {
|
||||
a->id = 0;
|
||||
a->cx = 0;
|
||||
a->cy = 0;
|
||||
a->cz = 0;
|
||||
a->version = 0;
|
||||
a->fragment_count = 0;
|
||||
a->fragments_received = 0;
|
||||
a->data_size = 0;
|
||||
a->active = false;
|
||||
a->complete = false;
|
||||
}
|
||||
|
||||
static void assembly_init(demo3d_chunk_assembly* a, const demo3d_chunk_msg_header* hdr) {
|
||||
assembly_reset(a);
|
||||
a->id = hdr->id;
|
||||
a->cx = hdr->cx;
|
||||
a->cy = hdr->cy;
|
||||
a->cz = hdr->cz;
|
||||
a->version = hdr->version;
|
||||
a->fragment_count = hdr->fragment_count;
|
||||
a->active = true;
|
||||
|
||||
u32 needed = DEMO3D_CHUNK_MAX_PAYLOAD * hdr->fragment_count;
|
||||
if (a->data_capacity < needed) {
|
||||
a->data_capacity = needed;
|
||||
a->data = pxl8_realloc(a->data, a->data_capacity);
|
||||
}
|
||||
}
|
||||
|
||||
static pxl8_result deserialize_vertex(pxl8_stream* s, demo3d_bsp_vertex* v) {
|
||||
v->position.x = pxl8_read_f32_be(s);
|
||||
v->position.y = pxl8_read_f32_be(s);
|
||||
v->position.z = pxl8_read_f32_be(s);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static pxl8_result deserialize_edge(pxl8_stream* s, demo3d_bsp_edge* e) {
|
||||
e->vertex[0] = pxl8_read_u16_be(s);
|
||||
e->vertex[1] = pxl8_read_u16_be(s);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static pxl8_result deserialize_plane(pxl8_stream* s, demo3d_bsp_plane* p) {
|
||||
p->normal.x = pxl8_read_f32_be(s);
|
||||
p->normal.y = pxl8_read_f32_be(s);
|
||||
p->normal.z = pxl8_read_f32_be(s);
|
||||
p->dist = pxl8_read_f32_be(s);
|
||||
p->type = (i32)pxl8_read_u32_be(s);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static pxl8_result deserialize_face(pxl8_stream* s, demo3d_bsp_face* f) {
|
||||
f->first_edge = pxl8_read_u32_be(s);
|
||||
f->lightmap_offset = pxl8_read_u32_be(s);
|
||||
f->num_edges = pxl8_read_u16_be(s);
|
||||
f->plane_id = pxl8_read_u16_be(s);
|
||||
f->side = pxl8_read_u16_be(s);
|
||||
pxl8_read_bytes(s, f->styles, 4);
|
||||
f->material_id = pxl8_read_u16_be(s);
|
||||
f->aabb_min.x = pxl8_read_f32_be(s);
|
||||
f->aabb_min.y = pxl8_read_f32_be(s);
|
||||
f->aabb_min.z = pxl8_read_f32_be(s);
|
||||
f->aabb_max.x = pxl8_read_f32_be(s);
|
||||
f->aabb_max.y = pxl8_read_f32_be(s);
|
||||
f->aabb_max.z = pxl8_read_f32_be(s);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static pxl8_result deserialize_node(pxl8_stream* s, demo3d_bsp_node* n) {
|
||||
n->children[0] = (i32)pxl8_read_u32_be(s);
|
||||
n->children[1] = (i32)pxl8_read_u32_be(s);
|
||||
n->first_face = pxl8_read_u16_be(s);
|
||||
n->maxs[0] = (i16)pxl8_read_u16_be(s);
|
||||
n->maxs[1] = (i16)pxl8_read_u16_be(s);
|
||||
n->maxs[2] = (i16)pxl8_read_u16_be(s);
|
||||
n->mins[0] = (i16)pxl8_read_u16_be(s);
|
||||
n->mins[1] = (i16)pxl8_read_u16_be(s);
|
||||
n->mins[2] = (i16)pxl8_read_u16_be(s);
|
||||
n->num_faces = pxl8_read_u16_be(s);
|
||||
n->plane_id = pxl8_read_u32_be(s);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static pxl8_result deserialize_leaf(pxl8_stream* s, demo3d_bsp_leaf* l) {
|
||||
pxl8_read_bytes(s, l->ambient_level, 4);
|
||||
l->contents = (i32)pxl8_read_u32_be(s);
|
||||
l->first_marksurface = pxl8_read_u16_be(s);
|
||||
l->maxs[0] = (i16)pxl8_read_u16_be(s);
|
||||
l->maxs[1] = (i16)pxl8_read_u16_be(s);
|
||||
l->maxs[2] = (i16)pxl8_read_u16_be(s);
|
||||
l->mins[0] = (i16)pxl8_read_u16_be(s);
|
||||
l->mins[1] = (i16)pxl8_read_u16_be(s);
|
||||
l->mins[2] = (i16)pxl8_read_u16_be(s);
|
||||
l->num_marksurfaces = pxl8_read_u16_be(s);
|
||||
l->visofs = (i32)pxl8_read_u32_be(s);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static pxl8_result deserialize_portal(pxl8_stream* s, demo3d_bsp_portal* p) {
|
||||
p->x0 = pxl8_read_f32_be(s);
|
||||
p->z0 = pxl8_read_f32_be(s);
|
||||
p->x1 = pxl8_read_f32_be(s);
|
||||
p->z1 = pxl8_read_f32_be(s);
|
||||
p->target_leaf = pxl8_read_u32_be(s);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static pxl8_result deserialize_cell_portals(pxl8_stream* s, demo3d_bsp_cell_portals* cp) {
|
||||
cp->num_portals = pxl8_read_u8(s);
|
||||
pxl8_read_u8(s);
|
||||
pxl8_read_u8(s);
|
||||
pxl8_read_u8(s);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
deserialize_portal(s, &cp->portals[i]);
|
||||
}
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
static demo3d_bsp* assembly_to_bsp(demo3d_chunk_assembly* a) {
|
||||
if (!a->complete || a->data_size < 48) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
demo3d_bsp* bsp = pxl8_calloc(1, sizeof(demo3d_bsp));
|
||||
if (!bsp) return NULL;
|
||||
|
||||
pxl8_stream s = pxl8_stream_create(a->data, (u32)a->data_size);
|
||||
|
||||
demo3d_bsp_wire_header wire_hdr;
|
||||
demo3d_protocol_deserialize_bsp_wire_header(a->data, 48, &wire_hdr);
|
||||
s.offset = 48;
|
||||
|
||||
if (wire_hdr.num_vertices > 0) {
|
||||
bsp->vertices = pxl8_calloc(wire_hdr.num_vertices, sizeof(demo3d_bsp_vertex));
|
||||
bsp->num_vertices = wire_hdr.num_vertices;
|
||||
for (u32 i = 0; i < wire_hdr.num_vertices; i++) {
|
||||
deserialize_vertex(&s, &bsp->vertices[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (wire_hdr.num_edges > 0) {
|
||||
bsp->edges = pxl8_calloc(wire_hdr.num_edges, sizeof(demo3d_bsp_edge));
|
||||
bsp->num_edges = wire_hdr.num_edges;
|
||||
for (u32 i = 0; i < wire_hdr.num_edges; i++) {
|
||||
deserialize_edge(&s, &bsp->edges[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (wire_hdr.num_surfedges > 0) {
|
||||
bsp->surfedges = pxl8_calloc(wire_hdr.num_surfedges, sizeof(i32));
|
||||
bsp->num_surfedges = wire_hdr.num_surfedges;
|
||||
for (u32 i = 0; i < wire_hdr.num_surfedges; i++) {
|
||||
bsp->surfedges[i] = (i32)pxl8_read_u32_be(&s);
|
||||
}
|
||||
}
|
||||
|
||||
if (wire_hdr.num_planes > 0) {
|
||||
bsp->planes = pxl8_calloc(wire_hdr.num_planes, sizeof(demo3d_bsp_plane));
|
||||
bsp->num_planes = wire_hdr.num_planes;
|
||||
for (u32 i = 0; i < wire_hdr.num_planes; i++) {
|
||||
deserialize_plane(&s, &bsp->planes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (wire_hdr.num_faces > 0) {
|
||||
bsp->faces = pxl8_calloc(wire_hdr.num_faces, sizeof(demo3d_bsp_face));
|
||||
bsp->num_faces = wire_hdr.num_faces;
|
||||
for (u32 i = 0; i < wire_hdr.num_faces; i++) {
|
||||
deserialize_face(&s, &bsp->faces[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (wire_hdr.num_nodes > 0) {
|
||||
bsp->nodes = pxl8_calloc(wire_hdr.num_nodes, sizeof(demo3d_bsp_node));
|
||||
bsp->num_nodes = wire_hdr.num_nodes;
|
||||
for (u32 i = 0; i < wire_hdr.num_nodes; i++) {
|
||||
deserialize_node(&s, &bsp->nodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (wire_hdr.num_leafs > 0) {
|
||||
bsp->leafs = pxl8_calloc(wire_hdr.num_leafs, sizeof(demo3d_bsp_leaf));
|
||||
bsp->num_leafs = wire_hdr.num_leafs;
|
||||
for (u32 i = 0; i < wire_hdr.num_leafs; i++) {
|
||||
deserialize_leaf(&s, &bsp->leafs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (wire_hdr.num_marksurfaces > 0) {
|
||||
bsp->marksurfaces = pxl8_calloc(wire_hdr.num_marksurfaces, sizeof(u16));
|
||||
bsp->num_marksurfaces = wire_hdr.num_marksurfaces;
|
||||
for (u32 i = 0; i < wire_hdr.num_marksurfaces; i++) {
|
||||
bsp->marksurfaces[i] = pxl8_read_u16_be(&s);
|
||||
}
|
||||
}
|
||||
|
||||
if (wire_hdr.num_cell_portals > 0) {
|
||||
bsp->cell_portals = pxl8_calloc(wire_hdr.num_cell_portals, sizeof(demo3d_bsp_cell_portals));
|
||||
bsp->num_cell_portals = wire_hdr.num_cell_portals;
|
||||
for (u32 i = 0; i < wire_hdr.num_cell_portals; i++) {
|
||||
deserialize_cell_portals(&s, &bsp->cell_portals[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (wire_hdr.visdata_size > 0) {
|
||||
bsp->visdata = pxl8_malloc(wire_hdr.visdata_size);
|
||||
bsp->visdata_size = wire_hdr.visdata_size;
|
||||
pxl8_read_bytes(&s, bsp->visdata, wire_hdr.visdata_size);
|
||||
}
|
||||
|
||||
if (wire_hdr.num_vertex_lights > 0) {
|
||||
bsp->vertex_lights = pxl8_calloc(wire_hdr.num_vertex_lights, sizeof(u32));
|
||||
bsp->num_vertex_lights = wire_hdr.num_vertex_lights;
|
||||
for (u32 i = 0; i < wire_hdr.num_vertex_lights; i++) {
|
||||
bsp->vertex_lights[i] = pxl8_read_u32_be(&s);
|
||||
}
|
||||
}
|
||||
|
||||
if (wire_hdr.num_heightfield > 0) {
|
||||
bsp->heightfield = pxl8_calloc(wire_hdr.num_heightfield, sizeof(f32));
|
||||
bsp->num_heightfield = wire_hdr.num_heightfield;
|
||||
for (u32 i = 0; i < wire_hdr.num_heightfield; i++) {
|
||||
u32 raw = pxl8_read_u32_be(&s);
|
||||
memcpy(&bsp->heightfield[i], &raw, sizeof(f32));
|
||||
}
|
||||
bsp->heightfield_w = pxl8_read_u16_be(&s);
|
||||
bsp->heightfield_h = pxl8_read_u16_be(&s);
|
||||
u32 ox_raw = pxl8_read_u32_be(&s);
|
||||
memcpy(&bsp->heightfield_ox, &ox_raw, sizeof(f32));
|
||||
u32 oz_raw = pxl8_read_u32_be(&s);
|
||||
memcpy(&bsp->heightfield_oz, &oz_raw, sizeof(f32));
|
||||
u32 cs_raw = pxl8_read_u32_be(&s);
|
||||
memcpy(&bsp->heightfield_cell_size, &cs_raw, sizeof(f32));
|
||||
}
|
||||
|
||||
u32 raw;
|
||||
raw = pxl8_read_u32_be(&s);
|
||||
memcpy(&bsp->bounds_min_x, &raw, sizeof(f32));
|
||||
raw = pxl8_read_u32_be(&s);
|
||||
memcpy(&bsp->bounds_min_z, &raw, sizeof(f32));
|
||||
raw = pxl8_read_u32_be(&s);
|
||||
memcpy(&bsp->bounds_max_x, &raw, sizeof(f32));
|
||||
raw = pxl8_read_u32_be(&s);
|
||||
memcpy(&bsp->bounds_max_z, &raw, sizeof(f32));
|
||||
|
||||
return bsp;
|
||||
}
|
||||
|
||||
static pxl8_result assemble_bsp(demo3d_chunk_cache* cache, demo3d_chunk_assembly* a) {
|
||||
demo3d_bsp* bsp = assembly_to_bsp(a);
|
||||
if (!bsp) {
|
||||
assembly_reset(a);
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
demo3d_chunk_cache_entry* entry = find_entry_bsp(cache, a->id);
|
||||
if (entry) {
|
||||
if (entry->chunk && entry->chunk->bsp) {
|
||||
demo3d_bsp_destroy(entry->chunk->bsp);
|
||||
}
|
||||
} else {
|
||||
entry = alloc_entry(cache);
|
||||
entry->chunk = demo3d_chunk_create_bsp(a->id);
|
||||
entry->valid = true;
|
||||
}
|
||||
|
||||
entry->chunk->bsp = bsp;
|
||||
entry->chunk->version = a->version;
|
||||
entry->last_used = cache->frame_counter;
|
||||
|
||||
assembly_reset(a);
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
demo3d_chunk_cache* demo3d_chunk_cache_create(void) {
|
||||
demo3d_chunk_cache* cache = pxl8_calloc(1, sizeof(demo3d_chunk_cache));
|
||||
if (!cache) return NULL;
|
||||
assembly_reset(&cache->assembly);
|
||||
return cache;
|
||||
}
|
||||
|
||||
void demo3d_chunk_cache_destroy(demo3d_chunk_cache* cache) {
|
||||
if (!cache) return;
|
||||
|
||||
for (u32 i = 0; i < cache->entry_count; i++) {
|
||||
demo3d_chunk_cache_entry* e = &cache->entries[i];
|
||||
if (e->chunk) demo3d_chunk_destroy(e->chunk);
|
||||
}
|
||||
|
||||
pxl8_free(cache->assembly.data);
|
||||
pxl8_free(cache);
|
||||
}
|
||||
|
||||
pxl8_result demo3d_chunk_cache_receive(demo3d_chunk_cache* cache,
|
||||
const demo3d_chunk_msg_header* hdr,
|
||||
const u8* payload, usize len) {
|
||||
if (!cache || !hdr || !payload) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
|
||||
demo3d_chunk_assembly* a = &cache->assembly;
|
||||
|
||||
bool new_assembly = !a->active ||
|
||||
a->id != hdr->id ||
|
||||
a->version != hdr->version;
|
||||
|
||||
if (new_assembly) {
|
||||
assembly_init(a, hdr);
|
||||
}
|
||||
|
||||
if (hdr->fragment_idx >= DEMO3D_CHUNK_MAX_FRAGMENTS) {
|
||||
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
u32 offset = (u32)hdr->fragment_idx * DEMO3D_CHUNK_MAX_PAYLOAD;
|
||||
u32 required = offset + (u32)len;
|
||||
|
||||
if (required > a->data_capacity) {
|
||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
memcpy(a->data + offset, payload, len);
|
||||
|
||||
if (required > a->data_size) {
|
||||
a->data_size = required;
|
||||
}
|
||||
|
||||
a->fragments_received++;
|
||||
|
||||
if (hdr->flags & DEMO3D_CHUNK_FLAG_FINAL) {
|
||||
a->complete = true;
|
||||
return assemble_bsp(cache, a);
|
||||
}
|
||||
|
||||
return PXL8_OK;
|
||||
}
|
||||
|
||||
demo3d_chunk* demo3d_chunk_cache_get_bsp(demo3d_chunk_cache* cache, u32 id) {
|
||||
if (!cache) return NULL;
|
||||
demo3d_chunk_cache_entry* e = find_entry_bsp(cache, id);
|
||||
if (e) {
|
||||
e->last_used = cache->frame_counter;
|
||||
return e->chunk;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void demo3d_chunk_cache_tick(demo3d_chunk_cache* cache) {
|
||||
if (!cache) return;
|
||||
cache->frame_counter++;
|
||||
}
|
||||
55
demo3d/client/world/demo3d_chunk_cache.h
Normal file
55
demo3d/client/world/demo3d_chunk_cache.h
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_mesh.h"
|
||||
#include "demo3d_protocol.h"
|
||||
#include "pxl8_types.h"
|
||||
#include "demo3d_chunk.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define DEMO3D_CHUNK_CACHE_SIZE 512
|
||||
#define DEMO3D_CHUNK_MAX_FRAGMENTS 255
|
||||
#define DEMO3D_CHUNK_MAX_DATA_SIZE 131072
|
||||
|
||||
typedef struct demo3d_chunk_cache_entry {
|
||||
demo3d_chunk* chunk;
|
||||
u64 last_used;
|
||||
bool valid;
|
||||
} demo3d_chunk_cache_entry;
|
||||
|
||||
typedef struct demo3d_chunk_assembly {
|
||||
u32 id;
|
||||
i32 cx, cy, cz;
|
||||
u32 version;
|
||||
u8 fragment_count;
|
||||
u8 fragments_received;
|
||||
u8* data;
|
||||
u32 data_size;
|
||||
u32 data_capacity;
|
||||
bool active;
|
||||
bool complete;
|
||||
} demo3d_chunk_assembly;
|
||||
|
||||
typedef struct demo3d_chunk_cache {
|
||||
demo3d_chunk_cache_entry entries[DEMO3D_CHUNK_CACHE_SIZE];
|
||||
demo3d_chunk_assembly assembly;
|
||||
u32 entry_count;
|
||||
u64 frame_counter;
|
||||
} demo3d_chunk_cache;
|
||||
|
||||
demo3d_chunk_cache* demo3d_chunk_cache_create(void);
|
||||
void demo3d_chunk_cache_destroy(demo3d_chunk_cache* cache);
|
||||
|
||||
pxl8_result demo3d_chunk_cache_receive(demo3d_chunk_cache* cache,
|
||||
const demo3d_chunk_msg_header* hdr,
|
||||
const u8* payload, usize len);
|
||||
|
||||
demo3d_chunk* demo3d_chunk_cache_get_bsp(demo3d_chunk_cache* cache, u32 id);
|
||||
|
||||
void demo3d_chunk_cache_tick(demo3d_chunk_cache* cache);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
496
demo3d/client/world/demo3d_entity.c
Normal file
496
demo3d/client/world/demo3d_entity.c
Normal file
|
|
@ -0,0 +1,496 @@
|
|||
#include "demo3d_entity.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_mem.h"
|
||||
|
||||
#define DEMO3D_ENTITY_COMPONENT_NAME_MAX 32
|
||||
#define DEMO3D_ENTITY_RELATIONSHIP_NAME_MAX 32
|
||||
|
||||
typedef struct demo3d_component_type {
|
||||
char name[DEMO3D_ENTITY_COMPONENT_NAME_MAX];
|
||||
u32 size;
|
||||
} demo3d_component_type;
|
||||
|
||||
typedef struct demo3d_component_storage {
|
||||
u32* sparse;
|
||||
void* dense_data;
|
||||
demo3d_entity* dense_entities;
|
||||
u32 count;
|
||||
} demo3d_component_storage;
|
||||
|
||||
typedef struct demo3d_relationship_type {
|
||||
char name[DEMO3D_ENTITY_RELATIONSHIP_NAME_MAX];
|
||||
} demo3d_relationship_type;
|
||||
|
||||
typedef struct demo3d_relationship_entry {
|
||||
demo3d_entity subject;
|
||||
demo3d_entity object;
|
||||
demo3d_entity_relationship rel;
|
||||
u32 next_by_subject;
|
||||
u32 next_by_object;
|
||||
} demo3d_relationship_entry;
|
||||
|
||||
struct demo3d_entity_pool {
|
||||
u32* generations;
|
||||
u32* free_list;
|
||||
u32 free_count;
|
||||
u32 capacity;
|
||||
u32 alive_count;
|
||||
|
||||
demo3d_component_type* component_types;
|
||||
demo3d_component_storage* component_storage;
|
||||
u32 component_type_count;
|
||||
u32 component_type_capacity;
|
||||
|
||||
demo3d_relationship_type* relationship_types;
|
||||
u32 relationship_type_count;
|
||||
u32 relationship_type_capacity;
|
||||
|
||||
demo3d_relationship_entry* relationships;
|
||||
u32* rel_by_subject;
|
||||
u32* rel_by_object;
|
||||
u32 relationship_count;
|
||||
u32 relationship_capacity;
|
||||
};
|
||||
|
||||
demo3d_entity_pool* demo3d_entity_pool_create(u32 capacity) {
|
||||
demo3d_entity_pool* pool = pxl8_calloc(1, sizeof(demo3d_entity_pool));
|
||||
if (!pool) return NULL;
|
||||
|
||||
pool->capacity = capacity;
|
||||
pool->generations = pxl8_calloc(capacity, sizeof(u32));
|
||||
pool->free_list = pxl8_malloc(capacity * sizeof(u32));
|
||||
|
||||
if (!pool->generations || !pool->free_list) {
|
||||
demo3d_entity_pool_destroy(pool);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < capacity; i++) {
|
||||
pool->free_list[i] = capacity - 1 - i;
|
||||
}
|
||||
pool->free_count = capacity;
|
||||
|
||||
pool->component_type_capacity = 16;
|
||||
pool->component_types = pxl8_calloc(pool->component_type_capacity, sizeof(demo3d_component_type));
|
||||
pool->component_storage = pxl8_calloc(pool->component_type_capacity, sizeof(demo3d_component_storage));
|
||||
|
||||
pool->relationship_type_capacity = 16;
|
||||
pool->relationship_types = pxl8_calloc(pool->relationship_type_capacity, sizeof(demo3d_relationship_type));
|
||||
|
||||
pool->relationship_capacity = 256;
|
||||
pool->relationships = pxl8_malloc(pool->relationship_capacity * sizeof(demo3d_relationship_entry));
|
||||
pool->rel_by_subject = pxl8_malloc(capacity * sizeof(u32));
|
||||
pool->rel_by_object = pxl8_malloc(capacity * sizeof(u32));
|
||||
|
||||
for (u32 i = 0; i < capacity; i++) {
|
||||
pool->rel_by_subject[i] = UINT32_MAX;
|
||||
pool->rel_by_object[i] = UINT32_MAX;
|
||||
}
|
||||
|
||||
return pool;
|
||||
}
|
||||
|
||||
void demo3d_entity_pool_clear(demo3d_entity_pool* pool) {
|
||||
if (!pool) return;
|
||||
|
||||
for (u32 i = 0; i < pool->capacity; i++) {
|
||||
pool->generations[i] = 0;
|
||||
pool->free_list[i] = pool->capacity - 1 - i;
|
||||
pool->rel_by_subject[i] = UINT32_MAX;
|
||||
pool->rel_by_object[i] = UINT32_MAX;
|
||||
}
|
||||
pool->free_count = pool->capacity;
|
||||
pool->alive_count = 0;
|
||||
|
||||
for (u32 i = 0; i < pool->component_type_count; i++) {
|
||||
pool->component_storage[i].count = 0;
|
||||
}
|
||||
|
||||
pool->relationship_count = 0;
|
||||
}
|
||||
|
||||
void demo3d_entity_pool_destroy(demo3d_entity_pool* pool) {
|
||||
if (!pool) return;
|
||||
|
||||
for (u32 i = 0; i < pool->component_type_count; i++) {
|
||||
pxl8_free(pool->component_storage[i].sparse);
|
||||
pxl8_free(pool->component_storage[i].dense_data);
|
||||
pxl8_free(pool->component_storage[i].dense_entities);
|
||||
}
|
||||
|
||||
pxl8_free(pool->component_types);
|
||||
pxl8_free(pool->component_storage);
|
||||
pxl8_free(pool->relationship_types);
|
||||
pxl8_free(pool->relationships);
|
||||
pxl8_free(pool->rel_by_subject);
|
||||
pxl8_free(pool->rel_by_object);
|
||||
pxl8_free(pool->generations);
|
||||
pxl8_free(pool->free_list);
|
||||
pxl8_free(pool);
|
||||
}
|
||||
|
||||
demo3d_entity demo3d_entity_spawn(demo3d_entity_pool* pool) {
|
||||
if (!pool || pool->free_count == 0) return DEMO3D_ENTITY_INVALID;
|
||||
|
||||
u32 idx = pool->free_list[--pool->free_count];
|
||||
pool->generations[idx]++;
|
||||
pool->alive_count++;
|
||||
|
||||
return (demo3d_entity){ .idx = idx, .gen = pool->generations[idx] };
|
||||
}
|
||||
|
||||
void demo3d_entity_despawn(demo3d_entity_pool* pool, demo3d_entity e) {
|
||||
if (!pool || !demo3d_entity_alive(pool, e)) return;
|
||||
|
||||
for (u32 i = 0; i < pool->component_type_count; i++) {
|
||||
demo3d_entity_component_remove(pool, e, i + 1);
|
||||
}
|
||||
|
||||
pool->free_list[pool->free_count++] = e.idx;
|
||||
pool->generations[e.idx]++;
|
||||
pool->alive_count--;
|
||||
}
|
||||
|
||||
bool demo3d_entity_alive(const demo3d_entity_pool* pool, demo3d_entity e) {
|
||||
if (!pool || e.idx >= pool->capacity) return false;
|
||||
return pool->generations[e.idx] == e.gen && e.gen != 0;
|
||||
}
|
||||
|
||||
u32 demo3d_entity_count(const demo3d_entity_pool* pool) {
|
||||
return pool ? pool->alive_count : 0;
|
||||
}
|
||||
|
||||
demo3d_entity_component demo3d_entity_component_register(demo3d_entity_pool* pool, const char* name, u32 size) {
|
||||
if (!pool || !name || size == 0) return DEMO3D_ENTITY_COMPONENT_INVALID;
|
||||
|
||||
demo3d_entity_component existing = demo3d_entity_component_find(pool, name);
|
||||
if (existing != DEMO3D_ENTITY_COMPONENT_INVALID) return existing;
|
||||
|
||||
if (pool->component_type_count >= pool->component_type_capacity) {
|
||||
u32 new_capacity = pool->component_type_capacity * 2;
|
||||
demo3d_component_type* new_types = pxl8_realloc(pool->component_types, new_capacity * sizeof(demo3d_component_type));
|
||||
demo3d_component_storage* new_storage = pxl8_realloc(pool->component_storage, new_capacity * sizeof(demo3d_component_storage));
|
||||
if (!new_types || !new_storage) return DEMO3D_ENTITY_COMPONENT_INVALID;
|
||||
|
||||
pool->component_types = new_types;
|
||||
pool->component_storage = new_storage;
|
||||
memset(&pool->component_types[pool->component_type_capacity], 0, (new_capacity - pool->component_type_capacity) * sizeof(demo3d_component_type));
|
||||
memset(&pool->component_storage[pool->component_type_capacity], 0, (new_capacity - pool->component_type_capacity) * sizeof(demo3d_component_storage));
|
||||
pool->component_type_capacity = new_capacity;
|
||||
}
|
||||
|
||||
u32 type_idx = pool->component_type_count++;
|
||||
strncpy(pool->component_types[type_idx].name, name, DEMO3D_ENTITY_COMPONENT_NAME_MAX - 1);
|
||||
pool->component_types[type_idx].size = size;
|
||||
|
||||
demo3d_component_storage* storage = &pool->component_storage[type_idx];
|
||||
storage->sparse = pxl8_malloc(pool->capacity * sizeof(u32));
|
||||
storage->dense_data = pxl8_malloc(pool->capacity * size);
|
||||
storage->dense_entities = pxl8_malloc(pool->capacity * sizeof(demo3d_entity));
|
||||
storage->count = 0;
|
||||
|
||||
for (u32 i = 0; i < pool->capacity; i++) {
|
||||
storage->sparse[i] = UINT32_MAX;
|
||||
}
|
||||
|
||||
return type_idx + 1;
|
||||
}
|
||||
|
||||
demo3d_entity_component demo3d_entity_component_find(const demo3d_entity_pool* pool, const char* name) {
|
||||
if (!pool || !name) return DEMO3D_ENTITY_COMPONENT_INVALID;
|
||||
|
||||
for (u32 i = 0; i < pool->component_type_count; i++) {
|
||||
if (strcmp(pool->component_types[i].name, name) == 0) {
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
return DEMO3D_ENTITY_COMPONENT_INVALID;
|
||||
}
|
||||
|
||||
const char* demo3d_entity_component_name(const demo3d_entity_pool* pool, demo3d_entity_component comp) {
|
||||
if (!pool || comp == 0 || comp > pool->component_type_count) return NULL;
|
||||
return pool->component_types[comp - 1].name;
|
||||
}
|
||||
|
||||
void* demo3d_entity_component_add(demo3d_entity_pool* pool, demo3d_entity e, demo3d_entity_component comp) {
|
||||
if (!pool || !demo3d_entity_alive(pool, e)) return NULL;
|
||||
if (comp == 0 || comp > pool->component_type_count) return NULL;
|
||||
|
||||
u32 type_idx = comp - 1;
|
||||
demo3d_component_storage* storage = &pool->component_storage[type_idx];
|
||||
u32 size = pool->component_types[type_idx].size;
|
||||
|
||||
if (storage->sparse[e.idx] != UINT32_MAX) {
|
||||
return (u8*)storage->dense_data + storage->sparse[e.idx] * size;
|
||||
}
|
||||
|
||||
u32 dense_idx = storage->count++;
|
||||
storage->sparse[e.idx] = dense_idx;
|
||||
storage->dense_entities[dense_idx] = e;
|
||||
|
||||
void* data = (u8*)storage->dense_data + dense_idx * size;
|
||||
memset(data, 0, size);
|
||||
return data;
|
||||
}
|
||||
|
||||
void* demo3d_entity_component_get(const demo3d_entity_pool* pool, demo3d_entity e, demo3d_entity_component comp) {
|
||||
if (!pool || !demo3d_entity_alive(pool, e)) return NULL;
|
||||
if (comp == 0 || comp > pool->component_type_count) return NULL;
|
||||
|
||||
u32 type_idx = comp - 1;
|
||||
const demo3d_component_storage* storage = &pool->component_storage[type_idx];
|
||||
|
||||
if (storage->sparse[e.idx] == UINT32_MAX) return NULL;
|
||||
|
||||
u32 size = pool->component_types[type_idx].size;
|
||||
return (u8*)storage->dense_data + storage->sparse[e.idx] * size;
|
||||
}
|
||||
|
||||
void demo3d_entity_component_remove(demo3d_entity_pool* pool, demo3d_entity e, demo3d_entity_component comp) {
|
||||
if (!pool || !demo3d_entity_alive(pool, e)) return;
|
||||
if (comp == 0 || comp > pool->component_type_count) return;
|
||||
|
||||
u32 type_idx = comp - 1;
|
||||
demo3d_component_storage* storage = &pool->component_storage[type_idx];
|
||||
u32 size = pool->component_types[type_idx].size;
|
||||
|
||||
u32 dense_idx = storage->sparse[e.idx];
|
||||
if (dense_idx == UINT32_MAX) return;
|
||||
|
||||
u32 last_idx = storage->count - 1;
|
||||
if (dense_idx != last_idx) {
|
||||
demo3d_entity last_entity = storage->dense_entities[last_idx];
|
||||
memcpy((u8*)storage->dense_data + dense_idx * size,
|
||||
(u8*)storage->dense_data + last_idx * size, size);
|
||||
storage->dense_entities[dense_idx] = last_entity;
|
||||
storage->sparse[last_entity.idx] = dense_idx;
|
||||
}
|
||||
|
||||
storage->sparse[e.idx] = UINT32_MAX;
|
||||
storage->count--;
|
||||
}
|
||||
|
||||
bool demo3d_entity_component_has(const demo3d_entity_pool* pool, demo3d_entity e, demo3d_entity_component comp) {
|
||||
if (!pool || !demo3d_entity_alive(pool, e)) return false;
|
||||
if (comp == 0 || comp > pool->component_type_count) return false;
|
||||
|
||||
return pool->component_storage[comp - 1].sparse[e.idx] != UINT32_MAX;
|
||||
}
|
||||
|
||||
demo3d_entity_relationship demo3d_entity_relationship_register(demo3d_entity_pool* pool, const char* name) {
|
||||
if (!pool || !name) return DEMO3D_ENTITY_RELATIONSHIP_INVALID;
|
||||
|
||||
demo3d_entity_relationship existing = demo3d_entity_relationship_find(pool, name);
|
||||
if (existing != DEMO3D_ENTITY_RELATIONSHIP_INVALID) return existing;
|
||||
|
||||
if (pool->relationship_type_count >= pool->relationship_type_capacity) {
|
||||
u32 new_capacity = pool->relationship_type_capacity * 2;
|
||||
demo3d_relationship_type* new_types = pxl8_realloc(pool->relationship_types, new_capacity * sizeof(demo3d_relationship_type));
|
||||
if (!new_types) return DEMO3D_ENTITY_RELATIONSHIP_INVALID;
|
||||
|
||||
pool->relationship_types = new_types;
|
||||
memset(&pool->relationship_types[pool->relationship_type_capacity], 0, (new_capacity - pool->relationship_type_capacity) * sizeof(demo3d_relationship_type));
|
||||
pool->relationship_type_capacity = new_capacity;
|
||||
}
|
||||
|
||||
u32 type_idx = pool->relationship_type_count++;
|
||||
strncpy(pool->relationship_types[type_idx].name, name, DEMO3D_ENTITY_RELATIONSHIP_NAME_MAX - 1);
|
||||
|
||||
return type_idx + 1;
|
||||
}
|
||||
|
||||
demo3d_entity_relationship demo3d_entity_relationship_find(const demo3d_entity_pool* pool, const char* name) {
|
||||
if (!pool || !name) return DEMO3D_ENTITY_RELATIONSHIP_INVALID;
|
||||
|
||||
for (u32 i = 0; i < pool->relationship_type_count; i++) {
|
||||
if (strcmp(pool->relationship_types[i].name, name) == 0) {
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
return DEMO3D_ENTITY_RELATIONSHIP_INVALID;
|
||||
}
|
||||
|
||||
const char* demo3d_entity_relationship_name(const demo3d_entity_pool* pool, demo3d_entity_relationship rel) {
|
||||
if (!pool || rel == 0 || rel > pool->relationship_type_count) return NULL;
|
||||
return pool->relationship_types[rel - 1].name;
|
||||
}
|
||||
|
||||
void demo3d_entity_relationship_add(demo3d_entity_pool* pool, demo3d_entity subject, demo3d_entity_relationship rel, demo3d_entity object) {
|
||||
if (!pool) return;
|
||||
if (!demo3d_entity_alive(pool, subject) || !demo3d_entity_alive(pool, object)) return;
|
||||
if (rel == 0 || rel > pool->relationship_type_count) return;
|
||||
if (demo3d_entity_relationship_has(pool, subject, rel, object)) return;
|
||||
|
||||
if (pool->relationship_count >= pool->relationship_capacity) {
|
||||
u32 new_capacity = pool->relationship_capacity * 2;
|
||||
demo3d_relationship_entry* new_rels = pxl8_realloc(pool->relationships, new_capacity * sizeof(demo3d_relationship_entry));
|
||||
if (!new_rels) return;
|
||||
pool->relationships = new_rels;
|
||||
pool->relationship_capacity = new_capacity;
|
||||
}
|
||||
|
||||
u32 entry_idx = pool->relationship_count++;
|
||||
demo3d_relationship_entry* entry = &pool->relationships[entry_idx];
|
||||
entry->subject = subject;
|
||||
entry->object = object;
|
||||
entry->rel = rel;
|
||||
|
||||
entry->next_by_subject = pool->rel_by_subject[subject.idx];
|
||||
pool->rel_by_subject[subject.idx] = entry_idx;
|
||||
|
||||
entry->next_by_object = pool->rel_by_object[object.idx];
|
||||
pool->rel_by_object[object.idx] = entry_idx;
|
||||
}
|
||||
|
||||
void demo3d_entity_relationship_remove(demo3d_entity_pool* pool, demo3d_entity subject, demo3d_entity_relationship rel, demo3d_entity object) {
|
||||
if (!pool) return;
|
||||
if (rel == 0 || rel > pool->relationship_type_count) return;
|
||||
|
||||
u32* prev_ptr = &pool->rel_by_subject[subject.idx];
|
||||
u32 idx = *prev_ptr;
|
||||
|
||||
while (idx != UINT32_MAX) {
|
||||
demo3d_relationship_entry* entry = &pool->relationships[idx];
|
||||
if (demo3d_entity_eq(entry->subject, subject) &&
|
||||
demo3d_entity_eq(entry->object, object) &&
|
||||
entry->rel == rel) {
|
||||
|
||||
*prev_ptr = entry->next_by_subject;
|
||||
|
||||
u32* obj_prev = &pool->rel_by_object[object.idx];
|
||||
while (*obj_prev != UINT32_MAX) {
|
||||
if (*obj_prev == idx) {
|
||||
*obj_prev = entry->next_by_object;
|
||||
break;
|
||||
}
|
||||
obj_prev = &pool->relationships[*obj_prev].next_by_object;
|
||||
}
|
||||
|
||||
if (idx != pool->relationship_count - 1) {
|
||||
u32 last_idx = pool->relationship_count - 1;
|
||||
demo3d_relationship_entry* last = &pool->relationships[last_idx];
|
||||
|
||||
u32* last_subj_prev = &pool->rel_by_subject[last->subject.idx];
|
||||
while (*last_subj_prev != UINT32_MAX) {
|
||||
if (*last_subj_prev == last_idx) {
|
||||
*last_subj_prev = idx;
|
||||
break;
|
||||
}
|
||||
last_subj_prev = &pool->relationships[*last_subj_prev].next_by_subject;
|
||||
}
|
||||
|
||||
u32* last_obj_prev = &pool->rel_by_object[last->object.idx];
|
||||
while (*last_obj_prev != UINT32_MAX) {
|
||||
if (*last_obj_prev == last_idx) {
|
||||
*last_obj_prev = idx;
|
||||
break;
|
||||
}
|
||||
last_obj_prev = &pool->relationships[*last_obj_prev].next_by_object;
|
||||
}
|
||||
|
||||
*entry = *last;
|
||||
}
|
||||
pool->relationship_count--;
|
||||
return;
|
||||
}
|
||||
prev_ptr = &entry->next_by_subject;
|
||||
idx = *prev_ptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool demo3d_entity_relationship_has(const demo3d_entity_pool* pool, demo3d_entity subject, demo3d_entity_relationship rel, demo3d_entity object) {
|
||||
if (!pool) return false;
|
||||
if (rel == 0 || rel > pool->relationship_type_count) return false;
|
||||
|
||||
u32 idx = pool->rel_by_subject[subject.idx];
|
||||
while (idx != UINT32_MAX) {
|
||||
const demo3d_relationship_entry* entry = &pool->relationships[idx];
|
||||
if (demo3d_entity_eq(entry->subject, subject) &&
|
||||
demo3d_entity_eq(entry->object, object) &&
|
||||
entry->rel == rel) {
|
||||
return true;
|
||||
}
|
||||
idx = entry->next_by_subject;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
u32 demo3d_entity_relationship_subjects(const demo3d_entity_pool* pool, demo3d_entity object, demo3d_entity_relationship rel, demo3d_entity* out, u32 max) {
|
||||
if (!pool || !out || max == 0) return 0;
|
||||
if (rel == 0 || rel > pool->relationship_type_count) return 0;
|
||||
|
||||
u32 count = 0;
|
||||
u32 idx = pool->rel_by_object[object.idx];
|
||||
while (idx != UINT32_MAX && count < max) {
|
||||
const demo3d_relationship_entry* entry = &pool->relationships[idx];
|
||||
if (demo3d_entity_eq(entry->object, object) && entry->rel == rel) {
|
||||
out[count++] = entry->subject;
|
||||
}
|
||||
idx = entry->next_by_object;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
u32 demo3d_entity_relationship_objects(const demo3d_entity_pool* pool, demo3d_entity subject, demo3d_entity_relationship rel, demo3d_entity* out, u32 max) {
|
||||
if (!pool || !out || max == 0) return 0;
|
||||
if (rel == 0 || rel > pool->relationship_type_count) return 0;
|
||||
|
||||
u32 count = 0;
|
||||
u32 idx = pool->rel_by_subject[subject.idx];
|
||||
while (idx != UINT32_MAX && count < max) {
|
||||
const demo3d_relationship_entry* entry = &pool->relationships[idx];
|
||||
if (demo3d_entity_eq(entry->subject, subject) && entry->rel == rel) {
|
||||
out[count++] = entry->object;
|
||||
}
|
||||
idx = entry->next_by_subject;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void demo3d_entity_each(demo3d_entity_pool* pool, demo3d_entity_component comp, demo3d_entity_each_fn fn, void* ctx) {
|
||||
if (!pool || !fn) return;
|
||||
if (comp == 0 || comp > pool->component_type_count) return;
|
||||
|
||||
demo3d_component_storage* storage = &pool->component_storage[comp - 1];
|
||||
for (u32 i = 0; i < storage->count; i++) {
|
||||
demo3d_entity e = storage->dense_entities[i];
|
||||
if (demo3d_entity_alive(pool, e)) {
|
||||
fn(pool, e, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void demo3d_entity_each_with(demo3d_entity_pool* pool, const demo3d_entity_component* comps, u32 count, demo3d_entity_each_fn fn, void* ctx) {
|
||||
if (!pool || !comps || !fn || count == 0) return;
|
||||
|
||||
u32 smallest_idx = 0;
|
||||
u32 smallest_count = UINT32_MAX;
|
||||
|
||||
for (u32 i = 0; i < count; i++) {
|
||||
if (comps[i] == 0 || comps[i] > pool->component_type_count) return;
|
||||
u32 storage_count = pool->component_storage[comps[i] - 1].count;
|
||||
if (storage_count < smallest_count) {
|
||||
smallest_count = storage_count;
|
||||
smallest_idx = i;
|
||||
}
|
||||
}
|
||||
|
||||
demo3d_component_storage* storage = &pool->component_storage[comps[smallest_idx] - 1];
|
||||
for (u32 i = 0; i < storage->count; i++) {
|
||||
demo3d_entity e = storage->dense_entities[i];
|
||||
if (!demo3d_entity_alive(pool, e)) continue;
|
||||
|
||||
bool has_all = true;
|
||||
for (u32 j = 0; j < count && has_all; j++) {
|
||||
if (j != smallest_idx) {
|
||||
has_all = demo3d_entity_component_has(pool, e, comps[j]);
|
||||
}
|
||||
}
|
||||
|
||||
if (has_all) {
|
||||
fn(pool, e, ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
67
demo3d/client/world/demo3d_entity.h
Normal file
67
demo3d/client/world/demo3d_entity.h
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#pragma once
|
||||
|
||||
#include "pxl8_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct demo3d_entity_pool demo3d_entity_pool;
|
||||
|
||||
typedef struct demo3d_entity {
|
||||
u32 idx;
|
||||
u32 gen;
|
||||
} demo3d_entity;
|
||||
|
||||
#define DEMO3D_ENTITY_INVALID ((demo3d_entity){0, 0})
|
||||
|
||||
typedef u32 demo3d_entity_component;
|
||||
typedef u32 demo3d_entity_relationship;
|
||||
|
||||
#define DEMO3D_ENTITY_COMPONENT_INVALID 0
|
||||
#define DEMO3D_ENTITY_RELATIONSHIP_INVALID 0
|
||||
|
||||
demo3d_entity_pool* demo3d_entity_pool_create(u32 capacity);
|
||||
void demo3d_entity_pool_clear(demo3d_entity_pool* pool);
|
||||
void demo3d_entity_pool_destroy(demo3d_entity_pool* pool);
|
||||
|
||||
demo3d_entity demo3d_entity_spawn(demo3d_entity_pool* pool);
|
||||
void demo3d_entity_despawn(demo3d_entity_pool* pool, demo3d_entity e);
|
||||
bool demo3d_entity_alive(const demo3d_entity_pool* pool, demo3d_entity e);
|
||||
u32 demo3d_entity_count(const demo3d_entity_pool* pool);
|
||||
|
||||
demo3d_entity_component demo3d_entity_component_register(demo3d_entity_pool* pool, const char* name, u32 size);
|
||||
demo3d_entity_component demo3d_entity_component_find(const demo3d_entity_pool* pool, const char* name);
|
||||
const char* demo3d_entity_component_name(const demo3d_entity_pool* pool, demo3d_entity_component comp);
|
||||
|
||||
void* demo3d_entity_component_add(demo3d_entity_pool* pool, demo3d_entity e, demo3d_entity_component comp);
|
||||
void* demo3d_entity_component_get(const demo3d_entity_pool* pool, demo3d_entity e, demo3d_entity_component comp);
|
||||
void demo3d_entity_component_remove(demo3d_entity_pool* pool, demo3d_entity e, demo3d_entity_component comp);
|
||||
bool demo3d_entity_component_has(const demo3d_entity_pool* pool, demo3d_entity e, demo3d_entity_component comp);
|
||||
|
||||
demo3d_entity_relationship demo3d_entity_relationship_register(demo3d_entity_pool* pool, const char* name);
|
||||
demo3d_entity_relationship demo3d_entity_relationship_find(const demo3d_entity_pool* pool, const char* name);
|
||||
const char* demo3d_entity_relationship_name(const demo3d_entity_pool* pool, demo3d_entity_relationship rel);
|
||||
|
||||
void demo3d_entity_relationship_add(demo3d_entity_pool* pool, demo3d_entity subject, demo3d_entity_relationship rel, demo3d_entity object);
|
||||
void demo3d_entity_relationship_remove(demo3d_entity_pool* pool, demo3d_entity subject, demo3d_entity_relationship rel, demo3d_entity object);
|
||||
bool demo3d_entity_relationship_has(const demo3d_entity_pool* pool, demo3d_entity subject, demo3d_entity_relationship rel, demo3d_entity object);
|
||||
|
||||
u32 demo3d_entity_relationship_subjects(const demo3d_entity_pool* pool, demo3d_entity object, demo3d_entity_relationship rel, demo3d_entity* out, u32 max);
|
||||
u32 demo3d_entity_relationship_objects(const demo3d_entity_pool* pool, demo3d_entity subject, demo3d_entity_relationship rel, demo3d_entity* out, u32 max);
|
||||
|
||||
typedef void (*demo3d_entity_each_fn)(demo3d_entity_pool* pool, demo3d_entity e, void* ctx);
|
||||
void demo3d_entity_each(demo3d_entity_pool* pool, demo3d_entity_component comp, demo3d_entity_each_fn fn, void* ctx);
|
||||
void demo3d_entity_each_with(demo3d_entity_pool* pool, const demo3d_entity_component* comps, u32 count, demo3d_entity_each_fn fn, void* ctx);
|
||||
|
||||
static inline bool demo3d_entity_valid(demo3d_entity e) {
|
||||
return e.idx != 0 || e.gen != 0;
|
||||
}
|
||||
|
||||
static inline bool demo3d_entity_eq(demo3d_entity a, demo3d_entity b) {
|
||||
return a.idx == b.idx && a.gen == b.gen;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
981
demo3d/client/world/demo3d_world.c
Normal file
981
demo3d/client/world/demo3d_world.c
Normal file
|
|
@ -0,0 +1,981 @@
|
|||
#include "demo3d_world.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "pxl8_platform.h"
|
||||
|
||||
#ifdef PXL8_ASYNC_THREADS
|
||||
#include <stdatomic.h>
|
||||
#include "pxl8_queue.h"
|
||||
#endif
|
||||
|
||||
#include "demo3d_bsp_render.h"
|
||||
#include "pxl8_io.h"
|
||||
#include "pxl8_gfx3d.h"
|
||||
#include "pxl8_log.h"
|
||||
#include "pxl8_mem.h"
|
||||
#include "demo3d_protocol.h"
|
||||
#include "demo3d_sim.h"
|
||||
|
||||
#define DEMO3D_VIS_MAX_NODES (DEMO3D_WORLD_MAX_LOADED_CHUNKS * 512)
|
||||
#define DEMO3D_VIS_MAX_QUEUE (DEMO3D_VIS_MAX_NODES * 4)
|
||||
#define DEMO3D_VIS_BYTES ((DEMO3D_VIS_MAX_NODES + 7) / 8)
|
||||
#define DEMO3D_WORLD_ENTITY_CAPACITY 256
|
||||
|
||||
typedef struct {
|
||||
u16 chunk_idx;
|
||||
u16 leaf_idx;
|
||||
pxl8_rect window;
|
||||
} world_vis_node;
|
||||
|
||||
struct demo3d_world {
|
||||
demo3d_loaded_chunk loaded[DEMO3D_WORLD_MAX_LOADED_CHUNKS];
|
||||
u32 loaded_count;
|
||||
demo3d_chunk* active_chunk;
|
||||
demo3d_bsp_render_state* active_render_state;
|
||||
|
||||
pxl8_gfx_material shared_materials[16];
|
||||
bool shared_material_set[16];
|
||||
|
||||
demo3d_chunk_cache* chunk_cache;
|
||||
demo3d_entity_pool* entities;
|
||||
|
||||
demo3d_sim_entity local_player;
|
||||
u64 client_tick;
|
||||
|
||||
pxl8_vec2 pointer_motion;
|
||||
demo3d_sim_config sim_config;
|
||||
|
||||
u8 vis_bits[DEMO3D_VIS_BYTES];
|
||||
u8* vis_ptrs[DEMO3D_WORLD_MAX_LOADED_CHUNKS];
|
||||
pxl8_rect vis_windows[DEMO3D_VIS_MAX_NODES];
|
||||
pxl8_rect* vis_win_ptrs[DEMO3D_WORLD_MAX_LOADED_CHUNKS];
|
||||
world_vis_node vis_queue[DEMO3D_VIS_MAX_QUEUE];
|
||||
|
||||
#ifdef PXL8_ASYNC_THREADS
|
||||
demo3d_sim_entity render_state[2];
|
||||
atomic_uint active_buffer;
|
||||
pxl8_thread* sim_thread;
|
||||
atomic_bool sim_running;
|
||||
atomic_bool sim_paused;
|
||||
demo3d_net* net;
|
||||
pxl8_queue input_queue;
|
||||
f32 sim_accumulator;
|
||||
#endif
|
||||
};
|
||||
|
||||
demo3d_world* demo3d_world_create(void) {
|
||||
demo3d_world* world = pxl8_calloc(1, sizeof(demo3d_world));
|
||||
if (!world) return NULL;
|
||||
|
||||
world->chunk_cache = demo3d_chunk_cache_create();
|
||||
world->entities = demo3d_entity_pool_create(DEMO3D_WORLD_ENTITY_CAPACITY);
|
||||
world->sim_config = (demo3d_sim_config){
|
||||
.move_speed = 180.0f,
|
||||
.ground_accel = 10.0f,
|
||||
.air_accel = 1.0f,
|
||||
.stop_speed = 100.0f,
|
||||
.friction = 6.0f,
|
||||
.gravity = 800.0f,
|
||||
.jump_velocity = 200.0f,
|
||||
.player_radius = 16.0f,
|
||||
.player_height = 72.0f,
|
||||
.max_pitch = 1.5f,
|
||||
};
|
||||
|
||||
if (!world->chunk_cache || !world->entities) {
|
||||
demo3d_world_destroy(world);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return world;
|
||||
}
|
||||
|
||||
void demo3d_world_destroy(demo3d_world* world) {
|
||||
if (!world) return;
|
||||
|
||||
for (u32 i = 0; i < world->loaded_count; i++) {
|
||||
demo3d_bsp_render_state_destroy(world->loaded[i].render_state);
|
||||
}
|
||||
demo3d_chunk_cache_destroy(world->chunk_cache);
|
||||
demo3d_entity_pool_destroy(world->entities);
|
||||
pxl8_free(world);
|
||||
}
|
||||
|
||||
demo3d_chunk_cache* demo3d_world_get_chunk_cache(demo3d_world* world) {
|
||||
if (!world) return NULL;
|
||||
return world->chunk_cache;
|
||||
}
|
||||
|
||||
demo3d_chunk* demo3d_world_active_chunk(demo3d_world* world) {
|
||||
if (!world) return NULL;
|
||||
return world->active_chunk;
|
||||
}
|
||||
|
||||
demo3d_sim_world demo3d_world_sim_world(const demo3d_world* world, pxl8_vec3 pos) {
|
||||
demo3d_sim_world sim = {0};
|
||||
const f32 chunk_size = 16.0f * 64.0f;
|
||||
sim.chunk_size = chunk_size;
|
||||
|
||||
i32 pcx = (i32)floorf(pos.x / chunk_size);
|
||||
i32 pcz = (i32)floorf(pos.z / chunk_size);
|
||||
sim.center_cx = pcx;
|
||||
sim.center_cz = pcz;
|
||||
|
||||
for (u32 i = 0; i < world->loaded_count; i++) {
|
||||
const demo3d_loaded_chunk* lc = &world->loaded[i];
|
||||
if (!lc->chunk || !lc->chunk->bsp) continue;
|
||||
i32 dx = lc->cx - pcx + 1;
|
||||
i32 dz = lc->cz - pcz + 1;
|
||||
if (dx >= 0 && dx <= 2 && dz >= 0 && dz <= 2) {
|
||||
sim.chunks[dz * 3 + dx] = lc->chunk->bsp;
|
||||
}
|
||||
}
|
||||
|
||||
return sim;
|
||||
}
|
||||
|
||||
static void entity_to_userdata(const demo3d_sim_entity* ent, u8* userdata) {
|
||||
u8* p = userdata;
|
||||
memcpy(p, &ent->pos.x, 4); p += 4;
|
||||
memcpy(p, &ent->pos.y, 4); p += 4;
|
||||
memcpy(p, &ent->pos.z, 4); p += 4;
|
||||
memcpy(p, &ent->yaw, 4); p += 4;
|
||||
memcpy(p, &ent->pitch, 4); p += 4;
|
||||
memcpy(p, &ent->vel.x, 4); p += 4;
|
||||
memcpy(p, &ent->vel.y, 4); p += 4;
|
||||
memcpy(p, &ent->vel.z, 4); p += 4;
|
||||
memcpy(p, &ent->flags, 4); p += 4;
|
||||
memcpy(p, &ent->kind, 2);
|
||||
}
|
||||
|
||||
static void userdata_to_entity(const u8* userdata, demo3d_sim_entity* ent) {
|
||||
const u8* p = userdata;
|
||||
memcpy(&ent->pos.x, p, 4); p += 4;
|
||||
memcpy(&ent->pos.y, p, 4); p += 4;
|
||||
memcpy(&ent->pos.z, p, 4); p += 4;
|
||||
memcpy(&ent->yaw, p, 4); p += 4;
|
||||
memcpy(&ent->pitch, p, 4); p += 4;
|
||||
memcpy(&ent->vel.x, p, 4); p += 4;
|
||||
memcpy(&ent->vel.y, p, 4); p += 4;
|
||||
memcpy(&ent->vel.z, p, 4); p += 4;
|
||||
memcpy(&ent->flags, p, 4); p += 4;
|
||||
memcpy(&ent->kind, p, 2);
|
||||
}
|
||||
|
||||
bool demo3d_world_point_solid(const demo3d_world* world, f32 x, f32 y, f32 z) {
|
||||
if (!world) return false;
|
||||
|
||||
if (world->active_chunk && world->active_chunk->bsp) {
|
||||
return demo3d_bsp_point_solid(world->active_chunk->bsp, (pxl8_vec3){x, y, z});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
pxl8_ray demo3d_world_ray(const demo3d_world* world, pxl8_vec3 from, pxl8_vec3 to) {
|
||||
pxl8_ray result = {0};
|
||||
|
||||
if (!world) return result;
|
||||
|
||||
pxl8_vec3 dir = pxl8_vec3_sub(to, from);
|
||||
f32 length = pxl8_vec3_length(dir);
|
||||
if (length < 0.001f) return result;
|
||||
|
||||
f32 step_size = 1.0f;
|
||||
f32 traveled = 0.0f;
|
||||
|
||||
while (traveled < length) {
|
||||
f32 t = traveled / length;
|
||||
pxl8_vec3 pos = {
|
||||
from.x + dir.x * t,
|
||||
from.y + dir.y * t,
|
||||
from.z + dir.z * t
|
||||
};
|
||||
|
||||
if (demo3d_world_point_solid(world, pos.x, pos.y, pos.z)) {
|
||||
result.hit = true;
|
||||
result.fraction = t;
|
||||
result.point = pos;
|
||||
|
||||
f32 eps = 0.1f;
|
||||
bool sx_neg = demo3d_world_point_solid(world, pos.x - eps, pos.y, pos.z);
|
||||
bool sx_pos = demo3d_world_point_solid(world, pos.x + eps, pos.y, pos.z);
|
||||
bool sy_neg = demo3d_world_point_solid(world, pos.x, pos.y - eps, pos.z);
|
||||
bool sy_pos = demo3d_world_point_solid(world, pos.x, pos.y + eps, pos.z);
|
||||
bool sz_neg = demo3d_world_point_solid(world, pos.x, pos.y, pos.z - eps);
|
||||
bool sz_pos = demo3d_world_point_solid(world, pos.x, pos.y, pos.z + eps);
|
||||
|
||||
result.normal = (pxl8_vec3){
|
||||
(sx_neg && !sx_pos) ? 1.0f : (!sx_neg && sx_pos) ? -1.0f : 0.0f,
|
||||
(sy_neg && !sy_pos) ? 1.0f : (!sy_neg && sy_pos) ? -1.0f : 0.0f,
|
||||
(sz_neg && !sz_pos) ? 1.0f : (!sz_neg && sz_pos) ? -1.0f : 0.0f
|
||||
};
|
||||
|
||||
f32 nl = pxl8_vec3_length(result.normal);
|
||||
if (nl > 0.001f) {
|
||||
result.normal = pxl8_vec3_scale(result.normal, 1.0f / nl);
|
||||
} else {
|
||||
result.normal = (pxl8_vec3){0, 1, 0};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
traveled += step_size;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pxl8_ray demo3d_world_sweep(const demo3d_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius) {
|
||||
pxl8_ray result = {0};
|
||||
|
||||
if (!world) return result;
|
||||
|
||||
f32 diag = radius * 0.707f;
|
||||
|
||||
bool dest_blocked = demo3d_world_point_solid(world, to.x, to.y, to.z) ||
|
||||
demo3d_world_point_solid(world, to.x + radius, to.y, to.z) ||
|
||||
demo3d_world_point_solid(world, to.x - radius, to.y, to.z) ||
|
||||
demo3d_world_point_solid(world, to.x, to.y, to.z + radius) ||
|
||||
demo3d_world_point_solid(world, to.x, to.y, to.z - radius) ||
|
||||
demo3d_world_point_solid(world, to.x + diag, to.y, to.z + diag) ||
|
||||
demo3d_world_point_solid(world, to.x + diag, to.y, to.z - diag) ||
|
||||
demo3d_world_point_solid(world, to.x - diag, to.y, to.z + diag) ||
|
||||
demo3d_world_point_solid(world, to.x - diag, to.y, to.z - diag);
|
||||
|
||||
if (dest_blocked) {
|
||||
result.hit = true;
|
||||
result.fraction = 0;
|
||||
result.point = from;
|
||||
pxl8_vec3 dir = pxl8_vec3_sub(to, from);
|
||||
f32 length = pxl8_vec3_length(dir);
|
||||
if (length > 0.001f) {
|
||||
result.normal = pxl8_vec3_scale(dir, -1.0f / length);
|
||||
} else {
|
||||
result.normal = (pxl8_vec3){0, 1, 0};
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void demo3d_world_update(demo3d_world* world, f32 dt) {
|
||||
(void)dt;
|
||||
if (!world) return;
|
||||
demo3d_chunk_cache_tick(world->chunk_cache);
|
||||
}
|
||||
|
||||
static inline bool vr_valid(pxl8_rect r) {
|
||||
return r.x0 < r.x1 && r.y0 < r.y1;
|
||||
}
|
||||
|
||||
static inline pxl8_rect vr_intersect(pxl8_rect a, pxl8_rect b) {
|
||||
return (pxl8_rect){
|
||||
.x0 = a.x0 > b.x0 ? a.x0 : b.x0,
|
||||
.y0 = a.y0 > b.y0 ? a.y0 : b.y0,
|
||||
.x1 = a.x1 < b.x1 ? a.x1 : b.x1,
|
||||
.y1 = a.y1 < b.y1 ? a.y1 : b.y1,
|
||||
};
|
||||
}
|
||||
|
||||
static pxl8_rect project_portal(f32 px0, f32 pz0, f32 px1, f32 pz1,
|
||||
f32 y_lo, f32 y_hi, const pxl8_mat4* vp) {
|
||||
pxl8_vec3 corners[4] = {
|
||||
{px0, y_lo, pz0}, {px1, y_lo, pz1},
|
||||
{px1, y_hi, pz1}, {px0, y_hi, pz0},
|
||||
};
|
||||
|
||||
const f32 NEAR_W = 0.001f;
|
||||
pxl8_vec4 clip[4];
|
||||
bool front[4];
|
||||
i32 fc = 0;
|
||||
|
||||
for (i32 i = 0; i < 4; i++) {
|
||||
clip[i] = pxl8_mat4_multiply_vec4(*vp, (pxl8_vec4){
|
||||
corners[i].x, corners[i].y, corners[i].z, 1.0f});
|
||||
front[i] = clip[i].w > NEAR_W;
|
||||
if (front[i]) fc++;
|
||||
}
|
||||
|
||||
if (fc == 0) return (pxl8_rect){0, 0, 0, 0};
|
||||
if (fc < 4) return (pxl8_rect){-1.0f, -1.0f, 1.0f, 1.0f};
|
||||
|
||||
pxl8_rect r = {1e30f, 1e30f, -1e30f, -1e30f};
|
||||
|
||||
for (i32 i = 0; i < 4; i++) {
|
||||
f32 iw = 1.0f / clip[i].w;
|
||||
f32 nx = clip[i].x * iw, ny = clip[i].y * iw;
|
||||
if (nx < r.x0) r.x0 = nx; if (nx > r.x1) r.x1 = nx;
|
||||
if (ny < r.y0) r.y0 = ny; if (ny > r.y1) r.y1 = ny;
|
||||
}
|
||||
|
||||
if (r.x0 < -1.0f) r.x0 = -1.0f;
|
||||
if (r.y0 < -1.0f) r.y0 = -1.0f;
|
||||
if (r.x1 > 1.0f) r.x1 = 1.0f;
|
||||
if (r.y1 > 1.0f) r.y1 = 1.0f;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static void compute_edge_leafs(demo3d_loaded_chunk* lc) {
|
||||
const f32 CHUNK_SIZE = 16.0f * 64.0f;
|
||||
const demo3d_bsp* bsp = lc->chunk->bsp;
|
||||
f32 cx0 = lc->cx * CHUNK_SIZE;
|
||||
f32 cz0 = lc->cz * CHUNK_SIZE;
|
||||
f32 cx1 = cx0 + CHUNK_SIZE;
|
||||
f32 cz1 = cz0 + CHUNK_SIZE;
|
||||
|
||||
memset(lc->edges, 0, sizeof(lc->edges));
|
||||
|
||||
for (u32 i = 0; i < bsp->num_leafs; i++) {
|
||||
const demo3d_bsp_leaf* leaf = &bsp->leafs[i];
|
||||
if (bsp->leafs[i].contents == -1) continue;
|
||||
|
||||
if ((f32)leaf->mins[2] <= (f32)((i16)cz0) + 1.0f && lc->edges[0].count < 16)
|
||||
lc->edges[0].leafs[lc->edges[0].count++] = (u16)i;
|
||||
if ((f32)leaf->maxs[2] >= (f32)((i16)cz1) - 1.0f && lc->edges[1].count < 16)
|
||||
lc->edges[1].leafs[lc->edges[1].count++] = (u16)i;
|
||||
if ((f32)leaf->mins[0] <= (f32)((i16)cx0) + 1.0f && lc->edges[2].count < 16)
|
||||
lc->edges[2].leafs[lc->edges[2].count++] = (u16)i;
|
||||
if ((f32)leaf->maxs[0] >= (f32)((i16)cx1) - 1.0f && lc->edges[3].count < 16)
|
||||
lc->edges[3].leafs[lc->edges[3].count++] = (u16)i;
|
||||
}
|
||||
}
|
||||
|
||||
static i32 world_find_chunk(const demo3d_world* world, i32 cx, i32 cz) {
|
||||
for (u32 i = 0; i < world->loaded_count; i++) {
|
||||
if (world->loaded[i].cx == cx && world->loaded[i].cz == cz &&
|
||||
world->loaded[i].chunk && world->loaded[i].chunk->bsp)
|
||||
return (i32)i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void world_mark_leaf_faces(const demo3d_bsp* bsp, demo3d_bsp_render_state* rs, u32 leaf_idx) {
|
||||
const demo3d_bsp_leaf* leaf = &bsp->leafs[leaf_idx];
|
||||
for (u32 i = 0; i < leaf->num_marksurfaces; i++) {
|
||||
u32 si = leaf->first_marksurface + i;
|
||||
if (si < bsp->num_marksurfaces) {
|
||||
u32 fi = bsp->marksurfaces[si];
|
||||
if (fi < bsp->num_faces && rs)
|
||||
rs->render_face_flags[fi] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool vis_try_enqueue(u8** vis, pxl8_rect** windows, world_vis_node* queue,
|
||||
u32* tail, u32 max_queue,
|
||||
u16 ci, u16 li, pxl8_rect nw) {
|
||||
if (!vis[ci] || !windows[ci]) return false;
|
||||
|
||||
u32 byte = li >> 3;
|
||||
u32 bit = 1 << (li & 7);
|
||||
|
||||
if (vis[ci][byte] & bit) {
|
||||
pxl8_rect* ex = &windows[ci][li];
|
||||
bool expanded = false;
|
||||
if (nw.x0 < ex->x0) { ex->x0 = nw.x0; expanded = true; }
|
||||
if (nw.y0 < ex->y0) { ex->y0 = nw.y0; expanded = true; }
|
||||
if (nw.x1 > ex->x1) { ex->x1 = nw.x1; expanded = true; }
|
||||
if (nw.y1 > ex->y1) { ex->y1 = nw.y1; expanded = true; }
|
||||
if (expanded && *tail < max_queue)
|
||||
queue[(*tail)++] = (world_vis_node){ci, li, *ex};
|
||||
return expanded;
|
||||
}
|
||||
|
||||
vis[ci][byte] |= bit;
|
||||
windows[ci][li] = nw;
|
||||
if (*tail < max_queue)
|
||||
queue[(*tail)++] = (world_vis_node){ci, li, nw};
|
||||
return true;
|
||||
}
|
||||
|
||||
static void world_compute_visibility(demo3d_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) {
|
||||
const f32 CHUNK_SIZE = 16.0f * 64.0f;
|
||||
const f32 PORTAL_Y_HI = 192.0f;
|
||||
|
||||
const pxl8_mat4* vp = pxl8_3d_get_view_proj(gfx);
|
||||
|
||||
i32 cam_ci = -1, cam_li = -1;
|
||||
for (u32 i = 0; i < world->loaded_count; i++) {
|
||||
demo3d_loaded_chunk* lc = &world->loaded[i];
|
||||
if (!lc->chunk || !lc->chunk->bsp) continue;
|
||||
const demo3d_bsp* bsp = lc->chunk->bsp;
|
||||
f32 cx0 = lc->cx * CHUNK_SIZE;
|
||||
f32 cz0 = lc->cz * CHUNK_SIZE;
|
||||
if (camera_pos.x < cx0 || camera_pos.x >= cx0 + CHUNK_SIZE ||
|
||||
camera_pos.z < cz0 || camera_pos.z >= cz0 + CHUNK_SIZE) continue;
|
||||
if (bsp->bounds_max_x > bsp->bounds_min_x &&
|
||||
(camera_pos.x < bsp->bounds_min_x || camera_pos.x >= bsp->bounds_max_x ||
|
||||
camera_pos.z < bsp->bounds_min_z || camera_pos.z >= bsp->bounds_max_z))
|
||||
continue;
|
||||
i32 leaf = demo3d_bsp_find_leaf(bsp, camera_pos);
|
||||
if (leaf >= 0 && (u32)leaf < bsp->num_leafs &&
|
||||
bsp->leafs[leaf].contents == -2) {
|
||||
cam_ci = (i32)i;
|
||||
cam_li = leaf;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (cam_ci < 0 || !vp) {
|
||||
for (u32 i = 0; i < world->loaded_count; i++) {
|
||||
demo3d_bsp_render_state* rs = world->loaded[i].render_state;
|
||||
if (!rs) continue;
|
||||
if (rs->render_face_flags)
|
||||
memset(rs->render_face_flags, 1, rs->num_faces);
|
||||
rs->exterior = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
memset(world->vis_bits, 0, sizeof(world->vis_bits));
|
||||
memset(world->vis_ptrs, 0, sizeof(world->vis_ptrs));
|
||||
memset(world->vis_win_ptrs, 0, sizeof(world->vis_win_ptrs));
|
||||
|
||||
u32 voff = 0, woff = 0;
|
||||
for (u32 i = 0; i < world->loaded_count; i++) {
|
||||
if (world->loaded[i].chunk && world->loaded[i].chunk->bsp) {
|
||||
u32 nl = world->loaded[i].chunk->bsp->num_leafs;
|
||||
u32 vbytes = (nl + 7) / 8;
|
||||
if (voff + vbytes > DEMO3D_VIS_BYTES || woff + nl > DEMO3D_VIS_MAX_NODES)
|
||||
continue;
|
||||
world->vis_ptrs[i] = world->vis_bits + voff;
|
||||
voff += vbytes;
|
||||
world->vis_win_ptrs[i] = world->vis_windows + woff;
|
||||
woff += nl;
|
||||
}
|
||||
}
|
||||
|
||||
if (!world->vis_ptrs[cam_ci] || !world->vis_win_ptrs[cam_ci]) return;
|
||||
|
||||
for (u32 i = 0; i < world->loaded_count; i++) {
|
||||
demo3d_bsp_render_state* rs = world->loaded[i].render_state;
|
||||
if (!rs) continue;
|
||||
if (rs->render_face_flags)
|
||||
memset(rs->render_face_flags, 0, rs->num_faces);
|
||||
rs->exterior = false;
|
||||
}
|
||||
|
||||
u32 head = 0, tail = 0;
|
||||
|
||||
pxl8_rect full_screen = {-1.0f, -1.0f, 1.0f, 1.0f};
|
||||
const demo3d_bsp* cam_bsp = world->loaded[cam_ci].chunk->bsp;
|
||||
bool cam_exterior = cam_bsp->leafs[cam_li].contents == 0 &&
|
||||
!(cam_bsp->bounds_max_x > cam_bsp->bounds_min_x);
|
||||
|
||||
world->vis_ptrs[cam_ci][cam_li >> 3] |= (1 << (cam_li & 7));
|
||||
world->vis_win_ptrs[cam_ci][cam_li] = full_screen;
|
||||
world->vis_queue[tail++] = (world_vis_node){(u16)cam_ci, (u16)cam_li, full_screen};
|
||||
|
||||
while (head < tail) {
|
||||
world_vis_node cur = world->vis_queue[head++];
|
||||
demo3d_loaded_chunk* lc = &world->loaded[cur.chunk_idx];
|
||||
const demo3d_bsp* bsp = lc->chunk->bsp;
|
||||
|
||||
world_mark_leaf_faces(bsp, lc->render_state, cur.leaf_idx);
|
||||
|
||||
if (bsp->cell_portals && cur.leaf_idx < bsp->num_cell_portals) {
|
||||
bool is_cam_leaf = (cur.chunk_idx == (u16)cam_ci && cur.leaf_idx == (u16)cam_li);
|
||||
bool is_cam_chunk = (cur.chunk_idx == (u16)cam_ci);
|
||||
const demo3d_bsp_cell_portals* cp = &bsp->cell_portals[cur.leaf_idx];
|
||||
for (u8 pi = 0; pi < cp->num_portals; pi++) {
|
||||
const demo3d_bsp_portal* p = &cp->portals[pi];
|
||||
u32 target = p->target_leaf;
|
||||
if (target >= bsp->num_leafs) continue;
|
||||
if (bsp->leafs[target].contents == -1) continue;
|
||||
if (is_cam_chunk && !demo3d_bsp_is_leaf_visible(bsp, cam_li, (i32)target)) continue;
|
||||
|
||||
if (is_cam_leaf || cam_exterior) {
|
||||
vis_try_enqueue(world->vis_ptrs, world->vis_win_ptrs,
|
||||
world->vis_queue, &tail, DEMO3D_VIS_MAX_QUEUE,
|
||||
cur.chunk_idx, (u16)target, full_screen);
|
||||
continue;
|
||||
}
|
||||
|
||||
pxl8_rect psr = project_portal(p->x0, p->z0, p->x1, p->z1,
|
||||
0.0f, PORTAL_Y_HI, vp);
|
||||
if (!vr_valid(psr)) continue;
|
||||
pxl8_rect nw = vr_intersect(cur.window, psr);
|
||||
if (!vr_valid(nw)) continue;
|
||||
|
||||
vis_try_enqueue(world->vis_ptrs, world->vis_win_ptrs,
|
||||
world->vis_queue, &tail, DEMO3D_VIS_MAX_QUEUE,
|
||||
cur.chunk_idx, (u16)target, nw);
|
||||
}
|
||||
}
|
||||
|
||||
const demo3d_bsp_leaf* leaf = &bsp->leafs[cur.leaf_idx];
|
||||
f32 chunk_x0 = lc->cx * CHUNK_SIZE;
|
||||
f32 chunk_z0 = lc->cz * CHUNK_SIZE;
|
||||
f32 chunk_x1 = chunk_x0 + CHUNK_SIZE;
|
||||
f32 chunk_z1 = chunk_z0 + CHUNK_SIZE;
|
||||
|
||||
struct { i32 dx, dz; bool at_edge; f32 bnd; } dirs[4] = {
|
||||
{ 0, -1, (f32)leaf->mins[2] <= (f32)((i16)chunk_z0) + 1.0f, chunk_z0 },
|
||||
{ 0, 1, (f32)leaf->maxs[2] >= (f32)((i16)chunk_z1) - 1.0f, chunk_z1 },
|
||||
{-1, 0, (f32)leaf->mins[0] <= (f32)((i16)chunk_x0) + 1.0f, chunk_x0 },
|
||||
{ 1, 0, (f32)leaf->maxs[0] >= (f32)((i16)chunk_x1) - 1.0f, chunk_x1 },
|
||||
};
|
||||
|
||||
static const u8 opposite_edge[4] = {1, 0, 3, 2};
|
||||
|
||||
for (u32 d = 0; d < 4; d++) {
|
||||
if (!dirs[d].at_edge) continue;
|
||||
i32 nci = world_find_chunk(world, lc->cx + dirs[d].dx, lc->cz + dirs[d].dz);
|
||||
if (nci < 0) continue;
|
||||
|
||||
const demo3d_bsp* nbsp = world->loaded[nci].chunk->bsp;
|
||||
const demo3d_edge_leafs* nedge = &world->loaded[nci].edges[opposite_edge[d]];
|
||||
|
||||
for (u8 k = 0; k < nedge->count; k++) {
|
||||
u16 nl = nedge->leafs[k];
|
||||
const demo3d_bsp_leaf* nleaf =  ->leafs[nl];
|
||||
|
||||
bool overlaps = false;
|
||||
if (dirs[d].dx != 0) {
|
||||
overlaps = leaf->maxs[2] > nleaf->mins[2] && leaf->mins[2] < nleaf->maxs[2];
|
||||
} else {
|
||||
overlaps = leaf->maxs[0] > nleaf->mins[0] && leaf->mins[0] < nleaf->maxs[0];
|
||||
}
|
||||
if (!overlaps) continue;
|
||||
|
||||
f32 bpx0, bpz0, bpx1, bpz1;
|
||||
if (dirs[d].dx != 0) {
|
||||
bpx0 = dirs[d].bnd; bpx1 = dirs[d].bnd;
|
||||
i16 zlo = leaf->mins[2] > nleaf->mins[2] ? leaf->mins[2] : nleaf->mins[2];
|
||||
i16 zhi = leaf->maxs[2] < nleaf->maxs[2] ? leaf->maxs[2] : nleaf->maxs[2];
|
||||
bpz0 = (f32)zlo; bpz1 = (f32)zhi;
|
||||
} else {
|
||||
bpz0 = dirs[d].bnd; bpz1 = dirs[d].bnd;
|
||||
i16 xlo = leaf->mins[0] > nleaf->mins[0] ? leaf->mins[0] : nleaf->mins[0];
|
||||
i16 xhi = leaf->maxs[0] < nleaf->maxs[0] ? leaf->maxs[0] : nleaf->maxs[0];
|
||||
bpx0 = (f32)xlo; bpx1 = (f32)xhi;
|
||||
}
|
||||
|
||||
if (cam_exterior) {
|
||||
vis_try_enqueue(world->vis_ptrs, world->vis_win_ptrs,
|
||||
world->vis_queue, &tail, DEMO3D_VIS_MAX_QUEUE,
|
||||
(u16)nci, (u16)nl, full_screen);
|
||||
continue;
|
||||
}
|
||||
|
||||
pxl8_rect psr = project_portal(bpx0, bpz0, bpx1, bpz1,
|
||||
0.0f, PORTAL_Y_HI, vp);
|
||||
if (!vr_valid(psr)) continue;
|
||||
pxl8_rect nw = vr_intersect(cur.window, psr);
|
||||
if (!vr_valid(nw)) continue;
|
||||
|
||||
vis_try_enqueue(world->vis_ptrs, world->vis_win_ptrs,
|
||||
world->vis_queue, &tail, DEMO3D_VIS_MAX_QUEUE,
|
||||
(u16)nci, (u16)nl, nw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void demo3d_world_render(demo3d_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) {
|
||||
if (!world || !gfx) return;
|
||||
|
||||
world_compute_visibility(world, gfx, camera_pos);
|
||||
|
||||
for (u32 i = 0; i < world->loaded_count; i++) {
|
||||
demo3d_loaded_chunk* lc = &world->loaded[i];
|
||||
if (!lc->chunk || !lc->chunk->bsp || !lc->render_state) continue;
|
||||
demo3d_bsp_render(gfx, lc->chunk->bsp, lc->render_state, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void apply_shared_materials(demo3d_world* world, demo3d_bsp_render_state* rs) {
|
||||
for (u16 i = 0; i < 16; i++) {
|
||||
if (world->shared_material_set[i]) {
|
||||
demo3d_bsp_set_material(rs, i, &world->shared_materials[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void demo3d_world_sync(demo3d_world* world, demo3d_net* net) {
|
||||
if (!world || !net) return;
|
||||
|
||||
if (!demo3d_net_has_chunk(net)) {
|
||||
u32 old_count = world->loaded_count;
|
||||
world->loaded_count = 0;
|
||||
world->active_chunk = NULL;
|
||||
world->active_render_state = NULL;
|
||||
for (u32 i = 0; i < old_count; i++) {
|
||||
demo3d_bsp_render_state_destroy(world->loaded[i].render_state);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
i32 center_cx = demo3d_net_chunk_cx(net);
|
||||
i32 center_cz = demo3d_net_chunk_cz(net);
|
||||
|
||||
demo3d_loaded_chunk old_loaded[DEMO3D_WORLD_MAX_LOADED_CHUNKS];
|
||||
u32 old_count = world->loaded_count;
|
||||
memcpy(old_loaded, world->loaded, sizeof(old_loaded));
|
||||
|
||||
demo3d_loaded_chunk new_loaded[DEMO3D_WORLD_MAX_LOADED_CHUNKS];
|
||||
u32 new_count = 0;
|
||||
demo3d_chunk* new_active_chunk = NULL;
|
||||
demo3d_bsp_render_state* new_active_rs = NULL;
|
||||
|
||||
for (i32 dz = -2; dz <= 2; dz++) {
|
||||
for (i32 dx = -2; dx <= 2; dx++) {
|
||||
i32 cx = center_cx + dx;
|
||||
i32 cz = center_cz + dz;
|
||||
u32 id = demo3d_chunk_hash(cx, cz);
|
||||
|
||||
demo3d_chunk* chunk = demo3d_chunk_cache_get_bsp(world->chunk_cache, id);
|
||||
if (!chunk || !chunk->bsp) {
|
||||
for (u32 j = 0; j < old_count; j++) {
|
||||
if (old_loaded[j].active && old_loaded[j].cx == cx && old_loaded[j].cz == cz) {
|
||||
u32 idx = new_count++;
|
||||
new_loaded[idx] = old_loaded[j];
|
||||
old_loaded[j].active = false;
|
||||
if (dx == 0 && dz == 0) {
|
||||
new_active_chunk = new_loaded[idx].chunk;
|
||||
new_active_rs = new_loaded[idx].render_state;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
demo3d_bsp_render_state* rs = NULL;
|
||||
for (u32 j = 0; j < old_count; j++) {
|
||||
if (old_loaded[j].active && old_loaded[j].cx == cx && old_loaded[j].cz == cz &&
|
||||
old_loaded[j].chunk == chunk) {
|
||||
rs = old_loaded[j].render_state;
|
||||
old_loaded[j].active = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!rs) {
|
||||
rs = demo3d_bsp_render_state_create(chunk->bsp->num_faces);
|
||||
if (rs && rs->render_face_flags)
|
||||
memset(rs->render_face_flags, 1, rs->num_faces);
|
||||
apply_shared_materials(world, rs);
|
||||
}
|
||||
|
||||
u32 idx = new_count++;
|
||||
new_loaded[idx] = (demo3d_loaded_chunk){
|
||||
.chunk = chunk,
|
||||
.render_state = rs,
|
||||
.cx = cx,
|
||||
.cz = cz,
|
||||
.active = true,
|
||||
};
|
||||
compute_edge_leafs(&new_loaded[idx]);
|
||||
|
||||
if (dx == 0 && dz == 0) {
|
||||
new_active_chunk = chunk;
|
||||
new_active_rs = rs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (u32 j = 0; j < old_count; j++) {
|
||||
if (!old_loaded[j].active) continue;
|
||||
if (new_count < DEMO3D_WORLD_MAX_LOADED_CHUNKS &&
|
||||
old_loaded[j].chunk && old_loaded[j].chunk->bsp &&
|
||||
demo3d_chunk_cache_get_bsp(world->chunk_cache, old_loaded[j].chunk->id)) {
|
||||
new_loaded[new_count++] = old_loaded[j];
|
||||
} else {
|
||||
demo3d_bsp_render_state_destroy(old_loaded[j].render_state);
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(world->loaded, new_loaded, sizeof(new_loaded));
|
||||
world->active_chunk = new_active_chunk;
|
||||
world->active_render_state = new_active_rs;
|
||||
world->loaded_count = new_count;
|
||||
}
|
||||
|
||||
void demo3d_world_set_bsp_material(demo3d_world* world, u16 material_id, const pxl8_gfx_material* material) {
|
||||
if (!world || !material || material_id >= 16) return;
|
||||
|
||||
world->shared_materials[material_id] = *material;
|
||||
world->shared_material_set[material_id] = true;
|
||||
|
||||
for (u32 i = 0; i < world->loaded_count; i++) {
|
||||
if (world->loaded[i].render_state) {
|
||||
demo3d_bsp_set_material(world->loaded[i].render_state, material_id, material);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void demo3d_world_set_sim_config(demo3d_world* world, const demo3d_sim_config* config) {
|
||||
if (!world || !config) return;
|
||||
world->sim_config = *config;
|
||||
}
|
||||
|
||||
void demo3d_world_init_local_player(demo3d_world* world, f32 x, f32 y, f32 z) {
|
||||
if (!world) return;
|
||||
world->local_player.pos = (pxl8_vec3){x, y, z};
|
||||
world->local_player.vel = (pxl8_vec3){0, 0, 0};
|
||||
world->local_player.yaw = 0;
|
||||
world->local_player.pitch = 0;
|
||||
world->local_player.flags = DEMO3D_SIM_FLAG_ALIVE | DEMO3D_SIM_FLAG_PLAYER | DEMO3D_SIM_FLAG_GROUNDED;
|
||||
world->local_player.kind = 0;
|
||||
world->client_tick = 0;
|
||||
world->pointer_motion = (pxl8_vec2){0};
|
||||
|
||||
#ifdef PXL8_ASYNC_THREADS
|
||||
world->render_state[0] = world->local_player;
|
||||
world->render_state[1] = world->local_player;
|
||||
#endif
|
||||
}
|
||||
|
||||
void demo3d_world_set_look(demo3d_world* world, f32 yaw, f32 pitch) {
|
||||
if (!world) return;
|
||||
world->local_player.yaw = yaw;
|
||||
world->local_player.pitch = pitch;
|
||||
world->pointer_motion = (pxl8_vec2){.yaw = yaw, .pitch = pitch};
|
||||
|
||||
#ifdef PXL8_ASYNC_THREADS
|
||||
world->render_state[0].yaw = yaw;
|
||||
world->render_state[0].pitch = pitch;
|
||||
world->render_state[1].yaw = yaw;
|
||||
world->render_state[1].pitch = pitch;
|
||||
#endif
|
||||
}
|
||||
|
||||
demo3d_sim_entity* demo3d_world_local_player(demo3d_world* world) {
|
||||
if (!world) return NULL;
|
||||
#ifdef PXL8_ASYNC_THREADS
|
||||
const demo3d_sim_entity* state = demo3d_world_get_render_state(world);
|
||||
if (!state) return NULL;
|
||||
if (!(state->flags & DEMO3D_SIM_FLAG_ALIVE)) return NULL;
|
||||
return (demo3d_sim_entity*)state;
|
||||
#else
|
||||
if (!(world->local_player.flags & DEMO3D_SIM_FLAG_ALIVE)) return NULL;
|
||||
return &world->local_player;
|
||||
#endif
|
||||
}
|
||||
|
||||
void demo3d_world_predict(demo3d_world* world, demo3d_net* net, const demo3d_input_msg* input, f32 dt) {
|
||||
if (!world || !net || !input) return;
|
||||
if (!(world->local_player.flags & DEMO3D_SIM_FLAG_ALIVE)) return;
|
||||
|
||||
demo3d_sim_world sim = demo3d_world_sim_world(world, world->local_player.pos);
|
||||
demo3d_sim_move_player(&world->local_player, input, &sim, &world->sim_config, dt);
|
||||
|
||||
world->client_tick++;
|
||||
|
||||
entity_to_userdata(&world->local_player, demo3d_net_predicted_state(net));
|
||||
demo3d_net_predicted_tick_set(net, world->client_tick);
|
||||
}
|
||||
|
||||
void demo3d_world_reconcile(demo3d_world* world, demo3d_net* net, f32 dt) {
|
||||
if (!world || !net) return;
|
||||
if (!(world->local_player.flags & DEMO3D_SIM_FLAG_ALIVE)) return;
|
||||
if (!demo3d_net_needs_correction(net)) return;
|
||||
|
||||
u64 player_id = demo3d_net_player_id(net);
|
||||
const u8* server_state = demo3d_net_entity_userdata(net, player_id);
|
||||
if (!server_state) return;
|
||||
|
||||
demo3d_sim_entity server_player = {0};
|
||||
userdata_to_entity(server_state, &server_player);
|
||||
|
||||
if (!(server_player.flags & DEMO3D_SIM_FLAG_ALIVE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
world->local_player = server_player;
|
||||
|
||||
const demo3d_snapshot_header* snap = demo3d_net_snapshot(net);
|
||||
u64 server_tick = snap ? snap->tick : 0;
|
||||
|
||||
for (u64 tick = server_tick + 1; tick <= world->client_tick; tick++) {
|
||||
const demo3d_input_msg* input = demo3d_net_input_at(net, tick);
|
||||
if (!input) continue;
|
||||
|
||||
demo3d_sim_world sim = demo3d_world_sim_world(world, world->local_player.pos);
|
||||
demo3d_sim_move_player(&world->local_player, input, &sim, &world->sim_config, dt);
|
||||
}
|
||||
|
||||
entity_to_userdata(&world->local_player, demo3d_net_predicted_state(net));
|
||||
}
|
||||
|
||||
#ifdef PXL8_ASYNC_THREADS
|
||||
|
||||
#define SIM_TIMESTEP (1.0f / 60.0f)
|
||||
|
||||
static void demo3d_world_sim_tick(demo3d_world* world, f32 dt) {
|
||||
bool alive = (world->local_player.flags & DEMO3D_SIM_FLAG_ALIVE) != 0;
|
||||
demo3d_input_msg merged = {0};
|
||||
demo3d_input_msg* input = NULL;
|
||||
|
||||
while ((input = pxl8_queue_pop(&world->input_queue))) {
|
||||
merged.look_dx += input->look_dx;
|
||||
merged.look_dy += input->look_dy;
|
||||
merged.move_x = input->move_x;
|
||||
merged.move_y = input->move_y;
|
||||
merged.buttons |= input->buttons;
|
||||
pxl8_free(input);
|
||||
}
|
||||
|
||||
if (!alive) {
|
||||
return;
|
||||
}
|
||||
|
||||
const f32 MAX_LOOK_DELTA = 100.0f;
|
||||
if (merged.look_dx > MAX_LOOK_DELTA) merged.look_dx = MAX_LOOK_DELTA;
|
||||
if (merged.look_dx < -MAX_LOOK_DELTA) merged.look_dx = -MAX_LOOK_DELTA;
|
||||
if (merged.look_dy > MAX_LOOK_DELTA) merged.look_dy = MAX_LOOK_DELTA;
|
||||
if (merged.look_dy < -MAX_LOOK_DELTA) merged.look_dy = -MAX_LOOK_DELTA;
|
||||
|
||||
merged.tick = world->client_tick;
|
||||
merged.timestamp = pxl8_get_ticks_ns();
|
||||
merged.yaw = world->pointer_motion.yaw;
|
||||
if (world->net) {
|
||||
demo3d_net_send_input(world->net, &merged);
|
||||
}
|
||||
|
||||
world->local_player.yaw = world->pointer_motion.yaw;
|
||||
world->local_player.pitch = world->pointer_motion.pitch;
|
||||
merged.look_dx = 0;
|
||||
merged.look_dy = 0;
|
||||
|
||||
demo3d_sim_world sim = demo3d_world_sim_world(world, world->local_player.pos);
|
||||
demo3d_sim_move_player(&world->local_player, &merged, &sim, &world->sim_config, dt);
|
||||
|
||||
if (world->net) {
|
||||
entity_to_userdata(&world->local_player, demo3d_net_predicted_state(world->net));
|
||||
demo3d_net_predicted_tick_set(world->net, world->client_tick);
|
||||
}
|
||||
|
||||
world->client_tick++;
|
||||
}
|
||||
|
||||
static void demo3d_world_swap_buffers(demo3d_world* world) {
|
||||
u32 back = atomic_load(&world->active_buffer) ^ 1;
|
||||
world->render_state[back] = world->local_player;
|
||||
atomic_store(&world->active_buffer, back);
|
||||
}
|
||||
|
||||
static int demo3d_world_sim_thread(void* data) {
|
||||
demo3d_world* world = (demo3d_world*)data;
|
||||
u64 last_time = pxl8_get_ticks_ns();
|
||||
|
||||
while (atomic_load(&world->sim_running)) {
|
||||
if (atomic_load(&world->sim_paused)) {
|
||||
last_time = pxl8_get_ticks_ns();
|
||||
pxl8_sleep_ms(1);
|
||||
continue;
|
||||
}
|
||||
|
||||
u64 now = pxl8_get_ticks_ns();
|
||||
f32 dt = (f32)(now - last_time) / 1e9f;
|
||||
last_time = now;
|
||||
|
||||
if (dt > 0.1f) dt = 0.1f;
|
||||
if (dt < 0.0001f) dt = 0.0001f;
|
||||
|
||||
world->sim_accumulator += dt;
|
||||
|
||||
while (world->sim_accumulator >= SIM_TIMESTEP) {
|
||||
demo3d_chunk_cache_tick(world->chunk_cache);
|
||||
|
||||
if (world->net) {
|
||||
pxl8_packet* pkt;
|
||||
while ((pkt = pxl8_net_pop_packet(world->net->transport))) {
|
||||
demo3d_net_dispatch_packet(world->net, pkt);
|
||||
pxl8_net_packet_free(pkt);
|
||||
}
|
||||
|
||||
demo3d_world_sync(world, world->net);
|
||||
demo3d_world_reconcile(world, world->net, SIM_TIMESTEP);
|
||||
}
|
||||
|
||||
demo3d_world_sim_tick(world, SIM_TIMESTEP);
|
||||
world->sim_accumulator -= SIM_TIMESTEP;
|
||||
}
|
||||
|
||||
demo3d_world_swap_buffers(world);
|
||||
pxl8_sleep_ms(1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void demo3d_world_start_sim_thread(demo3d_world* world, demo3d_net* net) {
|
||||
if (!world || world->sim_thread) return;
|
||||
|
||||
world->net = net;
|
||||
pxl8_queue_init(&world->input_queue);
|
||||
atomic_store(&world->active_buffer, 0);
|
||||
atomic_store(&world->sim_running, true);
|
||||
world->sim_accumulator = 0.0f;
|
||||
|
||||
world->render_state[0] = world->local_player;
|
||||
world->render_state[1] = world->local_player;
|
||||
|
||||
world->sim_thread = pxl8_thread_create(demo3d_world_sim_thread, "pxl8_sim", world);
|
||||
}
|
||||
|
||||
void demo3d_world_stop_sim_thread(demo3d_world* world) {
|
||||
if (!world || !world->sim_thread) return;
|
||||
|
||||
atomic_store(&world->sim_running, false);
|
||||
pxl8_thread_wait(world->sim_thread, NULL);
|
||||
world->sim_thread = NULL;
|
||||
|
||||
demo3d_input_msg* input;
|
||||
while ((input = pxl8_queue_pop(&world->input_queue))) {
|
||||
pxl8_free(input);
|
||||
}
|
||||
}
|
||||
|
||||
void demo3d_world_pause_sim(demo3d_world* world, bool paused) {
|
||||
if (!world) return;
|
||||
|
||||
if (paused) {
|
||||
atomic_store(&world->sim_paused, true);
|
||||
} else {
|
||||
demo3d_input_msg* input;
|
||||
while ((input = pxl8_queue_pop(&world->input_queue))) {
|
||||
pxl8_free(input);
|
||||
}
|
||||
world->sim_accumulator = 0.0f;
|
||||
atomic_store(&world->sim_paused, false);
|
||||
}
|
||||
}
|
||||
|
||||
void demo3d_world_push_input(demo3d_world* world, const demo3d_input_msg* input) {
|
||||
if (!world || !input) return;
|
||||
|
||||
world->pointer_motion.yaw -= input->look_dx * 0.008f;
|
||||
f32 pitch = world->pointer_motion.pitch - input->look_dy * 0.008f;
|
||||
if (pitch > world->sim_config.max_pitch) pitch = world->sim_config.max_pitch;
|
||||
if (pitch < -world->sim_config.max_pitch) pitch = -world->sim_config.max_pitch;
|
||||
world->pointer_motion.pitch = pitch;
|
||||
|
||||
demo3d_input_msg* copy = pxl8_malloc(sizeof(demo3d_input_msg));
|
||||
if (copy) {
|
||||
*copy = *input;
|
||||
if (!pxl8_queue_push(&world->input_queue, copy)) {
|
||||
pxl8_free(copy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const demo3d_sim_entity* demo3d_world_get_render_state(const demo3d_world* world) {
|
||||
if (!world) return NULL;
|
||||
u32 front = atomic_load(&((demo3d_world*)world)->active_buffer);
|
||||
return &world->render_state[front];
|
||||
}
|
||||
|
||||
f32 demo3d_world_get_interp_alpha(const demo3d_world* world) {
|
||||
if (!world) return 1.0f;
|
||||
return world->sim_accumulator / SIM_TIMESTEP;
|
||||
}
|
||||
|
||||
#endif
|
||||
71
demo3d/client/world/demo3d_world.h
Normal file
71
demo3d/client/world/demo3d_world.h
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
#pragma once
|
||||
|
||||
#include "demo3d_entity.h"
|
||||
#include "pxl8_gfx.h"
|
||||
#include "pxl8_gfx3d.h"
|
||||
#include "pxl8_math.h"
|
||||
#include "demo3d_net.h"
|
||||
#include "demo3d_sim.h"
|
||||
#include "pxl8_types.h"
|
||||
#include "demo3d_chunk.h"
|
||||
#include "demo3d_chunk_cache.h"
|
||||
#include "demo3d_bsp_render.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define DEMO3D_WORLD_MAX_LOADED_CHUNKS 64
|
||||
|
||||
typedef struct {
|
||||
u16 leafs[16];
|
||||
u8 count;
|
||||
} demo3d_edge_leafs;
|
||||
|
||||
typedef struct demo3d_loaded_chunk {
|
||||
demo3d_chunk* chunk;
|
||||
demo3d_bsp_render_state* render_state;
|
||||
demo3d_edge_leafs edges[4];
|
||||
i32 cx;
|
||||
i32 cz;
|
||||
bool active;
|
||||
} demo3d_loaded_chunk;
|
||||
|
||||
typedef struct demo3d_world demo3d_world;
|
||||
|
||||
demo3d_world* demo3d_world_create(void);
|
||||
void demo3d_world_destroy(demo3d_world* world);
|
||||
|
||||
demo3d_chunk_cache* demo3d_world_get_chunk_cache(demo3d_world* world);
|
||||
demo3d_chunk* demo3d_world_active_chunk(demo3d_world* world);
|
||||
|
||||
bool demo3d_world_point_solid(const demo3d_world* world, f32 x, f32 y, f32 z);
|
||||
pxl8_ray demo3d_world_ray(const demo3d_world* world, pxl8_vec3 from, pxl8_vec3 to);
|
||||
pxl8_ray demo3d_world_sweep(const demo3d_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius);
|
||||
|
||||
void demo3d_world_update(demo3d_world* world, f32 dt);
|
||||
void demo3d_world_render(demo3d_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos);
|
||||
void demo3d_world_sync(demo3d_world* world, demo3d_net* net);
|
||||
|
||||
void demo3d_world_set_bsp_material(demo3d_world* world, u16 material_id, const pxl8_gfx_material* material);
|
||||
|
||||
void demo3d_world_set_sim_config(demo3d_world* world, const demo3d_sim_config* config);
|
||||
void demo3d_world_init_local_player(demo3d_world* world, f32 x, f32 y, f32 z);
|
||||
void demo3d_world_set_look(demo3d_world* world, f32 yaw, f32 pitch);
|
||||
demo3d_sim_entity* demo3d_world_local_player(demo3d_world* world);
|
||||
demo3d_sim_world demo3d_world_sim_world(const demo3d_world* world, pxl8_vec3 pos);
|
||||
void demo3d_world_predict(demo3d_world* world, demo3d_net* net, const demo3d_input_msg* input, f32 dt);
|
||||
void demo3d_world_reconcile(demo3d_world* world, demo3d_net* net, f32 dt);
|
||||
|
||||
#ifdef PXL8_ASYNC_THREADS
|
||||
void demo3d_world_start_sim_thread(demo3d_world* world, demo3d_net* net);
|
||||
void demo3d_world_stop_sim_thread(demo3d_world* world);
|
||||
void demo3d_world_pause_sim(demo3d_world* world, bool paused);
|
||||
void demo3d_world_push_input(demo3d_world* world, const demo3d_input_msg* input);
|
||||
const demo3d_sim_entity* demo3d_world_get_render_state(const demo3d_world* world);
|
||||
f32 demo3d_world_get_interp_alpha(const demo3d_world* world);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
293
demo3d/main.fnl
Normal file
293
demo3d/main.fnl
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
(local pxl8 (require :pxl8))
|
||||
(local bit (require :bit))
|
||||
(local ffi (require :ffi))
|
||||
(local C ffi.C)
|
||||
(local core (require :pxl8.core))
|
||||
(local effects (require :pxl8.effects))
|
||||
(local game-world (require :mod.world))
|
||||
|
||||
(local entities (require :mod.entities))
|
||||
(local menu (require :mod.menu))
|
||||
(local sky (require :mod.sky))
|
||||
(local textures (require :mod.textures))
|
||||
|
||||
(local bob-amount 3.0)
|
||||
(local bob-speed 6.0)
|
||||
(local cam-smoothing 0.25)
|
||||
(local land-recovery-speed 20)
|
||||
(local land-squash-amount -4)
|
||||
(local player-eye-height 64)
|
||||
(local day-length 1800)
|
||||
|
||||
(local SIM_FLAG_GROUNDED 4)
|
||||
|
||||
(local sim-cfg (game-world.sim_config {:move_speed 150
|
||||
:gravity 600
|
||||
:jump_velocity 180
|
||||
:player_radius 12
|
||||
:player_height 72
|
||||
:max_pitch 1.5
|
||||
:friction 6.0
|
||||
:ground_accel 10.0
|
||||
:air_accel 1.0
|
||||
:stop_speed 100.0}))
|
||||
|
||||
(var auto-run-cancel-key nil)
|
||||
(var auto-run? false)
|
||||
(var bob-time 0)
|
||||
(var cam-pitch 0)
|
||||
(var day-time 0)
|
||||
(var cam-x 416)
|
||||
(var cam-y 0)
|
||||
(var cam-yaw 0)
|
||||
(var cam-z 416)
|
||||
(var camera nil)
|
||||
(var land-squash 0)
|
||||
(var last-dt 0.016)
|
||||
(var light-time 0)
|
||||
(var glows nil)
|
||||
(var lights nil)
|
||||
(var materials-set? false)
|
||||
(var network nil)
|
||||
(var real-time 0)
|
||||
(var smooth-cam-x 416)
|
||||
(var smooth-cam-z 416)
|
||||
(var was-grounded true)
|
||||
(var world nil)
|
||||
|
||||
(local MOSS_COLOR 200)
|
||||
(local GRASS_COLOR 200)
|
||||
(local STONE_WALL_START 2)
|
||||
(local WOOD_COLOR 88)
|
||||
|
||||
(local ASHLAR_COLOR 64)
|
||||
(local ASHLAR_MOSS 68)
|
||||
(local STONE_FLOOR_COLOR 72)
|
||||
(local STONE_TRIM_COLOR 72)
|
||||
|
||||
(fn preload []
|
||||
(when (not network)
|
||||
(let [net (C.demo3d_get_net core.sys)]
|
||||
(when (and (not= net nil) (C.demo3d_net_connected net))
|
||||
(set network net)
|
||||
(C.demo3d_net_spawn net cam-x cam-y cam-z cam-yaw cam-pitch))))
|
||||
(when (not world)
|
||||
(set world (game-world.World.get))
|
||||
(when world
|
||||
(world:set_sim_config sim-cfg))))
|
||||
|
||||
(fn is-ready []
|
||||
(if (not world)
|
||||
false
|
||||
(let [chunk (world:active_chunk)]
|
||||
(and chunk (chunk:ready)))))
|
||||
|
||||
(fn game-init []
|
||||
(pxl8.set_relative_mouse_mode true)
|
||||
(pxl8.load_palette "res/palettes/palette.ase")
|
||||
(for [i 0 7]
|
||||
(let [t (/ i 7)
|
||||
r 0xFF
|
||||
g (math.floor (+ 0x60 (* t (- 0xE0 0x60))))
|
||||
b (math.floor (+ 0x10 (* t (- 0x80 0x10))))]
|
||||
(pxl8.set_palette_rgb (+ entities.FIREBALL_COLOR i) r g b)))
|
||||
(pxl8.set_palette_rgb (- entities.FIREBALL_COLOR 1) 0xFF 0x20 0x10)
|
||||
(sky.reset-gradient)
|
||||
(sky.update-gradient 1 2 6 6 10 18)
|
||||
(pxl8.update_palette_deps)
|
||||
|
||||
(when (not camera)
|
||||
(set camera (pxl8.create_camera_3d)))
|
||||
(when (not glows)
|
||||
(set glows (pxl8.create_glows)))
|
||||
(when (not lights)
|
||||
(set lights (pxl8.create_lights)))
|
||||
|
||||
(menu.init)
|
||||
(entities.init textures)
|
||||
(sky.generate-stars)
|
||||
(preload)
|
||||
|
||||
(when world
|
||||
(world:init_local_player cam-x cam-y cam-z)
|
||||
(set smooth-cam-x cam-x)
|
||||
(set smooth-cam-z cam-z)))
|
||||
|
||||
(fn setup-materials []
|
||||
(when (not world) (lua "return"))
|
||||
(when materials-set? (lua "return"))
|
||||
(let [chunk (world:active_chunk)]
|
||||
(when (not chunk) (lua "return"))
|
||||
(when (not (chunk:ready)) (lua "return"))
|
||||
(let [dungeon-floor-tex (textures.wood-planks 44444 WOOD_COLOR)
|
||||
dungeon-wall-tex (textures.cobble-timber 55555 STONE_WALL_START MOSS_COLOR WOOD_COLOR)
|
||||
dungeon-trim-tex (textures.wood-trim 77777 WOOD_COLOR)
|
||||
court-floor-tex (textures.rough-stone 44442 STONE_FLOOR_COLOR)
|
||||
court-wall-tex (textures.ashlar-wall 55552 ASHLAR_COLOR ASHLAR_MOSS)
|
||||
court-trim-tex (textures.rough-stone 77772 STONE_TRIM_COLOR)
|
||||
grass-tex (textures.grass-top 44447 GRASS_COLOR)
|
||||
dungeon-floor-mat (pxl8.create_material {:texture dungeon-floor-tex :lighting true :double_sided true})
|
||||
dungeon-wall-mat (pxl8.create_material {:texture dungeon-wall-tex :lighting true :double_sided true})
|
||||
dungeon-trim-mat (pxl8.create_material {:texture dungeon-trim-tex :lighting true :double_sided true})
|
||||
court-floor-mat (pxl8.create_material {:texture court-floor-tex :lighting true :double_sided true})
|
||||
court-wall-mat (pxl8.create_material {:texture court-wall-tex :lighting true :double_sided true})
|
||||
court-trim-mat (pxl8.create_material {:texture court-trim-tex :lighting true :double_sided true})
|
||||
grass-mat (pxl8.create_material {:texture grass-tex :lighting true :double_sided true})]
|
||||
(world:set_bsp_material 0 dungeon-floor-mat)
|
||||
(world:set_bsp_material 1 dungeon-wall-mat)
|
||||
(world:set_bsp_material 3 dungeon-trim-mat)
|
||||
(world:set_bsp_material 4 court-floor-mat)
|
||||
(world:set_bsp_material 5 court-wall-mat)
|
||||
(world:set_bsp_material 6 court-trim-mat)
|
||||
(world:set_bsp_material 7 grass-mat)
|
||||
(set materials-set? true))))
|
||||
|
||||
(fn sample-input []
|
||||
(when (pxl8.key_pressed "`")
|
||||
(set auto-run? (not auto-run?))
|
||||
(when (and auto-run? (pxl8.key_down "w"))
|
||||
(set auto-run-cancel-key "w")))
|
||||
(when (and auto-run? (not auto-run-cancel-key) (or (pxl8.key_down "w") (pxl8.key_down "s")))
|
||||
(set auto-run? false)
|
||||
(when (pxl8.key_down "s")
|
||||
(set auto-run-cancel-key "s")))
|
||||
(when (and auto-run-cancel-key (not (pxl8.key_down auto-run-cancel-key)))
|
||||
(set auto-run-cancel-key nil))
|
||||
|
||||
(game-world.make_input_msg
|
||||
{:move_x (+ (if (pxl8.key_down "d") 1 0) (if (pxl8.key_down "a") -1 0))
|
||||
:move_y (+ (if (or (pxl8.key_down "w") auto-run?) 1 0)
|
||||
(if (and (pxl8.key_down "s") (not= auto-run-cancel-key "s")) -1 0))
|
||||
:look_dx (pxl8.mouse_dx)
|
||||
:look_dy (pxl8.mouse_dy)
|
||||
:buttons (if (pxl8.key_down "space") 1 0)}))
|
||||
|
||||
(global update (fn [dt]
|
||||
(set last-dt dt)
|
||||
|
||||
(when (pxl8.key_pressed "escape")
|
||||
(menu.toggle))
|
||||
|
||||
(when (menu.is-paused)
|
||||
(menu.update)
|
||||
(lua "return"))
|
||||
|
||||
(setup-materials)
|
||||
|
||||
(when world
|
||||
(let [input-msg (sample-input)
|
||||
player (world:local_player)]
|
||||
|
||||
(world:push_input input-msg)
|
||||
|
||||
(when player
|
||||
(set cam-x player.pos.x)
|
||||
(set cam-y player.pos.y)
|
||||
(set cam-z player.pos.z)
|
||||
(set cam-yaw player.yaw)
|
||||
(set cam-pitch player.pitch))
|
||||
|
||||
(let [now-grounded (if player (not= (bit.band player.flags SIM_FLAG_GROUNDED) 0) true)]
|
||||
(when (and (not was-grounded) now-grounded)
|
||||
(set land-squash land-squash-amount))
|
||||
(set was-grounded now-grounded))
|
||||
|
||||
(set smooth-cam-x (+ (* smooth-cam-x (- 1 cam-smoothing)) (* cam-x cam-smoothing)))
|
||||
(set smooth-cam-z (+ (* smooth-cam-z (- 1 cam-smoothing)) (* cam-z cam-smoothing)))
|
||||
|
||||
(when (< land-squash 0)
|
||||
(set land-squash (math.min 0 (+ land-squash (* land-recovery-speed dt)))))
|
||||
|
||||
(let [moving (or (not= input-msg.move_x 0) (not= input-msg.move_y 0))]
|
||||
(if (and moving was-grounded)
|
||||
(set bob-time (+ bob-time (* dt bob-speed)))
|
||||
(let [target-phase (* (math.floor (/ bob-time math.pi)) math.pi)]
|
||||
(set bob-time (+ (* bob-time 0.8) (* target-phase 0.2))))))
|
||||
|
||||
(set day-time (% (+ day-time (/ dt day-length)) 1))
|
||||
(set light-time (+ light-time (* dt 0.5)))
|
||||
(set real-time (+ real-time dt))))))
|
||||
|
||||
(global frame (fn []
|
||||
(pxl8.clear 1)
|
||||
(preload)
|
||||
|
||||
(when (or (not camera) (not world))
|
||||
(pxl8.text "Loading..." 280 180 1)
|
||||
(lua "return"))
|
||||
|
||||
(let [chunk (when world (world:active_chunk))
|
||||
ready (and chunk (chunk:ready))]
|
||||
(when ready
|
||||
(let [bob-offset (* (math.sin bob-time) bob-amount)
|
||||
eye-y (+ cam-y player-eye-height bob-offset land-squash)
|
||||
forward-x (- (math.sin cam-yaw))
|
||||
forward-z (- (math.cos cam-yaw))
|
||||
target-x (+ smooth-cam-x forward-x)
|
||||
target-y (+ eye-y (math.sin cam-pitch))
|
||||
target-z (+ smooth-cam-z forward-z)
|
||||
aspect (/ (pxl8.get_width) (pxl8.get_height))]
|
||||
|
||||
(camera:lookat [smooth-cam-x eye-y smooth-cam-z]
|
||||
[target-x target-y target-z]
|
||||
[0 1 0])
|
||||
(camera:set_perspective 1.047 aspect 1.0 4096.0)
|
||||
|
||||
(let [light-x (+ 384 (* 50 (math.cos light-time)))
|
||||
light-z (+ 324 (* 50 (math.sin light-time)))
|
||||
light-y 80
|
||||
phase (+ (* light-x 0.01) 1.7)
|
||||
f1 (* 0.08 (math.sin (+ (* real-time 2.5) phase)))
|
||||
f2 (* 0.05 (math.sin (+ (* real-time 4.1) (* phase 0.7))))
|
||||
f3 (* 0.03 (math.sin (+ (* real-time 7.3) (* phase 1.2))))
|
||||
flicker (+ 0.92 f1 f2 f3)
|
||||
light-intensity (math.floor (math.max 0 (math.min 255 (* 255 flicker))))
|
||||
r1 (* 0.06 (math.sin (+ (* real-time 1.8) (* phase 0.5))))
|
||||
r2 (* 0.04 (math.sin (+ (* real-time 3.2) phase)))
|
||||
light-radius (* 150 (+ 0.95 r1 r2))]
|
||||
(lights:clear)
|
||||
(lights:add light-x light-y light-z 2 light-intensity light-radius)
|
||||
|
||||
(pxl8.push_target)
|
||||
(pxl8.begin_frame_3d camera lights {
|
||||
:ambient 2
|
||||
:dither true
|
||||
:fog_density 0.002
|
||||
:celestial_dir [0.5 -0.8 0.3]
|
||||
:celestial_intensity 0.3})
|
||||
|
||||
(when (not (menu.is-wireframe))
|
||||
(sky.update-gradient 1 2 6 6 10 18))
|
||||
(sky.render smooth-cam-x eye-y smooth-cam-z (menu.is-wireframe))
|
||||
(pxl8.clear_depth)
|
||||
|
||||
(pxl8.set_wireframe (menu.is-wireframe))
|
||||
(world:render [smooth-cam-x eye-y smooth-cam-z])
|
||||
|
||||
(when chunk
|
||||
(entities.render-fireball light-x light-y light-z))
|
||||
|
||||
(pxl8.end_frame_3d)
|
||||
|
||||
(when (not (menu.is-wireframe))
|
||||
(sky.render-stars smooth-cam-x eye-y smooth-cam-z day-time 0 last-dt))
|
||||
|
||||
(pxl8.pop_target))
|
||||
|
||||
(pxl8.push_target)
|
||||
(let [cx (/ (pxl8.get_width) 2)
|
||||
cy (/ (pxl8.get_height) 2)
|
||||
crosshair-size 4
|
||||
crosshair-color 240]
|
||||
(pxl8.line (- cx crosshair-size) cy (+ cx crosshair-size) cy crosshair-color)
|
||||
(pxl8.line cx (- cy crosshair-size) cx (+ cy crosshair-size) crosshair-color))
|
||||
(pxl8.pop_target))))
|
||||
|
||||
(when (menu.is-paused)
|
||||
(pxl8.push_target)
|
||||
(menu.draw)
|
||||
(pxl8.pop_target))))
|
||||
|
||||
(global init (fn []
|
||||
(preload)
|
||||
(game-init)))
|
||||
132
demo3d/mod/entities.fnl
Normal file
132
demo3d/mod/entities.fnl
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
(local pxl8 (require :pxl8))
|
||||
|
||||
(local FIREBALL_COLOR 218)
|
||||
|
||||
(var fireball-mesh nil)
|
||||
|
||||
(fn create-fireball-mesh []
|
||||
(let [verts []
|
||||
indices []
|
||||
radius 5
|
||||
rings 4
|
||||
segments 6
|
||||
core-color (+ FIREBALL_COLOR 6)
|
||||
spike-color (- FIREBALL_COLOR 1)]
|
||||
|
||||
(table.insert verts {:x 0 :y radius :z 0 :nx 0 :ny 1 :nz 0 :color core-color :light 255})
|
||||
|
||||
(for [ring 1 (- rings 1)]
|
||||
(let [phi (* (/ ring rings) math.pi)
|
||||
sin-phi (math.sin phi)
|
||||
cos-phi (math.cos phi)
|
||||
y (* radius cos-phi)
|
||||
ring-radius (* radius sin-phi)]
|
||||
(for [seg 0 (- segments 1)]
|
||||
(let [theta (* (/ seg segments) math.pi 2)
|
||||
x (* ring-radius (math.cos theta))
|
||||
z (* ring-radius (math.sin theta))
|
||||
nx (* sin-phi (math.cos theta))
|
||||
nz (* sin-phi (math.sin theta))]
|
||||
(table.insert verts {:x x :y y :z z :nx nx :ny cos-phi :nz nz :color core-color :light 255})))))
|
||||
|
||||
(let [bottom-idx (length verts)]
|
||||
(table.insert verts {:x 0 :y (- radius) :z 0 :nx 0 :ny -1 :nz 0 :color core-color :light 255})
|
||||
|
||||
(for [seg 0 (- segments 1)]
|
||||
(let [next-seg (% (+ seg 1) segments)]
|
||||
(table.insert indices 0)
|
||||
(table.insert indices (+ 1 next-seg))
|
||||
(table.insert indices (+ 1 seg))))
|
||||
|
||||
(for [ring 0 (- rings 3)]
|
||||
(for [seg 0 (- segments 1)]
|
||||
(let [next-seg (% (+ seg 1) segments)
|
||||
curr-row (+ 1 (* ring segments))
|
||||
next-row (+ 1 (* (+ ring 1) segments))]
|
||||
(table.insert indices (+ curr-row seg))
|
||||
(table.insert indices (+ curr-row next-seg))
|
||||
(table.insert indices (+ next-row seg))
|
||||
(table.insert indices (+ curr-row next-seg))
|
||||
(table.insert indices (+ next-row next-seg))
|
||||
(table.insert indices (+ next-row seg)))))
|
||||
|
||||
(let [last-ring-start (+ 1 (* (- rings 2) segments))]
|
||||
(for [seg 0 (- segments 1)]
|
||||
(let [next-seg (% (+ seg 1) segments)]
|
||||
(table.insert indices bottom-idx)
|
||||
(table.insert indices (+ last-ring-start seg))
|
||||
(table.insert indices (+ last-ring-start next-seg))))))
|
||||
|
||||
(let [num-spikes 12
|
||||
spike-len 8
|
||||
base-size 1.2
|
||||
golden-ratio (/ (+ 1 (math.sqrt 5)) 2)]
|
||||
(for [i 0 (- num-spikes 1)]
|
||||
(let [y (- 1 (* (/ i (- num-spikes 1)) 2))
|
||||
r-at-y (math.sqrt (- 1 (* y y)))
|
||||
theta (* math.pi 2 i golden-ratio)
|
||||
nx (* r-at-y (math.cos theta))
|
||||
ny y
|
||||
nz (* r-at-y (math.sin theta))
|
||||
tx (if (> (math.abs ny) 0.9) 1 0)
|
||||
ty (if (> (math.abs ny) 0.9) 0 1)
|
||||
tz 0
|
||||
px (- (* ty nz) (* tz ny))
|
||||
py (- (* tz nx) (* tx nz))
|
||||
pz (- (* tx ny) (* ty nx))
|
||||
pl (math.sqrt (+ (* px px) (* py py) (* pz pz)))
|
||||
px (/ px pl) py (/ py pl) pz (/ pz pl)
|
||||
qx (- (* ny pz) (* nz py))
|
||||
qy (- (* nz px) (* nx pz))
|
||||
qz (- (* nx py) (* ny px))
|
||||
bx (* radius 0.8 nx)
|
||||
by (* radius 0.8 ny)
|
||||
bz (* radius 0.8 nz)
|
||||
sx (* (+ radius spike-len) nx)
|
||||
sy (* (+ radius spike-len) ny)
|
||||
sz (* (+ radius spike-len) nz)
|
||||
base-idx (length verts)]
|
||||
(table.insert verts {:x (+ bx (* base-size px) (* base-size qx))
|
||||
:y (+ by (* base-size py) (* base-size qy))
|
||||
:z (+ bz (* base-size pz) (* base-size qz))
|
||||
:nx nx :ny ny :nz nz :color core-color :light 255})
|
||||
(table.insert verts {:x (+ bx (* base-size px) (* (- base-size) qx))
|
||||
:y (+ by (* base-size py) (* (- base-size) qy))
|
||||
:z (+ bz (* base-size pz) (* (- base-size) qz))
|
||||
:nx nx :ny ny :nz nz :color core-color :light 255})
|
||||
(table.insert verts {:x (+ bx (* (- base-size) px) (* (- base-size) qx))
|
||||
:y (+ by (* (- base-size) py) (* (- base-size) qy))
|
||||
:z (+ bz (* (- base-size) pz) (* (- base-size) qz))
|
||||
:nx nx :ny ny :nz nz :color core-color :light 255})
|
||||
(table.insert verts {:x (+ bx (* (- base-size) px) (* base-size qx))
|
||||
:y (+ by (* (- base-size) py) (* base-size qy))
|
||||
:z (+ bz (* (- base-size) pz) (* base-size qz))
|
||||
:nx nx :ny ny :nz nz :color core-color :light 255})
|
||||
(table.insert verts {:x sx :y sy :z sz :nx nx :ny ny :nz nz :color spike-color :light 255})
|
||||
(table.insert indices base-idx)
|
||||
(table.insert indices (+ base-idx 1))
|
||||
(table.insert indices (+ base-idx 4))
|
||||
(table.insert indices (+ base-idx 1))
|
||||
(table.insert indices (+ base-idx 2))
|
||||
(table.insert indices (+ base-idx 4))
|
||||
(table.insert indices (+ base-idx 2))
|
||||
(table.insert indices (+ base-idx 3))
|
||||
(table.insert indices (+ base-idx 4))
|
||||
(table.insert indices (+ base-idx 3))
|
||||
(table.insert indices base-idx)
|
||||
(table.insert indices (+ base-idx 4)))))
|
||||
|
||||
(set fireball-mesh (pxl8.create_mesh verts indices))))
|
||||
|
||||
(fn init [textures]
|
||||
(when (not fireball-mesh)
|
||||
(create-fireball-mesh)))
|
||||
|
||||
(fn render-fireball [x y z]
|
||||
(when fireball-mesh
|
||||
(pxl8.draw_mesh fireball-mesh {:x x :y y :z z
|
||||
:emissive true})))
|
||||
|
||||
{:FIREBALL_COLOR FIREBALL_COLOR
|
||||
:init init
|
||||
:render-fireball render-fireball}
|
||||
172
demo3d/mod/menu.fnl
Normal file
172
demo3d/mod/menu.fnl
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
(local pxl8 (require :pxl8))
|
||||
|
||||
(var paused false)
|
||||
(var gui nil)
|
||||
(var current-panel :main)
|
||||
(var selected-item nil)
|
||||
(var current-items [])
|
||||
(var pending-action nil)
|
||||
|
||||
(var baked-lighting true)
|
||||
(var dynamic-lighting true)
|
||||
(var textures true)
|
||||
(var wireframe false)
|
||||
|
||||
(fn init []
|
||||
(set gui (pxl8.create_gui)))
|
||||
|
||||
(fn show []
|
||||
(set paused true)
|
||||
(set current-panel :main)
|
||||
(pxl8.set_relative_mouse_mode false)
|
||||
(pxl8.center_cursor))
|
||||
|
||||
(fn hide []
|
||||
(set paused false)
|
||||
(pxl8.set_relative_mouse_mode true))
|
||||
|
||||
(fn toggle []
|
||||
(when (not gui) (init))
|
||||
(if paused
|
||||
(hide)
|
||||
(show)))
|
||||
|
||||
(fn select-next []
|
||||
(when (> (length current-items) 0)
|
||||
(var found-idx nil)
|
||||
(for [i 1 (length current-items)]
|
||||
(when (= (. current-items i) selected-item)
|
||||
(set found-idx i)))
|
||||
(if found-idx
|
||||
(let [next-idx (+ found-idx 1)]
|
||||
(if (<= next-idx (length current-items))
|
||||
(set selected-item (. current-items next-idx))
|
||||
(set selected-item (. current-items 1))))
|
||||
(set selected-item (. current-items 1)))))
|
||||
|
||||
(fn select-prev []
|
||||
(when (> (length current-items) 0)
|
||||
(var found-idx nil)
|
||||
(for [i 1 (length current-items)]
|
||||
(when (= (. current-items i) selected-item)
|
||||
(set found-idx i)))
|
||||
(if found-idx
|
||||
(let [prev-idx (- found-idx 1)]
|
||||
(if (>= prev-idx 1)
|
||||
(set selected-item (. current-items prev-idx))
|
||||
(set selected-item (. current-items (length current-items)))))
|
||||
(set selected-item (. current-items (length current-items))))))
|
||||
|
||||
(fn update []
|
||||
(set pending-action nil)
|
||||
|
||||
(when gui
|
||||
(let [(mx my) (pxl8.get_mouse_pos)]
|
||||
(gui:cursor_move mx my))
|
||||
|
||||
(when (pxl8.mouse_pressed 1)
|
||||
(gui:cursor_down))
|
||||
|
||||
(when (pxl8.mouse_released 1)
|
||||
(gui:cursor_up))
|
||||
|
||||
(when (or (pxl8.key_pressed "down")
|
||||
(and (pxl8.key_pressed "tab") (not (pxl8.key_down "lshift")) (not (pxl8.key_down "rshift"))))
|
||||
(select-next))
|
||||
|
||||
(when (or (pxl8.key_pressed "up")
|
||||
(and (pxl8.key_pressed "tab") (or (pxl8.key_down "lshift") (pxl8.key_down "rshift"))))
|
||||
(select-prev))
|
||||
|
||||
(when (or (pxl8.key_pressed "return") (pxl8.key_pressed "space"))
|
||||
(when selected-item
|
||||
(set pending-action selected-item)))))
|
||||
|
||||
(fn menu-button [id x y w h label]
|
||||
(table.insert current-items label)
|
||||
(when (= selected-item nil)
|
||||
(set selected-item label))
|
||||
(let [is-selected (= selected-item label)]
|
||||
(when is-selected
|
||||
(pxl8.rect (- x 3) (- y 3) (+ w 6) (+ h 6) (pxl8.gui_color 4)))
|
||||
(let [clicked (gui:button id x y w h label)]
|
||||
(when clicked
|
||||
(set selected-item label))
|
||||
(or clicked (and is-selected (= pending-action label))))))
|
||||
|
||||
(fn draw-main-menu []
|
||||
(pxl8.gui_window 200 80 240 200 "pxl8 demo")
|
||||
|
||||
(when (menu-button 1 215 127 210 30 "Resume")
|
||||
(hide))
|
||||
|
||||
(when (menu-button 5 215 162 210 30 "GFX")
|
||||
(set current-panel :gfx)
|
||||
(set selected-item nil))
|
||||
|
||||
(when (menu-button 6 215 197 210 30 "SFX")
|
||||
(set current-panel :sfx)
|
||||
(set selected-item nil))
|
||||
|
||||
(when (menu-button 2 215 232 210 30 "Quit")
|
||||
(pxl8.quit)))
|
||||
|
||||
(fn draw-sfx-panel []
|
||||
(pxl8.gui_window 200 100 240 145 "SFX")
|
||||
|
||||
(pxl8.gui_label 215 147 "Volume/Devices: TODO" (pxl8.gui_color 4))
|
||||
|
||||
(when (menu-button 20 215 180 210 30 "Back")
|
||||
(set current-panel :main)
|
||||
(set selected-item nil)))
|
||||
|
||||
(fn draw-gfx-panel []
|
||||
(pxl8.gui_window 200 60 240 195 "GFX")
|
||||
|
||||
(let [baked-label (if baked-lighting "Baked Lighting: On" "Baked Lighting: Off")]
|
||||
(when (menu-button 40 215 107 210 24 baked-label)
|
||||
(set baked-lighting (not baked-lighting))))
|
||||
|
||||
(let [dynamic-label (if dynamic-lighting "Dynamic Lighting: On" "Dynamic Lighting: Off")]
|
||||
(when (menu-button 41 215 134 210 24 dynamic-label)
|
||||
(set dynamic-lighting (not dynamic-lighting))))
|
||||
|
||||
(let [tex-label (if textures "Textures: On" "Textures: Off")]
|
||||
(when (menu-button 42 215 161 210 24 tex-label)
|
||||
(set textures (not textures))))
|
||||
|
||||
(let [wire-label (if wireframe "Wireframe: On" "Wireframe: Off")]
|
||||
(when (menu-button 43 215 188 210 24 wire-label)
|
||||
(set wireframe (not wireframe))))
|
||||
|
||||
(when (menu-button 32 215 218 210 24 "Back")
|
||||
(set current-panel :main)
|
||||
(set selected-item nil)))
|
||||
|
||||
(fn draw []
|
||||
(set current-items [])
|
||||
(when gui
|
||||
(gui:begin_frame)
|
||||
|
||||
(case current-panel
|
||||
:main (draw-main-menu)
|
||||
:sfx (draw-sfx-panel)
|
||||
:gfx (draw-gfx-panel))
|
||||
|
||||
(if (gui:is_hovering)
|
||||
(pxl8.set_cursor :hand)
|
||||
(pxl8.set_cursor :arrow))
|
||||
|
||||
(gui:end_frame)))
|
||||
|
||||
{:init init
|
||||
:is-paused (fn [] paused)
|
||||
:is-baked-lighting (fn [] baked-lighting)
|
||||
:is-dynamic-lighting (fn [] dynamic-lighting)
|
||||
:is-textures (fn [] textures)
|
||||
:is-wireframe (fn [] wireframe)
|
||||
:toggle toggle
|
||||
:show show
|
||||
:hide hide
|
||||
:update update
|
||||
:draw draw}
|
||||
505
demo3d/mod/sky.fnl
Normal file
505
demo3d/mod/sky.fnl
Normal file
|
|
@ -0,0 +1,505 @@
|
|||
(local pxl8 (require :pxl8))
|
||||
|
||||
(local SKY_GRADIENT_START 144)
|
||||
(local SKY_GRADIENT_COUNT 16)
|
||||
(local sky-radius 900)
|
||||
(local sky-segments 16)
|
||||
(local sky-rings 16)
|
||||
|
||||
(local NUM_RANDOM_STARS 300)
|
||||
(local NUM_TINY_STARS 7000)
|
||||
(local NUM_BEACON_STARS 8)
|
||||
(local STAR_SEED 0xDEADBEEF)
|
||||
(local STAR_CYCLE_DAYS 90)
|
||||
(local TAU (* math.pi 2))
|
||||
|
||||
(local GLOW_TORCH pxl8.LIGHT_ORANGE)
|
||||
(local GLOW_MAGIC pxl8.LIGHT_PURPLE)
|
||||
(local GLOW_SILVER pxl8.LIGHT_WHITE)
|
||||
(local GLOW_CRIMSON pxl8.LIGHT_RED)
|
||||
(local GLOW_DEEP_BLUE pxl8.LIGHT_BLUE)
|
||||
|
||||
(local CONSTELLATIONS
|
||||
[{:stars [[-0.09 0.075 240] [-0.12 0.105 200] [-0.075 0.112 190]
|
||||
[-0.06 0.045 255] [-0.022 0.022 220] [0.03 0.0 200]
|
||||
[0.075 -0.018 190] [0.12 -0.009 180] [0.158 0.022 170]
|
||||
[0.18 0.06 160] [0.15 0.075 150]]}
|
||||
{:stars [[0.0 0.0 240] [0.04 0.0 220] [0.08 0.01 200]
|
||||
[0.08 0.05 190] [0.04 0.06 180] [0.0 0.05 200]
|
||||
[-0.02 0.08 170]]}
|
||||
{:stars [[-0.03 0.04 230] [0.03 0.04 230] [0.05 0.0 200]
|
||||
[0.03 -0.04 190] [-0.03 -0.04 190] [-0.05 0.0 200]
|
||||
[0.0 -0.06 170]]}
|
||||
{:stars [[-0.06 0.04 255] [-0.03 0.02 220] [0.0 0.0 240]
|
||||
[0.04 -0.025 200] [0.08 -0.05 180] [0.12 -0.075 160]
|
||||
[-0.02 -0.03 190] [-0.02 0.04 190]]}
|
||||
{:stars [[0.0 0.0 250] [-0.015 0.022 200] [-0.03 0.04 180]
|
||||
[0.015 0.015 190] [0.035 0.03 170] [0.0 -0.022 200]
|
||||
[-0.022 -0.038 180] [0.022 -0.038 180]
|
||||
[0.045 0.0 220] [0.07 0.0 200] [0.09 0.008 180]]}
|
||||
{:stars [[-0.06 0.0 200] [-0.03 0.015 210] [0.0 0.0 230]
|
||||
[0.03 -0.015 210] [0.06 0.0 200] [0.09 0.015 190]
|
||||
[0.12 0.0 180]]}
|
||||
{:stars [[0.0 0.0 250] [0.008 0.03 220] [-0.02 0.05 200]
|
||||
[0.015 0.055 190] [-0.035 0.07 170] [0.03 0.065 180]
|
||||
[0.0 -0.02 200] [-0.015 -0.045 180]]}
|
||||
{:stars [[0.0 0.0 255] [-0.025 0.012 200] [-0.055 0.018 180]
|
||||
[-0.08 0.012 160] [0.02 0.01 190] [0.045 0.018 180]
|
||||
[0.07 0.03 160] [0.0 -0.025 190]]}
|
||||
{:stars [[0.0 0.0 240] [0.03 0.025 220] [0.05 0.05 200]
|
||||
[-0.02 0.03 200] [-0.05 0.045 180] [0.01 -0.03 210]
|
||||
[0.04 -0.05 190] [-0.03 -0.06 170]]}
|
||||
{:stars [[0.0 0.0 230] [0.045 0.01 220] [0.025 0.04 210]
|
||||
[-0.015 0.035 200] [-0.04 0.005 210] [-0.025 -0.03 200]
|
||||
[0.02 -0.04 190]]}
|
||||
{:stars [[0.0 0.0 250] [0.05 0.015 200] [0.035 0.05 190]
|
||||
[-0.02 0.055 180] [-0.055 0.025 200] [-0.045 -0.025 190]
|
||||
[-0.01 -0.05 180] [0.04 -0.04 190]]}
|
||||
{:stars [[0.0 0.0 255] [0.025 0.015 180] [-0.015 0.025 170]
|
||||
[0.02 -0.025 175] [-0.03 -0.01 165]]}])
|
||||
|
||||
(var beacon-stars [])
|
||||
(var celestial-directions nil)
|
||||
(var celestial-projected nil)
|
||||
(var constellation-stars [])
|
||||
(var last-gradient-key nil)
|
||||
(var random-stars [])
|
||||
(var sky-mesh nil)
|
||||
(var star-count 0)
|
||||
(var star-directions nil)
|
||||
(var star-glows nil)
|
||||
(var star-projected nil)
|
||||
(var tiny-stars [])
|
||||
(var twinkle-time 0)
|
||||
|
||||
(fn generate-sky-gradient [zenith-r zenith-g zenith-b horizon-r horizon-g horizon-b]
|
||||
(for [i 0 (- SKY_GRADIENT_COUNT 1)]
|
||||
(let [t (/ i (- SKY_GRADIENT_COUNT 1))
|
||||
r (math.floor (+ zenith-r (* t (- horizon-r zenith-r))))
|
||||
g (math.floor (+ zenith-g (* t (- horizon-g zenith-g))))
|
||||
b (math.floor (+ zenith-b (* t (- horizon-b zenith-b))))]
|
||||
(pxl8.set_palette_rgb (+ SKY_GRADIENT_START i) r g b))))
|
||||
|
||||
(fn create-sky-dome []
|
||||
(let [verts []
|
||||
indices []]
|
||||
|
||||
(for [i 0 (- sky-rings 1)]
|
||||
(let [theta0 (* (/ i sky-rings) math.pi 0.5)
|
||||
theta1 (* (/ (+ i 1) sky-rings) math.pi 0.5)
|
||||
sin-t0 (math.sin theta0)
|
||||
cos-t0 (math.cos theta0)
|
||||
sin-t1 (math.sin theta1)
|
||||
cos-t1 (math.cos theta1)
|
||||
y0 (* sky-radius cos-t0)
|
||||
y1 (* sky-radius cos-t1)
|
||||
r0 (* sky-radius sin-t0)
|
||||
r1 (* sky-radius sin-t1)
|
||||
t0 (/ i sky-rings)
|
||||
t1 (/ (+ i 1) sky-rings)
|
||||
c0 (math.floor (+ SKY_GRADIENT_START (* t0 (- SKY_GRADIENT_COUNT 1)) 0.5))
|
||||
c1 (math.floor (+ SKY_GRADIENT_START (* t1 (- SKY_GRADIENT_COUNT 1)) 0.5))]
|
||||
|
||||
(for [j 0 (- sky-segments 1)]
|
||||
(let [phi0 (* (/ j sky-segments) math.pi 2)
|
||||
phi1 (* (/ (+ j 1) sky-segments) math.pi 2)
|
||||
cos-p0 (math.cos phi0)
|
||||
sin-p0 (math.sin phi0)
|
||||
cos-p1 (math.cos phi1)
|
||||
sin-p1 (math.sin phi1)
|
||||
x00 (* r0 cos-p0) z00 (* r0 sin-p0)
|
||||
x01 (* r0 cos-p1) z01 (* r0 sin-p1)
|
||||
x10 (* r1 cos-p0) z10 (* r1 sin-p0)
|
||||
x11 (* r1 cos-p1) z11 (* r1 sin-p1)
|
||||
nx00 (- (* sin-t0 cos-p0)) ny00 (- cos-t0) nz00 (- (* sin-t0 sin-p0))
|
||||
nx01 (- (* sin-t0 cos-p1)) ny01 (- cos-t0) nz01 (- (* sin-t0 sin-p1))
|
||||
nx10 (- (* sin-t1 cos-p0)) ny10 (- cos-t1) nz10 (- (* sin-t1 sin-p0))
|
||||
nx11 (- (* sin-t1 cos-p1)) ny11 (- cos-t1) nz11 (- (* sin-t1 sin-p1))
|
||||
base-idx (# verts)]
|
||||
|
||||
(if (= i 0)
|
||||
(do
|
||||
(table.insert verts {:x x00 :y y0 :z z00 :nx nx00 :ny ny00 :nz nz00 :color c0 :light 255})
|
||||
(table.insert verts {:x x11 :y y1 :z z11 :nx nx11 :ny ny11 :nz nz11 :color c1 :light 255})
|
||||
(table.insert verts {:x x10 :y y1 :z z10 :nx nx10 :ny ny10 :nz nz10 :color c1 :light 255})
|
||||
(table.insert indices base-idx)
|
||||
(table.insert indices (+ base-idx 2))
|
||||
(table.insert indices (+ base-idx 1)))
|
||||
(do
|
||||
(table.insert verts {:x x00 :y y0 :z z00 :nx nx00 :ny ny00 :nz nz00 :color c0 :light 255})
|
||||
(table.insert verts {:x x01 :y y0 :z z01 :nx nx01 :ny ny01 :nz nz01 :color c0 :light 255})
|
||||
(table.insert verts {:x x11 :y y1 :z z11 :nx nx11 :ny ny11 :nz nz11 :color c1 :light 255})
|
||||
(table.insert verts {:x x10 :y y1 :z z10 :nx nx10 :ny ny10 :nz nz10 :color c1 :light 255})
|
||||
(table.insert indices base-idx)
|
||||
(table.insert indices (+ base-idx 3))
|
||||
(table.insert indices (+ base-idx 2))
|
||||
(table.insert indices base-idx)
|
||||
(table.insert indices (+ base-idx 2))
|
||||
(table.insert indices (+ base-idx 1))))))))
|
||||
|
||||
(set sky-mesh (pxl8.create_mesh verts indices))))
|
||||
|
||||
(fn reset-gradient []
|
||||
(set last-gradient-key nil))
|
||||
|
||||
(fn update-gradient [zenith-r zenith-g zenith-b horizon-r horizon-g horizon-b]
|
||||
(let [key (.. zenith-r "," zenith-g "," zenith-b "," horizon-r "," horizon-g "," horizon-b)]
|
||||
(when (not= key last-gradient-key)
|
||||
(generate-sky-gradient zenith-r zenith-g zenith-b horizon-r horizon-g horizon-b)
|
||||
(set last-gradient-key key))))
|
||||
|
||||
(fn galactic-band-factor [dx dy dz]
|
||||
(let [band-len (math.sqrt (+ (* 0.6 0.6) (* 0.3 0.3) (* 0.742 0.742)))
|
||||
bx (/ 0.6 band-len)
|
||||
by (/ 0.3 band-len)
|
||||
bz (/ 0.742 band-len)
|
||||
dist (math.abs (+ (* dx bx) (* dy by) (* dz bz)))
|
||||
in-band (- 1 (math.min (* dist 3) 1))]
|
||||
(* in-band in-band)))
|
||||
|
||||
(fn compute-right [cx cy cz]
|
||||
(if (> (math.abs cy) 0.99)
|
||||
(values 1 0 0)
|
||||
(let [rx (- cz)
|
||||
rz cx
|
||||
rlen (math.sqrt (+ (* rx rx) (* rz rz)))]
|
||||
(values (/ rx rlen) 0 (/ rz rlen)))))
|
||||
|
||||
(fn generate-constellation-centers []
|
||||
(let [centers []
|
||||
seed (+ STAR_SEED 0xC0057E11)
|
||||
num (# CONSTELLATIONS)]
|
||||
(for [i 0 (- num 1)]
|
||||
(let [h1 (pxl8.hash32 (+ seed (* i 3)))
|
||||
h2 (pxl8.hash32 (+ seed (* i 3) 1))
|
||||
h1f (/ h1 0xFFFFFFFF)
|
||||
h2f (/ h2 0xFFFFFFFF)
|
||||
base-theta (* (/ i num) TAU)
|
||||
theta-jitter (* (- h1f 0.5) 0.3)
|
||||
theta-offset (if (= i 0) -0.3 (= i 1) 0.4 (= i 4) 0.5 0)
|
||||
theta (+ base-theta theta-jitter theta-offset)
|
||||
phi (if (or (= i 0) (= i 6)) (+ 0.25 (* h2f 0.3))
|
||||
(= i 9) (+ 0.5 (* h2f 0.3))
|
||||
(or (= i 1) (= i 4) (= i 7) (= i 10)) (+ 0.65 (* h2f 0.4))
|
||||
(= i 3) (+ 1.1 (* h2f 0.2))
|
||||
(+ 0.85 (* h2f 0.4)))
|
||||
sin-phi (math.sin phi)
|
||||
x (* sin-phi (math.cos theta))
|
||||
y (math.cos phi)
|
||||
z (* sin-phi (math.sin theta))
|
||||
len (math.sqrt (+ (* x x) (* y y) (* z z)))]
|
||||
(table.insert centers {:x (/ x len) :y (/ y len) :z (/ z len)})))
|
||||
centers))
|
||||
|
||||
(fn generate-constellation-stars-data []
|
||||
(let [centers (generate-constellation-centers)
|
||||
stars []]
|
||||
(for [i 0 (- (# CONSTELLATIONS) 1)]
|
||||
(let [constellation (. CONSTELLATIONS (+ i 1))
|
||||
center (. centers (+ i 1))
|
||||
(rx ry rz) (compute-right center.x center.y center.z)
|
||||
ux (- (* center.y rz) (* center.z ry))
|
||||
uy (- (* center.z rx) (* center.x rz))
|
||||
uz (- (* center.x ry) (* center.y rx))
|
||||
ulen (math.sqrt (+ (* ux ux) (* uy uy) (* uz uz)))
|
||||
ux (/ ux ulen) uy (/ uy ulen) uz (/ uz ulen)]
|
||||
(each [j star-data (ipairs constellation.stars)]
|
||||
(let [ox (. star-data 1)
|
||||
oy (. star-data 2)
|
||||
brightness (. star-data 3)
|
||||
dx (+ center.x (* rx ox) (* ux oy))
|
||||
dy (+ center.y (* ry ox) (* uy oy))
|
||||
dz (+ center.z (* rz ox) (* uz oy))
|
||||
dlen (math.sqrt (+ (* dx dx) (* dy dy) (* dz dz)))
|
||||
star-seed (pxl8.hash32 (+ STAR_SEED (* i 1000) (* (- j 1) 7)))
|
||||
color-type (% star-seed 6)
|
||||
glow (if (< color-type 2) GLOW_MAGIC GLOW_SILVER)
|
||||
is-anchor (and (= (- j 1) 0) (= i 11))]
|
||||
(table.insert stars {:dx (/ dx dlen) :dy (/ dy dlen) :dz (/ dz dlen)
|
||||
:brightness brightness
|
||||
:glow glow
|
||||
:is-anchor is-anchor})))))
|
||||
stars))
|
||||
|
||||
(fn generate-beacon-stars-data []
|
||||
(let [stars []
|
||||
seed (+ STAR_SEED 0xBEAC0000)]
|
||||
(for [i 0 (- NUM_BEACON_STARS 1)]
|
||||
(let [h1 (pxl8.hash32 (+ seed (* i 4)))
|
||||
h2 (pxl8.hash32 (+ seed (* i 4) 1))
|
||||
h3 (pxl8.hash32 (+ seed (* i 4) 2))
|
||||
theta (* (/ h1 0xFFFFFFFF) TAU)
|
||||
phi (+ 0.3 (* (/ h2 0xFFFFFFFF) 0.9))
|
||||
sin-phi (math.sin phi)
|
||||
x (* sin-phi (math.cos theta))
|
||||
y (math.cos phi)
|
||||
z (* sin-phi (math.sin theta))
|
||||
color-type (% h3 8)
|
||||
glow (if (< color-type 3) GLOW_MAGIC GLOW_SILVER)]
|
||||
(table.insert stars {:dx x :dy y :dz z :brightness 255 :glow glow})))
|
||||
stars))
|
||||
|
||||
(fn generate-random-stars-data []
|
||||
(let [stars []]
|
||||
(for [i 0 (- NUM_RANDOM_STARS 1)]
|
||||
(let [h1 (pxl8.hash32 (+ STAR_SEED (* i 5)))
|
||||
h2 (pxl8.hash32 (+ STAR_SEED (* i 5) 1))
|
||||
h3 (pxl8.hash32 (+ STAR_SEED (* i 5) 2))
|
||||
h4 (pxl8.hash32 (+ STAR_SEED (* i 5) 3))
|
||||
theta (* (/ h1 0xFFFFFFFF) TAU)
|
||||
phi (math.acos (- 1 (* (/ h2 0xFFFFFFFF) 0.85)))
|
||||
sin-phi (math.sin phi)
|
||||
dx (* sin-phi (math.cos theta))
|
||||
dy (math.cos phi)
|
||||
dz (* sin-phi (math.sin theta))
|
||||
brightness-raw (/ (% h3 256) 255)
|
||||
brightness (math.floor (+ 60 (* brightness-raw brightness-raw 140)))
|
||||
color-type (% h4 100)
|
||||
glow (if (< color-type 8) GLOW_TORCH
|
||||
(< color-type 16) GLOW_MAGIC
|
||||
GLOW_SILVER)]
|
||||
(when (> dy 0.05)
|
||||
(table.insert stars {:dx dx :dy dy :dz dz
|
||||
:brightness brightness :glow glow}))))
|
||||
stars))
|
||||
|
||||
(fn generate-tiny-stars-data []
|
||||
(let [stars []
|
||||
seed (+ STAR_SEED 0xCAFEBABE)]
|
||||
(for [i 0 (- NUM_TINY_STARS 1)]
|
||||
(let [h1 (pxl8.hash32 (+ seed (* i 4)))
|
||||
h2 (pxl8.hash32 (+ seed (* i 4) 1))
|
||||
h3 (pxl8.hash32 (+ seed (* i 4) 2))
|
||||
h4 (pxl8.hash32 (+ seed (* i 4) 3))
|
||||
theta (* (/ h1 0xFFFFFFFF) TAU)
|
||||
phi (math.acos (- 1 (* (/ h2 0xFFFFFFFF) 0.95)))
|
||||
sin-phi (math.sin phi)
|
||||
dx (* sin-phi (math.cos theta))
|
||||
dy (math.cos phi)
|
||||
dz (* sin-phi (math.sin theta))
|
||||
band-boost (galactic-band-factor dx dy dz)
|
||||
base-bright (+ 25 (% h3 40))
|
||||
brightness (+ base-bright (math.floor (* band-boost 35)))
|
||||
color-shift (% h4 100)
|
||||
glow (if (< color-shift 3) GLOW_TORCH
|
||||
(< color-shift 15) GLOW_MAGIC
|
||||
GLOW_SILVER)]
|
||||
(when (> dy -0.05)
|
||||
(table.insert stars {:dx dx :dy dy :dz dz
|
||||
:brightness brightness :glow glow}))))
|
||||
stars))
|
||||
|
||||
(fn pack-star-directions [stars idx]
|
||||
(var i idx)
|
||||
(each [_ star (ipairs stars)]
|
||||
(let [dir (. star-directions i)]
|
||||
(set dir.x star.dx)
|
||||
(set dir.y star.dy)
|
||||
(set dir.z star.dz))
|
||||
(set i (+ i 1)))
|
||||
i)
|
||||
|
||||
(fn generate-stars []
|
||||
(set tiny-stars (generate-tiny-stars-data))
|
||||
(set random-stars (generate-random-stars-data))
|
||||
(set constellation-stars (generate-constellation-stars-data))
|
||||
(set beacon-stars (generate-beacon-stars-data))
|
||||
|
||||
(set star-count (+ (# tiny-stars) (# random-stars)
|
||||
(# constellation-stars) (# beacon-stars)))
|
||||
(set star-directions (pxl8.create_vec3_array star-count))
|
||||
(when pxl8.create_glows
|
||||
(set star-glows (pxl8.create_glows 16384)))
|
||||
(set star-projected (pxl8.create_vec3_array star-count))
|
||||
(set celestial-directions (pxl8.create_vec3_array 2))
|
||||
(set celestial-projected (pxl8.create_vec3_array 2))
|
||||
(var idx 0)
|
||||
(set idx (pack-star-directions tiny-stars idx))
|
||||
(set idx (pack-star-directions random-stars idx))
|
||||
(set idx (pack-star-directions constellation-stars idx))
|
||||
(set idx (pack-star-directions beacon-stars idx)))
|
||||
|
||||
(fn star-brightness [time]
|
||||
(if (< time 0.18) 1.0
|
||||
(< time 0.26) (let [t (/ (- time 0.18) 0.08)
|
||||
ease (* t t (- 3.0 (* 2.0 t)))]
|
||||
(- 1.0 ease))
|
||||
(< time 0.74) 0.0
|
||||
(< time 0.82) (let [t (/ (- time 0.74) 0.08)
|
||||
ease (* t t (- 3.0 (* 2.0 t)))]
|
||||
ease)
|
||||
1.0))
|
||||
|
||||
(fn ease-celestial [t]
|
||||
(let [s (math.sin (* 2 t TAU))
|
||||
ease (/ (* 0.9 s) (* 2 TAU))]
|
||||
(- t ease)))
|
||||
|
||||
(fn sun-direction [time]
|
||||
(let [eased (ease-celestial time)
|
||||
angle (* (- eased 0.25) TAU)
|
||||
y (math.sin angle)
|
||||
xz (math.cos angle)
|
||||
sx (* xz 0.7)
|
||||
sz (* xz 0.7)
|
||||
len (math.sqrt (+ (* sx sx) (* y y) (* sz sz)))]
|
||||
(values (/ sx len) (/ y len) (/ sz len))))
|
||||
|
||||
(fn moon-direction [time]
|
||||
(let [eased (ease-celestial time)
|
||||
azimuth (* (- eased 0.75) TAU 0.5)
|
||||
elev (math.sin (* 25 (/ math.pi 180)))
|
||||
horiz (math.cos (* 25 (/ math.pi 180)))
|
||||
sx (* (- (math.cos azimuth)) horiz)
|
||||
sz (* (math.sin azimuth) horiz)]
|
||||
(values sx elev sz)))
|
||||
|
||||
(fn render-stars [cam-x cam-y cam-z time days dt]
|
||||
(set twinkle-time (+ twinkle-time (or dt 0)))
|
||||
(when (and (> star-count 0) star-glows)
|
||||
(let [time-val (or time 0)
|
||||
days-val (or days 0)
|
||||
star-bright (star-brightness time-val)
|
||||
fade-in (* star-bright star-bright)
|
||||
time-factor (/ twinkle-time 60)
|
||||
star-rotation (/ (* (+ days-val time-val) TAU) STAR_CYCLE_DAYS)
|
||||
t (pxl8.mat4_translate cam-x cam-y cam-z)
|
||||
r (pxl8.mat4_rotate_y star-rotation)
|
||||
s (pxl8.mat4_scale sky-radius sky-radius sky-radius)
|
||||
transform (pxl8.mat4_multiply t (pxl8.mat4_multiply r s))
|
||||
tiny-count (# tiny-stars)
|
||||
random-count (# random-stars)
|
||||
constellation-count (# constellation-stars)
|
||||
beacon-count (# beacon-stars)
|
||||
random-offset tiny-count
|
||||
constellation-offset (+ tiny-count random-count)
|
||||
beacon-offset (+ tiny-count random-count constellation-count)]
|
||||
|
||||
(star-glows:clear)
|
||||
(pxl8.project_points star-directions star-projected star-count transform)
|
||||
|
||||
(when (> star-bright 0.02)
|
||||
(for [i 0 (- tiny-count 1)]
|
||||
(let [screen (. star-projected i)]
|
||||
(when (> screen.z 0)
|
||||
(let [star (. tiny-stars (+ i 1))
|
||||
int (math.floor (* star.brightness fade-in))]
|
||||
(when (> int 8)
|
||||
(star-glows:add (math.floor (+ screen.x 0.5))
|
||||
(math.floor (+ screen.y 0.5))
|
||||
1 int star.glow pxl8.GLOW_CIRCLE))))))
|
||||
|
||||
(for [i 0 (- constellation-count 1)]
|
||||
(let [screen (. star-projected (+ constellation-offset i))]
|
||||
(when (> screen.z 0)
|
||||
(let [star (. constellation-stars (+ i 1))
|
||||
phase (+ (* i 1.618) (* time-val 2.5))
|
||||
twinkle (+ 0.85 (* 0.15 (math.sin (* phase 6.28))))
|
||||
int (math.floor (* star.brightness fade-in twinkle 1.5))
|
||||
sx (math.floor (+ screen.x 0.5))
|
||||
sy (math.floor (+ screen.y 0.5))]
|
||||
(if star.is-anchor
|
||||
(do
|
||||
(star-glows:add sx sy 4 (* int 2) star.glow pxl8.GLOW_CIRCLE)
|
||||
(star-glows:add sx sy 8 (math.floor (/ int 2)) star.glow pxl8.GLOW_CIRCLE))
|
||||
(> star.brightness 220)
|
||||
(do
|
||||
(star-glows:add sx sy 3 (math.floor (* int 1.5)) star.glow pxl8.GLOW_DIAMOND)
|
||||
(star-glows:add sx sy 5 (math.floor (/ int 2)) star.glow pxl8.GLOW_CIRCLE))
|
||||
(> star.brightness 180)
|
||||
(do
|
||||
(star-glows:add sx sy 2 int star.glow pxl8.GLOW_DIAMOND)
|
||||
(star-glows:add sx sy 4 (math.floor (/ int 3)) star.glow pxl8.GLOW_CIRCLE))
|
||||
(do
|
||||
(star-glows:add sx sy 2 (math.floor (/ (* int 2) 3)) star.glow pxl8.GLOW_DIAMOND)
|
||||
(star-glows:add sx sy 3 (math.floor (/ int 4)) star.glow pxl8.GLOW_CIRCLE)))))))
|
||||
|
||||
(for [i 0 (- beacon-count 1)]
|
||||
(let [screen (. star-projected (+ beacon-offset i))]
|
||||
(when (> screen.z 0)
|
||||
(let [star (. beacon-stars (+ i 1))
|
||||
phase (+ (* i 2.718) (* time-val 1.5))
|
||||
twinkle (+ 0.9 (* 0.1 (math.sin (* phase 6.28))))
|
||||
int (math.floor (* 400 fade-in twinkle))
|
||||
sx (math.floor (+ screen.x 0.5))
|
||||
sy (math.floor (+ screen.y 0.5))]
|
||||
(star-glows:add sx sy 4 int star.glow pxl8.GLOW_CIRCLE)
|
||||
(star-glows:add sx sy 7 (math.floor (/ int 3)) star.glow pxl8.GLOW_CIRCLE)))))
|
||||
|
||||
(for [i 0 (- random-count 1)]
|
||||
(let [screen (. star-projected (+ random-offset i))]
|
||||
(when (> screen.z 0)
|
||||
(let [star (. random-stars (+ i 1))
|
||||
phase (+ (* i 2.137) (* time-factor 3.0))
|
||||
twinkle (+ 0.75 (* 0.25 (math.sin (* phase 6.28))))
|
||||
int (math.floor (* star.brightness fade-in twinkle))
|
||||
sx (math.floor (+ screen.x 0.5))
|
||||
sy (math.floor (+ screen.y 0.5))]
|
||||
(when (> star.brightness 180)
|
||||
(star-glows:add sx sy 2 (math.floor (* int 1.5)) star.glow pxl8.GLOW_CIRCLE))
|
||||
(when (and (<= star.brightness 180) (> star.brightness 120))
|
||||
(star-glows:add sx sy 2 int star.glow pxl8.GLOW_CIRCLE)))))))
|
||||
|
||||
(let [(sun-dx sun-dy sun-dz) (sun-direction time-val)]
|
||||
(when (> sun-dy 0)
|
||||
(let [dir (. celestial-directions 0)]
|
||||
(set dir.x sun-dx)
|
||||
(set dir.y sun-dy)
|
||||
(set dir.z sun-dz))
|
||||
(let [ct (pxl8.mat4_translate cam-x cam-y cam-z)
|
||||
cs (pxl8.mat4_scale sky-radius sky-radius sky-radius)
|
||||
cel-transform (pxl8.mat4_multiply ct cs)]
|
||||
(pxl8.project_points celestial-directions celestial-projected 1 cel-transform)
|
||||
(let [screen (. celestial-projected 0)]
|
||||
(when (> screen.z 0)
|
||||
(let [sx (math.floor (+ screen.x 0.5))
|
||||
sy (math.floor (+ screen.y 0.5))
|
||||
horizon-factor (- 1 sun-dy)
|
||||
size-scale (+ 1 (* horizon-factor 0.6))
|
||||
r-outer (math.floor (* 50 size-scale))
|
||||
r-mid (math.floor (* 38 size-scale))
|
||||
r-inner (math.floor (* 26 size-scale))]
|
||||
(star-glows:add sx sy r-outer 80 GLOW_CRIMSON pxl8.GLOW_CIRCLE 0)
|
||||
(star-glows:add sx sy r-mid 180 GLOW_CRIMSON pxl8.GLOW_CIRCLE 0)
|
||||
(star-glows:add sx sy r-inner 255 GLOW_TORCH pxl8.GLOW_CIRCLE 0)))))))
|
||||
|
||||
(when (> star-bright 0.02)
|
||||
(let [(moon-dx moon-dy moon-dz) (moon-direction time-val)]
|
||||
(when (> moon-dy 0)
|
||||
(let [dir (. celestial-directions 1)]
|
||||
(set dir.x moon-dx)
|
||||
(set dir.y moon-dy)
|
||||
(set dir.z moon-dz))
|
||||
(let [ct (pxl8.mat4_translate cam-x cam-y cam-z)
|
||||
cs (pxl8.mat4_scale sky-radius sky-radius sky-radius)
|
||||
cel-transform (pxl8.mat4_multiply ct cs)]
|
||||
(pxl8.project_points celestial-directions celestial-projected 2 cel-transform)
|
||||
(let [screen (. celestial-projected 1)]
|
||||
(when (> screen.z 0)
|
||||
(let [sx (math.floor (+ screen.x 0.5))
|
||||
sy (math.floor (+ screen.y 0.5))]
|
||||
(star-glows:add sx sy 6 (math.floor (* 500 fade-in)) GLOW_SILVER pxl8.GLOW_DIAMOND)
|
||||
(star-glows:add sx sy 10 (math.floor (* 200 fade-in)) GLOW_SILVER pxl8.GLOW_CIRCLE)
|
||||
(star-glows:add sx sy 16 (math.floor (* 60 fade-in)) GLOW_DEEP_BLUE pxl8.GLOW_CIRCLE))))))))
|
||||
|
||||
|
||||
|
||||
(when (> (star-glows:count) 0)
|
||||
(star-glows:render)))))
|
||||
|
||||
(fn render [cam-x cam-y cam-z wireframe]
|
||||
(when (not sky-mesh) (create-sky-dome))
|
||||
(when sky-mesh
|
||||
(pxl8.draw_mesh sky-mesh {:x cam-x :y cam-y :z cam-z :wireframe wireframe})))
|
||||
|
||||
{:render render
|
||||
:render-stars render-stars
|
||||
:generate-stars generate-stars
|
||||
:reset-gradient reset-gradient
|
||||
:update-gradient update-gradient
|
||||
:star-brightness star-brightness
|
||||
:ease-celestial ease-celestial
|
||||
:sun-direction sun-direction
|
||||
:moon-direction moon-direction
|
||||
:SKY_GRADIENT_START SKY_GRADIENT_START
|
||||
:SKY_GRADIENT_COUNT SKY_GRADIENT_COUNT}
|
||||
384
demo3d/mod/textures.fnl
Normal file
384
demo3d/mod/textures.fnl
Normal file
|
|
@ -0,0 +1,384 @@
|
|||
(local pxl8 (require :pxl8))
|
||||
(local procgen (require :pxl8.procgen))
|
||||
|
||||
(local textures {})
|
||||
|
||||
(fn build-graph [seed builder]
|
||||
(let [g (pxl8.create_graph 128)
|
||||
x (g:add_node procgen.OP_INPUT_X 0 0 0 0 0)
|
||||
y (g:add_node procgen.OP_INPUT_Y 0 0 0 0 0)
|
||||
ctx {:graph g :x x :y y}]
|
||||
(g:set_seed seed)
|
||||
(let [output (builder ctx)]
|
||||
(g:set_output output)
|
||||
g)))
|
||||
|
||||
(fn const [ctx val]
|
||||
(ctx.graph:add_node procgen.OP_CONST 0 0 0 0 val))
|
||||
|
||||
(fn add [ctx a b]
|
||||
(ctx.graph:add_node procgen.OP_ADD a b 0 0 0))
|
||||
|
||||
(fn sub [ctx a b]
|
||||
(ctx.graph:add_node procgen.OP_SUB a b 0 0 0))
|
||||
|
||||
(fn mul [ctx a b]
|
||||
(ctx.graph:add_node procgen.OP_MUL a b 0 0 0))
|
||||
|
||||
(fn div [ctx a b]
|
||||
(ctx.graph:add_node procgen.OP_DIV a b 0 0 0))
|
||||
|
||||
(fn min-op [ctx a b]
|
||||
(ctx.graph:add_node procgen.OP_MIN a b 0 0 0))
|
||||
|
||||
(fn max-op [ctx a b]
|
||||
(ctx.graph:add_node procgen.OP_MAX a b 0 0 0))
|
||||
|
||||
(fn abs [ctx a]
|
||||
(ctx.graph:add_node procgen.OP_ABS a 0 0 0 0))
|
||||
|
||||
(fn floor [ctx a]
|
||||
(ctx.graph:add_node procgen.OP_FLOOR a 0 0 0 0))
|
||||
|
||||
(fn fract [ctx a]
|
||||
(ctx.graph:add_node procgen.OP_FRACT a 0 0 0 0))
|
||||
|
||||
(fn lerp [ctx a b t]
|
||||
(ctx.graph:add_node procgen.OP_LERP a b t 0 0))
|
||||
|
||||
(fn clamp [ctx val lo hi]
|
||||
(ctx.graph:add_node procgen.OP_CLAMP val lo hi 0 0))
|
||||
|
||||
(fn select [ctx cond a b]
|
||||
(ctx.graph:add_node procgen.OP_SELECT a b cond 0 0))
|
||||
|
||||
(fn smoothstep [ctx edge0 edge1 x]
|
||||
(ctx.graph:add_node procgen.OP_SMOOTHSTEP edge0 edge1 x 0 0))
|
||||
|
||||
(fn noise-value [ctx scale]
|
||||
(let [s (const ctx scale)]
|
||||
(ctx.graph:add_node procgen.OP_NOISE_VALUE ctx.x ctx.y s 0 0)))
|
||||
|
||||
(fn noise-value-at [ctx x y scale]
|
||||
(let [s (const ctx scale)]
|
||||
(ctx.graph:add_node procgen.OP_NOISE_VALUE x y s 0 0)))
|
||||
|
||||
(fn noise-perlin [ctx scale]
|
||||
(let [s (const ctx scale)]
|
||||
(ctx.graph:add_node procgen.OP_NOISE_PERLIN ctx.x ctx.y s 0 0)))
|
||||
|
||||
(fn noise-fbm [ctx octaves scale persistence]
|
||||
(let [s (const ctx scale)
|
||||
p (const ctx persistence)]
|
||||
(ctx.graph:add_node procgen.OP_NOISE_FBM ctx.x ctx.y s p octaves)))
|
||||
|
||||
(fn noise-fbm-at [ctx x y octaves scale persistence]
|
||||
(let [s (const ctx scale)
|
||||
p (const ctx persistence)]
|
||||
(ctx.graph:add_node procgen.OP_NOISE_FBM x y s p octaves)))
|
||||
|
||||
(fn noise-ridged [ctx octaves scale persistence]
|
||||
(let [s (const ctx scale)
|
||||
p (const ctx persistence)]
|
||||
(ctx.graph:add_node procgen.OP_NOISE_RIDGED ctx.x ctx.y s p octaves)))
|
||||
|
||||
(fn noise-turbulence [ctx octaves scale persistence]
|
||||
(let [s (const ctx scale)
|
||||
p (const ctx persistence)]
|
||||
(ctx.graph:add_node procgen.OP_NOISE_TURBULENCE ctx.x ctx.y s p octaves)))
|
||||
|
||||
(fn voronoi-cell [ctx scale]
|
||||
(let [s (const ctx scale)]
|
||||
(ctx.graph:add_node procgen.OP_VORONOI_CELL ctx.x ctx.y s 0 0)))
|
||||
|
||||
(fn voronoi-edge [ctx scale]
|
||||
(let [s (const ctx scale)]
|
||||
(ctx.graph:add_node procgen.OP_VORONOI_EDGE ctx.x ctx.y s 0 0)))
|
||||
|
||||
(fn voronoi-id [ctx scale]
|
||||
(let [s (const ctx scale)]
|
||||
(ctx.graph:add_node procgen.OP_VORONOI_ID ctx.x ctx.y s 0 0)))
|
||||
|
||||
(fn gradient-linear [ctx angle]
|
||||
(let [a (const ctx angle)]
|
||||
(ctx.graph:add_node procgen.OP_GRADIENT_LINEAR ctx.x ctx.y a 0 0)))
|
||||
|
||||
(fn gradient-radial [ctx cx cy]
|
||||
(let [center-x (const ctx cx)
|
||||
center-y (const ctx cy)]
|
||||
(ctx.graph:add_node procgen.OP_GRADIENT_RADIAL ctx.x ctx.y center-x center-y 0)))
|
||||
|
||||
(fn quantize [ctx val base range]
|
||||
(let [r (const ctx range)]
|
||||
(ctx.graph:add_node procgen.OP_QUANTIZE val r 0 0 base)))
|
||||
|
||||
(fn textures.mossy-cobblestone [seed base-color moss-color]
|
||||
(let [g (build-graph seed
|
||||
(fn [ctx]
|
||||
(let [cell (voronoi-cell ctx 6)
|
||||
edge (voronoi-edge ctx 6)
|
||||
mortar-threshold (const ctx 0.05)
|
||||
is-mortar (sub ctx mortar-threshold edge)
|
||||
mortar-color (const ctx (- base-color 2))
|
||||
stone-detail (noise-value ctx 48)
|
||||
stone-base (mul ctx cell (const ctx 0.6))
|
||||
stone-combined (add ctx stone-base (mul ctx stone-detail (const ctx 0.4)))
|
||||
stone-quant (quantize ctx stone-combined base-color 8)
|
||||
moss-pattern (noise-fbm ctx 4 10 0.5)
|
||||
moss-detail (noise-value ctx 64)
|
||||
moss-var (add ctx (mul ctx moss-pattern (const ctx 0.7))
|
||||
(mul ctx moss-detail (const ctx 0.3)))
|
||||
moss-threshold (const ctx 0.55)
|
||||
has-moss (sub ctx moss-pattern moss-threshold)
|
||||
moss-quant (quantize ctx moss-var moss-color 6)
|
||||
stone-or-moss (select ctx has-moss moss-quant stone-quant)]
|
||||
(select ctx is-mortar mortar-color stone-or-moss))))]
|
||||
(let [tex-id (g:eval_texture 64 64)]
|
||||
(g:destroy)
|
||||
tex-id)))
|
||||
|
||||
(fn textures.ashlar-wall [seed base-color moss-color]
|
||||
(let [g (build-graph seed
|
||||
(fn [ctx]
|
||||
(let [cell (voronoi-cell ctx 5)
|
||||
edge (voronoi-edge ctx 5)
|
||||
cell-id (voronoi-id ctx 5)
|
||||
mortar-threshold (const ctx 0.12)
|
||||
is-mortar (sub ctx mortar-threshold edge)
|
||||
stone-detail (noise-fbm ctx 3 32 0.5)
|
||||
stone-tint (mul ctx cell-id (const ctx 0.4))
|
||||
stone-shade (mul ctx cell (const ctx 0.3))
|
||||
stone-combined (add ctx stone-detail (add ctx stone-tint stone-shade))
|
||||
stone-quant (quantize ctx stone-combined base-color 10)
|
||||
crack-moss (noise-fbm ctx 3 16 0.5)
|
||||
moss-in-crack (mul ctx crack-moss (sub ctx (const ctx 0.2) edge))
|
||||
moss-threshold (const ctx 0.06)
|
||||
has-moss (sub ctx moss-in-crack moss-threshold)
|
||||
moss-quant (quantize ctx crack-moss moss-color 4)
|
||||
mortar-color (const ctx (+ base-color 1))
|
||||
stone-or-moss (select ctx has-moss moss-quant stone-quant)]
|
||||
(select ctx is-mortar mortar-color stone-or-moss))))]
|
||||
(let [tex-id (g:eval_texture 64 64)]
|
||||
(g:destroy)
|
||||
tex-id)))
|
||||
|
||||
(fn textures.gradient-sky [seed zenith-color horizon-color]
|
||||
(let [g (build-graph seed
|
||||
(fn [ctx]
|
||||
(let [grad (gradient-linear ctx (* math.pi 0.5))
|
||||
zenith (const ctx zenith-color)
|
||||
horizon (const ctx horizon-color)
|
||||
range (const ctx (- horizon-color zenith-color))]
|
||||
(quantize ctx grad zenith-color (- horizon-color zenith-color)))))]
|
||||
(let [tex-id (g:eval_texture 64 64)]
|
||||
(g:destroy)
|
||||
tex-id)))
|
||||
|
||||
(fn textures.wood-planks [seed base-color]
|
||||
(let [g (build-graph seed
|
||||
(fn [ctx]
|
||||
(let [tex-size 64
|
||||
plank-count 2
|
||||
pixel-x (floor ctx (mul ctx ctx.x (const ctx tex-size)))
|
||||
plank-x (div ctx pixel-x (const ctx (/ tex-size plank-count)))
|
||||
plank-fract (fract ctx plank-x)
|
||||
edge-dist (min-op ctx plank-fract (sub ctx (const ctx 1.0) plank-fract))
|
||||
gap-threshold (const ctx 0.04)
|
||||
is-gap (sub ctx gap-threshold edge-dist)
|
||||
plank-tint (mul ctx (noise-value ctx 6) (const ctx 0.25))
|
||||
grain-x (mul ctx ctx.x (const ctx 12))
|
||||
grain-y (mul ctx ctx.y (const ctx 2))
|
||||
grain-base (noise-fbm-at ctx grain-x grain-y 3 4 0.6)
|
||||
grain-fine-x (mul ctx ctx.x (const ctx 48))
|
||||
grain-fine-y (mul ctx ctx.y (const ctx 6))
|
||||
grain-fine (noise-value-at ctx grain-fine-x grain-fine-y 1)
|
||||
grain (add ctx (mul ctx grain-base (const ctx 0.6))
|
||||
(mul ctx grain-fine (const ctx 0.4)))
|
||||
wood-val (add ctx grain plank-tint)
|
||||
wood-quant (quantize ctx wood-val base-color 6)
|
||||
gap-shade (sub ctx wood-val (const ctx 0.15))
|
||||
gap-quant (quantize ctx gap-shade base-color 6)]
|
||||
(select ctx is-gap gap-quant wood-quant))))]
|
||||
(let [tex-id (g:eval_texture 64 64)]
|
||||
(g:destroy)
|
||||
tex-id)))
|
||||
|
||||
(fn textures.diagonal-planks [seed base-color]
|
||||
(let [g (build-graph seed
|
||||
(fn [ctx]
|
||||
(let [tex-size 64
|
||||
plank-count 4
|
||||
diag (add ctx ctx.x ctx.y)
|
||||
pixel-diag (floor ctx (mul ctx diag (const ctx tex-size)))
|
||||
plank-diag (div ctx pixel-diag (const ctx (/ tex-size plank-count)))
|
||||
plank-fract (fract ctx plank-diag)
|
||||
edge-dist (min-op ctx plank-fract (sub ctx (const ctx 1.0) plank-fract))
|
||||
gap-threshold (const ctx 0.06)
|
||||
is-gap (sub ctx gap-threshold edge-dist)
|
||||
plank-id (floor ctx plank-diag)
|
||||
plank-tint (mul ctx (noise-value-at ctx plank-id (const ctx 0) 8) (const ctx 0.2))
|
||||
grain-diag (mul ctx diag (const ctx 8))
|
||||
grain-perp (sub ctx ctx.x ctx.y)
|
||||
grain-base (noise-fbm-at ctx grain-diag grain-perp 3 6 0.5)
|
||||
grain-fine (noise-value-at ctx (mul ctx grain-diag (const ctx 4)) grain-perp 1)
|
||||
grain (add ctx (mul ctx grain-base (const ctx 0.5))
|
||||
(mul ctx grain-fine (const ctx 0.3)))
|
||||
wood-val (add ctx grain plank-tint)
|
||||
wood-quant (quantize ctx wood-val base-color 5)
|
||||
gap-shade (sub ctx wood-val (const ctx 0.2))
|
||||
gap-quant (quantize ctx gap-shade base-color 5)]
|
||||
(select ctx is-gap gap-quant wood-quant))))]
|
||||
(let [tex-id (g:eval_texture 64 64)]
|
||||
(g:destroy)
|
||||
tex-id)))
|
||||
|
||||
(fn textures.cobble-timber [seed stone-color moss-color wood-color]
|
||||
(let [g (build-graph seed
|
||||
(fn [ctx]
|
||||
(let [warp-x (noise-fbm ctx 2 3 0.5)
|
||||
warp-y (noise-value ctx 4)
|
||||
warped-x (add ctx ctx.x (mul ctx warp-x (const ctx 0.15)))
|
||||
warped-y (add ctx ctx.y (mul ctx warp-y (const ctx 0.15)))
|
||||
cell (ctx.graph:add_node procgen.OP_VORONOI_CELL warped-x warped-y (const ctx 5) 0 0)
|
||||
edge (ctx.graph:add_node procgen.OP_VORONOI_EDGE warped-x warped-y (const ctx 5) 0 0)
|
||||
mortar-threshold (const ctx 0.08)
|
||||
is-mortar (sub ctx mortar-threshold edge)
|
||||
mortar-color (const ctx 79)
|
||||
stone-detail (noise-value ctx 48)
|
||||
stone-base (mul ctx cell (const ctx 0.6))
|
||||
stone-combined (add ctx stone-base (mul ctx stone-detail (const ctx 0.4)))
|
||||
stone-quant (quantize ctx stone-combined stone-color 8)
|
||||
moss-pattern (noise-fbm ctx 4 10 0.5)
|
||||
moss-detail (noise-value ctx 64)
|
||||
moss-var (add ctx (mul ctx moss-pattern (const ctx 0.7))
|
||||
(mul ctx moss-detail (const ctx 0.3)))
|
||||
moss-threshold (const ctx 0.55)
|
||||
has-moss (sub ctx moss-pattern moss-threshold)
|
||||
moss-quant (quantize ctx moss-var moss-color 6)
|
||||
stone-or-moss (select ctx has-moss moss-quant stone-quant)]
|
||||
(select ctx is-mortar mortar-color stone-or-moss))))]
|
||||
(let [tex-id (g:eval_texture 64 64)]
|
||||
(g:destroy)
|
||||
tex-id)))
|
||||
|
||||
(fn textures.plaster-wall [seed base-color]
|
||||
(let [g (build-graph seed
|
||||
(fn [ctx]
|
||||
(let [plaster-base (noise-fbm ctx 3 24 0.4)
|
||||
plaster-detail (noise-value ctx 64)
|
||||
plaster-rough (noise-turbulence ctx 2 32 0.5)
|
||||
combined (add ctx (mul ctx plaster-base (const ctx 0.5))
|
||||
(add ctx (mul ctx plaster-detail (const ctx 0.3))
|
||||
(mul ctx plaster-rough (const ctx 0.2))))
|
||||
crack-noise (noise-ridged ctx 2 8 0.6)
|
||||
crack-threshold (const ctx 0.75)
|
||||
has-crack (sub ctx crack-noise crack-threshold)
|
||||
crack-color (const ctx (- base-color 2))
|
||||
plaster-quant (quantize ctx combined base-color 4)]
|
||||
(select ctx has-crack crack-color plaster-quant))))]
|
||||
(let [tex-id (g:eval_texture 64 64)]
|
||||
(g:destroy)
|
||||
tex-id)))
|
||||
|
||||
(fn textures.timber-frame [seed wood-color plaster-color]
|
||||
(let [g (build-graph seed
|
||||
(fn [ctx]
|
||||
(let [h-beam-count 2
|
||||
v-beam-count 1.5
|
||||
beam-half-width 0.08
|
||||
scaled-x (mul ctx ctx.x (const ctx h-beam-count))
|
||||
scaled-y (mul ctx ctx.y (const ctx v-beam-count))
|
||||
dist-to-h-beam (abs ctx (sub ctx (fract ctx scaled-y) (const ctx 0.5)))
|
||||
dist-to-v-beam (abs ctx (sub ctx (fract ctx scaled-x) (const ctx 0.5)))
|
||||
is-h-timber (sub ctx beam-half-width dist-to-h-beam)
|
||||
is-v-timber (sub ctx beam-half-width dist-to-v-beam)
|
||||
wood-grain (noise-fbm ctx 2 48 0.5)
|
||||
wood-quant (quantize ctx wood-grain wood-color 4)
|
||||
plaster-noise (noise-fbm ctx 3 16 0.4)
|
||||
plaster-quant (quantize ctx plaster-noise plaster-color 3)
|
||||
timber-or-plaster (select ctx is-h-timber wood-quant
|
||||
(select ctx is-v-timber wood-quant plaster-quant))]
|
||||
timber-or-plaster)))]
|
||||
(let [tex-id (g:eval_texture 64 64)]
|
||||
(g:destroy)
|
||||
tex-id)))
|
||||
|
||||
(fn textures.door []
|
||||
(let [(tex err) (pxl8.load_sprite "res/textures/door.ase")]
|
||||
(if tex
|
||||
tex
|
||||
(do (pxl8.error (.. "Failed to load res/textures/door.ase, error: " (tostring err))) nil))))
|
||||
|
||||
(fn textures.wood-trim [seed base-color]
|
||||
(let [g (build-graph seed
|
||||
(fn [ctx]
|
||||
(let [plank-tint (mul ctx (noise-value ctx 6) (const ctx 0.25))
|
||||
grain-x (mul ctx ctx.x (const ctx 12))
|
||||
grain-y (mul ctx ctx.y (const ctx 2))
|
||||
grain-base (noise-fbm-at ctx grain-x grain-y 3 4 0.6)
|
||||
grain-fine-x (mul ctx ctx.x (const ctx 48))
|
||||
grain-fine-y (mul ctx ctx.y (const ctx 6))
|
||||
grain-fine (noise-value-at ctx grain-fine-x grain-fine-y 1)
|
||||
grain (add ctx (mul ctx grain-base (const ctx 0.6))
|
||||
(mul ctx grain-fine (const ctx 0.4)))
|
||||
wood-val (add ctx grain plank-tint)]
|
||||
(quantize ctx wood-val base-color 6))))]
|
||||
(let [tex-id (g:eval_texture 64 16)]
|
||||
(g:destroy)
|
||||
tex-id)))
|
||||
|
||||
(fn textures.rough-stone [seed base-color]
|
||||
(let [g (build-graph seed
|
||||
(fn [ctx]
|
||||
(let [cell (voronoi-cell ctx 8)
|
||||
edge (voronoi-edge ctx 8)
|
||||
crack-threshold (const ctx 0.03)
|
||||
is-crack (sub ctx crack-threshold edge)
|
||||
crack-color (const ctx (- base-color 3))
|
||||
roughness (noise-turbulence ctx 3 24 0.6)
|
||||
detail (noise-value ctx 48)
|
||||
combined (add ctx (mul ctx roughness (const ctx 0.5))
|
||||
(add ctx (mul ctx cell (const ctx 0.3))
|
||||
(mul ctx detail (const ctx 0.2))))
|
||||
stone-quant (quantize ctx combined base-color 6)]
|
||||
(select ctx is-crack crack-color stone-quant))))]
|
||||
(let [tex-id (g:eval_texture 64 64)]
|
||||
(g:destroy)
|
||||
tex-id)))
|
||||
|
||||
(fn textures.packed-dirt [seed base-color]
|
||||
(let [g (build-graph seed
|
||||
(fn [ctx]
|
||||
(let [base-noise (noise-fbm ctx 3 16 0.5)
|
||||
detail (noise-value ctx 32)
|
||||
combined (add ctx (mul ctx base-noise (const ctx 0.6))
|
||||
(mul ctx detail (const ctx 0.4)))
|
||||
dirt-quant (quantize ctx combined base-color 5)
|
||||
pebble (voronoi-cell ctx 12)
|
||||
pebble-edge (voronoi-edge ctx 12)
|
||||
pebble-threshold (const ctx 0.08)
|
||||
is-pebble (sub ctx pebble-threshold pebble-edge)
|
||||
pebble-quant (quantize ctx pebble (- base-color 2) 4)]
|
||||
(select ctx is-pebble pebble-quant dirt-quant))))]
|
||||
(let [tex-id (g:eval_texture 64 64)]
|
||||
(g:destroy)
|
||||
tex-id)))
|
||||
|
||||
(fn textures.grass-top [seed base-color]
|
||||
(let [g (build-graph seed
|
||||
(fn [ctx]
|
||||
(let [blade-x (mul ctx ctx.x (const ctx 32))
|
||||
blade-y (mul ctx ctx.y (const ctx 4))
|
||||
blades (noise-fbm-at ctx blade-x blade-y 3 6 0.5)
|
||||
fine (noise-value ctx 64)
|
||||
patch (noise-fbm ctx 2 6 0.4)
|
||||
combined (add ctx (mul ctx blades (const ctx 0.4))
|
||||
(add ctx (mul ctx fine (const ctx 0.3))
|
||||
(mul ctx patch (const ctx 0.3))))
|
||||
grass-quant (quantize ctx combined base-color 6)]
|
||||
grass-quant)))]
|
||||
(let [tex-id (g:eval_texture 64 64)]
|
||||
(g:destroy)
|
||||
tex-id)))
|
||||
|
||||
textures
|
||||
159
demo3d/mod/world.lua
Normal file
159
demo3d/mod/world.lua
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
local ffi = require("ffi")
|
||||
local C = ffi.C
|
||||
local core = require("pxl8.core")
|
||||
|
||||
local world = {}
|
||||
|
||||
local Bsp = {}
|
||||
Bsp.__index = Bsp
|
||||
|
||||
function Bsp:face_count()
|
||||
return C.demo3d_bsp_face_count(self._ptr)
|
||||
end
|
||||
|
||||
function Bsp:face_normal(face_id)
|
||||
return C.demo3d_bsp_face_normal(self._ptr, face_id)
|
||||
end
|
||||
|
||||
function Bsp:light_at(x, y, z, ambient)
|
||||
return C.demo3d_bsp_light_at(self._ptr, x, y, z, ambient or 0)
|
||||
end
|
||||
|
||||
function Bsp:face_set_material(face_id, material_id)
|
||||
C.demo3d_bsp_face_set_material(self._ptr, face_id, material_id)
|
||||
end
|
||||
|
||||
world.Bsp = Bsp
|
||||
|
||||
local Chunk = {}
|
||||
Chunk.__index = Chunk
|
||||
|
||||
function Chunk:bsp()
|
||||
if self._ptr == nil then return nil end
|
||||
if self._ptr.bsp == nil then return nil end
|
||||
return setmetatable({ _ptr = self._ptr.bsp }, Bsp)
|
||||
end
|
||||
|
||||
function Chunk:ready()
|
||||
return self._ptr ~= nil and self._ptr.bsp ~= nil
|
||||
end
|
||||
|
||||
function Chunk:version()
|
||||
if self._ptr == nil then return 0 end
|
||||
return self._ptr.version
|
||||
end
|
||||
|
||||
world.Chunk = Chunk
|
||||
|
||||
local World = {}
|
||||
World.__index = World
|
||||
|
||||
function World.get()
|
||||
local w = C.demo3d_get_world(core.sys)
|
||||
if w == nil then return nil end
|
||||
return setmetatable({ _ptr = w }, World)
|
||||
end
|
||||
|
||||
function World:active_chunk()
|
||||
local ptr = C.demo3d_world_active_chunk(self._ptr)
|
||||
if ptr == nil then return nil end
|
||||
return setmetatable({ _ptr = ptr }, Chunk)
|
||||
end
|
||||
|
||||
function World:init_local_player(x, y, z)
|
||||
C.demo3d_world_init_local_player(self._ptr, x, y, z)
|
||||
end
|
||||
|
||||
function World:set_look(yaw, pitch)
|
||||
C.demo3d_world_set_look(self._ptr, yaw, pitch or 0)
|
||||
end
|
||||
|
||||
function World:local_player()
|
||||
local ptr = C.demo3d_world_local_player(self._ptr)
|
||||
if ptr == nil then return nil end
|
||||
return ptr
|
||||
end
|
||||
|
||||
function World:point_solid(x, y, z)
|
||||
return C.demo3d_world_point_solid(self._ptr, x, y, z)
|
||||
end
|
||||
|
||||
function World:ray(from_x, from_y, from_z, to_x, to_y, to_z)
|
||||
local from = ffi.new("pxl8_vec3", {x = from_x, y = from_y, z = from_z})
|
||||
local to = ffi.new("pxl8_vec3", {x = to_x, y = to_y, z = to_z})
|
||||
return C.demo3d_world_ray(self._ptr, from, to)
|
||||
end
|
||||
|
||||
function World:render(camera_pos)
|
||||
local vec = ffi.new("pxl8_vec3", {x = camera_pos[1], y = camera_pos[2], z = camera_pos[3]})
|
||||
C.demo3d_world_render(self._ptr, core.gfx, vec)
|
||||
end
|
||||
|
||||
function World:set_bsp_material(material_id, material)
|
||||
C.demo3d_world_set_bsp_material(self._ptr, material_id, material._ptr)
|
||||
end
|
||||
|
||||
function World:sweep(from_x, from_y, from_z, to_x, to_y, to_z, radius)
|
||||
local from = ffi.new("pxl8_vec3", {x = from_x, y = from_y, z = from_z})
|
||||
local to = ffi.new("pxl8_vec3", {x = to_x, y = to_y, z = to_z})
|
||||
return C.demo3d_world_sweep(self._ptr, from, to, radius)
|
||||
end
|
||||
|
||||
function World:set_sim_config(config)
|
||||
C.demo3d_world_set_sim_config(self._ptr, config)
|
||||
end
|
||||
|
||||
function World:push_input(input_msg)
|
||||
C.demo3d_world_push_input(self._ptr, input_msg)
|
||||
end
|
||||
|
||||
world.World = World
|
||||
|
||||
function world.sim_config(opts)
|
||||
opts = opts or {}
|
||||
return ffi.new("demo3d_sim_config", {
|
||||
move_speed = opts.move_speed or 180.0,
|
||||
ground_accel = opts.ground_accel or 10.0,
|
||||
air_accel = opts.air_accel or 1.0,
|
||||
stop_speed = opts.stop_speed or 100.0,
|
||||
friction = opts.friction or 6.0,
|
||||
gravity = opts.gravity or 800.0,
|
||||
jump_velocity = opts.jump_velocity or 200.0,
|
||||
player_radius = opts.player_radius or 16.0,
|
||||
player_height = opts.player_height or 72.0,
|
||||
max_pitch = opts.max_pitch or 1.5,
|
||||
})
|
||||
end
|
||||
|
||||
function world.sim_move_player(entity, input_msg, sim_world, config, dt)
|
||||
C.demo3d_sim_move_player(entity, input_msg, sim_world, config, dt)
|
||||
end
|
||||
|
||||
function world.sim_trace(sim_world, from_x, from_y, from_z, to_x, to_y, to_z, radius, height)
|
||||
local from = ffi.new("pxl8_vec3", {x = from_x, y = from_y, z = from_z})
|
||||
local to = ffi.new("pxl8_vec3", {x = to_x, y = to_y, z = to_z})
|
||||
return C.demo3d_sim_trace(sim_world, from, to, radius, height)
|
||||
end
|
||||
|
||||
function world.sim_check_ground(sim_world, x, y, z, radius)
|
||||
local pos = ffi.new("pxl8_vec3", {x = x, y = y, z = z})
|
||||
return C.demo3d_sim_check_ground(sim_world, pos, radius)
|
||||
end
|
||||
|
||||
function World:sim_world(x, y, z)
|
||||
local pos = ffi.new("pxl8_vec3", {x = x, y = y, z = z})
|
||||
return C.demo3d_world_sim_world(self._ptr, pos)
|
||||
end
|
||||
|
||||
function world.make_input_msg(opts)
|
||||
opts = opts or {}
|
||||
return ffi.new("demo3d_input_msg", {
|
||||
move_x = opts.move_x or 0,
|
||||
move_y = opts.move_y or 0,
|
||||
look_dx = opts.look_dx or 0,
|
||||
look_dy = opts.look_dy or 0,
|
||||
buttons = opts.buttons or 0,
|
||||
})
|
||||
end
|
||||
|
||||
return world
|
||||
BIN
demo3d/res/palettes/palette.ase
Normal file
BIN
demo3d/res/palettes/palette.ase
Normal file
Binary file not shown.
BIN
demo3d/res/textures/door.ase
Normal file
BIN
demo3d/res/textures/door.ase
Normal file
Binary file not shown.
2
demo3d/server/.cargo/config.toml
Normal file
2
demo3d/server/.cargo/config.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[unstable]
|
||||
build-std = ["core", "alloc"]
|
||||
1
demo3d/server/.gitignore
vendored
Normal file
1
demo3d/server/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
target/
|
||||
263
demo3d/server/Cargo.lock
generated
Normal file
263
demo3d/server/Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.72.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools",
|
||||
"log",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "demo3d-server"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cc",
|
||||
"libc",
|
||||
"libm",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.180"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.105"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.114"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
40
demo3d/server/Cargo.toml
Normal file
40
demo3d/server/Cargo.toml
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
[package]
|
||||
name = "demo3d-server"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
name = "demo3d_server"
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = "0.72"
|
||||
cc = "1.2"
|
||||
|
||||
[dependencies]
|
||||
libc = { version = "0.2", default-features = false }
|
||||
libm = { version = "0.2", default-features = false }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows-sys = { version = "0.61", default-features = false, features = [
|
||||
"Win32_System_Memory",
|
||||
"Win32_System_Performance",
|
||||
"Win32_System_Threading",
|
||||
"Win32_Networking_WinSock",
|
||||
] }
|
||||
|
||||
[[bin]]
|
||||
name = "demo3d-server"
|
||||
path = "src/main.rs"
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 2
|
||||
panic = "abort"
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
strip = true
|
||||
68
demo3d/server/build.rs
Normal file
68
demo3d/server/build.rs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||
let root = PathBuf::from(&manifest_dir).join("../..");
|
||||
let framework_src = root.join("src");
|
||||
let game_client = root.join("demo3d/client");
|
||||
|
||||
println!("cargo:rerun-if-changed=../client/bsp/demo3d_bsp.h");
|
||||
println!("cargo:rerun-if-changed=../client/protocol/demo3d_protocol.c");
|
||||
println!("cargo:rerun-if-changed=../client/protocol/demo3d_protocol.h");
|
||||
println!("cargo:rerun-if-changed=../client/sim/demo3d_sim.c");
|
||||
println!("cargo:rerun-if-changed=../client/sim/demo3d_sim.h");
|
||||
println!("cargo:rerun-if-changed=../../src/core/pxl8_log.c");
|
||||
println!("cargo:rerun-if-changed=../../src/core/pxl8_log.h");
|
||||
println!("cargo:rerun-if-changed=../../src/core/pxl8_types.h");
|
||||
println!("cargo:rerun-if-changed=../../src/math/pxl8_math.c");
|
||||
println!("cargo:rerun-if-changed=../../src/math/pxl8_math.h");
|
||||
|
||||
cc::Build::new()
|
||||
.file(framework_src.join("core/pxl8_log.c"))
|
||||
.file(framework_src.join("platform/pxl8_mem.c"))
|
||||
.file(framework_src.join("math/pxl8_math.c"))
|
||||
.file(framework_src.join("math/pxl8_noise.c"))
|
||||
.file(game_client.join("sim/demo3d_sim.c"))
|
||||
.include(framework_src.join("core"))
|
||||
.include(framework_src.join("math"))
|
||||
.include(framework_src.join("platform"))
|
||||
.include(game_client.join("bsp"))
|
||||
.include(game_client.join("net"))
|
||||
.include(game_client.join("sim"))
|
||||
.compile("pxl8");
|
||||
|
||||
let bindings = bindgen::Builder::default()
|
||||
.header(framework_src.join("core/pxl8_log.h").to_str().unwrap())
|
||||
.header(framework_src.join("math/pxl8_noise.h").to_str().unwrap())
|
||||
.header(game_client.join("sim/demo3d_sim.h").to_str().unwrap())
|
||||
.clang_arg(format!("-I{}", framework_src.join("core").display()))
|
||||
.clang_arg(format!("-I{}", framework_src.join("math").display()))
|
||||
.clang_arg(format!("-I{}", framework_src.join("platform").display()))
|
||||
.clang_arg(format!("-I{}", game_client.join("bsp").display()))
|
||||
.clang_arg(format!("-I{}", game_client.join("net").display()))
|
||||
.clang_arg(format!("-I{}", game_client.join("sim").display()))
|
||||
.blocklist_item("FP_NAN")
|
||||
.blocklist_item("FP_INFINITE")
|
||||
.blocklist_item("FP_ZERO")
|
||||
.blocklist_item("FP_SUBNORMAL")
|
||||
.blocklist_item("FP_NORMAL")
|
||||
.blocklist_type("pxl8_vec2")
|
||||
.blocklist_type("pxl8_vec3")
|
||||
.blocklist_type("pxl8_vec4")
|
||||
.blocklist_type("pxl8_mat4")
|
||||
.blocklist_item(".*_simd.*")
|
||||
.blocklist_item("PXL8_SIMD.*")
|
||||
.blocklist_type("__m128.*")
|
||||
.blocklist_type(".*32x4_t|.*16x8_t")
|
||||
.raw_line("pub use crate::math::{pxl8_vec2, pxl8_vec3, pxl8_vec4, pxl8_mat4};")
|
||||
.use_core()
|
||||
.rustified_enum(".*")
|
||||
.generate()
|
||||
.expect("Unable to generate bindings");
|
||||
|
||||
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||
bindings
|
||||
.write_to_file(out_path.join("bindings.rs"))
|
||||
.expect("Couldn't write bindings");
|
||||
}
|
||||
2
demo3d/server/rust-toolchain.toml
Normal file
2
demo3d/server/rust-toolchain.toml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[toolchain]
|
||||
channel = "nightly"
|
||||
58
demo3d/server/src/allocator.rs
Normal file
58
demo3d/server/src/allocator.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
use core::alloc::{GlobalAlloc, Layout};
|
||||
|
||||
pub struct Allocator;
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
unsafe impl GlobalAlloc for Allocator {
|
||||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||
unsafe { libc::memalign(layout.align(), layout.size()) as *mut u8 }
|
||||
}
|
||||
|
||||
unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
|
||||
unsafe { libc::free(ptr as *mut libc::c_void) }
|
||||
}
|
||||
|
||||
unsafe fn realloc(&self, ptr: *mut u8, _layout: Layout, new_size: usize) -> *mut u8 {
|
||||
unsafe { libc::realloc(ptr as *mut libc::c_void, new_size) as *mut u8 }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
unsafe impl GlobalAlloc for Allocator {
|
||||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||
let mut ptr: *mut libc::c_void = core::ptr::null_mut();
|
||||
let align = layout.align().max(8);
|
||||
let size = layout.size();
|
||||
let result = unsafe { libc::posix_memalign(&mut ptr, align, size) };
|
||||
if result != 0 {
|
||||
return core::ptr::null_mut();
|
||||
}
|
||||
ptr as *mut u8
|
||||
}
|
||||
|
||||
unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
|
||||
unsafe { libc::free(ptr as *mut libc::c_void) }
|
||||
}
|
||||
|
||||
unsafe fn realloc(&self, ptr: *mut u8, _layout: Layout, new_size: usize) -> *mut u8 {
|
||||
unsafe { libc::realloc(ptr as *mut libc::c_void, new_size) as *mut u8 }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
unsafe impl GlobalAlloc for Allocator {
|
||||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||
use windows_sys::Win32::System::Memory::*;
|
||||
unsafe { HeapAlloc(GetProcessHeap(), 0, layout.size()) as *mut u8 }
|
||||
}
|
||||
|
||||
unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
|
||||
use windows_sys::Win32::System::Memory::*;
|
||||
unsafe { HeapFree(GetProcessHeap(), 0, ptr as *mut core::ffi::c_void) };
|
||||
}
|
||||
|
||||
unsafe fn realloc(&self, ptr: *mut u8, _layout: Layout, new_size: usize) -> *mut u8 {
|
||||
use windows_sys::Win32::System::Memory::*;
|
||||
unsafe { HeapReAlloc(GetProcessHeap(), 0, ptr as *mut core::ffi::c_void, new_size) as *mut u8 }
|
||||
}
|
||||
}
|
||||
270
demo3d/server/src/bsp.rs
Normal file
270
demo3d/server/src/bsp.rs
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
use alloc::boxed::Box;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::math::Vec3;
|
||||
use crate::bindings::*;
|
||||
|
||||
pub type Vertex = demo3d_bsp_vertex;
|
||||
pub type Edge = demo3d_bsp_edge;
|
||||
pub type Face = demo3d_bsp_face;
|
||||
pub type Plane = demo3d_bsp_plane;
|
||||
pub type Node = demo3d_bsp_node;
|
||||
pub type Leaf = demo3d_bsp_leaf;
|
||||
pub type Portal = demo3d_bsp_portal;
|
||||
pub type CellPortals = demo3d_bsp_cell_portals;
|
||||
|
||||
impl Default for Edge {
|
||||
fn default() -> Self {
|
||||
Self { vertex: [0, 0] }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Face {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
first_edge: 0,
|
||||
lightmap_offset: 0,
|
||||
num_edges: 0,
|
||||
plane_id: 0,
|
||||
side: 0,
|
||||
styles: [0; 4],
|
||||
material_id: 0,
|
||||
aabb_min: Vec3::ZERO,
|
||||
aabb_max: Vec3::ZERO,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Leaf {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
ambient_level: [0; 4],
|
||||
contents: 0,
|
||||
first_marksurface: 0,
|
||||
maxs: [0; 3],
|
||||
mins: [0; 3],
|
||||
num_marksurfaces: 0,
|
||||
visofs: -1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Node {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
children: [0, 0],
|
||||
first_face: 0,
|
||||
maxs: [0; 3],
|
||||
mins: [0; 3],
|
||||
num_faces: 0,
|
||||
plane_id: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Plane {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
dist: 0.0,
|
||||
normal: Vec3::ZERO,
|
||||
type_: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Vertex {
|
||||
fn default() -> Self {
|
||||
Self { position: Vec3::ZERO }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Portal {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
x0: 0.0,
|
||||
z0: 0.0,
|
||||
x1: 0.0,
|
||||
z1: 0.0,
|
||||
target_leaf: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CellPortals {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
portals: [Portal::default(); 4],
|
||||
num_portals: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Face {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
first_edge: self.first_edge,
|
||||
lightmap_offset: self.lightmap_offset,
|
||||
num_edges: self.num_edges,
|
||||
plane_id: self.plane_id,
|
||||
side: self.side,
|
||||
styles: self.styles,
|
||||
material_id: self.material_id,
|
||||
aabb_min: self.aabb_min,
|
||||
aabb_max: self.aabb_max,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Plane {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
dist: self.dist,
|
||||
normal: self.normal,
|
||||
type_: self.type_,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Vertex {
|
||||
fn clone(&self) -> Self {
|
||||
Self { position: self.position }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Bsp {
|
||||
inner: demo3d_bsp,
|
||||
pub cell_portals: Box<[CellPortals]>,
|
||||
pub edges: Box<[Edge]>,
|
||||
pub faces: Box<[Face]>,
|
||||
pub leafs: Box<[Leaf]>,
|
||||
pub marksurfaces: Box<[u16]>,
|
||||
pub nodes: Box<[Node]>,
|
||||
pub planes: Box<[Plane]>,
|
||||
pub surfedges: Box<[i32]>,
|
||||
pub vertex_lights: Box<[u32]>,
|
||||
pub vertices: Box<[Vertex]>,
|
||||
pub visdata: Box<[u8]>,
|
||||
pub heightfield: Box<[f32]>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct BspBuilder {
|
||||
pub cell_portals: Vec<CellPortals>,
|
||||
pub edges: Vec<Edge>,
|
||||
pub faces: Vec<Face>,
|
||||
pub leafs: Vec<Leaf>,
|
||||
pub marksurfaces: Vec<u16>,
|
||||
pub nodes: Vec<Node>,
|
||||
pub planes: Vec<Plane>,
|
||||
pub surfedges: Vec<i32>,
|
||||
pub vertex_lights: Vec<u32>,
|
||||
pub vertices: Vec<Vertex>,
|
||||
pub visdata: Vec<u8>,
|
||||
pub heightfield: Vec<f32>,
|
||||
pub heightfield_w: u16,
|
||||
pub heightfield_h: u16,
|
||||
pub heightfield_ox: f32,
|
||||
pub heightfield_oz: f32,
|
||||
pub heightfield_cell_size: f32,
|
||||
pub bounds_min_x: f32,
|
||||
pub bounds_min_z: f32,
|
||||
pub bounds_max_x: f32,
|
||||
pub bounds_max_z: f32,
|
||||
}
|
||||
|
||||
impl BspBuilder {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BspBuilder> for Bsp {
|
||||
fn from(b: BspBuilder) -> Self {
|
||||
let cell_portals = b.cell_portals.into_boxed_slice();
|
||||
let edges = b.edges.into_boxed_slice();
|
||||
let faces = b.faces.into_boxed_slice();
|
||||
let leafs = b.leafs.into_boxed_slice();
|
||||
let marksurfaces = b.marksurfaces.into_boxed_slice();
|
||||
let nodes = b.nodes.into_boxed_slice();
|
||||
let planes = b.planes.into_boxed_slice();
|
||||
let surfedges = b.surfedges.into_boxed_slice();
|
||||
let vertex_lights = b.vertex_lights.into_boxed_slice();
|
||||
let vertices = b.vertices.into_boxed_slice();
|
||||
let visdata = b.visdata.into_boxed_slice();
|
||||
let heightfield = b.heightfield.into_boxed_slice();
|
||||
|
||||
let inner = demo3d_bsp {
|
||||
cell_portals: if cell_portals.is_empty() { core::ptr::null_mut() } else { cell_portals.as_ptr() as *mut _ },
|
||||
edges: if edges.is_empty() { core::ptr::null_mut() } else { edges.as_ptr() as *mut _ },
|
||||
faces: if faces.is_empty() { core::ptr::null_mut() } else { faces.as_ptr() as *mut _ },
|
||||
leafs: if leafs.is_empty() { core::ptr::null_mut() } else { leafs.as_ptr() as *mut _ },
|
||||
lightdata: core::ptr::null_mut(),
|
||||
lightmaps: core::ptr::null_mut(),
|
||||
marksurfaces: if marksurfaces.is_empty() { core::ptr::null_mut() } else { marksurfaces.as_ptr() as *mut _ },
|
||||
models: core::ptr::null_mut(),
|
||||
nodes: if nodes.is_empty() { core::ptr::null_mut() } else { nodes.as_ptr() as *mut _ },
|
||||
planes: if planes.is_empty() { core::ptr::null_mut() } else { planes.as_ptr() as *mut _ },
|
||||
surfedges: if surfedges.is_empty() { core::ptr::null_mut() } else { surfedges.as_ptr() as *mut _ },
|
||||
vertex_lights: if vertex_lights.is_empty() { core::ptr::null_mut() } else { vertex_lights.as_ptr() as *mut _ },
|
||||
vertices: if vertices.is_empty() { core::ptr::null_mut() } else { vertices.as_ptr() as *mut _ },
|
||||
visdata: if visdata.is_empty() { core::ptr::null_mut() } else { visdata.as_ptr() as *mut _ },
|
||||
heightfield: if heightfield.is_empty() { core::ptr::null_mut() } else { heightfield.as_ptr() as *mut _ },
|
||||
lightdata_size: 0,
|
||||
num_cell_portals: cell_portals.len() as u32,
|
||||
num_edges: edges.len() as u32,
|
||||
num_faces: faces.len() as u32,
|
||||
num_leafs: leafs.len() as u32,
|
||||
num_lightmaps: 0,
|
||||
num_marksurfaces: marksurfaces.len() as u32,
|
||||
num_models: 0,
|
||||
num_nodes: nodes.len() as u32,
|
||||
num_planes: planes.len() as u32,
|
||||
num_surfedges: surfedges.len() as u32,
|
||||
num_vertex_lights: vertex_lights.len() as u32,
|
||||
num_vertices: vertices.len() as u32,
|
||||
num_heightfield: heightfield.len() as u32,
|
||||
heightfield_ox: b.heightfield_ox,
|
||||
heightfield_oz: b.heightfield_oz,
|
||||
heightfield_cell_size: b.heightfield_cell_size,
|
||||
heightfield_w: b.heightfield_w,
|
||||
heightfield_h: b.heightfield_h,
|
||||
visdata_size: visdata.len() as u32,
|
||||
bounds_min_x: b.bounds_min_x,
|
||||
bounds_min_z: b.bounds_min_z,
|
||||
bounds_max_x: b.bounds_max_x,
|
||||
bounds_max_z: b.bounds_max_z,
|
||||
};
|
||||
|
||||
Self {
|
||||
inner,
|
||||
cell_portals,
|
||||
edges,
|
||||
faces,
|
||||
leafs,
|
||||
marksurfaces,
|
||||
nodes,
|
||||
planes,
|
||||
surfedges,
|
||||
vertex_lights,
|
||||
vertices,
|
||||
visdata,
|
||||
heightfield,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Bsp {
|
||||
pub fn as_c_bsp(&self) -> &demo3d_bsp {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
pub fn trace(&self, from: Vec3, to: Vec3, radius: f32) -> Vec3 {
|
||||
unsafe { demo3d_bsp_trace(&self.inner, from, to, radius) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Bsp {
|
||||
fn default() -> Self {
|
||||
BspBuilder::new().into()
|
||||
}
|
||||
}
|
||||
26
demo3d/server/src/chunk.rs
Normal file
26
demo3d/server/src/chunk.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
pub mod stream;
|
||||
|
||||
use crate::bsp::Bsp;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
pub enum ChunkId {
|
||||
Bsp { cx: i32, cz: i32 },
|
||||
}
|
||||
|
||||
pub enum Chunk {
|
||||
Bsp { cx: i32, cz: i32, bsp: Bsp, version: u32 },
|
||||
}
|
||||
|
||||
impl Chunk {
|
||||
pub fn version(&self) -> u32 {
|
||||
match self {
|
||||
Chunk::Bsp { version, .. } => *version,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_bsp(&self) -> Option<&Bsp> {
|
||||
match self {
|
||||
Chunk::Bsp { bsp, .. } => Some(bsp),
|
||||
}
|
||||
}
|
||||
}
|
||||
57
demo3d/server/src/chunk/stream.rs
Normal file
57
demo3d/server/src/chunk/stream.rs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
use alloc::collections::BTreeMap;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::chunk::ChunkId;
|
||||
use crate::transport::ChunkMessage;
|
||||
|
||||
pub struct ClientChunkState {
|
||||
known: BTreeMap<ChunkId, u32>,
|
||||
pending: Vec<ChunkId>,
|
||||
pending_messages: Vec<ChunkMessage>,
|
||||
}
|
||||
|
||||
impl ClientChunkState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
known: BTreeMap::new(),
|
||||
pending: Vec::new(),
|
||||
pending_messages: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request(&mut self, id: ChunkId) {
|
||||
if !self.known.contains_key(&id) && !self.pending.contains(&id) {
|
||||
self.pending.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_pending(&mut self) -> Option<ChunkId> {
|
||||
if self.pending.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(self.pending.remove(0))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn queue_messages(&mut self, msgs: Vec<ChunkMessage>) {
|
||||
self.pending_messages.extend(msgs);
|
||||
}
|
||||
|
||||
pub fn next_message(&mut self) -> Option<ChunkMessage> {
|
||||
if self.pending_messages.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(self.pending_messages.remove(0))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mark_sent(&mut self, id: ChunkId, version: u32) {
|
||||
self.known.insert(id, version);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ClientChunkState {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
38
demo3d/server/src/lib.rs
Normal file
38
demo3d/server/src/lib.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#![no_std]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
mod allocator;
|
||||
pub mod bsp;
|
||||
pub mod chunk;
|
||||
pub mod log;
|
||||
pub mod math;
|
||||
pub mod procgen;
|
||||
pub mod sim;
|
||||
pub mod transport;
|
||||
pub mod world;
|
||||
|
||||
use core::panic::PanicInfo;
|
||||
|
||||
#[global_allocator]
|
||||
static ALLOCATOR: allocator::Allocator = allocator::Allocator;
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(_info: &PanicInfo) -> ! {
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[allow(dead_code, non_camel_case_types, non_snake_case, non_upper_case_globals)]
|
||||
pub mod bindings {
|
||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||
}
|
||||
|
||||
pub use bsp::*;
|
||||
pub use chunk::*;
|
||||
pub use chunk::stream::*;
|
||||
pub use math::*;
|
||||
pub use procgen::generate_chunk;
|
||||
pub use bindings::*;
|
||||
pub use sim::*;
|
||||
pub use transport::*;
|
||||
pub use world::*;
|
||||
139
demo3d/server/src/log.rs
Normal file
139
demo3d/server/src/log.rs
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
use crate::bindings::{pxl8_log, pxl8_log_init};
|
||||
use core::ffi::c_char;
|
||||
|
||||
static mut G_LOG: pxl8_log = pxl8_log {
|
||||
handler: None,
|
||||
level: crate::bindings::pxl8_log_level::PXL8_LOG_LEVEL_DEBUG,
|
||||
};
|
||||
|
||||
pub fn init() {
|
||||
unsafe {
|
||||
pxl8_log_init(&raw mut G_LOG);
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! pxl8_debug {
|
||||
($($arg:tt)*) => {{
|
||||
use ::core::fmt::Write;
|
||||
let mut buf = $crate::log::LogBuffer::new();
|
||||
let _ = ::core::write!(&mut buf, $($arg)*);
|
||||
buf.push(0);
|
||||
unsafe {
|
||||
$crate::bindings::pxl8_log_write_debug(
|
||||
concat!(file!(), "\0").as_ptr() as *const ::core::ffi::c_char,
|
||||
line!() as ::core::ffi::c_int,
|
||||
c"%s".as_ptr() as *const ::core::ffi::c_char,
|
||||
buf.as_ptr(),
|
||||
);
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! pxl8_error {
|
||||
($($arg:tt)*) => {{
|
||||
use ::core::fmt::Write;
|
||||
let mut buf = $crate::log::LogBuffer::new();
|
||||
let _ = ::core::write!(&mut buf, $($arg)*);
|
||||
buf.push(0);
|
||||
unsafe {
|
||||
$crate::bindings::pxl8_log_write_error(
|
||||
concat!(file!(), "\0").as_ptr() as *const ::core::ffi::c_char,
|
||||
line!() as ::core::ffi::c_int,
|
||||
c"%s".as_ptr() as *const ::core::ffi::c_char,
|
||||
buf.as_ptr(),
|
||||
);
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! pxl8_info {
|
||||
($($arg:tt)*) => {{
|
||||
use ::core::fmt::Write;
|
||||
let mut buf = $crate::log::LogBuffer::new();
|
||||
let _ = ::core::write!(&mut buf, $($arg)*);
|
||||
buf.push(0);
|
||||
unsafe {
|
||||
$crate::bindings::pxl8_log_write_info(
|
||||
c"%s".as_ptr() as *const ::core::ffi::c_char,
|
||||
buf.as_ptr(),
|
||||
);
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! pxl8_trace {
|
||||
($($arg:tt)*) => {{
|
||||
use ::core::fmt::Write;
|
||||
let mut buf = $crate::log::LogBuffer::new();
|
||||
let _ = ::core::write!(&mut buf, $($arg)*);
|
||||
buf.push(0);
|
||||
unsafe {
|
||||
$crate::bindings::pxl8_log_write_trace(
|
||||
concat!(file!(), "\0").as_ptr() as *const ::core::ffi::c_char,
|
||||
line!() as ::core::ffi::c_int,
|
||||
c"%s".as_ptr() as *const ::core::ffi::c_char,
|
||||
buf.as_ptr(),
|
||||
);
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! pxl8_warn {
|
||||
($($arg:tt)*) => {{
|
||||
use ::core::fmt::Write;
|
||||
let mut buf = $crate::log::LogBuffer::new();
|
||||
let _ = ::core::write!(&mut buf, $($arg)*);
|
||||
buf.push(0);
|
||||
unsafe {
|
||||
$crate::bindings::pxl8_log_write_warn(
|
||||
concat!(file!(), "\0").as_ptr() as *const ::core::ffi::c_char,
|
||||
line!() as ::core::ffi::c_int,
|
||||
c"%s".as_ptr() as *const ::core::ffi::c_char,
|
||||
buf.as_ptr(),
|
||||
);
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
pub struct LogBuffer {
|
||||
buf: [u8; 1024],
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl LogBuffer {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
buf: [0; 1024],
|
||||
pos: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_ptr(&self) -> *const c_char {
|
||||
self.buf.as_ptr() as *const c_char
|
||||
}
|
||||
|
||||
pub fn push(&mut self, b: u8) {
|
||||
if self.pos < self.buf.len() {
|
||||
self.buf[self.pos] = b;
|
||||
self.pos += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Write for LogBuffer {
|
||||
fn write_str(&mut self, s: &str) -> core::fmt::Result {
|
||||
for b in s.bytes() {
|
||||
if self.pos >= self.buf.len() - 1 {
|
||||
break;
|
||||
}
|
||||
self.buf[self.pos] = b;
|
||||
self.pos += 1;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
323
demo3d/server/src/main.rs
Normal file
323
demo3d/server/src/main.rs
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use demo3d_server::*;
|
||||
use demo3d_server::chunk::ChunkId;
|
||||
use demo3d_server::chunk::stream::ClientChunkState;
|
||||
use demo3d_server::bindings::demo3d_cmd_type::*;
|
||||
use demo3d_server::bindings::demo3d_msg_type::*;
|
||||
|
||||
const TICK_RATE: u64 = 30;
|
||||
const TICK_NS: u64 = 1_000_000_000 / TICK_RATE;
|
||||
|
||||
#[cfg(unix)]
|
||||
fn get_time_ns() -> u64 {
|
||||
let mut ts = libc::timespec { tv_sec: 0, tv_nsec: 0 };
|
||||
unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut ts) };
|
||||
(ts.tv_sec as u64) * 1_000_000_000 + (ts.tv_nsec as u64)
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn get_time_ns() -> u64 {
|
||||
use windows_sys::Win32::System::Performance::*;
|
||||
static mut FREQ: i64 = 0;
|
||||
unsafe {
|
||||
if FREQ == 0 {
|
||||
QueryPerformanceFrequency(&raw mut FREQ);
|
||||
}
|
||||
let mut count: i64 = 0;
|
||||
QueryPerformanceCounter(&mut count);
|
||||
((count as u128 * 1_000_000_000) / FREQ as u128) as u64
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn sleep_ms(ms: u64) {
|
||||
let ts = libc::timespec {
|
||||
tv_sec: (ms / 1000) as i64,
|
||||
tv_nsec: ((ms % 1000) * 1_000_000) as i64,
|
||||
};
|
||||
unsafe { libc::nanosleep(&ts, core::ptr::null_mut()) };
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn sleep_ms(ms: u64) {
|
||||
use windows_sys::Win32::System::Threading::Sleep;
|
||||
unsafe { Sleep(ms as u32) };
|
||||
}
|
||||
|
||||
fn extract_spawn_position(payload: &[u8]) -> (f32, f32, f32, f32, f32) {
|
||||
let x = f32::from_be_bytes([payload[0], payload[1], payload[2], payload[3]]);
|
||||
let y = f32::from_be_bytes([payload[4], payload[5], payload[6], payload[7]]);
|
||||
let z = f32::from_be_bytes([payload[8], payload[9], payload[10], payload[11]]);
|
||||
let yaw = f32::from_be_bytes([payload[12], payload[13], payload[14], payload[15]]);
|
||||
let pitch = f32::from_be_bytes([payload[16], payload[17], payload[18], payload[19]]);
|
||||
(x, y, z, yaw, pitch)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
|
||||
log::init();
|
||||
let mut transport = match transport::Transport::bind(transport::DEFAULT_PORT) {
|
||||
Some(t) => {
|
||||
pxl8_info!("[SERVER] Bound to port 7777");
|
||||
t
|
||||
}
|
||||
None => {
|
||||
pxl8_error!("[SERVER] Failed to bind to port 7777");
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
let mut sim = Simulation::new();
|
||||
let mut player_id: Option<u64> = None;
|
||||
let mut last_client_tick: u64 = 0;
|
||||
let mut client_chunks = ClientChunkState::new();
|
||||
let mut player_cx: i32 = 0;
|
||||
let mut player_cz: i32 = 0;
|
||||
let mut has_sent_initial_enter = false;
|
||||
|
||||
let mut sequence: u32 = 0;
|
||||
let mut last_tick = get_time_ns();
|
||||
let mut entities_buf = [demo3d_server::demo3d_entity_state {
|
||||
entity_id: 0,
|
||||
userdata: [0u8; 56],
|
||||
}; 64];
|
||||
let mut inputs_buf: [demo3d_server::demo3d_input_msg; 16] = unsafe { core::mem::zeroed() };
|
||||
|
||||
loop {
|
||||
let now = get_time_ns();
|
||||
let elapsed = now.saturating_sub(last_tick);
|
||||
|
||||
if elapsed >= TICK_NS {
|
||||
last_tick = now;
|
||||
let dt = (elapsed as f32) / 1_000_000_000.0;
|
||||
|
||||
let mut latest_input: Option<demo3d_server::demo3d_input_msg> = None;
|
||||
while let Some(msg_type) = transport.recv() {
|
||||
match msg_type {
|
||||
x if x == DEMO3D_MSG_INPUT as u8 => {
|
||||
latest_input = Some(transport.get_input());
|
||||
}
|
||||
x if x == DEMO3D_MSG_COMMAND as u8 => {
|
||||
let cmd = transport.get_command();
|
||||
if cmd.cmd_type == DEMO3D_CMD_SPAWN_ENTITY as u16 {
|
||||
let (x, y, z, _yaw, _pitch) = extract_spawn_position(&cmd.payload);
|
||||
player_id = Some(sim.spawn_player(x, y, z) as u64);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(input) = latest_input {
|
||||
last_client_tick = input.tick;
|
||||
inputs_buf[0] = input;
|
||||
sim.step(&inputs_buf[..1], dt);
|
||||
} else {
|
||||
sim.step(&[], dt);
|
||||
}
|
||||
|
||||
let mut count = 0;
|
||||
sim.generate_snapshot(|state| {
|
||||
if count < entities_buf.len() {
|
||||
entities_buf[count] = *state;
|
||||
count += 1;
|
||||
}
|
||||
});
|
||||
|
||||
let header = demo3d_server::demo3d_snapshot_header {
|
||||
entity_count: count as u16,
|
||||
event_count: 0,
|
||||
player_id: player_id.unwrap_or(0),
|
||||
tick: last_client_tick,
|
||||
time: sim.time,
|
||||
};
|
||||
|
||||
transport.send_snapshot(&header, &entities_buf[..count], sequence);
|
||||
sequence = sequence.wrapping_add(1);
|
||||
|
||||
if let Some(pid) = player_id {
|
||||
if let Some((px, _py, pz)) = sim.get_player_position(pid) {
|
||||
let new_cx = libm::floorf(px / 1024.0) as i32;
|
||||
let new_cz = libm::floorf(pz / 1024.0) as i32;
|
||||
|
||||
for dz in -2..=2_i32 {
|
||||
for dx in -2..=2_i32 {
|
||||
let cx = new_cx + dx;
|
||||
let cz = new_cz + dz;
|
||||
sim.world.generate_bsp(cx, cz);
|
||||
client_chunks.request(ChunkId::Bsp { cx, cz });
|
||||
}
|
||||
}
|
||||
|
||||
if !has_sent_initial_enter || player_cx != new_cx || player_cz != new_cz {
|
||||
player_cx = new_cx;
|
||||
player_cz = new_cz;
|
||||
sim.world.set_active(ChunkId::Bsp { cx: new_cx, cz: new_cz });
|
||||
transport.send_chunk_enter(new_cx, new_cz, sequence);
|
||||
sequence = sequence.wrapping_add(1);
|
||||
has_sent_initial_enter = true;
|
||||
}
|
||||
|
||||
let mut burst = false;
|
||||
while let Some(chunk_id) = client_chunks.next_pending() {
|
||||
match chunk_id {
|
||||
ChunkId::Bsp { cx, cz } => {
|
||||
if let Some(chunk) = sim.world.get(&chunk_id) {
|
||||
if let Some(bsp) = chunk.as_bsp() {
|
||||
let msgs = bsp_to_messages(bsp, cx, cz, chunk.version());
|
||||
client_chunks.queue_messages(msgs);
|
||||
client_chunks.mark_sent(chunk_id, chunk.version());
|
||||
burst = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let send_limit = if burst { 64 } else { 8 };
|
||||
for _ in 0..send_limit {
|
||||
if let Some(msg) = client_chunks.next_message() {
|
||||
transport.send_chunk(&msg, sequence);
|
||||
sequence = sequence.wrapping_add(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sleep_ms(1);
|
||||
}
|
||||
}
|
||||
|
||||
fn bsp_to_messages(bsp: &bsp::Bsp, cx: i32, cz: i32, version: u32) -> Vec<transport::ChunkMessage> {
|
||||
let mut data = Vec::new();
|
||||
|
||||
data.extend_from_slice(&(bsp.vertices.len() as u32).to_be_bytes());
|
||||
data.extend_from_slice(&(bsp.edges.len() as u32).to_be_bytes());
|
||||
data.extend_from_slice(&(bsp.faces.len() as u32).to_be_bytes());
|
||||
data.extend_from_slice(&(bsp.planes.len() as u32).to_be_bytes());
|
||||
data.extend_from_slice(&(bsp.nodes.len() as u32).to_be_bytes());
|
||||
data.extend_from_slice(&(bsp.leafs.len() as u32).to_be_bytes());
|
||||
data.extend_from_slice(&(bsp.surfedges.len() as u32).to_be_bytes());
|
||||
data.extend_from_slice(&(bsp.marksurfaces.len() as u32).to_be_bytes());
|
||||
data.extend_from_slice(&(bsp.cell_portals.len() as u32).to_be_bytes());
|
||||
data.extend_from_slice(&(bsp.visdata.len() as u32).to_be_bytes());
|
||||
data.extend_from_slice(&(bsp.vertex_lights.len() as u32).to_be_bytes());
|
||||
data.extend_from_slice(&(bsp.heightfield.len() as u32).to_be_bytes());
|
||||
|
||||
for v in &bsp.vertices {
|
||||
data.extend_from_slice(&v.position.x.to_be_bytes());
|
||||
data.extend_from_slice(&v.position.y.to_be_bytes());
|
||||
data.extend_from_slice(&v.position.z.to_be_bytes());
|
||||
}
|
||||
|
||||
for e in &bsp.edges {
|
||||
data.extend_from_slice(&e.vertex[0].to_be_bytes());
|
||||
data.extend_from_slice(&e.vertex[1].to_be_bytes());
|
||||
}
|
||||
|
||||
for se in &bsp.surfedges {
|
||||
data.extend_from_slice(&se.to_be_bytes());
|
||||
}
|
||||
|
||||
for p in &bsp.planes {
|
||||
data.extend_from_slice(&p.normal.x.to_be_bytes());
|
||||
data.extend_from_slice(&p.normal.y.to_be_bytes());
|
||||
data.extend_from_slice(&p.normal.z.to_be_bytes());
|
||||
data.extend_from_slice(&p.dist.to_be_bytes());
|
||||
data.extend_from_slice(&p.type_.to_be_bytes());
|
||||
}
|
||||
|
||||
for f in &bsp.faces {
|
||||
data.extend_from_slice(&f.first_edge.to_be_bytes());
|
||||
data.extend_from_slice(&f.lightmap_offset.to_be_bytes());
|
||||
data.extend_from_slice(&f.num_edges.to_be_bytes());
|
||||
data.extend_from_slice(&f.plane_id.to_be_bytes());
|
||||
data.extend_from_slice(&f.side.to_be_bytes());
|
||||
data.extend_from_slice(&f.styles);
|
||||
data.extend_from_slice(&f.material_id.to_be_bytes());
|
||||
data.extend_from_slice(&f.aabb_min.x.to_be_bytes());
|
||||
data.extend_from_slice(&f.aabb_min.y.to_be_bytes());
|
||||
data.extend_from_slice(&f.aabb_min.z.to_be_bytes());
|
||||
data.extend_from_slice(&f.aabb_max.x.to_be_bytes());
|
||||
data.extend_from_slice(&f.aabb_max.y.to_be_bytes());
|
||||
data.extend_from_slice(&f.aabb_max.z.to_be_bytes());
|
||||
}
|
||||
|
||||
for n in &bsp.nodes {
|
||||
data.extend_from_slice(&n.children[0].to_be_bytes());
|
||||
data.extend_from_slice(&n.children[1].to_be_bytes());
|
||||
data.extend_from_slice(&n.first_face.to_be_bytes());
|
||||
data.extend_from_slice(&n.maxs[0].to_be_bytes());
|
||||
data.extend_from_slice(&n.maxs[1].to_be_bytes());
|
||||
data.extend_from_slice(&n.maxs[2].to_be_bytes());
|
||||
data.extend_from_slice(&n.mins[0].to_be_bytes());
|
||||
data.extend_from_slice(&n.mins[1].to_be_bytes());
|
||||
data.extend_from_slice(&n.mins[2].to_be_bytes());
|
||||
data.extend_from_slice(&n.num_faces.to_be_bytes());
|
||||
data.extend_from_slice(&n.plane_id.to_be_bytes());
|
||||
}
|
||||
|
||||
for l in &bsp.leafs {
|
||||
data.extend_from_slice(&l.ambient_level);
|
||||
data.extend_from_slice(&l.contents.to_be_bytes());
|
||||
data.extend_from_slice(&l.first_marksurface.to_be_bytes());
|
||||
data.extend_from_slice(&l.maxs[0].to_be_bytes());
|
||||
data.extend_from_slice(&l.maxs[1].to_be_bytes());
|
||||
data.extend_from_slice(&l.maxs[2].to_be_bytes());
|
||||
data.extend_from_slice(&l.mins[0].to_be_bytes());
|
||||
data.extend_from_slice(&l.mins[1].to_be_bytes());
|
||||
data.extend_from_slice(&l.mins[2].to_be_bytes());
|
||||
data.extend_from_slice(&l.num_marksurfaces.to_be_bytes());
|
||||
data.extend_from_slice(&l.visofs.to_be_bytes());
|
||||
}
|
||||
|
||||
for ms in &bsp.marksurfaces {
|
||||
data.extend_from_slice(&ms.to_be_bytes());
|
||||
}
|
||||
|
||||
for cp in &bsp.cell_portals {
|
||||
data.push(cp.num_portals);
|
||||
data.push(0);
|
||||
data.push(0);
|
||||
data.push(0);
|
||||
for i in 0..4 {
|
||||
data.extend_from_slice(&cp.portals[i].x0.to_be_bytes());
|
||||
data.extend_from_slice(&cp.portals[i].z0.to_be_bytes());
|
||||
data.extend_from_slice(&cp.portals[i].x1.to_be_bytes());
|
||||
data.extend_from_slice(&cp.portals[i].z1.to_be_bytes());
|
||||
data.extend_from_slice(&cp.portals[i].target_leaf.to_be_bytes());
|
||||
}
|
||||
}
|
||||
|
||||
data.extend_from_slice(&bsp.visdata);
|
||||
|
||||
for vl in &bsp.vertex_lights {
|
||||
data.extend_from_slice(&vl.to_be_bytes());
|
||||
}
|
||||
|
||||
if !bsp.heightfield.is_empty() {
|
||||
for h in &bsp.heightfield {
|
||||
data.extend_from_slice(&h.to_be_bytes());
|
||||
}
|
||||
let c_bsp = bsp.as_c_bsp();
|
||||
data.extend_from_slice(&c_bsp.heightfield_w.to_be_bytes());
|
||||
data.extend_from_slice(&c_bsp.heightfield_h.to_be_bytes());
|
||||
data.extend_from_slice(&c_bsp.heightfield_ox.to_be_bytes());
|
||||
data.extend_from_slice(&c_bsp.heightfield_oz.to_be_bytes());
|
||||
data.extend_from_slice(&c_bsp.heightfield_cell_size.to_be_bytes());
|
||||
}
|
||||
|
||||
let c_bsp = bsp.as_c_bsp();
|
||||
data.extend_from_slice(&c_bsp.bounds_min_x.to_be_bytes());
|
||||
data.extend_from_slice(&c_bsp.bounds_min_z.to_be_bytes());
|
||||
data.extend_from_slice(&c_bsp.bounds_max_x.to_be_bytes());
|
||||
data.extend_from_slice(&c_bsp.bounds_max_z.to_be_bytes());
|
||||
|
||||
transport::ChunkMessage::from_bsp(data, cx, cz, version)
|
||||
}
|
||||
106
demo3d/server/src/math.rs
Normal file
106
demo3d/server/src/math.rs
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
use core::ops::{Add, Mul, Sub};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
pub struct Vec2 {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
pub struct Vec3 {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub z: f32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
pub struct Vec4 {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub z: f32,
|
||||
pub w: f32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
pub struct Mat4 {
|
||||
pub m: [f32; 16],
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types)]
|
||||
pub type pxl8_vec2 = Vec2;
|
||||
#[allow(non_camel_case_types)]
|
||||
pub type pxl8_vec3 = Vec3;
|
||||
#[allow(non_camel_case_types)]
|
||||
pub type pxl8_vec4 = Vec4;
|
||||
#[allow(non_camel_case_types)]
|
||||
pub type pxl8_mat4 = Mat4;
|
||||
|
||||
impl Vec3 {
|
||||
pub const ZERO: Vec3 = Vec3 { x: 0.0, y: 0.0, z: 0.0 };
|
||||
pub const Y: Vec3 = Vec3 { x: 0.0, y: 1.0, z: 0.0 };
|
||||
|
||||
pub fn new(x: f32, y: f32, z: f32) -> Self {
|
||||
Self { x, y, z }
|
||||
}
|
||||
|
||||
pub fn dot(self, rhs: Self) -> f32 {
|
||||
self.x * rhs.x + self.y * rhs.y + self.z * rhs.z
|
||||
}
|
||||
|
||||
pub fn cross(self, rhs: Self) -> Self {
|
||||
Self {
|
||||
x: self.y * rhs.z - self.z * rhs.y,
|
||||
y: self.z * rhs.x - self.x * rhs.z,
|
||||
z: self.x * rhs.y - self.y * rhs.x,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn length(self) -> f32 {
|
||||
libm::sqrtf(self.dot(self))
|
||||
}
|
||||
|
||||
pub fn normalize(self) -> Self {
|
||||
let len_sq = self.dot(self);
|
||||
if len_sq < 1e-12 {
|
||||
return Self::ZERO;
|
||||
}
|
||||
self * (1.0 / libm::sqrtf(len_sq))
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Vec3 {
|
||||
type Output = Self;
|
||||
fn add(self, rhs: Self) -> Self {
|
||||
Self {
|
||||
x: self.x + rhs.x,
|
||||
y: self.y + rhs.y,
|
||||
z: self.z + rhs.z,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Vec3 {
|
||||
type Output = Self;
|
||||
fn sub(self, rhs: Self) -> Self {
|
||||
Self {
|
||||
x: self.x - rhs.x,
|
||||
y: self.y - rhs.y,
|
||||
z: self.z - rhs.z,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<f32> for Vec3 {
|
||||
type Output = Self;
|
||||
fn mul(self, rhs: f32) -> Self {
|
||||
Self {
|
||||
x: self.x * rhs,
|
||||
y: self.y * rhs,
|
||||
z: self.z * rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
2231
demo3d/server/src/procgen.rs
Normal file
2231
demo3d/server/src/procgen.rs
Normal file
File diff suppressed because it is too large
Load diff
229
demo3d/server/src/sim.rs
Normal file
229
demo3d/server/src/sim.rs
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
use alloc::vec::Vec;
|
||||
|
||||
use crate::chunk::ChunkId;
|
||||
use crate::math::Vec3;
|
||||
use crate::bindings::*;
|
||||
use crate::world::World;
|
||||
|
||||
pub type Entity = demo3d_sim_entity;
|
||||
|
||||
const ALIVE: u32 = DEMO3D_SIM_FLAG_ALIVE;
|
||||
const PLAYER: u32 = DEMO3D_SIM_FLAG_PLAYER;
|
||||
|
||||
const MAX_ENTITIES: usize = 1024;
|
||||
|
||||
impl Default for Entity {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
pos: Vec3::ZERO,
|
||||
vel: Vec3::ZERO,
|
||||
yaw: 0.0,
|
||||
pitch: 0.0,
|
||||
flags: 0,
|
||||
kind: 0,
|
||||
_pad: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Entity {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
pos: self.pos,
|
||||
vel: self.vel,
|
||||
yaw: self.yaw,
|
||||
pitch: self.pitch,
|
||||
flags: self.flags,
|
||||
kind: self.kind,
|
||||
_pad: self._pad,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Simulation {
|
||||
pub entities: Vec<Entity>,
|
||||
pub free_list: Vec<u32>,
|
||||
pub player: Option<u32>,
|
||||
pub tick: u64,
|
||||
pub time: f32,
|
||||
pub world: World,
|
||||
}
|
||||
|
||||
impl Simulation {
|
||||
pub fn new() -> Self {
|
||||
let mut entities = Vec::with_capacity(MAX_ENTITIES);
|
||||
entities.resize(MAX_ENTITIES, Entity::default());
|
||||
|
||||
let mut free_list = Vec::with_capacity(MAX_ENTITIES);
|
||||
for i in (0..MAX_ENTITIES).rev() {
|
||||
free_list.push(i as u32);
|
||||
}
|
||||
|
||||
Self {
|
||||
entities,
|
||||
free_list,
|
||||
player: None,
|
||||
tick: 0,
|
||||
time: 0.0,
|
||||
world: World::new(12345),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn(&mut self) -> Option<u32> {
|
||||
let idx = self.free_list.pop()?;
|
||||
self.entities[idx as usize] = Entity {
|
||||
flags: ALIVE,
|
||||
..Default::default()
|
||||
};
|
||||
Some(idx)
|
||||
}
|
||||
|
||||
pub fn despawn(&mut self, id: u32) {
|
||||
if (id as usize) < self.entities.len() {
|
||||
self.entities[id as usize].flags = 0;
|
||||
self.free_list.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn_player(&mut self, x: f32, y: f32, z: f32) -> u32 {
|
||||
let id = self.spawn().unwrap_or(0);
|
||||
let ent = &mut self.entities[id as usize];
|
||||
ent.flags = ALIVE | PLAYER;
|
||||
ent.pos = Vec3::new(x, y, z);
|
||||
self.player = Some(id);
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
pub fn step(&mut self, inputs: &[demo3d_input_msg], dt: f32) {
|
||||
self.tick += 1;
|
||||
self.time += dt;
|
||||
|
||||
for input in inputs {
|
||||
self.move_player(input, dt);
|
||||
}
|
||||
|
||||
self.integrate(dt);
|
||||
}
|
||||
|
||||
fn sim_config() -> demo3d_sim_config {
|
||||
demo3d_sim_config {
|
||||
move_speed: 180.0,
|
||||
ground_accel: 10.0,
|
||||
air_accel: 1.0,
|
||||
stop_speed: 100.0,
|
||||
friction: 6.0,
|
||||
gravity: 800.0,
|
||||
jump_velocity: 200.0,
|
||||
player_radius: 16.0,
|
||||
player_height: 72.0,
|
||||
max_pitch: 1.5,
|
||||
}
|
||||
}
|
||||
|
||||
fn make_sim_world(&self, player_pos: Vec3) -> demo3d_sim_world {
|
||||
let chunk_size: f32 = 16.0 * 64.0;
|
||||
let center_cx = libm::floorf(player_pos.x / chunk_size) as i32;
|
||||
let center_cz = libm::floorf(player_pos.z / chunk_size) as i32;
|
||||
|
||||
let mut sim = demo3d_sim_world {
|
||||
chunks: [core::ptr::null(); 9],
|
||||
center_cx,
|
||||
center_cz,
|
||||
chunk_size,
|
||||
};
|
||||
|
||||
for dz in -1..=1i32 {
|
||||
for dx in -1..=1i32 {
|
||||
let cx = center_cx + dx;
|
||||
let cz = center_cz + dz;
|
||||
let id = ChunkId::Bsp { cx, cz };
|
||||
if let Some(chunk) = self.world.get(&id) {
|
||||
if let Some(bsp) = chunk.as_bsp() {
|
||||
let idx = ((dz + 1) * 3 + (dx + 1)) as usize;
|
||||
sim.chunks[idx] = bsp.as_c_bsp();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sim
|
||||
}
|
||||
|
||||
fn integrate(&mut self, dt: f32) {
|
||||
let cfg = Self::sim_config();
|
||||
for i in 0..self.entities.len() {
|
||||
let ent = &self.entities[i];
|
||||
if ent.flags & ALIVE == 0 || ent.flags & PLAYER != 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let world = self.make_sim_world(ent.pos);
|
||||
let ent = &mut self.entities[i];
|
||||
unsafe {
|
||||
demo3d_sim_integrate(ent, &world, &cfg, dt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn move_player(&mut self, input: &demo3d_input_msg, dt: f32) {
|
||||
let cfg = Self::sim_config();
|
||||
let Some(id) = self.player else { return };
|
||||
let ent = &self.entities[id as usize];
|
||||
if ent.flags & ALIVE == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let world = self.make_sim_world(ent.pos);
|
||||
let ent = &mut self.entities[id as usize];
|
||||
unsafe {
|
||||
demo3d_sim_move_player(ent, input, &world, &cfg, dt);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_snapshot<F>(&self, mut writer: F)
|
||||
where
|
||||
F: FnMut(&demo3d_entity_state),
|
||||
{
|
||||
for (i, ent) in self.entities.iter().enumerate() {
|
||||
if ent.flags & ALIVE == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut state = demo3d_entity_state {
|
||||
entity_id: i as u64,
|
||||
userdata: [0u8; 56],
|
||||
};
|
||||
|
||||
let bytes = &mut state.userdata;
|
||||
bytes[0..4].copy_from_slice(&ent.pos.x.to_be_bytes());
|
||||
bytes[4..8].copy_from_slice(&ent.pos.y.to_be_bytes());
|
||||
bytes[8..12].copy_from_slice(&ent.pos.z.to_be_bytes());
|
||||
bytes[12..16].copy_from_slice(&ent.yaw.to_be_bytes());
|
||||
bytes[16..20].copy_from_slice(&ent.pitch.to_be_bytes());
|
||||
bytes[20..24].copy_from_slice(&ent.vel.x.to_be_bytes());
|
||||
bytes[24..28].copy_from_slice(&ent.vel.y.to_be_bytes());
|
||||
bytes[28..32].copy_from_slice(&ent.vel.z.to_be_bytes());
|
||||
bytes[32..36].copy_from_slice(&ent.flags.to_be_bytes());
|
||||
bytes[36..38].copy_from_slice(&ent.kind.to_be_bytes());
|
||||
|
||||
writer(&state);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_player_position(&self, player_id: u64) -> Option<(f32, f32, f32)> {
|
||||
let idx = player_id as usize;
|
||||
if idx < self.entities.len() {
|
||||
let ent = &self.entities[idx];
|
||||
if ent.flags & ALIVE != 0 && ent.flags & PLAYER != 0 {
|
||||
return Some((ent.pos.x, ent.pos.y, ent.pos.z));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Simulation {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
488
demo3d/server/src/transport.rs
Normal file
488
demo3d/server/src/transport.rs
Normal file
|
|
@ -0,0 +1,488 @@
|
|||
use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
use crate::bindings::*;
|
||||
use crate::bindings::demo3d_msg_type::*;
|
||||
pub const DEFAULT_PORT: u16 = 7777;
|
||||
pub const CHUNK_MAX_PAYLOAD: usize = 1400;
|
||||
pub const CHUNK_FLAG_FINAL: u8 = 0x04;
|
||||
|
||||
pub const CHUNK_TYPE_BSP: u8 = 1;
|
||||
|
||||
pub fn chunk_hash(cx: i32, cz: i32) -> u32 {
|
||||
let h = (cx as u32).wrapping_mul(374761393).wrapping_add((cz as u32).wrapping_mul(668265263));
|
||||
h ^ (h >> 16)
|
||||
}
|
||||
|
||||
pub struct ChunkMessage {
|
||||
pub chunk_type: u8,
|
||||
pub id: u32,
|
||||
pub cx: i32,
|
||||
pub cy: i32,
|
||||
pub cz: i32,
|
||||
pub version: u32,
|
||||
pub flags: u8,
|
||||
pub fragment_idx: u8,
|
||||
pub fragment_count: u8,
|
||||
pub payload: Vec<u8>,
|
||||
}
|
||||
|
||||
impl ChunkMessage {
|
||||
pub fn from_bsp(data: Vec<u8>, cx: i32, cz: i32, version: u32) -> Vec<ChunkMessage> {
|
||||
let total_size = data.len();
|
||||
let id = chunk_hash(cx, cz);
|
||||
|
||||
if total_size <= CHUNK_MAX_PAYLOAD {
|
||||
return vec![ChunkMessage {
|
||||
chunk_type: CHUNK_TYPE_BSP,
|
||||
id,
|
||||
cx,
|
||||
cy: 0,
|
||||
cz,
|
||||
version,
|
||||
flags: CHUNK_FLAG_FINAL,
|
||||
fragment_idx: 0,
|
||||
fragment_count: 1,
|
||||
payload: data,
|
||||
}];
|
||||
}
|
||||
|
||||
let fragment_count = (total_size + CHUNK_MAX_PAYLOAD - 1) / CHUNK_MAX_PAYLOAD;
|
||||
let mut messages = Vec::new();
|
||||
|
||||
for i in 0..fragment_count {
|
||||
let start = i * CHUNK_MAX_PAYLOAD;
|
||||
let end = ((i + 1) * CHUNK_MAX_PAYLOAD).min(total_size);
|
||||
let is_final = i == fragment_count - 1;
|
||||
|
||||
messages.push(ChunkMessage {
|
||||
chunk_type: CHUNK_TYPE_BSP,
|
||||
id,
|
||||
cx,
|
||||
cy: 0,
|
||||
cz,
|
||||
version,
|
||||
flags: if is_final { CHUNK_FLAG_FINAL } else { 0 },
|
||||
fragment_idx: i as u8,
|
||||
fragment_count: fragment_count as u8,
|
||||
payload: data[start..end].to_vec(),
|
||||
});
|
||||
}
|
||||
|
||||
messages
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
mod sys {
|
||||
use libc::{c_int, c_void, sockaddr, sockaddr_in, socklen_t};
|
||||
|
||||
pub type RawSocket = c_int;
|
||||
pub const INVALID_SOCKET: RawSocket = -1;
|
||||
|
||||
pub fn init() {}
|
||||
|
||||
pub fn socket() -> RawSocket {
|
||||
unsafe { libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0) }
|
||||
}
|
||||
|
||||
pub fn bind(sock: RawSocket, port: u16) -> c_int {
|
||||
let addr = sockaddr_in {
|
||||
sin_family: libc::AF_INET as u16,
|
||||
sin_port: port.to_be(),
|
||||
sin_addr: libc::in_addr { s_addr: 0 },
|
||||
sin_zero: [0; 8],
|
||||
};
|
||||
unsafe { libc::bind(sock, &addr as *const _ as *const sockaddr, core::mem::size_of::<sockaddr_in>() as socklen_t) }
|
||||
}
|
||||
|
||||
pub fn set_nonblocking(sock: RawSocket) -> c_int {
|
||||
unsafe {
|
||||
let flags = libc::fcntl(sock, libc::F_GETFL, 0);
|
||||
libc::fcntl(sock, libc::F_SETFL, flags | libc::O_NONBLOCK)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recvfrom(sock: RawSocket, buf: &mut [u8], addr: &mut sockaddr_in) -> isize {
|
||||
let mut addr_len = core::mem::size_of::<sockaddr_in>() as socklen_t;
|
||||
unsafe {
|
||||
libc::recvfrom(
|
||||
sock,
|
||||
buf.as_mut_ptr() as *mut c_void,
|
||||
buf.len(),
|
||||
0,
|
||||
addr as *mut _ as *mut sockaddr,
|
||||
&mut addr_len,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sendto(sock: RawSocket, buf: &[u8], addr: &sockaddr_in) -> isize {
|
||||
unsafe {
|
||||
libc::sendto(
|
||||
sock,
|
||||
buf.as_ptr() as *const c_void,
|
||||
buf.len(),
|
||||
0,
|
||||
addr as *const _ as *const sockaddr,
|
||||
core::mem::size_of::<sockaddr_in>() as socklen_t,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close(sock: RawSocket) {
|
||||
unsafe { libc::close(sock) };
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
mod sys {
|
||||
use libc::{c_int, c_void, sockaddr, sockaddr_in, socklen_t};
|
||||
|
||||
pub type RawSocket = c_int;
|
||||
pub const INVALID_SOCKET: RawSocket = -1;
|
||||
|
||||
pub fn init() {}
|
||||
|
||||
pub fn socket() -> RawSocket {
|
||||
unsafe { libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0) }
|
||||
}
|
||||
|
||||
pub fn bind(sock: RawSocket, port: u16) -> c_int {
|
||||
let addr = sockaddr_in {
|
||||
sin_len: core::mem::size_of::<sockaddr_in>() as u8,
|
||||
sin_family: libc::AF_INET as u8,
|
||||
sin_port: port.to_be(),
|
||||
sin_addr: libc::in_addr { s_addr: 0 },
|
||||
sin_zero: [0; 8],
|
||||
};
|
||||
unsafe { libc::bind(sock, &addr as *const _ as *const sockaddr, core::mem::size_of::<sockaddr_in>() as socklen_t) }
|
||||
}
|
||||
|
||||
pub fn set_nonblocking(sock: RawSocket) -> c_int {
|
||||
unsafe {
|
||||
let flags = libc::fcntl(sock, libc::F_GETFL, 0);
|
||||
libc::fcntl(sock, libc::F_SETFL, flags | libc::O_NONBLOCK)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recvfrom(sock: RawSocket, buf: &mut [u8], addr: &mut sockaddr_in) -> isize {
|
||||
let mut addr_len = core::mem::size_of::<sockaddr_in>() as socklen_t;
|
||||
unsafe {
|
||||
libc::recvfrom(
|
||||
sock,
|
||||
buf.as_mut_ptr() as *mut c_void,
|
||||
buf.len(),
|
||||
0,
|
||||
addr as *mut _ as *mut sockaddr,
|
||||
&mut addr_len,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sendto(sock: RawSocket, buf: &[u8], addr: &sockaddr_in) -> isize {
|
||||
unsafe {
|
||||
libc::sendto(
|
||||
sock,
|
||||
buf.as_ptr() as *const c_void,
|
||||
buf.len(),
|
||||
0,
|
||||
addr as *const _ as *const sockaddr,
|
||||
core::mem::size_of::<sockaddr_in>() as socklen_t,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close(sock: RawSocket) {
|
||||
unsafe { libc::close(sock) };
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
mod sys {
|
||||
use windows_sys::Win32::Networking::WinSock as ws;
|
||||
|
||||
pub type RawSocket = ws::SOCKET;
|
||||
pub const INVALID_SOCKET: RawSocket = ws::INVALID_SOCKET;
|
||||
|
||||
pub fn init() {
|
||||
unsafe {
|
||||
let mut wsa: ws::WSADATA = core::mem::zeroed();
|
||||
ws::WSAStartup(0x0202, &mut wsa);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn socket() -> RawSocket {
|
||||
unsafe { ws::socket(ws::AF_INET as i32, ws::SOCK_DGRAM as i32, 0) }
|
||||
}
|
||||
|
||||
pub fn bind(sock: RawSocket, port: u16) -> i32 {
|
||||
let addr = ws::SOCKADDR_IN {
|
||||
sin_family: ws::AF_INET,
|
||||
sin_port: port.to_be(),
|
||||
sin_addr: ws::IN_ADDR { S_un: ws::IN_ADDR_0 { S_addr: 0 } },
|
||||
sin_zero: [0; 8],
|
||||
};
|
||||
unsafe { ws::bind(sock, &addr as *const _ as *const ws::SOCKADDR, core::mem::size_of::<ws::SOCKADDR_IN>() as i32) }
|
||||
}
|
||||
|
||||
pub fn set_nonblocking(sock: RawSocket) -> i32 {
|
||||
let mut nonblocking: u32 = 1;
|
||||
unsafe { ws::ioctlsocket(sock, ws::FIONBIO as i32, &mut nonblocking) }
|
||||
}
|
||||
|
||||
pub fn recvfrom(sock: RawSocket, buf: &mut [u8], addr: &mut ws::SOCKADDR_IN) -> i32 {
|
||||
let mut addr_len = core::mem::size_of::<ws::SOCKADDR_IN>() as i32;
|
||||
unsafe {
|
||||
ws::recvfrom(
|
||||
sock,
|
||||
buf.as_mut_ptr(),
|
||||
buf.len() as i32,
|
||||
0,
|
||||
addr as *mut _ as *mut ws::SOCKADDR,
|
||||
&mut addr_len,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sendto(sock: RawSocket, buf: &[u8], addr: &ws::SOCKADDR_IN) -> i32 {
|
||||
unsafe {
|
||||
ws::sendto(
|
||||
sock,
|
||||
buf.as_ptr(),
|
||||
buf.len() as i32,
|
||||
0,
|
||||
addr as *const _ as *const ws::SOCKADDR,
|
||||
core::mem::size_of::<ws::SOCKADDR_IN>() as i32,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close(sock: RawSocket) {
|
||||
unsafe { ws::closesocket(sock) };
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
type SockAddr = libc::sockaddr_in;
|
||||
|
||||
#[cfg(windows)]
|
||||
type SockAddr = windows_sys::Win32::Networking::WinSock::SOCKADDR_IN;
|
||||
|
||||
|
||||
pub struct Transport {
|
||||
client_addr: SockAddr,
|
||||
has_client: bool,
|
||||
recv_buf: [u8; 512],
|
||||
send_buf: [u8; 2048],
|
||||
socket: sys::RawSocket,
|
||||
}
|
||||
|
||||
impl Transport {
|
||||
pub fn bind(port: u16) -> Option<Self> {
|
||||
sys::init();
|
||||
let sock = sys::socket();
|
||||
if sock == sys::INVALID_SOCKET {
|
||||
return None;
|
||||
}
|
||||
|
||||
if sys::bind(sock, port) < 0 {
|
||||
sys::close(sock);
|
||||
return None;
|
||||
}
|
||||
|
||||
if sys::set_nonblocking(sock) < 0 {
|
||||
sys::close(sock);
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Self {
|
||||
client_addr: unsafe { core::mem::zeroed() },
|
||||
has_client: false,
|
||||
recv_buf: [0u8; 512],
|
||||
send_buf: [0u8; 2048],
|
||||
socket: sock,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn recv(&mut self) -> Option<u8> {
|
||||
let mut addr: SockAddr = unsafe { core::mem::zeroed() };
|
||||
let len = sys::recvfrom(self.socket, &mut self.recv_buf, &mut addr);
|
||||
|
||||
if len <= 0 || (len as usize) < size_of::<demo3d_msg_header>() {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.client_addr = addr;
|
||||
self.has_client = true;
|
||||
|
||||
let header = self.deserialize_header();
|
||||
Some(header.type_)
|
||||
}
|
||||
|
||||
pub fn get_input(&self) -> demo3d_input_msg {
|
||||
self.deserialize_input()
|
||||
}
|
||||
|
||||
pub fn get_command(&self) -> demo3d_command_msg {
|
||||
self.deserialize_command()
|
||||
}
|
||||
|
||||
pub fn send_snapshot(
|
||||
&mut self,
|
||||
header: &demo3d_snapshot_header,
|
||||
entities: &[demo3d_entity_state],
|
||||
sequence: u32,
|
||||
) {
|
||||
if !self.has_client {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut offset = 0;
|
||||
|
||||
let msg_header = demo3d_msg_header {
|
||||
sequence,
|
||||
size: 0,
|
||||
type_: DEMO3D_MSG_SNAPSHOT as u8,
|
||||
version: DEMO3D_PROTOCOL_VERSION as u8,
|
||||
};
|
||||
offset += self.serialize_header(&msg_header, offset);
|
||||
offset += self.serialize_snapshot_header(header, offset);
|
||||
|
||||
for entity in entities {
|
||||
offset += self.serialize_entity_state(entity, offset);
|
||||
}
|
||||
|
||||
sys::sendto(self.socket, &self.send_buf[..offset], &self.client_addr);
|
||||
}
|
||||
|
||||
pub fn send_chunk(&mut self, msg: &ChunkMessage, sequence: u32) {
|
||||
if !self.has_client {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut offset = 0;
|
||||
|
||||
let msg_header = demo3d_msg_header {
|
||||
sequence,
|
||||
size: 0,
|
||||
type_: DEMO3D_MSG_CHUNK as u8,
|
||||
version: DEMO3D_PROTOCOL_VERSION as u8,
|
||||
};
|
||||
offset += self.serialize_header(&msg_header, offset);
|
||||
offset += self.serialize_chunk_msg_header(msg, offset);
|
||||
|
||||
let payload_len = msg.payload.len().min(self.send_buf.len() - offset);
|
||||
self.send_buf[offset..offset + payload_len].copy_from_slice(&msg.payload[..payload_len]);
|
||||
offset += payload_len;
|
||||
|
||||
sys::sendto(self.socket, &self.send_buf[..offset], &self.client_addr);
|
||||
}
|
||||
|
||||
pub fn send_chunk_enter(&mut self, cx: i32, cz: i32, sequence: u32) {
|
||||
if !self.has_client {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut offset = 0;
|
||||
|
||||
let msg_header = demo3d_msg_header {
|
||||
sequence,
|
||||
size: 0,
|
||||
type_: DEMO3D_MSG_CHUNK_ENTER as u8,
|
||||
version: DEMO3D_PROTOCOL_VERSION as u8,
|
||||
};
|
||||
offset += self.serialize_header(&msg_header, offset);
|
||||
|
||||
let buf = &mut self.send_buf[offset..];
|
||||
buf[0..4].copy_from_slice(&cx.to_be_bytes());
|
||||
buf[4..8].copy_from_slice(&cz.to_be_bytes());
|
||||
offset += 8;
|
||||
|
||||
sys::sendto(self.socket, &self.send_buf[..offset], &self.client_addr);
|
||||
}
|
||||
|
||||
fn serialize_chunk_msg_header(&mut self, msg: &ChunkMessage, offset: usize) -> usize {
|
||||
let buf = &mut self.send_buf[offset..];
|
||||
buf[0] = msg.chunk_type;
|
||||
buf[1] = msg.flags;
|
||||
buf[2] = msg.fragment_idx;
|
||||
buf[3] = msg.fragment_count;
|
||||
buf[4..8].copy_from_slice(&msg.id.to_be_bytes());
|
||||
buf[8..12].copy_from_slice(&(msg.cx as u32).to_be_bytes());
|
||||
buf[12..16].copy_from_slice(&(msg.cy as u32).to_be_bytes());
|
||||
buf[16..20].copy_from_slice(&(msg.cz as u32).to_be_bytes());
|
||||
buf[20..24].copy_from_slice(&msg.version.to_be_bytes());
|
||||
let payload_size = msg.payload.len() as u16;
|
||||
buf[24..26].copy_from_slice(&payload_size.to_be_bytes());
|
||||
buf[26..28].copy_from_slice(&[0u8; 2]);
|
||||
28
|
||||
}
|
||||
|
||||
fn serialize_header(&mut self, h: &demo3d_msg_header, offset: usize) -> usize {
|
||||
let buf = &mut self.send_buf[offset..];
|
||||
buf[0..4].copy_from_slice(&h.sequence.to_be_bytes());
|
||||
buf[4..6].copy_from_slice(&h.size.to_be_bytes());
|
||||
buf[6] = h.type_;
|
||||
buf[7] = h.version;
|
||||
8
|
||||
}
|
||||
|
||||
fn serialize_snapshot_header(&mut self, h: &demo3d_snapshot_header, offset: usize) -> usize {
|
||||
let buf = &mut self.send_buf[offset..];
|
||||
buf[0..2].copy_from_slice(&h.entity_count.to_be_bytes());
|
||||
buf[2..4].copy_from_slice(&h.event_count.to_be_bytes());
|
||||
buf[4..12].copy_from_slice(&h.player_id.to_be_bytes());
|
||||
buf[12..20].copy_from_slice(&h.tick.to_be_bytes());
|
||||
buf[20..24].copy_from_slice(&h.time.to_be_bytes());
|
||||
24
|
||||
}
|
||||
|
||||
fn serialize_entity_state(&mut self, e: &demo3d_entity_state, offset: usize) -> usize {
|
||||
let buf = &mut self.send_buf[offset..];
|
||||
buf[0..8].copy_from_slice(&e.entity_id.to_be_bytes());
|
||||
buf[8..64].copy_from_slice(&e.userdata);
|
||||
64
|
||||
}
|
||||
|
||||
fn deserialize_header(&self) -> demo3d_msg_header {
|
||||
let buf = &self.recv_buf;
|
||||
demo3d_msg_header {
|
||||
sequence: u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]),
|
||||
size: u16::from_be_bytes([buf[4], buf[5]]),
|
||||
type_: buf[6],
|
||||
version: buf[7],
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_input(&self) -> demo3d_input_msg {
|
||||
let buf = &self.recv_buf[8..];
|
||||
demo3d_input_msg {
|
||||
buttons: u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]),
|
||||
look_dx: f32::from_be_bytes([buf[4], buf[5], buf[6], buf[7]]),
|
||||
look_dy: f32::from_be_bytes([buf[8], buf[9], buf[10], buf[11]]),
|
||||
move_x: f32::from_be_bytes([buf[12], buf[13], buf[14], buf[15]]),
|
||||
move_y: f32::from_be_bytes([buf[16], buf[17], buf[18], buf[19]]),
|
||||
yaw: f32::from_be_bytes([buf[20], buf[21], buf[22], buf[23]]),
|
||||
tick: u64::from_be_bytes([buf[24], buf[25], buf[26], buf[27], buf[28], buf[29], buf[30], buf[31]]),
|
||||
timestamp: u64::from_be_bytes([buf[32], buf[33], buf[34], buf[35], buf[36], buf[37], buf[38], buf[39]]),
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_command(&self) -> demo3d_command_msg {
|
||||
let buf = &self.recv_buf[8..];
|
||||
let mut cmd = demo3d_command_msg {
|
||||
cmd_type: u16::from_be_bytes([buf[0], buf[1]]),
|
||||
payload: [0u8; 64],
|
||||
payload_size: u16::from_be_bytes([buf[66], buf[67]]),
|
||||
tick: u64::from_be_bytes([buf[68], buf[69], buf[70], buf[71], buf[72], buf[73], buf[74], buf[75]]),
|
||||
};
|
||||
cmd.payload.copy_from_slice(&buf[2..66]);
|
||||
cmd
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Transport {
|
||||
fn drop(&mut self) {
|
||||
sys::close(self.socket);
|
||||
}
|
||||
}
|
||||
47
demo3d/server/src/world.rs
Normal file
47
demo3d/server/src/world.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
use alloc::collections::BTreeMap;
|
||||
|
||||
use crate::chunk::{Chunk, ChunkId};
|
||||
use crate::procgen::generate_chunk;
|
||||
|
||||
pub struct World {
|
||||
active: Option<ChunkId>,
|
||||
chunks: BTreeMap<ChunkId, Chunk>,
|
||||
pub seed: u64,
|
||||
}
|
||||
|
||||
impl World {
|
||||
pub fn new(seed: u64) -> Self {
|
||||
Self {
|
||||
chunks: BTreeMap::new(),
|
||||
active: None,
|
||||
seed,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, id: &ChunkId) -> Option<&Chunk> {
|
||||
self.chunks.get(id)
|
||||
}
|
||||
|
||||
pub fn active(&self) -> Option<&Chunk> {
|
||||
self.active.as_ref().and_then(|id| self.chunks.get(id))
|
||||
}
|
||||
|
||||
pub fn set_active(&mut self, id: ChunkId) {
|
||||
self.active = Some(id);
|
||||
}
|
||||
|
||||
pub fn generate_bsp(&mut self, cx: i32, cz: i32) -> &Chunk {
|
||||
let chunk_id = ChunkId::Bsp { cx, cz };
|
||||
if !self.chunks.contains_key(&chunk_id) {
|
||||
let bsp = generate_chunk(cx, cz, self.seed);
|
||||
self.chunks.insert(chunk_id, Chunk::Bsp { cx, cz, bsp, version: 1 });
|
||||
}
|
||||
self.chunks.get(&chunk_id).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for World {
|
||||
fn default() -> Self {
|
||||
Self::new(12345)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue