From 1341b30920f3bafff58081eb4089ee5ba213cbf6 Mon Sep 17 00:00:00 2001 From: asrael Date: Sat, 7 Mar 2026 07:19:40 -0600 Subject: [PATCH] fix(bsp): fill in exterior cells --- demo/mod/first_person3d.fnl | 4 +- pxl8d/src/bsp.rs | 8 + pxl8d/src/main.rs | 6 + pxl8d/src/procgen.rs | 469 +++++++++++++++++++++++++++-- src/bsp/pxl8_bsp.h | 4 + src/sim/pxl8_sim.c | 295 ++++++++++++++---- src/world/pxl8_world.c | 46 ++- src/world/pxl8_world_chunk_cache.c | 10 + 8 files changed, 740 insertions(+), 102 deletions(-) diff --git a/demo/mod/first_person3d.fnl b/demo/mod/first_person3d.fnl index b5214a8..68717d8 100644 --- a/demo/mod/first_person3d.fnl +++ b/demo/mod/first_person3d.fnl @@ -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) diff --git a/pxl8d/src/bsp.rs b/pxl8d/src/bsp.rs index 252494f..9653d62 100644 --- a/pxl8d/src/bsp.rs +++ b/pxl8d/src/bsp.rs @@ -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 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 { diff --git a/pxl8d/src/main.rs b/pxl8d/src/main.rs index 695045f..da3d2a9 100644 --- a/pxl8d/src/main.rs +++ b/pxl8d/src/main.rs @@ -313,5 +313,11 @@ fn bsp_to_messages(bsp: &bsp::Bsp, cx: i32, cz: i32, version: u32) -> Vec (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 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) { 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 = 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) { 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 = 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 { 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(¶ms, &doorways, &entries), + Biome::Courtyard => generate_courtyard_grid(¶ms, &entries), + _ => return None, + }; + + Some(grid) +} + +fn get_chunk_config(cx: i32, cz: i32) -> (Vec, Vec) { 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(¶ms, &doorways, &entries), - Biome::Courtyard => generate_courtyard(¶ms, &entries), + Biome::Dungeon => generate_rooms(¶ms, &doorways, &entries, world_seed), + Biome::Courtyard => generate_courtyard(¶ms, &entries, world_seed), Biome::Exterior => generate_exterior(¶ms, world_seed), } } diff --git a/src/bsp/pxl8_bsp.h b/src/bsp/pxl8_bsp.h index aedbb63..3fdb06b 100644 --- a/src/bsp/pxl8_bsp.h +++ b/src/bsp/pxl8_bsp.h @@ -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 diff --git a/src/sim/pxl8_sim.c b/src/sim/pxl8_sim.c index 9d97a4a..ff19667 100644 --- a/src/sim/pxl8_sim.c +++ b/src/sim/pxl8_sim.c @@ -2,6 +2,15 @@ #include +#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; diff --git a/src/world/pxl8_world.c b/src/world/pxl8_world.c index c1d57e1..aa217a3 100644 --- a/src/world/pxl8_world.c +++ b/src/world/pxl8_world.c @@ -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) { diff --git a/src/world/pxl8_world_chunk_cache.c b/src/world/pxl8_world_chunk_cache.c index 203ff79..063dcd9 100644 --- a/src/world/pxl8_world_chunk_cache.c +++ b/src/world/pxl8_world_chunk_cache.c @@ -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; }