extern crate alloc; use alloc::vec; use alloc::vec::Vec; use libm::sqrtf; use crate::bsp::{Bsp, BspBuilder, CellPortals, Edge, Face, Leaf, Node, Plane, Portal, Vertex}; use crate::math::{Vec3, Vec3Ext}; pub const CELL_SIZE: f32 = 64.0; pub const WALL_HEIGHT: f32 = 128.0; pub const TRIM_HEIGHT: f32 = 12.0; pub const PVS_MAX_DEPTH: u32 = 64; #[derive(Clone, Copy)] pub struct LightSource { pub position: Vec3, pub intensity: f32, pub radius: f32, } #[derive(Clone, Copy)] pub struct ProcgenParams { pub width: i32, pub height: i32, pub seed: u32, pub min_room_size: i32, pub max_room_size: i32, pub num_rooms: i32, } impl Default for ProcgenParams { fn default() -> Self { Self { width: 16, height: 16, seed: 12345, min_room_size: 3, max_room_size: 6, num_rooms: 8, } } } struct RoomGrid { cells: Vec, width: i32, height: i32, } impl RoomGrid { fn new(width: i32, height: i32) -> Self { Self { cells: vec![0; (width * height) as usize], width, height, } } fn get(&self, x: i32, y: i32) -> u8 { if x < 0 || x >= self.width || y < 0 || y >= self.height { return 1; } self.cells[(y * self.width + x) as usize] } fn set(&mut self, x: i32, y: i32, value: u8) { if x >= 0 && x < self.width && y >= 0 && y < self.height { self.cells[(y * self.width + x) as usize] = value; } } fn fill(&mut self, value: u8) { for cell in &mut self.cells { *cell = value; } } } struct Rng { state: u32, } impl Rng { fn new(seed: u32) -> Self { Self { state: seed } } fn next(&mut self) -> u32 { self.state = self.state.wrapping_mul(1103515245).wrapping_add(12345); (self.state >> 16) & 0x7FFF } } #[derive(Clone, Copy)] struct Bounds { x: i32, y: i32, w: i32, h: i32, } impl Bounds { fn intersects(&self, other: &Bounds) -> bool { !(self.x + self.w <= other.x || other.x + other.w <= self.x || self.y + self.h <= other.y || other.y + other.h <= self.y) } } struct BspBuildContext<'a> { bsp: &'a mut BspBuilder, grid: &'a RoomGrid, node_count: u32, plane_offset: u32, } fn carve_corridor_h(grid: &mut RoomGrid, x1: i32, x2: i32, y: i32) { let start = x1.min(x2); let end = x1.max(x2); for x in start..=end { grid.set(x, y, 0); grid.set(x, y - 1, 0); grid.set(x, y + 1, 0); } } fn carve_corridor_v(grid: &mut RoomGrid, y1: i32, y2: i32, x: i32) { let start = y1.min(y2); let end = y1.max(y2); for y in start..=end { grid.set(x, y, 0); grid.set(x - 1, y, 0); grid.set(x + 1, y, 0); } } 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); for i in 0..4 { let v = verts[vert_idx + i].position; if v.x < face.aabb_min.x { face.aabb_min.x = v.x; } if v.x > face.aabb_max.x { face.aabb_max.x = v.x; } if v.y < face.aabb_min.y { face.aabb_min.y = v.y; } if v.y > face.aabb_max.y { face.aabb_max.y = v.y; } if v.z < face.aabb_min.z { face.aabb_min.z = v.z; } if v.z > face.aabb_max.z { face.aabb_max.z = v.z; } } } fn build_bsp_node_grid(ctx: &mut BspBuildContext, x0: i32, y0: i32, x1: i32, y1: i32, depth: i32) -> i32 { if x1 - x0 == 1 && y1 - y0 == 1 { let leaf_idx = y0 * ctx.grid.width + x0; return -(leaf_idx + 1); } let node_idx = ctx.node_count; ctx.node_count += 1; let plane_idx = ctx.plane_offset; ctx.plane_offset += 1; if depth % 2 == 0 { let mid_x = (x0 + x1) / 2; let split_pos = mid_x as f32 * CELL_SIZE; ctx.bsp.planes[plane_idx as usize] = Plane { normal: Vec3::new(1.0, 0.0, 0.0), dist: split_pos, type_: 0, }; let child0 = build_bsp_node_grid(ctx, mid_x, y0, x1, y1, depth + 1); let child1 = build_bsp_node_grid(ctx, x0, y0, mid_x, y1, depth + 1); ctx.bsp.nodes[node_idx as usize] = Node { plane_id: plane_idx, children: [child0, child1], ..Default::default() }; } else { let mid_y = (y0 + y1) / 2; let split_pos = mid_y as f32 * CELL_SIZE; ctx.bsp.planes[plane_idx as usize] = Plane { normal: Vec3::new(0.0, 0.0, 1.0), dist: split_pos, type_: 0, }; let child0 = build_bsp_node_grid(ctx, x0, mid_y, x1, y1, depth + 1); let child1 = build_bsp_node_grid(ctx, x0, y0, x1, mid_y, depth + 1); ctx.bsp.nodes[node_idx as usize] = Node { plane_id: plane_idx, children: [child0, child1], ..Default::default() }; } node_idx as i32 } fn build_bsp_node(ctx: &mut BspBuildContext, x0: i32, y0: i32, x1: i32, y1: i32, depth: i32, floor_leaf_idx: i32) -> i32 { let node_idx = ctx.node_count; ctx.node_count += 1; let plane_idx = ctx.plane_offset; ctx.plane_offset += 1; ctx.bsp.planes[plane_idx as usize] = Plane { normal: Vec3::new(0.0, 1.0, 0.0), dist: 0.0, type_: 1, }; let above_floor = build_bsp_node_grid(ctx, x0, y0, x1, y1, depth); ctx.bsp.nodes[node_idx as usize] = Node { plane_id: plane_idx, children: [above_floor, -(floor_leaf_idx + 1)], ..Default::default() }; node_idx as i32 } fn build_cell_portals(grid: &RoomGrid) -> Vec { let total_cells = (grid.width * grid.height) as usize; let mut portals = vec![CellPortals::default(); total_cells]; for y in 0..grid.height { for x in 0..grid.width { if grid.get(x, y) != 0 { continue; } let c = (y * grid.width + x) as usize; let cx = x as f32 * CELL_SIZE; let cz = y as f32 * CELL_SIZE; if x > 0 && grid.get(x - 1, y) == 0 { let p = &mut portals[c]; let idx = p.num_portals as usize; p.portals[idx] = Portal { x0: cx, z0: cz, x1: cx, z1: cz + CELL_SIZE, target_leaf: (y * grid.width + (x - 1)) as u32, }; p.num_portals += 1; } if x < grid.width - 1 && grid.get(x + 1, y) == 0 { let p = &mut portals[c]; let idx = p.num_portals as usize; p.portals[idx] = Portal { x0: cx + CELL_SIZE, z0: cz + CELL_SIZE, x1: cx + CELL_SIZE, z1: cz, target_leaf: (y * grid.width + (x + 1)) as u32, }; p.num_portals += 1; } if y > 0 && grid.get(x, y - 1) == 0 { let p = &mut portals[c]; let idx = p.num_portals as usize; p.portals[idx] = Portal { x0: cx + CELL_SIZE, z0: cz, x1: cx, z1: cz, target_leaf: ((y - 1) * grid.width + x) as u32, }; p.num_portals += 1; } if y < grid.height - 1 && grid.get(x, y + 1) == 0 { let p = &mut portals[c]; let idx = p.num_portals as usize; p.portals[idx] = Portal { x0: cx, z0: cz + CELL_SIZE, x1: cx + CELL_SIZE, z1: cz + CELL_SIZE, target_leaf: ((y + 1) * grid.width + x) as u32, }; p.num_portals += 1; } } } portals } #[derive(Clone, Copy)] struct FloodEntry { leaf: u32, depth: u32, } fn portal_flood_bfs( start_leaf: u32, portals: &[CellPortals], leafs: &[Leaf], pvs: &mut [u8], num_leafs: u32, ) { let pvs_bytes = ((num_leafs + 7) / 8) as usize; let mut visited = vec![0u8; pvs_bytes]; let mut queue = Vec::with_capacity(num_leafs as usize); pvs[(start_leaf >> 3) as usize] |= 1 << (start_leaf & 7); visited[(start_leaf >> 3) as usize] |= 1 << (start_leaf & 7); queue.push(FloodEntry { leaf: start_leaf, depth: 0 }); let mut head = 0; while head < queue.len() { let e = queue[head]; head += 1; if e.depth >= PVS_MAX_DEPTH { continue; } if leafs[e.leaf as usize].contents == -1 { continue; } let cp = &portals[e.leaf as usize]; for i in 0..cp.num_portals { let target = cp.portals[i as usize].target_leaf; let byte = (target >> 3) as usize; let bit = target & 7; if visited[byte] & (1 << bit) != 0 { continue; } visited[byte] |= 1 << bit; if leafs[target as usize].contents == -1 { continue; } pvs[byte] |= 1 << bit; queue.push(FloodEntry { leaf: target, depth: e.depth + 1 }); } } } fn compute_leaf_pvs( start_leaf: u32, portals: &[CellPortals], leafs: &[Leaf], num_leafs: u32, ) -> Vec { let pvs_bytes = ((num_leafs + 7) / 8) as usize; let mut pvs = vec![0u8; pvs_bytes]; portal_flood_bfs(start_leaf, portals, leafs, &mut pvs, num_leafs); pvs } fn rle_compress_pvs(pvs: &[u8]) -> Vec { let mut out = Vec::new(); let mut i = 0; while i < pvs.len() { if pvs[i] != 0 { out.push(pvs[i]); i += 1; } else { let mut count = 0u8; while i < pvs.len() && pvs[i] == 0 && count < 255 { count += 1; i += 1; } out.push(0); out.push(count); } } out } fn build_pvs_data(bsp: &mut BspBuilder, portals: &[CellPortals]) { let num_leafs = bsp.leafs.len() as u32; let mut visdata = Vec::new(); for leaf in 0..num_leafs { if bsp.leafs[leaf as usize].contents == -1 { bsp.leafs[leaf as usize].visofs = -1; continue; } let pvs = compute_leaf_pvs(leaf, portals, &bsp.leafs, num_leafs); let compressed = rle_compress_pvs(&pvs); bsp.leafs[leaf as usize].visofs = visdata.len() as i32; visdata.extend_from_slice(&compressed); } bsp.visdata = visdata; } fn compute_vertex_light( pos: Vec3, normal: Vec3, lights: &[LightSource], ambient: f32, ) -> f32 { let mut total = ambient; for light in lights { let to_light = Vec3::new( light.position.x - pos.x, light.position.y - pos.y, light.position.z - pos.z, ); let dist_sq = to_light.x * to_light.x + to_light.y * to_light.y + to_light.z * to_light.z; let dist = sqrtf(dist_sq).max(1.0); if dist > light.radius { continue; } let inv_dist = 1.0 / dist; let light_dir = Vec3::new( to_light.x * inv_dist, to_light.y * inv_dist, to_light.z * inv_dist, ); let ndotl = (normal.x * light_dir.x + normal.y * light_dir.y + normal.z * light_dir.z).max(0.0); let attenuation = (1.0 - dist / light.radius).max(0.0); let attenuation = attenuation * attenuation; total += light.intensity * ndotl * attenuation; } total.min(1.0) } fn compute_bsp_vertex_lighting(bsp: &mut BspBuilder, lights: &[LightSource], ambient: f32) { if bsp.vertices.is_empty() { return; } bsp.vertex_lights = vec![0u32; bsp.vertices.len()]; for f in 0..bsp.faces.len() { let face = &bsp.faces[f]; let normal = if (face.plane_id as usize) < bsp.planes.len() { bsp.planes[face.plane_id as usize].normal } else { Vec3::new(0.0, 1.0, 0.0) }; for e in 0..face.num_edges { let surfedge_idx = (face.first_edge + e as u32) as usize; if surfedge_idx >= bsp.surfedges.len() { continue; } let edge_idx = bsp.surfedges[surfedge_idx]; let vert_idx = if edge_idx >= 0 { let ei = edge_idx as usize; if ei >= bsp.edges.len() { continue; } bsp.edges[ei].vertex[0] as usize } else { let ei = (-edge_idx) as usize; if ei >= bsp.edges.len() { continue; } bsp.edges[ei].vertex[1] as usize }; if vert_idx >= bsp.vertices.len() { continue; } let pos = bsp.vertices[vert_idx].position; let light = compute_vertex_light(pos, normal, lights, ambient); let light_byte = (light * 255.0) as u8; bsp.vertex_lights[vert_idx] = ((light_byte as u32) << 24) | 0x00FFFFFF; } } } fn grid_to_bsp(bsp: &mut BspBuilder, grid: &RoomGrid) { let mut wall_count = 0; let mut floor_ceiling_count = 0; for y in 0..grid.height { for x in 0..grid.width { if grid.get(x, y) == 0 { if grid.get(x - 1, y) == 1 { wall_count += 1; } if grid.get(x + 1, y) == 1 { wall_count += 1; } if grid.get(x, y - 1) == 1 { wall_count += 1; } if grid.get(x, y + 1) == 1 { wall_count += 1; } floor_ceiling_count += 1; } } } let face_count = wall_count * 2 + floor_ceiling_count; let vertex_count = face_count * 4; let total_cells = (grid.width * grid.height) as usize; let max_nodes = 2 * total_cells + 1; let total_planes = face_count + max_nodes + 1; bsp.vertices = vec![Vertex::default(); vertex_count]; bsp.faces = vec![Face::default(); face_count]; bsp.planes = vec![Plane::default(); total_planes]; bsp.edges = vec![Edge::default(); vertex_count]; bsp.surfedges = vec![0i32; vertex_count]; bsp.nodes = vec![Node::default(); max_nodes]; let mut face_cell = vec![0u32; face_count]; let mut vert_idx = 0usize; let mut face_idx = 0usize; let mut edge_idx = 0usize; for y in 0..grid.height { for x in 0..grid.width { if grid.get(x, y) == 0 { let fx = x as f32 * CELL_SIZE; let fy = y as f32 * CELL_SIZE; let cell_idx = (y * grid.width + x) as u32; if grid.get(x - 1, y) == 1 { bsp.vertices[vert_idx + 0].position = Vec3::new(fx, TRIM_HEIGHT, fy); bsp.vertices[vert_idx + 1].position = Vec3::new(fx, WALL_HEIGHT, fy); bsp.vertices[vert_idx + 2].position = Vec3::new(fx, WALL_HEIGHT, fy + CELL_SIZE); bsp.vertices[vert_idx + 3].position = Vec3::new(fx, TRIM_HEIGHT, fy + CELL_SIZE); bsp.planes[face_idx].normal = Vec3::new(1.0, 0.0, 0.0); bsp.planes[face_idx].dist = fx; bsp.faces[face_idx].plane_id = face_idx as u16; bsp.faces[face_idx].num_edges = 4; bsp.faces[face_idx].first_edge = edge_idx as u32; bsp.faces[face_idx].material_id = 1; for i in 0..4 { bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16; bsp.edges[edge_idx + i].vertex[1] = (vert_idx + ((i + 1) % 4)) as u16; bsp.surfedges[edge_idx + i] = (edge_idx + i) as i32; } compute_face_aabb(&mut bsp.faces[face_idx], &bsp.vertices, vert_idx); face_cell[face_idx] = cell_idx; vert_idx += 4; edge_idx += 4; face_idx += 1; bsp.vertices[vert_idx + 0].position = Vec3::new(fx, 0.0, fy); bsp.vertices[vert_idx + 1].position = Vec3::new(fx, TRIM_HEIGHT, fy); bsp.vertices[vert_idx + 2].position = Vec3::new(fx, TRIM_HEIGHT, fy + CELL_SIZE); bsp.vertices[vert_idx + 3].position = Vec3::new(fx, 0.0, fy + CELL_SIZE); bsp.planes[face_idx].normal = Vec3::new(1.0, 0.0, 0.0); bsp.planes[face_idx].dist = fx; bsp.faces[face_idx].plane_id = face_idx as u16; bsp.faces[face_idx].num_edges = 4; bsp.faces[face_idx].first_edge = edge_idx as u32; bsp.faces[face_idx].material_id = 3; for i in 0..4 { bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16; bsp.edges[edge_idx + i].vertex[1] = (vert_idx + ((i + 1) % 4)) as u16; bsp.surfedges[edge_idx + i] = (edge_idx + i) as i32; } compute_face_aabb(&mut bsp.faces[face_idx], &bsp.vertices, vert_idx); face_cell[face_idx] = cell_idx; vert_idx += 4; edge_idx += 4; face_idx += 1; } if grid.get(x + 1, y) == 1 { bsp.vertices[vert_idx + 0].position = Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, fy); bsp.vertices[vert_idx + 1].position = Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, fy + CELL_SIZE); bsp.vertices[vert_idx + 2].position = Vec3::new(fx + CELL_SIZE, WALL_HEIGHT, fy + CELL_SIZE); bsp.vertices[vert_idx + 3].position = Vec3::new(fx + CELL_SIZE, WALL_HEIGHT, fy); bsp.planes[face_idx].normal = Vec3::new(-1.0, 0.0, 0.0); bsp.planes[face_idx].dist = -(fx + CELL_SIZE); bsp.faces[face_idx].plane_id = face_idx as u16; bsp.faces[face_idx].num_edges = 4; bsp.faces[face_idx].first_edge = edge_idx as u32; bsp.faces[face_idx].material_id = 1; for i in 0..4 { bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16; bsp.edges[edge_idx + i].vertex[1] = (vert_idx + ((i + 1) % 4)) as u16; bsp.surfedges[edge_idx + i] = (edge_idx + i) as i32; } compute_face_aabb(&mut bsp.faces[face_idx], &bsp.vertices, vert_idx); face_cell[face_idx] = cell_idx; vert_idx += 4; edge_idx += 4; face_idx += 1; bsp.vertices[vert_idx + 0].position = Vec3::new(fx + CELL_SIZE, 0.0, fy); bsp.vertices[vert_idx + 1].position = Vec3::new(fx + CELL_SIZE, 0.0, fy + CELL_SIZE); bsp.vertices[vert_idx + 2].position = Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, fy + CELL_SIZE); bsp.vertices[vert_idx + 3].position = Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, fy); bsp.planes[face_idx].normal = Vec3::new(-1.0, 0.0, 0.0); bsp.planes[face_idx].dist = -(fx + CELL_SIZE); bsp.faces[face_idx].plane_id = face_idx as u16; bsp.faces[face_idx].num_edges = 4; bsp.faces[face_idx].first_edge = edge_idx as u32; bsp.faces[face_idx].material_id = 3; for i in 0..4 { bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16; bsp.edges[edge_idx + i].vertex[1] = (vert_idx + ((i + 1) % 4)) as u16; bsp.surfedges[edge_idx + i] = (edge_idx + i) as i32; } compute_face_aabb(&mut bsp.faces[face_idx], &bsp.vertices, vert_idx); face_cell[face_idx] = cell_idx; vert_idx += 4; edge_idx += 4; face_idx += 1; } if grid.get(x, y - 1) == 1 { bsp.vertices[vert_idx + 0].position = Vec3::new(fx, TRIM_HEIGHT, fy); bsp.vertices[vert_idx + 1].position = Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, fy); bsp.vertices[vert_idx + 2].position = Vec3::new(fx + CELL_SIZE, WALL_HEIGHT, fy); bsp.vertices[vert_idx + 3].position = Vec3::new(fx, WALL_HEIGHT, fy); bsp.planes[face_idx].normal = Vec3::new(0.0, 0.0, 1.0); bsp.planes[face_idx].dist = fy; bsp.faces[face_idx].plane_id = face_idx as u16; bsp.faces[face_idx].num_edges = 4; bsp.faces[face_idx].first_edge = edge_idx as u32; bsp.faces[face_idx].material_id = 1; for i in 0..4 { bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16; bsp.edges[edge_idx + i].vertex[1] = (vert_idx + ((i + 1) % 4)) as u16; bsp.surfedges[edge_idx + i] = (edge_idx + i) as i32; } compute_face_aabb(&mut bsp.faces[face_idx], &bsp.vertices, vert_idx); face_cell[face_idx] = cell_idx; vert_idx += 4; edge_idx += 4; face_idx += 1; bsp.vertices[vert_idx + 0].position = Vec3::new(fx, 0.0, fy); bsp.vertices[vert_idx + 1].position = Vec3::new(fx + CELL_SIZE, 0.0, fy); bsp.vertices[vert_idx + 2].position = Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, fy); bsp.vertices[vert_idx + 3].position = Vec3::new(fx, TRIM_HEIGHT, fy); bsp.planes[face_idx].normal = Vec3::new(0.0, 0.0, 1.0); bsp.planes[face_idx].dist = fy; bsp.faces[face_idx].plane_id = face_idx as u16; bsp.faces[face_idx].num_edges = 4; bsp.faces[face_idx].first_edge = edge_idx as u32; bsp.faces[face_idx].material_id = 3; for i in 0..4 { bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16; bsp.edges[edge_idx + i].vertex[1] = (vert_idx + ((i + 1) % 4)) as u16; bsp.surfedges[edge_idx + i] = (edge_idx + i) as i32; } compute_face_aabb(&mut bsp.faces[face_idx], &bsp.vertices, vert_idx); face_cell[face_idx] = cell_idx; vert_idx += 4; edge_idx += 4; face_idx += 1; } if grid.get(x, y + 1) == 1 { bsp.vertices[vert_idx + 0].position = Vec3::new(fx, TRIM_HEIGHT, fy + CELL_SIZE); bsp.vertices[vert_idx + 1].position = Vec3::new(fx, WALL_HEIGHT, fy + CELL_SIZE); bsp.vertices[vert_idx + 2].position = Vec3::new(fx + CELL_SIZE, WALL_HEIGHT, fy + CELL_SIZE); bsp.vertices[vert_idx + 3].position = Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, fy + CELL_SIZE); bsp.planes[face_idx].normal = Vec3::new(0.0, 0.0, -1.0); bsp.planes[face_idx].dist = -(fy + CELL_SIZE); bsp.faces[face_idx].plane_id = face_idx as u16; bsp.faces[face_idx].num_edges = 4; bsp.faces[face_idx].first_edge = edge_idx as u32; bsp.faces[face_idx].material_id = 1; for i in 0..4 { bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16; bsp.edges[edge_idx + i].vertex[1] = (vert_idx + ((i + 1) % 4)) as u16; bsp.surfedges[edge_idx + i] = (edge_idx + i) as i32; } compute_face_aabb(&mut bsp.faces[face_idx], &bsp.vertices, vert_idx); face_cell[face_idx] = cell_idx; vert_idx += 4; edge_idx += 4; face_idx += 1; bsp.vertices[vert_idx + 0].position = Vec3::new(fx, 0.0, fy + CELL_SIZE); bsp.vertices[vert_idx + 1].position = Vec3::new(fx, TRIM_HEIGHT, fy + CELL_SIZE); bsp.vertices[vert_idx + 2].position = Vec3::new(fx + CELL_SIZE, TRIM_HEIGHT, fy + CELL_SIZE); bsp.vertices[vert_idx + 3].position = Vec3::new(fx + CELL_SIZE, 0.0, fy + CELL_SIZE); bsp.planes[face_idx].normal = Vec3::new(0.0, 0.0, -1.0); bsp.planes[face_idx].dist = -(fy + CELL_SIZE); bsp.faces[face_idx].plane_id = face_idx as u16; bsp.faces[face_idx].num_edges = 4; bsp.faces[face_idx].first_edge = edge_idx as u32; bsp.faces[face_idx].material_id = 3; for i in 0..4 { bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16; bsp.edges[edge_idx + i].vertex[1] = (vert_idx + ((i + 1) % 4)) as u16; bsp.surfedges[edge_idx + i] = (edge_idx + i) as i32; } compute_face_aabb(&mut bsp.faces[face_idx], &bsp.vertices, vert_idx); face_cell[face_idx] = cell_idx; vert_idx += 4; edge_idx += 4; face_idx += 1; } } } } for y in 0..grid.height { for x in 0..grid.width { if grid.get(x, y) == 0 { let fx = x as f32 * CELL_SIZE; let fy = y as f32 * CELL_SIZE; let cell_idx = (y * grid.width + x) as u32; bsp.vertices[vert_idx + 0].position = Vec3::new(fx, 0.0, fy); bsp.vertices[vert_idx + 1].position = Vec3::new(fx, 0.0, fy + CELL_SIZE); bsp.vertices[vert_idx + 2].position = Vec3::new(fx + CELL_SIZE, 0.0, fy + CELL_SIZE); bsp.vertices[vert_idx + 3].position = Vec3::new(fx + CELL_SIZE, 0.0, fy); bsp.planes[face_idx].normal = Vec3::new(0.0, 1.0, 0.0); bsp.planes[face_idx].dist = 0.0; bsp.faces[face_idx].plane_id = face_idx as u16; bsp.faces[face_idx].num_edges = 4; bsp.faces[face_idx].first_edge = edge_idx as u32; bsp.faces[face_idx].material_id = 0; for i in 0..4 { bsp.edges[edge_idx + i].vertex[0] = (vert_idx + i) as u16; bsp.edges[edge_idx + i].vertex[1] = (vert_idx + ((i + 1) % 4)) as u16; bsp.surfedges[edge_idx + i] = (edge_idx + i) as i32; } compute_face_aabb(&mut bsp.faces[face_idx], &bsp.vertices, vert_idx); face_cell[face_idx] = cell_idx; vert_idx += 4; edge_idx += 4; face_idx += 1; } } } bsp.vertices.truncate(vert_idx); bsp.faces.truncate(face_idx); bsp.edges.truncate(edge_idx); bsp.surfedges.truncate(edge_idx); bsp.leafs = vec![Leaf::default(); total_cells + 1]; bsp.marksurfaces = vec![0u16; face_idx]; let mut faces_per_cell = vec![0u32; total_cells]; let mut cell_offset = vec![0u32; total_cells]; let mut cell_cursor = vec![0u32; total_cells]; for i in 0..face_idx { faces_per_cell[face_cell[i] as usize] += 1; } let mut offset = 0u32; for c in 0..total_cells { cell_offset[c] = offset; offset += faces_per_cell[c]; } for i in 0..face_idx { let c = face_cell[i] as usize; bsp.marksurfaces[(cell_offset[c] + cell_cursor[c]) as usize] = i as u16; cell_cursor[c] += 1; } for y in 0..grid.height { for x in 0..grid.width { let c = (y * grid.width + x) as usize; let leaf = &mut bsp.leafs[c]; let fx = x as f32 * CELL_SIZE; let fz = y as f32 * CELL_SIZE; leaf.mins[0] = fx as i16; leaf.mins[1] = 0; leaf.mins[2] = fz as i16; leaf.maxs[0] = (fx + CELL_SIZE) as i16; leaf.maxs[1] = WALL_HEIGHT as i16; leaf.maxs[2] = (fz + CELL_SIZE) as i16; 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 = 0; leaf.num_marksurfaces = 0; } leaf.visofs = -1; } } let floor_leaf_idx = total_cells as i32; let floor_leaf = &mut bsp.leafs[total_cells]; floor_leaf.contents = -1; floor_leaf.mins = [0, i16::MIN, 0]; floor_leaf.maxs = [ (grid.width as f32 * CELL_SIZE) as i16, 0, (grid.height as f32 * CELL_SIZE) as i16, ]; floor_leaf.visofs = -1; let face_count_final = face_idx; let mut ctx = BspBuildContext { bsp, grid, node_count: 0, plane_offset: face_count_final as u32, }; build_bsp_node(&mut ctx, 0, 0, grid.width, grid.height, 0, floor_leaf_idx); let node_count = ctx.node_count as usize; let plane_count = ctx.plane_offset as usize; ctx.bsp.nodes.truncate(node_count); ctx.bsp.planes.truncate(plane_count); let portals = build_cell_portals(grid); build_pvs_data(ctx.bsp, &portals); ctx.bsp.cell_portals = portals; } pub fn generate_rooms(params: &ProcgenParams) -> Bsp { let mut rng = Rng::new(params.seed); let mut grid = RoomGrid::new(params.width, params.height); grid.fill(1); let mut rooms: Vec = Vec::new(); let max_attempts = params.num_rooms * 10; for _ in 0..max_attempts { if rooms.len() >= params.num_rooms as usize { break; } let w = params.min_room_size + (rng.next() % (params.max_room_size - params.min_room_size + 1) as u32) as i32; let h = params.min_room_size + (rng.next() % (params.max_room_size - params.min_room_size + 1) as u32) as i32; let x = 1 + (rng.next() % (params.width - w - 2) as u32) as i32; let y = 1 + (rng.next() % (params.height - h - 2) as u32) as i32; let new_room = Bounds { x, y, w, h }; let overlaps = rooms.iter().any(|r| new_room.intersects(r)); if !overlaps { for ry in y..(y + h) { for rx in x..(x + w) { grid.set(rx, ry, 0); } } if !rooms.is_empty() { let prev = rooms.last().unwrap(); let new_cx = x + w / 2; let new_cy = y + h / 2; let prev_cx = prev.x + prev.w / 2; let prev_cy = prev.y + prev.h / 2; if rng.next() % 2 == 0 { carve_corridor_h(&mut grid, prev_cx, new_cx, prev_cy); carve_corridor_v(&mut grid, prev_cy, new_cy, new_cx); } else { carve_corridor_v(&mut grid, prev_cy, new_cy, prev_cx); carve_corridor_h(&mut grid, prev_cx, new_cx, new_cy); } } rooms.push(new_room); } } let mut bsp = BspBuilder::new(); grid_to_bsp(&mut bsp, &grid); let light_height = 80.0; let lights: Vec = rooms.iter().map(|room| { let cx = (room.x as f32 + room.w as f32 / 2.0) * CELL_SIZE; let cy = (room.y as f32 + room.h as f32 / 2.0) * CELL_SIZE; LightSource { position: Vec3::new(cx, light_height, cy), intensity: 0.8, radius: 300.0, } }).collect(); compute_bsp_vertex_lighting(&mut bsp, &lights, 0.1); bsp.into() } pub fn generate(params: &ProcgenParams) -> Bsp { generate_rooms(params) }