feat: courtyard layout generation for BSP 2

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
asrael 2026-02-27 06:50:49 -06:00
parent 5a565844dd
commit 2fb4042fba
2 changed files with 151 additions and 5 deletions

View file

@ -6,6 +6,7 @@ extern crate alloc;
use pxl8d::*;
use pxl8d::chunk::ChunkId;
use pxl8d::chunk::stream::ClientChunkState;
use pxl8d::procgen::LayoutMode;
const TICK_RATE: u64 = 30;
const TICK_NS: u64 = 1_000_000_000 / TICK_RATE;
@ -126,9 +127,8 @@ pub extern "C" fn main(_argc: i32, _argv: *const *const u8) -> i32 {
width: 20,
height: 20,
seed: 12345u32.wrapping_add(2),
min_room_size: 14,
max_room_size: 16,
num_rooms: 1,
layout: LayoutMode::Courtyard,
..ProcgenParams::default()
}),
_ => None,
};

View file

@ -19,6 +19,12 @@ pub struct LightSource {
pub radius: f32,
}
#[derive(Clone, Copy, PartialEq)]
pub enum LayoutMode {
Dungeon,
Courtyard,
}
#[derive(Clone, Copy)]
pub struct ProcgenParams {
pub width: i32,
@ -27,6 +33,7 @@ pub struct ProcgenParams {
pub min_room_size: i32,
pub max_room_size: i32,
pub num_rooms: i32,
pub layout: LayoutMode,
}
impl Default for ProcgenParams {
@ -38,6 +45,7 @@ impl Default for ProcgenParams {
min_room_size: 3,
max_room_size: 6,
num_rooms: 8,
layout: LayoutMode::Dungeon,
}
}
}
@ -1074,6 +1082,144 @@ pub fn generate_rooms(params: &ProcgenParams) -> Bsp {
bsp.into()
}
pub fn generate(params: &ProcgenParams) -> Bsp {
generate_rooms(params)
pub fn generate_courtyard(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<Bounds> = Vec::new();
let cx = params.width / 2;
let cy = params.height / 2;
let court_half = 4;
let court_x = cx - court_half;
let court_y = cy - court_half;
let court_w = court_half * 2;
let court_h = court_half * 2;
for ry in court_y..(court_y + court_h) {
for rx in court_x..(court_x + court_w) {
grid.set(rx, ry, 0);
}
}
rooms.push(Bounds { x: court_x, y: court_y, w: court_w, h: court_h });
let wing_dirs: [(i32, i32); 4] = [(0, -1), (0, 1), (-1, 0), (1, 0)];
for &(dx, dy) in &wing_dirs {
let room_w = 3 + (rng.next() % 3) as i32;
let room_h = 3 + (rng.next() % 3) as i32;
let (rx, ry) = if dx != 0 {
let rx = if dx > 0 { court_x + court_w + 2 } else { court_x - room_w - 2 };
let ry = cy - room_h / 2;
(rx, ry)
} else {
let rx = cx - room_w / 2;
let ry = if dy > 0 { court_y + court_h + 2 } else { court_y - room_h - 2 };
(rx, ry)
};
let rx = rx.max(1).min(params.width - room_w - 1);
let ry = ry.max(1).min(params.height - room_h - 1);
for y in ry..(ry + room_h) {
for x in rx..(rx + room_w) {
grid.set(x, y, 0);
}
}
let room_cx = rx + room_w / 2;
let room_cy = ry + room_h / 2;
if dx != 0 {
carve_corridor_h(&mut grid, cx, room_cx, room_cy);
} else {
carve_corridor_v(&mut grid, cy, room_cy, room_cx);
}
let primary = Bounds { x: rx, y: ry, w: room_w, h: room_h };
rooms.push(primary);
let extra_count = 1 + (rng.next() % 2) as i32;
for _ in 0..extra_count {
let ew = 2 + (rng.next() % 3) as i32;
let eh = 2 + (rng.next() % 3) as i32;
let (ex, ey) = if dx != 0 {
let ex = if dx > 0 { rx + room_w + 1 } else { rx - ew - 1 };
let ey = ry + (rng.next() % room_h.max(1) as u32) as i32 - eh / 2;
(ex, ey)
} else {
let ex = rx + (rng.next() % room_w.max(1) as u32) as i32 - ew / 2;
let ey = if dy > 0 { ry + room_h + 1 } else { ry - eh - 1 };
(ex, ey)
};
let ex = ex.max(1).min(params.width - ew - 1);
let ey = ey.max(1).min(params.height - eh - 1);
for y in ey..(ey + eh) {
for x in ex..(ex + ew) {
grid.set(x, y, 0);
}
}
let ecx = ex + ew / 2;
let ecy = ey + eh / 2;
if rng.next() % 2 == 0 {
carve_corridor_h(&mut grid, room_cx, ecx, room_cy);
carve_corridor_v(&mut grid, room_cy, ecy, ecx);
} else {
carve_corridor_v(&mut grid, room_cy, ecy, room_cx);
carve_corridor_h(&mut grid, room_cx, ecx, ecy);
}
rooms.push(Bounds { x: ex, y: ey, w: ew, h: eh });
}
}
let mut bsp = BspBuilder::new();
grid_to_bsp(&mut bsp, &grid);
let light_height = 80.0;
let fireball_pos = Vec3::new(384.0, light_height, 324.0);
let fireball_exclusion_radius = 150.0;
let lights: Vec<LightSource> = rooms.iter().filter_map(|room| {
let room_cx = (room.x as f32 + room.w as f32 / 2.0) * CELL_SIZE;
let room_cz = (room.y as f32 + room.h as f32 / 2.0) * CELL_SIZE;
let ddx = room_cx - fireball_pos.x;
let ddz = room_cz - fireball_pos.z;
if ddx * ddx + ddz * ddz < fireball_exclusion_radius * fireball_exclusion_radius {
return None;
}
let intensity = if room.w >= 6 && room.h >= 6 { 2.0 } else { 1.5 };
let radius = if room.w >= 6 && room.h >= 6 { 250.0 } else { 160.0 };
Some(LightSource {
position: Vec3::new(room_cx, light_height, room_cz),
intensity,
radius,
})
}).collect();
let mut lights = lights;
lights.push(LightSource {
position: Vec3::new(860.0, light_height, 416.0),
intensity: 1.2,
radius: 120.0,
});
compute_bsp_vertex_lighting(&mut bsp, &lights);
bsp.into()
}
pub fn generate(params: &ProcgenParams) -> Bsp {
match params.layout {
LayoutMode::Dungeon => generate_rooms(params),
LayoutMode::Courtyard => generate_courtyard(params),
}
}