fix(bsp): fill in exterior cells

This commit is contained in:
asrael 2026-03-07 07:19:40 -06:00
parent ab0d2a4b04
commit 1341b30920
8 changed files with 740 additions and 102 deletions

View file

@ -8,8 +8,8 @@
(local sky (require :mod.sky))
(local textures (require :mod.textures))
(local bob-amount 4.0)
(local bob-speed 8.0)
(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)

View file

@ -166,6 +166,10 @@ pub struct BspBuilder {
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 {
@ -225,6 +229,10 @@ impl From<BspBuilder> for Bsp {
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 {

View file

@ -313,5 +313,11 @@ fn bsp_to_messages(bsp: &bsp::Bsp, cx: i32, cz: i32, version: u32) -> Vec<transp
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)
}

View file

@ -199,6 +199,45 @@ fn carve_entries(grid: &mut RoomGrid, entries: &[ChunkEntry]) {
}
}
fn shrink_grid(grid: &RoomGrid, entries: &[ChunkEntry]) -> (RoomGrid, i32, i32) {
let (mut min_x, mut min_z, mut max_x, mut max_z) = (grid.width, grid.height, 0i32, 0i32);
for z in 0..grid.height {
for x in 0..grid.width {
if grid.get(x, z) == 0 {
if x < min_x { min_x = x; }
if x > max_x { max_x = x; }
if z < min_z { min_z = z; }
if z > max_z { max_z = z; }
}
}
}
min_x = (min_x - 1).max(0);
min_z = (min_z - 1).max(0);
max_x = (max_x + 1).min(grid.width - 1);
max_z = (max_z + 1).min(grid.height - 1);
for e in entries {
match e.edge {
ChunkEdge::West => min_x = 0,
ChunkEdge::East => max_x = grid.width - 1,
ChunkEdge::North => min_z = 0,
ChunkEdge::South => max_z = grid.height - 1,
}
}
let tw = max_x - min_x + 1;
let th = max_z - min_z + 1;
let mut tight = RoomGrid::new(tw, th);
for z in 0..th {
for x in 0..tw {
tight.set(x, z, grid.get(x + min_x, z + min_z));
}
}
(tight, min_x, min_z)
}
fn compute_face_aabb(face: &mut Face, verts: &[Vertex], vert_idx: usize) {
face.aabb_min = Vec3::new(1e30, 1e30, 1e30);
face.aabb_max = Vec3::new(-1e30, -1e30, -1e30);
@ -364,6 +403,8 @@ fn build_cell_portals(grid: &RoomGrid, origin_x: f32, origin_z: f32) -> Vec<Cell
}
}
portals
}
@ -790,6 +831,14 @@ struct BspMaterials {
const DUNGEON_MATS: BspMaterials = BspMaterials { floor: 0, wall: 1, trim: 3 };
const COURTYARD_MATS: BspMaterials = BspMaterials { floor: 4, wall: 5, trim: 6 };
fn is_adj_wall(grid: &RoomGrid, x: i32, y: i32) -> bool {
if x < 0 || x >= grid.width || y < 0 || y >= grid.height { return false; }
grid.get(x, y) == 1 && (
grid.get(x - 1, y) == 0 || grid.get(x + 1, y) == 0 ||
grid.get(x, y - 1) == 0 || grid.get(x, y + 1) == 0
)
}
fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid, doorways: &[Doorway], entries: &[ChunkEntry], mats: &BspMaterials, origin_x: f32, origin_z: f32) {
let mut wall_count = 0;
let mut floor_ceiling_count = 0;
@ -831,16 +880,29 @@ fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid, doorways: &[Doorway], entr
}
}
let mut solid_floor_count = 0;
let mut filler_floor_count = 0;
let mut ext_wall_count = 0;
let mut wall_top_count = 0;
for y in 0..grid.height {
for x in 0..grid.width {
if grid.get(x, y) == 1 {
solid_floor_count += 1;
let adj_room = grid.get(x-1,y)==0 || grid.get(x+1,y)==0
|| grid.get(x,y-1)==0 || grid.get(x,y+1)==0;
if adj_room {
wall_top_count += 1;
if grid.get(x-1,y) != 0 && !is_adj_wall(grid, x-1, y) { ext_wall_count += 1; }
if grid.get(x+1,y) != 0 && !is_adj_wall(grid, x+1, y) { ext_wall_count += 1; }
if grid.get(x,y-1) != 0 && !is_adj_wall(grid, x, y-1) { ext_wall_count += 1; }
if grid.get(x,y+1) != 0 && !is_adj_wall(grid, x, y+1) { ext_wall_count += 1; }
} else {
filler_floor_count += 1;
}
}
}
}
let face_count = wall_count * 2 + floor_ceiling_count + doorway_extra + partition_faces + solid_floor_count;
let face_count = wall_count * 2 + floor_ceiling_count + doorway_extra + partition_faces
+ filler_floor_count + ext_wall_count * 2 + wall_top_count;
let vertex_count = face_count * 4;
let total_cells = (grid.width * grid.height) as usize;
@ -1126,12 +1188,74 @@ fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid, doorways: &[Doorway], entr
for x in 0..grid.width {
if grid.get(x, y) == 1 {
let fx = origin_x + x as f32 * CELL_SIZE;
let fy = origin_z + y as f32 * CELL_SIZE;
let fz = origin_z + y as f32 * CELL_SIZE;
let cell_idx = (y * grid.width + x) as u32;
emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx,
[Vec3::new(fx, 0.0, fy), Vec3::new(fx, 0.0, fy + CELL_SIZE),
Vec3::new(fx + CELL_SIZE, 0.0, fy + CELL_SIZE), Vec3::new(fx + CELL_SIZE, 0.0, fy)],
up, 0.0, 7, cell_idx);
let adj_room = grid.get(x-1,y)==0 || grid.get(x+1,y)==0
|| grid.get(x,y-1)==0 || grid.get(x,y+1)==0;
if adj_room {
let room_ci = if grid.get(x-1,y)==0 { ((y)*grid.width+(x-1)) as u32 }
else if grid.get(x+1,y)==0 { ((y)*grid.width+(x+1)) as u32 }
else if grid.get(x,y-1)==0 { ((y-1)*grid.width+(x)) as u32 }
else { ((y+1)*grid.width+(x)) as u32 };
emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx,
[Vec3::new(fx, WALL_HEIGHT, fz), Vec3::new(fx + CELL_SIZE, WALL_HEIGHT, fz),
Vec3::new(fx + CELL_SIZE, WALL_HEIGHT, fz + CELL_SIZE), Vec3::new(fx, WALL_HEIGHT, fz + CELL_SIZE)],
up, WALL_HEIGHT, mats.wall, room_ci);
if grid.get(x-1,y) != 0 && !is_adj_wall(grid, x-1, y) {
let n = Vec3::new(-1.0, 0.0, 0.0);
emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx,
[Vec3::new(fx, TRIM_HEIGHT, fz + CELL_SIZE), Vec3::new(fx, TRIM_HEIGHT, fz),
Vec3::new(fx, WALL_HEIGHT, fz), Vec3::new(fx, WALL_HEIGHT, fz + CELL_SIZE)],
n, -fx, mats.wall, room_ci);
emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx,
[Vec3::new(fx, 0.0, fz + CELL_SIZE), Vec3::new(fx, 0.0, fz),
Vec3::new(fx, TRIM_HEIGHT, fz), Vec3::new(fx, TRIM_HEIGHT, fz + CELL_SIZE)],
n, -fx, mats.trim, room_ci);
}
if grid.get(x+1,y) != 0 && !is_adj_wall(grid, x+1, y) {
let wx = fx + CELL_SIZE;
let n = Vec3::new(1.0, 0.0, 0.0);
emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx,
[Vec3::new(wx, TRIM_HEIGHT, fz), Vec3::new(wx, TRIM_HEIGHT, fz + CELL_SIZE),
Vec3::new(wx, WALL_HEIGHT, fz + CELL_SIZE), Vec3::new(wx, WALL_HEIGHT, fz)],
n, wx, mats.wall, room_ci);
emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx,
[Vec3::new(wx, 0.0, fz), Vec3::new(wx, 0.0, fz + CELL_SIZE),
Vec3::new(wx, TRIM_HEIGHT, fz + CELL_SIZE), Vec3::new(wx, TRIM_HEIGHT, fz)],
n, wx, mats.trim, room_ci);
}
if grid.get(x,y-1) != 0 && !is_adj_wall(grid, x, y-1) {
let n = Vec3::new(0.0, 0.0, -1.0);
emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx,
[Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, fz), Vec3::new(fx, TRIM_HEIGHT, fz),
Vec3::new(fx, WALL_HEIGHT, fz), Vec3::new(fx + CELL_SIZE, WALL_HEIGHT, fz)],
n, -fz, mats.wall, room_ci);
emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx,
[Vec3::new(fx + CELL_SIZE, 0.0, fz), Vec3::new(fx, 0.0, fz),
Vec3::new(fx, TRIM_HEIGHT, fz), Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, fz)],
n, -fz, mats.trim, room_ci);
}
if grid.get(x,y+1) != 0 && !is_adj_wall(grid, x, y+1) {
let wz = fz + CELL_SIZE;
let n = Vec3::new(0.0, 0.0, 1.0);
emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx,
[Vec3::new(fx, TRIM_HEIGHT, wz), Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, wz),
Vec3::new(fx + CELL_SIZE, WALL_HEIGHT, wz), Vec3::new(fx, WALL_HEIGHT, wz)],
n, wz, mats.wall, room_ci);
emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx,
[Vec3::new(fx, 0.0, wz), Vec3::new(fx + CELL_SIZE, 0.0, wz),
Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, wz), Vec3::new(fx, TRIM_HEIGHT, wz)],
n, wz, mats.trim, room_ci);
}
} else {
emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx,
[Vec3::new(fx, 0.0, fz), Vec3::new(fx, 0.0, fz + CELL_SIZE),
Vec3::new(fx + CELL_SIZE, 0.0, fz + CELL_SIZE), Vec3::new(fx + CELL_SIZE, 0.0, fz)],
up, 0.0, 7, cell_idx);
}
}
}
}
@ -1181,13 +1305,16 @@ fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid, doorways: &[Doorway], entr
if grid.get(x, y) == 0 {
leaf.contents = -2;
leaf.first_marksurface = cell_offset[c] as u16;
leaf.num_marksurfaces = faces_per_cell[c] as u16;
} else {
leaf.contents = -1;
leaf.first_marksurface = cell_offset[c] as u16;
leaf.num_marksurfaces = faces_per_cell[c] as u16;
let adjacent_to_room =
grid.get(x - 1, y) == 0 ||
grid.get(x + 1, y) == 0 ||
grid.get(x, y - 1) == 0 ||
grid.get(x, y + 1) == 0;
leaf.contents = if adjacent_to_room { -1 } else { 0 };
}
leaf.first_marksurface = cell_offset[c] as u16;
leaf.num_marksurfaces = faces_per_cell[c] as u16;
leaf.visofs = -1;
}
}
@ -1274,10 +1401,10 @@ fn grid_to_bsp_exterior(bsp: &mut BspBuilder, grid: &RoomGrid, origin_x: f32, or
let wx = origin_x + vx as f32 * CELL_SIZE;
let wz = origin_z + vz as f32 * CELL_SIZE;
let h = unsafe {
crate::pxl8::pxl8_fbm(wx * 0.005, wz * 0.005, world_seed + 5000, 4)
crate::pxl8::pxl8_fbm(wx * 0.002, wz * 0.002, world_seed + 5000, 4)
};
let blend = edge_blend(wx, wz, world_seed);
corner_heights[vz as usize * vw + vx as usize] = h * 64.0 * blend;
corner_heights[vz as usize * vw + vx as usize] = h * 128.0 * blend;
}
}
@ -1286,6 +1413,28 @@ fn grid_to_bsp_exterior(bsp: &mut BspBuilder, grid: &RoomGrid, origin_x: f32, or
if border_south { border_face_count += grid.width; }
if border_west { border_face_count += grid.height; }
if border_east { border_face_count += grid.height; }
if border_north && border_west { border_face_count += 1; }
if border_north && border_east { border_face_count += 1; }
if border_south && border_west { border_face_count += 1; }
if border_south && border_east { border_face_count += 1; }
let bg_north = if border_north { generate_building_grid(cx, cz - 1, world_seed) } else { None };
let bg_south = if border_south { generate_building_grid(cx, cz + 1, world_seed) } else { None };
let bg_west = if border_west { generate_building_grid(cx - 1, cz, world_seed) } else { None };
let bg_east = if border_east { generate_building_grid(cx + 1, cz, world_seed) } else { None };
let mut building_face_count = 0usize;
for bg in [&bg_north, &bg_south, &bg_west, &bg_east] {
if let Some(g) = bg {
for y in 0..g.height {
for x in 0..g.width {
if g.get(x, y) != 0 && !is_adj_wall(g, x, y) {
building_face_count += 1;
}
}
}
}
}
let mut face_count = 0;
for y in 0..grid.height {
@ -1295,7 +1444,7 @@ fn grid_to_bsp_exterior(bsp: &mut BspBuilder, grid: &RoomGrid, origin_x: f32, or
}
}
}
face_count += border_face_count as usize;
face_count += border_face_count as usize + building_face_count;
let vertex_count = face_count * 4;
let max_nodes = 2 * total_cells + 1;
@ -1400,6 +1549,124 @@ fn grid_to_bsp_exterior(bsp: &mut BspBuilder, grid: &RoomGrid, origin_x: f32, or
}
}
if border_north && border_west {
let fx_outer = origin_x - CELL_SIZE;
let fx_inner = origin_x;
let fz_outer = origin_z - CELL_SIZE;
let fz_inner = origin_z;
let h = corner_heights[0];
let cell_idx = 0u32;
emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx,
[Vec3::new(fx_outer, 0.0, fz_outer), Vec3::new(fx_inner, 0.0, fz_outer),
Vec3::new(fx_inner, h, fz_inner), Vec3::new(fx_outer, 0.0, fz_inner)],
up, 0.0, 7, cell_idx);
}
if border_north && border_east {
let last_col = grid.width as usize;
let fx_inner = origin_x + grid.width as f32 * CELL_SIZE;
let fx_outer = fx_inner + CELL_SIZE;
let fz_outer = origin_z - CELL_SIZE;
let fz_inner = origin_z;
let h = corner_heights[last_col];
let cell_idx = (grid.width - 1) as u32;
emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx,
[Vec3::new(fx_inner, 0.0, fz_outer), Vec3::new(fx_outer, 0.0, fz_outer),
Vec3::new(fx_outer, 0.0, fz_inner), Vec3::new(fx_inner, h, fz_inner)],
up, 0.0, 7, cell_idx);
}
if border_south && border_west {
let last_row = grid.height as usize;
let fx_outer = origin_x - CELL_SIZE;
let fx_inner = origin_x;
let fz_inner = origin_z + grid.height as f32 * CELL_SIZE;
let fz_outer = fz_inner + CELL_SIZE;
let h = corner_heights[last_row * vw];
let cell_idx = ((grid.height - 1) * grid.width) as u32;
emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx,
[Vec3::new(fx_outer, 0.0, fz_inner), Vec3::new(fx_inner, h, fz_inner),
Vec3::new(fx_inner, 0.0, fz_outer), Vec3::new(fx_outer, 0.0, fz_outer)],
up, 0.0, 7, cell_idx);
}
if border_south && border_east {
let last_row = grid.height as usize;
let last_col = grid.width as usize;
let fx_inner = origin_x + grid.width as f32 * CELL_SIZE;
let fx_outer = fx_inner + CELL_SIZE;
let fz_inner = origin_z + grid.height as f32 * CELL_SIZE;
let fz_outer = fz_inner + CELL_SIZE;
let h = corner_heights[last_row * vw + last_col];
let cell_idx = ((grid.height - 1) * grid.width + grid.width - 1) as u32;
emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx,
[Vec3::new(fx_inner, h, fz_inner), Vec3::new(fx_outer, 0.0, fz_inner),
Vec3::new(fx_outer, 0.0, fz_outer), Vec3::new(fx_inner, 0.0, fz_outer)],
up, 0.0, 7, cell_idx);
}
if let Some(ref bg) = bg_north {
let bg_oz = (cz - 1) as f32 * chunk_size;
for by in 0..bg.height {
for bx in 0..bg.width {
if bg.get(bx, by) != 0 && !is_adj_wall(bg, bx, by) {
let fx = origin_x + bx as f32 * CELL_SIZE;
let fz = bg_oz + by as f32 * CELL_SIZE;
let cell_idx = bx as u32;
emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx,
[Vec3::new(fx, 0.0, fz), Vec3::new(fx + CELL_SIZE, 0.0, fz),
Vec3::new(fx + CELL_SIZE, 0.0, fz + CELL_SIZE), Vec3::new(fx, 0.0, fz + CELL_SIZE)],
up, 0.0, 7, cell_idx);
}
}
}
}
if let Some(ref bg) = bg_south {
let bg_oz = (cz + 1) as f32 * chunk_size;
for by in 0..bg.height {
for bx in 0..bg.width {
if bg.get(bx, by) != 0 && !is_adj_wall(bg, bx, by) {
let fx = origin_x + bx as f32 * CELL_SIZE;
let fz = bg_oz + by as f32 * CELL_SIZE;
let cell_idx = ((grid.height - 1) * grid.width + bx) as u32;
emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx,
[Vec3::new(fx, 0.0, fz), Vec3::new(fx + CELL_SIZE, 0.0, fz),
Vec3::new(fx + CELL_SIZE, 0.0, fz + CELL_SIZE), Vec3::new(fx, 0.0, fz + CELL_SIZE)],
up, 0.0, 7, cell_idx);
}
}
}
}
if let Some(ref bg) = bg_west {
let bg_ox = (cx - 1) as f32 * chunk_size;
for by in 0..bg.height {
for bx in 0..bg.width {
if bg.get(bx, by) != 0 && !is_adj_wall(bg, bx, by) {
let fx = bg_ox + bx as f32 * CELL_SIZE;
let fz = origin_z + by as f32 * CELL_SIZE;
let cell_idx = (by * grid.width) as u32;
emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx,
[Vec3::new(fx, 0.0, fz), Vec3::new(fx + CELL_SIZE, 0.0, fz),
Vec3::new(fx + CELL_SIZE, 0.0, fz + CELL_SIZE), Vec3::new(fx, 0.0, fz + CELL_SIZE)],
up, 0.0, 7, cell_idx);
}
}
}
}
if let Some(ref bg) = bg_east {
let bg_ox = (cx + 1) as f32 * chunk_size;
for by in 0..bg.height {
for bx in 0..bg.width {
if bg.get(bx, by) != 0 && !is_adj_wall(bg, bx, by) {
let fx = bg_ox + bx as f32 * CELL_SIZE;
let fz = origin_z + by as f32 * CELL_SIZE;
let cell_idx = (by * grid.width + grid.width - 1) as u32;
emit_quad(bsp, &mut face_cell, &mut vert_idx, &mut face_idx, &mut edge_idx,
[Vec3::new(fx, 0.0, fz), Vec3::new(fx + CELL_SIZE, 0.0, fz),
Vec3::new(fx + CELL_SIZE, 0.0, fz + CELL_SIZE), Vec3::new(fx, 0.0, fz + CELL_SIZE)],
up, 0.0, 7, cell_idx);
}
}
}
}
bsp.vertices.truncate(vert_idx);
bsp.faces.truncate(face_idx);
bsp.edges.truncate(edge_idx);
@ -1450,31 +1717,35 @@ fn grid_to_bsp_exterior(bsp: &mut BspBuilder, grid: &RoomGrid, origin_x: f32, or
}
if border_north {
let ext = if bg_north.is_some() { chunk_size } else { CELL_SIZE };
for x in 0..grid.width {
let c = (0 * grid.width + x) as usize;
let fz = origin_z - CELL_SIZE;
bsp.leafs[c].mins[2] = fz as i16;
bsp.leafs[c].mins[2] = (origin_z - ext) as i16;
if bg_north.is_some() { bsp.leafs[c].mins[1] = -64; }
}
}
if border_south {
let ext = if bg_south.is_some() { chunk_size } else { CELL_SIZE };
for x in 0..grid.width {
let c = ((grid.height - 1) * grid.width + x) as usize;
let fz = origin_z + (grid.height + 1) as f32 * CELL_SIZE;
bsp.leafs[c].maxs[2] = fz as i16;
bsp.leafs[c].maxs[2] = (origin_z + grid.height as f32 * CELL_SIZE + ext) as i16;
if bg_south.is_some() { bsp.leafs[c].mins[1] = -64; }
}
}
if border_west {
let ext = if bg_west.is_some() { chunk_size } else { CELL_SIZE };
for y in 0..grid.height {
let c = (y * grid.width + 0) as usize;
let fx = origin_x - CELL_SIZE;
bsp.leafs[c].mins[0] = fx as i16;
bsp.leafs[c].mins[0] = (origin_x - ext) as i16;
if bg_west.is_some() { bsp.leafs[c].mins[1] = -64; }
}
}
if border_east {
let ext = if bg_east.is_some() { chunk_size } else { CELL_SIZE };
for y in 0..grid.height {
let c = (y * grid.width + grid.width - 1) as usize;
let fx = origin_x + (grid.width + 1) as f32 * CELL_SIZE;
bsp.leafs[c].maxs[0] = fx as i16;
bsp.leafs[c].maxs[0] = (origin_x + grid.width as f32 * CELL_SIZE + ext) as i16;
if bg_east.is_some() { bsp.leafs[c].mins[1] = -64; }
}
}
@ -1508,7 +1779,7 @@ fn grid_to_bsp_exterior(bsp: &mut BspBuilder, grid: &RoomGrid, origin_x: f32, or
ctx.bsp.heightfield_cell_size = CELL_SIZE;
}
pub fn generate_rooms(params: &ProcgenParams, doorways: &[Doorway], entries: &[ChunkEntry]) -> Bsp {
fn generate_dungeon_grid(params: &ProcgenParams, doorways: &[Doorway], entries: &[ChunkEntry]) -> (RoomGrid, Vec<Bounds>) {
let mut rng = Rng::new(params.seed);
let mut grid = RoomGrid::new(params.width, params.height);
@ -1574,8 +1845,49 @@ pub fn generate_rooms(params: &ProcgenParams, doorways: &[Doorway], entries: &[C
carve_entries(&mut grid, entries);
(grid, rooms)
}
pub fn generate_rooms(params: &ProcgenParams, doorways: &[Doorway], entries: &[ChunkEntry], world_seed: u64) -> Bsp {
let (grid, rooms) = generate_dungeon_grid(params, doorways, entries);
let (tight, x_off, z_off) = shrink_grid(&grid, entries);
let tight_origin_x = params.origin_x + x_off as f32 * CELL_SIZE;
let tight_origin_z = params.origin_z + z_off as f32 * CELL_SIZE;
let tight_entries: Vec<ChunkEntry> = entries.iter().map(|e| {
let cell = match e.edge {
ChunkEdge::West | ChunkEdge::East => e.cell - z_off,
ChunkEdge::North | ChunkEdge::South => e.cell - x_off,
};
ChunkEntry { edge: e.edge, cell }
}).collect();
let mut bsp = BspBuilder::new();
grid_to_bsp(&mut bsp, &grid, doorways, entries, &DUNGEON_MATS, params.origin_x, params.origin_z);
grid_to_bsp(&mut bsp, &tight, doorways, &tight_entries, &DUNGEON_MATS, tight_origin_x, tight_origin_z);
bsp.bounds_min_x = tight_origin_x;
bsp.bounds_min_z = tight_origin_z;
bsp.bounds_max_x = tight_origin_x + tight.width as f32 * CELL_SIZE;
bsp.bounds_max_z = tight_origin_z + tight.height as f32 * CELL_SIZE;
let vw = 17usize;
let vh = 17usize;
bsp.heightfield = vec![0.0f32; vw * vh];
for vz in 0..vh as i32 {
for vx in 0..vw as i32 {
let wx = params.origin_x + vx as f32 * CELL_SIZE;
let wz = params.origin_z + vz as f32 * CELL_SIZE;
let h = unsafe { crate::pxl8::pxl8_fbm(wx * 0.002, wz * 0.002, world_seed + 5000, 4) };
let blend = edge_blend(wx, wz, world_seed);
bsp.heightfield[vz as usize * vw + vx as usize] = h * 128.0 * blend;
}
}
bsp.heightfield_w = 17;
bsp.heightfield_h = 17;
bsp.heightfield_ox = params.origin_x;
bsp.heightfield_oz = params.origin_z;
bsp.heightfield_cell_size = CELL_SIZE;
let light_height = 80.0;
@ -1603,7 +1915,7 @@ pub fn generate_rooms(params: &ProcgenParams, doorways: &[Doorway], entries: &[C
bsp.into()
}
pub fn generate_courtyard(params: &ProcgenParams, entries: &[ChunkEntry]) -> Bsp {
fn generate_courtyard_grid(params: &ProcgenParams, entries: &[ChunkEntry]) -> (RoomGrid, Vec<Bounds>) {
let mut rng = Rng::new(params.seed);
let mut grid = RoomGrid::new(params.width, params.height);
grid.fill(1);
@ -1701,8 +2013,49 @@ pub fn generate_courtyard(params: &ProcgenParams, entries: &[ChunkEntry]) -> Bsp
carve_entries(&mut grid, entries);
(grid, rooms)
}
pub fn generate_courtyard(params: &ProcgenParams, entries: &[ChunkEntry], world_seed: u64) -> Bsp {
let (grid, rooms) = generate_courtyard_grid(params, entries);
let (tight, x_off, z_off) = shrink_grid(&grid, entries);
let tight_origin_x = params.origin_x + x_off as f32 * CELL_SIZE;
let tight_origin_z = params.origin_z + z_off as f32 * CELL_SIZE;
let tight_entries: Vec<ChunkEntry> = entries.iter().map(|e| {
let cell = match e.edge {
ChunkEdge::West | ChunkEdge::East => e.cell - z_off,
ChunkEdge::North | ChunkEdge::South => e.cell - x_off,
};
ChunkEntry { edge: e.edge, cell }
}).collect();
let mut bsp = BspBuilder::new();
grid_to_bsp(&mut bsp, &grid, &[], entries, &COURTYARD_MATS, params.origin_x, params.origin_z);
grid_to_bsp(&mut bsp, &tight, &[], &tight_entries, &COURTYARD_MATS, tight_origin_x, tight_origin_z);
bsp.bounds_min_x = tight_origin_x;
bsp.bounds_min_z = tight_origin_z;
bsp.bounds_max_x = tight_origin_x + tight.width as f32 * CELL_SIZE;
bsp.bounds_max_z = tight_origin_z + tight.height as f32 * CELL_SIZE;
let vw = 17usize;
let vh = 17usize;
bsp.heightfield = vec![0.0f32; vw * vh];
for vz in 0..vh as i32 {
for vx in 0..vw as i32 {
let wx = params.origin_x + vx as f32 * CELL_SIZE;
let wz = params.origin_z + vz as f32 * CELL_SIZE;
let h = unsafe { crate::pxl8::pxl8_fbm(wx * 0.002, wz * 0.002, world_seed + 5000, 4) };
let blend = edge_blend(wx, wz, world_seed);
bsp.heightfield[vz as usize * vw + vx as usize] = h * 128.0 * blend;
}
}
bsp.heightfield_w = 17;
bsp.heightfield_h = 17;
bsp.heightfield_ox = params.origin_x;
bsp.heightfield_oz = params.origin_z;
bsp.heightfield_cell_size = CELL_SIZE;
let light_height = 80.0;
@ -1763,7 +2116,11 @@ pub fn generate_exterior(params: &ProcgenParams, world_seed: u64) -> Bsp {
let mut bsp = BspBuilder::new();
grid_to_bsp_exterior(&mut bsp, &grid, params.origin_x, params.origin_z, world_seed);
let lights = vec![LightSource {
let chunk_size = 16.0 * CELL_SIZE;
let cx = libm::floorf(params.origin_x / chunk_size) as i32;
let cz = libm::floorf(params.origin_z / chunk_size) as i32;
let mut lights = vec![LightSource {
position: Vec3::new(
params.origin_x + params.width as f32 * CELL_SIZE * 0.5,
200.0,
@ -1772,17 +2129,33 @@ pub fn generate_exterior(params: &ProcgenParams, world_seed: u64) -> Bsp {
intensity: 2.0,
radius: 2000.0,
}];
for &(dx, dz) in &[(0, -1), (0, 1), (-1, 0), (1, 0)] {
if select_biome(cx + dx, cz + dz, world_seed) != Biome::Exterior {
lights.push(LightSource {
position: Vec3::new(
(cx + dx) as f32 * chunk_size + chunk_size * 0.5,
200.0,
(cz + dz) as f32 * chunk_size + chunk_size * 0.5,
),
intensity: 2.0,
radius: 2000.0,
});
}
}
compute_bsp_vertex_lighting(&mut bsp, &lights);
bsp.into()
}
pub fn generate_chunk(cx: i32, cz: i32, world_seed: u64) -> Bsp {
fn generate_building_grid(cx: i32, cz: i32, world_seed: u64) -> Option<RoomGrid> {
let biome = select_biome(cx, cz, world_seed);
if biome == Biome::Exterior { return None; }
let seed = chunk_seed(world_seed, cx, cz);
let origin_x = cx as f32 * 16.0 * CELL_SIZE;
let origin_z = cz as f32 * 16.0 * CELL_SIZE;
let params = ProcgenParams {
seed,
origin_x,
@ -1790,6 +2163,18 @@ pub fn generate_chunk(cx: i32, cz: i32, world_seed: u64) -> Bsp {
..Default::default()
};
let (doorways, entries) = get_chunk_config(cx, cz);
let (grid, _) = match biome {
Biome::Dungeon => generate_dungeon_grid(&params, &doorways, &entries),
Biome::Courtyard => generate_courtyard_grid(&params, &entries),
_ => return None,
};
Some(grid)
}
fn get_chunk_config(cx: i32, cz: i32) -> (Vec<Doorway>, Vec<ChunkEntry>) {
let mut doorways = Vec::new();
let mut entries = Vec::new();
@ -1820,9 +2205,27 @@ pub fn generate_chunk(cx: i32, cz: i32, world_seed: u64) -> Bsp {
entries.push(ChunkEntry { edge: ChunkEdge::South, cell: 8 });
}
(doorways, entries)
}
pub fn generate_chunk(cx: i32, cz: i32, world_seed: u64) -> Bsp {
let biome = select_biome(cx, cz, world_seed);
let seed = chunk_seed(world_seed, cx, cz);
let origin_x = cx as f32 * 16.0 * CELL_SIZE;
let origin_z = cz as f32 * 16.0 * CELL_SIZE;
let params = ProcgenParams {
seed,
origin_x,
origin_z,
..Default::default()
};
let (doorways, entries) = get_chunk_config(cx, cz);
match biome {
Biome::Dungeon => generate_rooms(&params, &doorways, &entries),
Biome::Courtyard => generate_courtyard(&params, &entries),
Biome::Dungeon => generate_rooms(&params, &doorways, &entries, world_seed),
Biome::Courtyard => generate_courtyard(&params, &entries, world_seed),
Biome::Exterior => generate_exterior(&params, world_seed),
}
}

View file

@ -132,6 +132,10 @@ typedef struct pxl8_bsp {
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;
} pxl8_bsp;
#ifdef __cplusplus

View file

@ -2,6 +2,15 @@
#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 pxl8_bsp* bsp, pxl8_vec3 pos) {
if (!bsp || bsp->num_nodes == 0) return -1;
@ -16,39 +25,175 @@ static i32 bsp_find_leaf(const pxl8_bsp* bsp, pxl8_vec3 pos) {
return -(node_id + 1);
}
static i32 bsp_contents_from(const pxl8_bsp* bsp, i32 node_id, pxl8_vec3 pos) {
while (node_id >= 0) {
const pxl8_bsp_node* node = &bsp->nodes[node_id];
const pxl8_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 pxl8_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 pxl8_bsp_node* node = &bsp->nodes[node_id];
const pxl8_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 pxl8_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 pxl8_bsp_point_solid(const pxl8_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 bool bsp_point_clear(const pxl8_bsp* bsp, f32 x, f32 y, f32 z, f32 radius) {
if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x, y, z})) return false;
if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x + radius, y, z})) return false;
if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x - radius, y, z})) return false;
if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x, y, z + radius})) return false;
if (pxl8_bsp_point_solid(bsp, (pxl8_vec3){x, y, z - radius})) return false;
return true;
static void trace_offsets(const pxl8_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 pxl8_bsp_trace(const pxl8_bsp* bsp, pxl8_vec3 from, pxl8_vec3 to, f32 radius) {
if (!bsp || bsp->num_nodes == 0) return to;
if (bsp_point_clear(bsp, to.x, to.y, to.z, radius)) {
return to;
}
f32 frac;
pxl8_vec3 normal;
trace_offsets(bsp, from, to, radius, &frac, &normal);
if (frac >= 1.0f) return to;
bool x_ok = bsp_point_clear(bsp, to.x, from.y, from.z, radius);
bool y_ok = bsp_point_clear(bsp, from.x, to.y, from.z, radius);
bool z_ok = bsp_point_clear(bsp, from.x, from.y, to.z, radius);
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,
};
pxl8_vec3 result = from;
if (x_ok) result.x = to.x;
if (y_ok) result.y = to.y;
if (z_ok) result.z = to.z;
f32 remaining = 1.0f - frac;
if (remaining <= 0.0f) return hit;
return result;
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 pxl8_bsp* sim_bsp_at(const pxl8_sim_world* world, f32 x, f32 z) {
@ -79,59 +224,93 @@ static f32 bsp_terrain_height(const pxl8_bsp* bsp, f32 x, f32 z) {
return h0 + (h1 - h0) * fz;
}
static bool sim_point_solid(const pxl8_sim_world* world, f32 x, f32 y, f32 z) {
const pxl8_bsp* bsp = sim_bsp_at(world, x, z);
return pxl8_bsp_point_solid(bsp, (pxl8_vec3){x, y, z});
}
static f32 sim_terrain_height(const pxl8_sim_world* world, f32 x, f32 z) {
const pxl8_bsp* bsp = sim_bsp_at(world, x, z);
return bsp_terrain_height(bsp, x, z);
}
static bool sim_point_clear(const pxl8_sim_world* world, f32 x, f32 y, f32 z, f32 radius) {
if (sim_point_solid(world, x, y, z)) return false;
if (sim_point_solid(world, x + radius, y, z)) return false;
if (sim_point_solid(world, x - radius, y, z)) return false;
if (sim_point_solid(world, x, y, z + radius)) return false;
if (sim_point_solid(world, x, y, z - radius)) return false;
return true;
}
static void sim_trace_offsets(const pxl8_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},
};
static f32 find_landing_y(const pxl8_sim_world* world, pxl8_vec3 pos, f32 target_y, f32 radius, f32 height) {
f32 hi = pos.y;
f32 lo = target_y;
for (i32 i = 0; i < 8; i++) {
f32 mid = (hi + lo) * 0.5f;
pxl8_vec3 test = {pos.x, mid, pos.z};
pxl8_vec3 result = pxl8_sim_trace(world, pos, test, radius, height);
if (result.y > mid + 0.01f) {
lo = mid;
} else {
hi = mid;
*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 pxl8_bsp* bsp_s = sim_bsp_at(world, s.x, s.z);
const pxl8_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;
}
}
}
}
return hi;
}
pxl8_vec3 pxl8_sim_trace(const pxl8_sim_world* world, pxl8_vec3 from, pxl8_vec3 to, f32 radius, f32 height) {
(void)height;
if (!world || world->chunk_size <= 0) return to;
if (sim_point_clear(world, to.x, to.y, to.z, radius)) {
return to;
}
f32 frac;
pxl8_vec3 normal;
sim_trace_offsets(world, from, to, radius, &frac, &normal);
if (frac >= 1.0f) return to;
bool x_ok = sim_point_clear(world, to.x, from.y, from.z, radius);
bool y_ok = sim_point_clear(world, from.x, to.y, from.z, radius);
bool z_ok = sim_point_clear(world, from.x, from.y, to.z, radius);
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,
};
pxl8_vec3 result = from;
if (x_ok) result.x = to.x;
if (y_ok) result.y = to.y;
if (z_ok) result.z = to.z;
f32 remaining = 1.0f - frac;
if (remaining <= 0.0f) return hit;
return result;
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 pxl8_sim_check_ground(const pxl8_sim_world* world, pxl8_vec3 pos, f32 radius) {
@ -201,10 +380,6 @@ void pxl8_sim_move_player(pxl8_sim_entity* ent, const pxl8_input_msg* input,
pxl8_vec3 new_pos = pxl8_sim_trace(world, ent->pos, target, cfg->player_radius, cfg->player_height);
if (ent->vel.y < 0 && new_pos.y > target.y + 0.01f) {
new_pos.y = find_landing_y(world, new_pos, target.y, 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;
@ -256,10 +431,6 @@ void pxl8_sim_integrate(pxl8_sim_entity* ent, const pxl8_sim_world* world,
pxl8_vec3 new_pos = pxl8_sim_trace(world, ent->pos, target, cfg->player_radius, cfg->player_height);
if (ent->vel.y < 0 && new_pos.y > target.y + 0.01f) {
new_pos.y = find_landing_y(world, new_pos, target.y, 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;

View file

@ -330,8 +330,8 @@ static void compute_edge_leafs(pxl8_loaded_chunk* lc) {
memset(lc->edges, 0, sizeof(lc->edges));
for (u32 i = 0; i < bsp->num_leafs; i++) {
if (bsp->leafs[i].contents == -1) continue;
const pxl8_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;
@ -402,13 +402,37 @@ static void world_compute_visibility(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3
for (u32 i = 0; i < world->loaded_count; i++) {
pxl8_loaded_chunk* lc = &world->loaded[i];
if (!lc->chunk || !lc->chunk->bsp) continue;
const pxl8_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;
i32 leaf = pxl8_bsp_find_leaf(lc->chunk->bsp, camera_pos);
if (leaf >= 0 && (u32)leaf < lc->chunk->bsp->num_leafs &&
lc->chunk->bsp->leafs[leaf].contents != -1) {
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 = pxl8_bsp_find_leaf(bsp, camera_pos);
if (leaf >= 0 && (u32)leaf < bsp->num_leafs &&
bsp->leafs[leaf].contents != -1 &&
(!(bsp->bounds_max_x > bsp->bounds_min_x) || bsp->leafs[leaf].contents == -2)) {
cam_ci = (i32)i;
cam_li = leaf;
break;
}
}
if (cam_ci < 0) {
for (u32 i = 0; i < world->loaded_count; i++) {
pxl8_loaded_chunk* lc = &world->loaded[i];
if (!lc->chunk || !lc->chunk->bsp) continue;
const pxl8_bsp* bsp = lc->chunk->bsp;
if (bsp->bounds_max_x > bsp->bounds_min_x) continue;
i32 leaf = pxl8_bsp_find_leaf(bsp, camera_pos);
if (leaf < 0 || (u32)leaf >= bsp->num_leafs) continue;
const pxl8_bsp_leaf* l = &bsp->leafs[leaf];
if (l->contents == -1) continue;
if (camera_pos.x < (f32)l->mins[0] || camera_pos.x > (f32)l->maxs[0] ||
camera_pos.z < (f32)l->mins[2] || camera_pos.z > (f32)l->maxs[2]) continue;
cam_ci = (i32)i;
cam_li = leaf;
break;
@ -457,6 +481,10 @@ static void world_compute_visibility(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3
u32 head = 0, tail = 0;
pxl8_rect full_screen = {-1.0f, -1.0f, 1.0f, 1.0f};
const pxl8_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};
@ -479,7 +507,7 @@ static void world_compute_visibility(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3
if (bsp->leafs[target].contents == -1) continue;
if (is_cam_chunk && !pxl8_bsp_is_leaf_visible(bsp, cam_li, (i32)target)) continue;
if (is_cam_leaf) {
if (is_cam_leaf || cam_exterior) {
vis_try_enqueue(world->vis_ptrs, world->vis_win_ptrs,
world->vis_queue, &tail, PXL8_VIS_MAX_QUEUE,
cur.chunk_idx, (u16)target, full_screen);
@ -546,6 +574,13 @@ static void world_compute_visibility(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3
bpx0 = (f32)xlo; bpx1 = (f32)xhi;
}
if (cam_exterior) {
vis_try_enqueue(world->vis_ptrs, world->vis_win_ptrs,
world->vis_queue, &tail, PXL8_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;
@ -558,6 +593,7 @@ static void world_compute_visibility(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3
}
}
}
}
void pxl8_world_render(pxl8_world* world, pxl8_gfx* gfx, pxl8_vec3 camera_pos) {

View file

@ -285,6 +285,16 @@ static pxl8_bsp* assembly_to_bsp(pxl8_world_chunk_assembly* a) {
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;
}