2026-04-14 01:28:38 -05:00
|
|
|
#include "demo3d_sim.h"
|
2026-01-31 09:31:17 -06:00
|
|
|
|
|
|
|
|
#include <math.h>
|
|
|
|
|
|
2026-02-27 06:50:49 -06:00
|
|
|
#define DIST_EPSILON 0.03125f
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
|
f32 fraction;
|
|
|
|
|
pxl8_vec3 normal;
|
|
|
|
|
bool all_solid;
|
|
|
|
|
bool start_solid;
|
|
|
|
|
} trace_result;
|
|
|
|
|
|
2026-04-14 01:28:38 -05:00
|
|
|
static i32 bsp_find_leaf(const demo3d_bsp* bsp, pxl8_vec3 pos) {
|
2026-01-31 09:31:17 -06:00
|
|
|
if (!bsp || bsp->num_nodes == 0) return -1;
|
|
|
|
|
|
|
|
|
|
i32 node_id = 0;
|
|
|
|
|
while (node_id >= 0) {
|
2026-04-14 01:28:38 -05:00
|
|
|
const demo3d_bsp_node* node = &bsp->nodes[node_id];
|
|
|
|
|
const demo3d_bsp_plane* plane = &bsp->planes[node->plane_id];
|
2026-01-31 09:31:17 -06:00
|
|
|
f32 dist = pxl8_vec3_dot(pos, plane->normal) - plane->dist;
|
|
|
|
|
node_id = node->children[dist < 0 ? 1 : 0];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return -(node_id + 1);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 01:28:38 -05:00
|
|
|
static i32 bsp_contents_from(const demo3d_bsp* bsp, i32 node_id, pxl8_vec3 pos) {
|
2026-02-27 06:50:49 -06:00
|
|
|
while (node_id >= 0) {
|
2026-04-14 01:28:38 -05:00
|
|
|
const demo3d_bsp_node* node = &bsp->nodes[node_id];
|
|
|
|
|
const demo3d_bsp_plane* plane = &bsp->planes[node->plane_id];
|
2026-02-27 06:50:49 -06:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 01:28:38 -05:00
|
|
|
static bool bsp_recursive_trace(const demo3d_bsp* bsp, i32 node_id,
|
2026-02-27 06:50:49 -06:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 01:28:38 -05:00
|
|
|
const demo3d_bsp_node* node = &bsp->nodes[node_id];
|
|
|
|
|
const demo3d_bsp_plane* plane = &bsp->planes[node->plane_id];
|
2026-02-27 06:50:49 -06:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 01:28:38 -05:00
|
|
|
static trace_result bsp_trace_line(const demo3d_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to) {
|
2026-02-27 06:50:49 -06:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 01:28:38 -05:00
|
|
|
bool demo3d_bsp_point_solid(const demo3d_bsp* bsp, pxl8_vec3 pos) {
|
2026-02-27 06:50:49 -06:00
|
|
|
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;
|
2026-01-31 09:31:17 -06:00
|
|
|
i32 leaf = bsp_find_leaf(bsp, pos);
|
|
|
|
|
if (leaf < 0 || (u32)leaf >= bsp->num_leafs) return true;
|
|
|
|
|
return bsp->leafs[leaf].contents == -1;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 01:28:38 -05:00
|
|
|
static void trace_offsets(const demo3d_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to,
|
2026-02-27 06:50:49 -06:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-31 09:31:17 -06:00
|
|
|
}
|
|
|
|
|
|
2026-04-14 01:28:38 -05:00
|
|
|
pxl8_vec3 demo3d_bsp_trace(const demo3d_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to, f32 radius) {
|
2026-01-31 09:31:17 -06:00
|
|
|
if (!bsp || bsp->num_nodes == 0) return to;
|
|
|
|
|
|
2026-02-27 06:50:49 -06:00
|
|
|
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;
|
2026-01-31 09:31:17 -06:00
|
|
|
|
2026-02-27 06:50:49 -06:00
|
|
|
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,
|
|
|
|
|
};
|
|
|
|
|
}
|
2026-01-31 09:31:17 -06:00
|
|
|
|
2026-04-14 01:28:38 -05:00
|
|
|
static const demo3d_bsp* sim_bsp_at(const demo3d_sim_world* world, f32 x, f32 z) {
|
2026-02-27 06:50:49 -06:00
|
|
|
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];
|
|
|
|
|
}
|
2026-01-31 09:31:17 -06:00
|
|
|
|
2026-04-14 01:28:38 -05:00
|
|
|
static f32 bsp_terrain_height(const demo3d_bsp* bsp, f32 x, f32 z) {
|
2026-02-27 06:50:49 -06:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 01:28:38 -05:00
|
|
|
static f32 sim_terrain_height(const demo3d_sim_world* world, f32 x, f32 z) {
|
|
|
|
|
const demo3d_bsp* bsp = sim_bsp_at(world, x, z);
|
2026-02-27 06:50:49 -06:00
|
|
|
return bsp_terrain_height(bsp, x, z);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 01:28:38 -05:00
|
|
|
static void sim_trace_offsets(const demo3d_sim_world* world, pxl8_vec3 from, pxl8_vec3 to,
|
2026-02-27 06:50:49 -06:00
|
|
|
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 };
|
2026-04-14 01:28:38 -05:00
|
|
|
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);
|
2026-02-27 06:50:49 -06:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-31 09:31:17 -06:00
|
|
|
}
|
|
|
|
|
|
2026-04-14 01:28:38 -05:00
|
|
|
pxl8_vec3 demo3d_sim_trace(const demo3d_sim_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius, f32 height) {
|
2026-02-27 01:22:35 -06:00
|
|
|
(void)height;
|
2026-02-27 06:50:49 -06:00
|
|
|
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,
|
|
|
|
|
};
|
2026-01-31 09:31:17 -06:00
|
|
|
|
2026-02-27 06:50:49 -06:00
|
|
|
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,
|
|
|
|
|
};
|
2026-01-31 09:31:17 -06:00
|
|
|
|
2026-02-27 06:50:49 -06:00
|
|
|
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,
|
|
|
|
|
};
|
2026-01-31 09:31:17 -06:00
|
|
|
}
|
|
|
|
|
|
2026-04-14 01:28:38 -05:00
|
|
|
bool demo3d_sim_check_ground(const demo3d_sim_world* world, pxl8_vec3 pos, f32 radius) {
|
2026-02-27 06:50:49 -06:00
|
|
|
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;
|
2026-01-31 09:31:17 -06:00
|
|
|
|
|
|
|
|
pxl8_vec3 down = {pos.x, pos.y - 2.0f, pos.z};
|
2026-04-14 01:28:38 -05:00
|
|
|
pxl8_vec3 result = demo3d_sim_trace(world, pos, down, radius, 0.0f);
|
2026-01-31 09:31:17 -06:00
|
|
|
|
|
|
|
|
return result.y > down.y;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 01:28:38 -05:00
|
|
|
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) {
|
2026-02-27 01:22:35 -06:00
|
|
|
if (!ent || !input || !cfg) return;
|
2026-04-14 01:28:38 -05:00
|
|
|
if (!(ent->flags & DEMO3D_SIM_FLAG_ALIVE)) return;
|
2026-01-31 09:31:17 -06:00
|
|
|
|
|
|
|
|
ent->yaw -= input->look_dx * 0.008f;
|
|
|
|
|
f32 new_pitch = ent->pitch - input->look_dy * 0.008f;
|
2026-02-27 01:22:35 -06:00
|
|
|
if (new_pitch > cfg->max_pitch) new_pitch = cfg->max_pitch;
|
|
|
|
|
if (new_pitch < -cfg->max_pitch) new_pitch = -cfg->max_pitch;
|
2026-01-31 09:31:17 -06:00
|
|
|
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;
|
2026-02-27 01:22:35 -06:00
|
|
|
target_speed = cfg->move_speed;
|
2026-01-31 09:31:17 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ent->vel.x = move_dir.x * target_speed;
|
|
|
|
|
ent->vel.z = move_dir.z * target_speed;
|
|
|
|
|
|
2026-04-14 01:28:38 -05:00
|
|
|
bool grounded = (ent->flags & DEMO3D_SIM_FLAG_GROUNDED) != 0;
|
2026-01-31 09:31:17 -06:00
|
|
|
|
|
|
|
|
if (grounded && (input->buttons & 1)) {
|
2026-02-27 01:22:35 -06:00
|
|
|
ent->vel.y = cfg->jump_velocity;
|
2026-04-14 01:28:38 -05:00
|
|
|
ent->flags &= ~DEMO3D_SIM_FLAG_GROUNDED;
|
2026-01-31 09:31:17 -06:00
|
|
|
grounded = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!grounded) {
|
2026-02-27 01:22:35 -06:00
|
|
|
ent->vel.y -= cfg->gravity * dt;
|
2026-01-31 09:31:17 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pxl8_vec3 target = {
|
|
|
|
|
ent->pos.x + ent->vel.x * dt,
|
|
|
|
|
ent->pos.y + ent->vel.y * dt,
|
|
|
|
|
ent->pos.z + ent->vel.z * dt
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-27 06:50:49 -06:00
|
|
|
if (grounded) {
|
|
|
|
|
f32 th_dest = sim_terrain_height(world, target.x, target.z);
|
|
|
|
|
if (th_dest > -1e8f) target.y = th_dest;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 01:28:38 -05:00
|
|
|
pxl8_vec3 new_pos = demo3d_sim_trace(world, ent->pos, target, cfg->player_radius, cfg->player_height);
|
2026-02-27 01:22:35 -06:00
|
|
|
|
2026-02-27 06:50:49 -06:00
|
|
|
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;
|
2026-02-27 01:22:35 -06:00
|
|
|
}
|
|
|
|
|
|
2026-01-31 09:31:17 -06:00
|
|
|
ent->pos = new_pos;
|
|
|
|
|
|
2026-04-14 01:28:38 -05:00
|
|
|
if (demo3d_sim_check_ground(world, ent->pos, cfg->player_radius)) {
|
|
|
|
|
ent->flags |= DEMO3D_SIM_FLAG_GROUNDED;
|
2026-01-31 09:31:17 -06:00
|
|
|
if (ent->vel.y < 0) ent->vel.y = 0;
|
|
|
|
|
} else {
|
2026-04-14 01:28:38 -05:00
|
|
|
ent->flags &= ~DEMO3D_SIM_FLAG_GROUNDED;
|
2026-01-31 09:31:17 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 01:28:38 -05:00
|
|
|
void demo3d_sim_integrate(demo3d_sim_entity* ent, const demo3d_sim_world* world,
|
|
|
|
|
const demo3d_sim_config* cfg, f32 dt) {
|
2026-02-27 01:22:35 -06:00
|
|
|
if (!ent || !cfg) return;
|
2026-04-14 01:28:38 -05:00
|
|
|
if (!(ent->flags & DEMO3D_SIM_FLAG_ALIVE)) return;
|
|
|
|
|
if (ent->flags & DEMO3D_SIM_FLAG_PLAYER) return;
|
2026-01-31 09:31:17 -06:00
|
|
|
|
2026-04-14 01:28:38 -05:00
|
|
|
bool grounded = (ent->flags & DEMO3D_SIM_FLAG_GROUNDED) != 0;
|
2026-01-31 09:31:17 -06:00
|
|
|
|
2026-02-27 01:22:35 -06:00
|
|
|
ent->vel.y -= cfg->gravity * dt;
|
2026-01-31 09:31:17 -06:00
|
|
|
|
|
|
|
|
if (grounded) {
|
|
|
|
|
f32 speed = sqrtf(ent->vel.x * ent->vel.x + ent->vel.z * ent->vel.z);
|
|
|
|
|
if (speed > 0.0f) {
|
2026-02-27 01:22:35 -06:00
|
|
|
f32 drop = speed * cfg->friction * dt;
|
2026-01-31 09:31:17 -06:00
|
|
|
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
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-27 06:50:49 -06:00
|
|
|
if (grounded) {
|
|
|
|
|
f32 th_dest = sim_terrain_height(world, target.x, target.z);
|
|
|
|
|
if (th_dest > -1e8f) target.y = th_dest;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 01:28:38 -05:00
|
|
|
pxl8_vec3 new_pos = demo3d_sim_trace(world, ent->pos, target, cfg->player_radius, cfg->player_height);
|
2026-02-27 01:22:35 -06:00
|
|
|
|
2026-02-27 06:50:49 -06:00
|
|
|
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;
|
2026-02-27 01:22:35 -06:00
|
|
|
}
|
|
|
|
|
|
2026-01-31 09:31:17 -06:00
|
|
|
ent->pos = new_pos;
|
|
|
|
|
|
2026-04-14 01:28:38 -05:00
|
|
|
if (demo3d_sim_check_ground(world, ent->pos, cfg->player_radius)) {
|
|
|
|
|
ent->flags |= DEMO3D_SIM_FLAG_GROUNDED;
|
2026-01-31 09:31:17 -06:00
|
|
|
if (ent->vel.y < 0.0f) ent->vel.y = 0.0f;
|
|
|
|
|
} else {
|
2026-04-14 01:28:38 -05:00
|
|
|
ent->flags &= ~DEMO3D_SIM_FLAG_GROUNDED;
|
2026-01-31 09:31:17 -06:00
|
|
|
}
|
|
|
|
|
}
|