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
}
}
|