summaryrefslogtreecommitdiff
path: root/src/proc_gen/mod.rs
blob: 4a1003aa75ef4e9f9bd439bb446abf9da20da2b0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
pub mod herringbone;
pub mod tileset_loader;

use glam::IVec2;

use crate::{
    config::{CHUNK_SIZE, TILESET_SCALING},
    proc_gen::{
        herringbone::{split_position, variant_index},
        tileset_loader::{Tile, TileOrientation, Tileset, TilesetPixelType, load_tileset},
    },
    sim::{cell::Cell, cell_manager::chunk::Chunk, entity::EntityDef},
};

#[inline]
fn lerp(a: f32, b: f32, t: f32) -> f32 {
    a + (b - a) * t
}

const CHUNK_PIXELS: i32 = CHUNK_SIZE / TILESET_SCALING;
const PIXELS_NEIGHBOURHOOD_SIZE: i32 = CHUNK_PIXELS + 2;

// 2d slice of solidity; chunk + 1 margin
struct BiomeChunkContext {
    instantaneous_solidity: [f32; (PIXELS_NEIGHBOURHOOD_SIZE * PIXELS_NEIGHBOURHOOD_SIZE) as usize],
}

impl BiomeChunkContext {
    fn lerped_solidity_at(&self, local: IVec2) -> f32 {
        // pixel coord centre of cell coord local
        let pixel_position = (local.as_vec2() + 0.5) / TILESET_SCALING as f32 + 0.5;
        let pixel = pixel_position.floor();
        // distance from the current pixel to the centre of the cell
        let frac = pixel_position - pixel;
        let pixel = pixel.as_ivec2();

        let at = |dx, dy| {
            self.instantaneous_solidity
                [((pixel.x + dx) + (pixel.y + dy) * PIXELS_NEIGHBOURHOOD_SIZE) as usize]
        };

        // linearly interpolate between the solidity of the horizontal cells above and below according to the fractional x component
        let top = lerp(at(0, 0), at(1, 0), frac.x);
        let bottom = lerp(at(0, 1), at(1, 1), frac.x);

        // and then interpolate between those according to the y component
        lerp(top, bottom, frac.y) - 0.5
    }
}

pub trait Biome {
    fn cell(&self, solidity: f32, world: IVec2) -> Cell;

    fn derive_entities_from_tile(&self, _tile: &Tile) -> Vec<EntityDef> {
        Vec::new()
    }
}

pub struct TilesetWorldGenerator {
    tileset: Tileset,
    biome: Box<dyn Biome>,
}

impl TilesetWorldGenerator {
    pub fn new(tileset_path: &str, biome: Box<dyn Biome>) -> Self {
        TilesetWorldGenerator {
            tileset: load_tileset(tileset_path),
            biome,
        }
    }

    fn pixel_at(&self, pixel: IVec2) -> TilesetPixelType {
        let (grid, local, orientation) =
            split_position(pixel, self.tileset.dimensions.short as i32);

        let tiles = match orientation {
            TileOrientation::Horizontal => &self.tileset.horizontal_tiles,
            TileOrientation::Vertical => &self.tileset.vertical_tiles,
        };

        tiles[variant_index(grid, tiles.len())].pixel_at(local)
    }

    fn structure_around(&self, chunk_position: IVec2) -> BiomeChunkContext {
        let origin = chunk_position * CHUNK_PIXELS - 1;
        let mut occupancy = [0.0; (PIXELS_NEIGHBOURHOOD_SIZE * PIXELS_NEIGHBOURHOOD_SIZE) as usize];

        for y in 0..PIXELS_NEIGHBOURHOOD_SIZE {
            for x in 0..PIXELS_NEIGHBOURHOOD_SIZE {
                occupancy[(x + y * PIXELS_NEIGHBOURHOOD_SIZE) as usize] =
                    match self.pixel_at(origin + IVec2::new(x, y)) {
                        TilesetPixelType::Void => 0.0,
                        TilesetPixelType::Terrain => 1.0,
                    };
            }
        }

        BiomeChunkContext {
            instantaneous_solidity: occupancy,
        }
    }

    pub fn generate_chunk(&self, chunk_position: IVec2) -> Chunk {
        let mut chunk = Chunk::void();
        let structure = self.structure_around(chunk_position);
        let chunk_min = chunk_position * CHUNK_SIZE;

        for y in 0..CHUNK_SIZE {
            for x in 0..CHUNK_SIZE {
                let local = IVec2::new(x, y);
                chunk.cells[(x + y * CHUNK_SIZE) as usize] = self
                    .biome
                    .cell(structure.lerped_solidity_at(local), chunk_min + local);
            }
        }

        chunk.mark_collider_dirty(0);

        chunk
    }
}