use crate::types::SdfNode;
use glam::Vec3;
#[derive(Debug, Clone)]
pub struct CaveConfig {
pub max_depth: f32,
pub min_depth: f32,
pub tunnel_radius: f32,
pub octaves: u32,
pub frequency: f32,
pub density: f32,
pub seed: u64,
}
impl Default for CaveConfig {
fn default() -> Self {
Self {
max_depth: 50.0,
min_depth: 5.0,
tunnel_radius: 3.0,
octaves: 3,
frequency: 0.05,
density: 0.3,
seed: 42,
}
}
}
pub fn generate_cave_sdf(config: &CaveConfig) -> SdfNode {
let mut rng = config.seed;
let num_tunnels = (config.density * 10.0).max(1.0) as u32;
let segments_per_tunnel = 8u32;
let mut cave_parts: Vec<SdfNode> = Vec::new();
for _ in 0..num_tunnels {
rng = lcg_next(rng);
let mut px = (lcg_float(rng) * 2.0 + -1.0) * 20.0;
rng = lcg_next(rng);
let mut py = -(lcg_float(rng) * (config.max_depth - config.min_depth) + config.min_depth);
rng = lcg_next(rng);
let mut pz = (lcg_float(rng) * 2.0 + -1.0) * 20.0;
for _ in 0..segments_per_tunnel {
rng = lcg_next(rng);
let dx = (lcg_float(rng) * 2.0 + -1.0) * 5.0;
rng = lcg_next(rng);
let dy = (lcg_float(rng) * 2.0 + -1.0) * 2.0;
rng = lcg_next(rng);
let dz = (lcg_float(rng) * 2.0 + -1.0) * 5.0;
rng = lcg_next(rng);
let radius = config.tunnel_radius * (lcg_float(rng) * 1.0 + 0.5);
let segment = SdfNode::sphere(radius).translate(px, py, pz);
cave_parts.push(segment);
px += dx;
py = (py + dy).clamp(-config.max_depth, -config.min_depth);
pz += dz;
}
}
if cave_parts.is_empty() {
return SdfNode::sphere(0.01).translate(0.0, -10000.0, 0.0);
}
let mut result = cave_parts.remove(0);
for part in cave_parts {
result = result.smooth_union(part, config.tunnel_radius * 0.5);
}
if config.octaves > 0 {
result = result.noise(config.frequency, 1.0 / config.frequency, config.seed as u32);
}
result
}
pub fn generate_chamber(center: Vec3, radius: f32, height: f32) -> SdfNode {
SdfNode::box3d(radius, height * 0.5, radius)
.round(radius * 0.3)
.translate(center.x, center.y, center.z)
}
#[inline]
const fn lcg_next(state: u64) -> u64 {
state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407)
}
#[inline]
fn lcg_float(state: u64) -> f32 {
((state >> 16) as u32 as f32) / (u32::MAX as f32)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::eval::eval;
#[test]
fn test_generate_cave_sdf() {
let config = CaveConfig {
density: 0.2,
octaves: 0, ..Default::default()
};
let cave = generate_cave_sdf(&config);
let d = eval(&cave, Vec3::new(0.0, -20.0, 0.0));
assert!(d.is_finite(), "Cave SDF should return finite distance");
}
#[test]
fn test_cave_config_default() {
let config = CaveConfig::default();
assert_eq!(config.max_depth, 50.0);
assert_eq!(config.min_depth, 5.0);
assert!(config.density > 0.0);
}
#[test]
fn test_generate_chamber() {
let chamber = generate_chamber(Vec3::new(0.0, -10.0, 0.0), 5.0, 4.0);
let d = eval(&chamber, Vec3::new(0.0, -10.0, 0.0));
assert!(d < 0.0, "Chamber center should be inside, got {}", d);
let d = eval(&chamber, Vec3::new(100.0, 100.0, 100.0));
assert!(d > 0.0);
}
#[test]
fn test_cave_deterministic() {
let config = CaveConfig {
seed: 123,
octaves: 0,
..Default::default()
};
let cave1 = generate_cave_sdf(&config);
let cave2 = generate_cave_sdf(&config);
let p = Vec3::new(5.0, -15.0, 3.0);
let d1 = eval(&cave1, p);
let d2 = eval(&cave2, p);
assert!(
(d1 - d2).abs() < 0.001,
"Same seed should produce same cave"
);
}
#[test]
fn test_cave_zero_density() {
let config = CaveConfig {
density: 0.0,
octaves: 0,
..Default::default()
};
let cave = generate_cave_sdf(&config);
let d = eval(&cave, Vec3::ZERO);
assert!(d.is_finite());
}
}