Skip to main content

decay/
procgen.rs

1/// Value noise at integer coordinates using a hash.
2pub fn value_noise_2d(x: i32, y: i32, seed: u32) -> f32 {
3    let h = hash2d(x, y, seed);
4    (h & 0xFFFF) as f32 / 65535.0
5}
6
7/// Smoothed value noise with bilinear interpolation.
8pub fn smooth_noise_2d(x: f32, y: f32, seed: u32) -> f32 {
9    let ix = x.floor() as i32;
10    let iy = y.floor() as i32;
11    let fx = x - ix as f32;
12    let fy = y - iy as f32;
13
14    let v00 = value_noise_2d(ix, iy, seed);
15    let v10 = value_noise_2d(ix + 1, iy, seed);
16    let v01 = value_noise_2d(ix, iy + 1, seed);
17    let v11 = value_noise_2d(ix + 1, iy + 1, seed);
18
19    let sx = fx * fx * (3.0 - 2.0 * fx);
20    let sy = fy * fy * (3.0 - 2.0 * fy);
21
22    let top = v00 + (v10 - v00) * sx;
23    let bot = v01 + (v11 - v01) * sx;
24    top + (bot - top) * sy
25}
26
27fn hash2d(x: i32, y: i32, seed: u32) -> u32 {
28    let mut h = seed;
29    h = h.wrapping_add(x as u32).wrapping_mul(0x9E3779B9);
30    h = h.wrapping_add(y as u32).wrapping_mul(0x517CC1B7);
31    h ^= h >> 16;
32    h = h.wrapping_mul(0x85EBCA6B);
33    h ^= h >> 13;
34    h = h.wrapping_mul(0xC2B2AE35);
35    h ^= h >> 16;
36    h
37}
38
39/// BSP (Binary Space Partition) room generation.
40/// Returns a list of (x, y, w, h) room rectangles.
41pub fn bsp_rooms(
42    width: usize,
43    height: usize,
44    min_room: usize,
45    seed: u32,
46) -> Vec<(usize, usize, usize, usize)> {
47    let mut rooms = Vec::new();
48    let mut rng_state = seed | 1;
49    bsp_split(0, 0, width, height, min_room, &mut rng_state, &mut rooms);
50    rooms
51}
52
53fn xorshift(state: &mut u32) -> u32 {
54    let mut x = *state;
55    x ^= x << 13;
56    x ^= x >> 17;
57    x ^= x << 5;
58    *state = x;
59    x
60}
61
62fn bsp_split(
63    x: usize,
64    y: usize,
65    w: usize,
66    h: usize,
67    min_room: usize,
68    rng: &mut u32,
69    rooms: &mut Vec<(usize, usize, usize, usize)>,
70) {
71    if w < min_room * 2 && h < min_room * 2 {
72        let margin = 1;
73        let rw = min_room.max(w.saturating_sub(margin * 2));
74        let rh = min_room.max(h.saturating_sub(margin * 2));
75        rooms.push((x + margin.min(w / 2), y + margin.min(h / 2), rw.min(w), rh.min(h)));
76        return;
77    }
78    let split_h = if w < min_room * 2 {
79        true
80    } else if h < min_room * 2 {
81        false
82    } else {
83        xorshift(rng) % 2 == 0
84    };
85    if split_h {
86        let split = min_room + (xorshift(rng) as usize % (h - min_room * 2 + 1).max(1));
87        bsp_split(x, y, w, split, min_room, rng, rooms);
88        bsp_split(x, y + split, w, h - split, min_room, rng, rooms);
89    } else {
90        let split = min_room + (xorshift(rng) as usize % (w - min_room * 2 + 1).max(1));
91        bsp_split(x, y, split, h, min_room, rng, rooms);
92        bsp_split(x + split, y, w - split, h, min_room, rng, rooms);
93    }
94}
95
96/// Drunkard's walk cave generation. Returns a grid of booleans (true = floor).
97pub fn drunkard_walk(width: usize, height: usize, steps: usize, seed: u32) -> Vec<Vec<bool>> {
98    let mut grid = vec![vec![false; width]; height];
99    let mut x = width / 2;
100    let mut y = height / 2;
101    let mut rng_state = seed | 1;
102
103    grid[y][x] = true;
104    for _ in 0..steps {
105        let dir = xorshift(&mut rng_state) % 4;
106        match dir {
107            0 if y > 0 => y -= 1,
108            1 if y + 1 < height => y += 1,
109            2 if x > 0 => x -= 1,
110            3 if x + 1 < width => x += 1,
111            _ => {}
112        }
113        grid[y][x] = true;
114    }
115    grid
116}