use glam::Vec3;
use crate::csg;
use crate::mesh::{Mesh, cuboid};
use crate::palette::{Palette, lerp};
use crate::recipe::DungeonTheme;
use super::model::{Corridor, Door, Room};
const T: f32 = 0.4;
const DOOR_W: f32 = 2.2;
const DOOR_H: f32 = 2.6;
const CORR_W: f32 = 3.0;
pub fn wall_color(theme: DungeonTheme, pal: &Palette) -> Vec3 {
match theme {
DungeonTheme::Crypt => pal.rock[1],
DungeonTheme::Cavern => pal.rock[0],
DungeonTheme::Sewer => lerp(pal.rock[1], pal.foliage[2], 0.35),
DungeonTheme::Mine => lerp(pal.rock[1], pal.trunk, 0.4),
DungeonTheme::Temple => lerp(pal.rock[0], pal.terrain[3], 0.35),
DungeonTheme::Fortress => pal.rock[0],
}
}
fn floor_color(pal: &Palette) -> Vec3 {
pal.terrain[1]
}
pub fn room_mesh(
room: &Room,
theme: DungeonTheme,
pal: &Palette,
detail: f32,
include_ceiling: bool,
) -> Mesh {
let _ = detail; let wall = wall_color(theme, pal);
let floor_c = floor_color(pal);
let ceil_c = wall * 0.55;
let (mn, mx) = (room.min, room.max);
let cx = (mn.x + mx.x) * 0.5;
let cz = (mn.z + mx.z) * 0.5;
let hx = (mx.x - mn.x) * 0.5;
let hz = (mx.z - mn.z) * 0.5;
let h = mx.y;
let mut m = Mesh::new();
m.merge(&cuboid(
Vec3::new(cx, -T * 0.5, cz),
Vec3::new(hx + T, T * 0.5, hz + T),
floor_c,
));
if include_ceiling {
m.merge(&cuboid(
Vec3::new(cx, h + T * 0.5, cz),
Vec3::new(hx + T, T * 0.5, hz + T),
ceil_c,
));
}
m.merge(&cuboid(
Vec3::new(cx, h * 0.5, mn.z - T * 0.5),
Vec3::new(hx + T, h * 0.5, T * 0.5),
wall,
));
m.merge(&cuboid(
Vec3::new(cx, h * 0.5, mx.z + T * 0.5),
Vec3::new(hx + T, h * 0.5, T * 0.5),
wall,
));
m.merge(&cuboid(
Vec3::new(mn.x - T * 0.5, h * 0.5, cz),
Vec3::new(T * 0.5, h * 0.5, hz),
wall,
));
m.merge(&cuboid(
Vec3::new(mx.x + T * 0.5, h * 0.5, cz),
Vec3::new(T * 0.5, h * 0.5, hz),
wall,
));
m
}
pub fn carve_doorways(mut walls: Mesh, doors: &[Door]) -> Mesh {
for d in doors {
let cy0 = 0.02;
let cutter = cuboid(
Vec3::new(d.pos.x, (cy0 + DOOR_H) * 0.5, d.pos.z),
Vec3::new(DOOR_W * 0.5, (DOOR_H - cy0) * 0.5, DOOR_W * 0.5),
Vec3::ONE,
);
walls = csg::subtract(&walls, &cutter);
}
walls
}
pub fn corridor_mesh(
c: &Corridor,
theme: DungeonTheme,
pal: &Palette,
detail: f32,
include_ceiling: bool,
) -> Mesh {
let _ = detail;
let wall = wall_color(theme, pal) * 0.92;
let floor_c = floor_color(pal);
let ceil_c = wall * 0.5;
let h = (super::model::DungeonModel::ceiling(theme) - 0.6).max(3.0);
let mut m = Mesh::new();
for seg in c.path.windows(2) {
let (a, b) = (seg[0], seg[1]);
segment_shell(&mut m, a, b, h, floor_c, wall, ceil_c, include_ceiling);
}
m
}
#[allow(clippy::too_many_arguments)]
fn segment_shell(
m: &mut Mesh,
a: Vec3,
b: Vec3,
h: f32,
floor_c: Vec3,
wall: Vec3,
ceil_c: Vec3,
include_ceiling: bool,
) {
let center = (a + b) * 0.5;
let along_x = (b.x - a.x).abs() >= (b.z - a.z).abs();
let len = if along_x {
(b.x - a.x).abs()
} else {
(b.z - a.z).abs()
};
let half_along = len * 0.5 + CORR_W * 0.5; let hw = CORR_W * 0.5;
let (half_x, half_z) = if along_x {
(half_along, hw)
} else {
(hw, half_along)
};
m.merge(&cuboid(
Vec3::new(center.x, -T * 0.5, center.z),
Vec3::new(half_x + T, T * 0.5, half_z + T),
floor_c,
));
if include_ceiling {
m.merge(&cuboid(
Vec3::new(center.x, h + T * 0.5, center.z),
Vec3::new(half_x + T, T * 0.5, half_z + T),
ceil_c,
));
}
if along_x {
for sz in [-1.0f32, 1.0] {
m.merge(&cuboid(
Vec3::new(center.x, h * 0.5, center.z + sz * (hw + T * 0.5)),
Vec3::new(half_x, h * 0.5, T * 0.5),
wall,
));
}
} else {
for sx in [-1.0f32, 1.0] {
m.merge(&cuboid(
Vec3::new(center.x + sx * (hw + T * 0.5), h * 0.5, center.z),
Vec3::new(T * 0.5, h * 0.5, half_z),
wall,
));
}
}
}
#[cfg(test)]
mod tests {
use super::super::model::DungeonModel;
use super::*;
use crate::palette::by_name;
use crate::recipe::DungeonParams;
fn params(json: &str) -> DungeonParams {
serde_json::from_str(json).unwrap()
}
#[test]
fn room_geometry_has_floor_walls_ceiling_and_carved_door() {
let p = params(r#"{"kind":"dungeon","type":"crypt","rooms":2}"#);
let m = DungeonModel::new(&p, &by_name("necrotic")).unwrap();
let solid = room_mesh(
&m.rooms[0],
DungeonTheme::Crypt,
&by_name("necrotic"),
1.0,
true,
);
solid.validate().unwrap();
let doors: Vec<Door> = m.doors.iter().filter(|d| d.room == 0).cloned().collect();
assert!(!doors.is_empty(), "connected room 0 must have a doorway");
let carved = carve_doorways(
room_mesh(
&m.rooms[0],
DungeonTheme::Crypt,
&by_name("necrotic"),
1.0,
true,
),
&doors,
);
carved.validate().unwrap();
assert!(carved.indices.len().is_multiple_of(3));
assert!(carved.positions.len() > 100);
assert!(carved.positions.len() != solid.positions.len());
}
#[test]
fn corridor_mesh_builds() {
let p = params(r#"{"kind":"dungeon","type":"mine","size":"small"}"#);
let m = DungeonModel::new(&p, &by_name("volcanic")).unwrap();
let c = corridor_mesh(
&m.corridors[0],
DungeonTheme::Mine,
&by_name("volcanic"),
1.0,
true,
);
c.validate().unwrap();
assert!(c.triangle_count() > 0);
let open = corridor_mesh(
&m.corridors[0],
DungeonTheme::Mine,
&by_name("volcanic"),
1.0,
false,
);
open.validate().unwrap();
assert!(open.triangle_count() < c.triangle_count());
}
}