twmap/automapper/
execute.rs1use ndarray::Array2;
2use vek::Vec2;
3
4use crate::automapper::{Automapper, Chance, Condition, Config, Rule, TileCondition};
5use crate::convert::TryTo;
6use crate::{AutomapperConfig, Tile, TilesLayer};
7
8struct DoubleView<'tiles> {
9 tiles: &'tiles mut Array2<Tile>,
10 copy: Option<Array2<Tile>>,
11}
12
13impl DoubleView<'_> {
14 fn view(&self) -> &Array2<Tile> {
15 match &self.copy {
16 Some(copy) => copy,
17 None => self.tiles,
18 }
19 }
20}
21
22impl TileCondition {
23 fn applies(&self, tile: Option<Tile>) -> bool {
24 match self {
25 TileCondition::Outside => tile.is_none(),
26 TileCondition::Index(id) => tile.map(|t| t.id) == Some(*id),
27 TileCondition::Tile(t) => tile == Some(*t),
28 }
29 }
30}
31
32impl Rule {
33 fn applies(&self, tiles: &Array2<Tile>, position: Vec2<usize>) -> bool {
34 let position_i64: Vec2<i64> = position.numcast().unwrap();
35 let offset_i64: Vec2<i64> = self.offset.numcast().unwrap();
36 let index = position_i64 + offset_i64;
37 let tile = if index.x < 0 || index.y < 0 {
38 None
39 } else {
40 let index: Vec2<usize> = index.numcast().unwrap();
41 tiles.get((index.y, index.x)).copied()
42 };
43
44 let id = tile.map(|t| t.id);
45 match &self.condition {
46 Condition::Empty => id == Some(0),
47 Condition::Full => id != Some(0),
48 Condition::WhiteList(list) => list.iter().any(|cond| cond.applies(tile)),
49 Condition::BlackList(list) => !list.iter().any(|cond| cond.applies(tile)),
50 }
51 }
52}
53
54const DEFAULT_RULE: Rule = Rule {
55 offset: Vec2::new(0, 0),
56 condition: Condition::Full,
57};
58
59fn hash_u32(mut n: u32) -> u32 {
60 n = n.wrapping_add(1);
61 n ^= n >> 17;
62 n = n.wrapping_mul(0xed5ad4bb);
63 n ^= n >> 11;
64 n = n.wrapping_mul(0xac4c1b51);
65 n ^= n >> 15;
66 n = n.wrapping_mul(0x31848bab);
67 n ^= n >> 14;
68 n
69}
70
71const HASH_MAX: u32 = 2_u32.pow(16);
72const PRIME: u32 = 31;
73
74fn hash_location(seed: u32, run: u32, rule: u32, x: u32, y: u32) -> u32 {
75 let mut hash: u32 = 1;
76
77 for n in [seed, run, rule, x, y] {
78 hash = hash.wrapping_mul(PRIME).wrapping_add(hash_u32(n));
79 }
80 hash = hash_u32(hash.wrapping_mul(PRIME));
81 hash % HASH_MAX
82}
83
84fn random_u32() -> u32 {
85 let mut u32_bytes: [u8; 4] = [0; 4];
86 getrandom::getrandom(&mut u32_bytes).unwrap();
87 u32::from_le_bytes(u32_bytes)
88}
89
90impl Chance {
91 fn chance(&self) -> Option<f32> {
92 match self {
93 Chance::Always => None,
94 Chance::OneOutOf(n) => Some(n.recip()),
95 Chance::Percentage(p) => Some(p / 100.),
96 }
97 }
98
99 fn u32_chance(&self) -> Option<u32> {
100 self.chance().map(|f| (f * HASH_MAX as f32) as u32)
101 }
102}
103
104impl Config {
105 pub fn run(&self, mut seed: u32, tiles: &mut Array2<Tile>) {
106 seed = match seed {
107 0 => random_u32(),
108 _ => seed,
109 };
110 let height = tiles.shape()[0];
111 let width = tiles.shape()[1];
112 for (run_i, run) in self.runs.iter().enumerate() {
113 let copy = run.layer_copy.then(|| tiles.clone());
114 let tilemap = DoubleView { tiles, copy };
115 for (rule_i, rule) in run.rules.iter().enumerate() {
116 let chance = rule.chance.u32_chance();
117 for y in 0..height {
118 for x in 0..width {
119 let position = Vec2::new(x, y);
120 let default =
121 !rule.default_rule || DEFAULT_RULE.applies(tilemap.view(), position);
122 if default
123 && rule
124 .conditions
125 .iter()
126 .all(|cond| cond.applies(tilemap.view(), position))
127 {
128 if let Some(chance) = chance {
129 if hash_location(
130 seed,
131 run_i.try_to(),
132 rule_i.try_to(),
133 x.try_to(),
134 y.try_to(),
135 ) >= chance
136 {
137 continue;
138 }
139 }
140 tilemap.tiles[(y, x)] = rule.tile;
141 }
142 }
143 }
144 }
145 }
146 }
147}
148
149#[derive(Debug)]
150pub struct ConfigOutOfBounds;
151
152impl Automapper {
153 pub fn run(
154 &self,
155 automapper_config: &AutomapperConfig,
156 tiles: &mut Array2<Tile>,
157 ) -> Result<(), ConfigOutOfBounds> {
158 let index = match automapper_config.config {
159 None => return Ok(()),
160 Some(i) => i,
161 };
162 let config = match self.configs.get(index.try_to::<usize>()) {
163 None => return Err(ConfigOutOfBounds),
164 Some(c) => c,
165 };
166 config.run(automapper_config.seed, tiles);
167 Ok(())
168 }
169}
170
171impl TilesLayer {
172 pub fn run_automapper(&mut self, automapper: &Automapper) -> Result<(), ConfigOutOfBounds> {
174 automapper.run(&self.automapper_config, self.tiles.unwrap_mut())
175 }
176}