tilewright_source/transitions/
resolve.rs1use super::{Draw, Transitions};
7use crate::{CHUNK_SIZE, Grid, Plane};
8use std::collections::BTreeMap;
9
10const SIDE: usize = CHUNK_SIZE as usize;
12
13const CELLS: usize = SIDE * SIDE;
15
16const WINDOW: usize = SIDE + 2;
18
19pub trait TerrainSource {
23 fn chunk_ground(&self, layer: i32, cx: i64, cy: i64) -> Option<&[Grid]>;
26}
27
28impl TerrainSource for Plane {
29 fn chunk_ground(&self, layer: i32, cx: i64, cy: i64) -> Option<&[Grid]> {
30 self.chunks
31 .get(&(layer, cx, cy))
32 .map(|chunk| chunk.ground.as_slice())
33 }
34}
35
36const fn mix(value: u64) -> u64 {
38 let value = (value ^ (value >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
39 let value = (value ^ (value >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
40 value ^ (value >> 31)
41}
42
43#[must_use]
47pub fn variant_hash(seed: u32, layer: i32, x: i64, y: i64) -> u64 {
48 let hash = mix(u64::from(seed));
49 let hash = mix(hash ^ i64::from(layer).cast_unsigned());
50 let hash = mix(hash ^ x.cast_unsigned());
51 mix(hash ^ y.cast_unsigned())
52}
53
54#[derive(Clone, Debug, Eq, PartialEq)]
58pub struct ResolvedChunk<'a> {
59 layers: Vec<Vec<Option<&'a str>>>,
61}
62
63impl<'a> ResolvedChunk<'a> {
64 #[must_use]
66 pub fn value(&self, ground: usize, index: usize) -> Option<&'a str> {
67 self.layers.get(ground)?.get(index).copied().flatten()
68 }
69
70 #[must_use]
72 pub fn at(&self, ground: usize, [x, y]: [u16; 2]) -> Option<&'a str> {
73 let (x, y) = (usize::from(x), usize::from(y));
74 if x < SIDE && y < SIDE {
75 self.value(ground, y * SIDE + x)
76 } else {
77 None
78 }
79 }
80
81 #[must_use]
83 pub fn ground_layers(&self) -> usize {
84 self.layers.len()
85 }
86
87 pub fn values(&self) -> impl Iterator<Item = &'a str> + '_ {
89 self.layers
90 .iter()
91 .flat_map(|tiles| tiles.iter().filter_map(|tile| *tile))
92 }
93
94 #[must_use]
97 pub fn grids(&self) -> Vec<Grid> {
98 self.layers
99 .iter()
100 .map(|tiles| {
101 let mut indexes: BTreeMap<&str, u16> = BTreeMap::new();
102 let mut palette = Vec::new();
103 let cells = tiles
104 .iter()
105 .map(|tile| {
106 tile.map_or(0, |value| {
107 *indexes.entry(value).or_insert_with(|| {
108 palette.push(value.to_owned());
109 u16::try_from(palette.len()).unwrap_or(u16::MAX)
111 })
112 })
113 })
114 .collect();
115 Grid { palette, cells }
116 })
117 .collect()
118 }
119}
120
121fn neighbour_bits(neighbours: &[Option<usize>; 8], test: impl Fn(Option<usize>) -> bool) -> u8 {
123 let mut bits = 0;
124 for (bit, neighbour) in neighbours.iter().enumerate() {
125 if test(*neighbour) {
126 bits |= 1 << bit;
127 }
128 }
129 bits
130}
131
132fn cell(grid: Option<&Grid>, index: usize) -> Option<&str> {
135 grid?.value(index)
136}
137
138impl Transitions {
139 #[must_use]
153 pub fn resolve_chunk<'a, S>(
154 &'a self,
155 source: &'a S,
156 layer: i32,
157 cx: i64,
158 cy: i64,
159 ) -> ResolvedChunk<'a>
160 where
161 S: TerrainSource + ?Sized,
162 {
163 let own = source.chunk_ground(layer, cx, cy).unwrap_or(&[]);
164 let mut layers: Vec<Vec<Option<&'a str>>> = (0..self.ground_layers)
165 .map(|ground| {
166 (0..CELLS)
167 .map(|index| cell(own.get(ground), index))
168 .collect()
169 })
170 .collect();
171 let window = self.window(source, layer, cx, cy);
172 let terrain_at = |x: usize, y: usize| window.get(y * WINDOW + x).copied().flatten();
173 let mut drawn: Vec<Option<(usize, &'a str)>> = vec![None; self.ground_layers];
174 let side = i64::from(CHUNK_SIZE);
177 let (left, top) = (cx.wrapping_mul(side), cy.wrapping_mul(side));
178 for (y, plane_y) in (1..=SIDE).zip((0..).map(|offset: i64| top.wrapping_add(offset))) {
179 for (x, plane_x) in (1..=SIDE).zip((0..).map(|offset: i64| left.wrapping_add(offset))) {
180 let Some(terrain) = terrain_at(x, y) else {
181 continue;
182 };
183 let neighbours = [
184 terrain_at(x, y - 1),
185 terrain_at(x + 1, y - 1),
186 terrain_at(x + 1, y),
187 terrain_at(x + 1, y + 1),
188 terrain_at(x, y + 1),
189 terrain_at(x - 1, y + 1),
190 terrain_at(x - 1, y),
191 terrain_at(x - 1, y - 1),
192 ];
193 let index = (y - 1) * SIDE + (x - 1);
194 let blocked = (0..self.ground_layers.min(8)).fold(0_u8, |blocked, output| {
198 if output == self.terrain_layer || cell(own.get(output), index).is_some() {
199 blocked | (1 << output)
200 } else {
201 blocked
202 }
203 });
204 self.draw_tile(
205 terrain,
206 &neighbours,
207 blocked,
208 (layer, plane_x, plane_y),
209 &mut drawn,
210 );
211 for (output, entry) in drawn.iter().enumerate() {
212 if let (Some((_, tile)), Some(slot)) = (
213 entry,
214 layers
215 .get_mut(output)
216 .and_then(|tiles| tiles.get_mut(index)),
217 ) {
218 *slot = Some(*tile);
219 }
220 }
221 }
222 }
223 ResolvedChunk { layers }
224 }
225
226 fn draw_tile<'a>(
239 &'a self,
240 terrain: usize,
241 neighbours: &[Option<usize>; 8],
242 blocked: u8,
243 (layer, x, y): (i32, i64, i64),
244 drawn: &mut [Option<(usize, &'a str)>],
245 ) {
246 drawn.fill(None);
247 let owners = std::iter::once(terrain).chain(neighbours.iter().enumerate().filter_map(
248 |(seen, neighbour)| {
249 let neighbour = (*neighbour)?;
250 (neighbour != terrain && !neighbours[..seen].contains(&Some(neighbour)))
251 .then_some(neighbour)
252 },
253 ));
254 for owner in owners {
255 let Some(range) = self
256 .terrains
257 .get(owner)
258 .map(|owner| owner.transitions.clone())
259 else {
260 continue;
261 };
262 let same = neighbour_bits(neighbours, |neighbour| neighbour == Some(owner));
265 for position in range.rev() {
266 let Some(transition) = self.list.get(position) else {
267 continue;
268 };
269 let output = transition.layer;
270 if blocked & (1 << output.min(7)) != 0
271 || drawn
272 .get(output)
273 .is_some_and(|entry| entry.is_some_and(|(at, _)| at > position))
274 {
275 continue;
276 }
277 let shows = if owner == terrain {
280 if transition.draw != Draw::Inside {
281 continue;
282 }
283 neighbour_bits(neighbours, |neighbour| {
284 neighbour.is_none_or(|neighbour| !transition.blends_with(neighbour))
285 })
286 } else if transition.draw == Draw::Outside && transition.blends_with(terrain) {
287 same
288 } else {
289 continue;
290 };
291 if !transition.draws(shows) {
292 continue;
293 }
294 let hash = variant_hash(transition.seed, layer, x, y);
295 if let (Some(tile), Some(slot)) =
296 (transition.draw_at(shows, hash), drawn.get_mut(output))
297 {
298 *slot = Some((position, tile));
299 }
300 }
301 }
302 }
303
304 fn window<S>(&self, source: &S, layer: i32, cx: i64, cy: i64) -> Vec<Option<usize>>
307 where
308 S: TerrainSource + ?Sized,
309 {
310 let mut window = vec![None; WINDOW * WINDOW];
311 for dy in -1_i64..=1 {
312 for dx in -1_i64..=1 {
313 let (Some(x), Some(y)) = (cx.checked_add(dx), cy.checked_add(dy)) else {
314 continue;
315 };
316 let Some(grid) = source
317 .chunk_ground(layer, x, y)
318 .and_then(|grids| grids.get(self.terrain_layer))
319 else {
320 continue;
321 };
322 let mut terrains: BTreeMap<u16, Option<usize>> = BTreeMap::new();
325 let span = |offset: i64| -> (usize, usize, usize) {
328 match offset {
329 -1 => (0, 1, SIDE - 1),
330 0 => (1, SIDE + 1, 0),
331 _ => (SIDE + 1, SIDE + 2, 0),
332 }
333 };
334 let (column_start, column_end, first_column) = span(dx);
335 let (row_start, row_end, first_row) = span(dy);
336 for (row, local_y) in (row_start..row_end).zip(first_row..) {
337 for (column, local_x) in (column_start..column_end).zip(first_column..) {
338 let Some(&value) = grid.cells.get(local_y * SIDE + local_x) else {
339 continue;
340 };
341 if value == 0 {
342 continue;
343 }
344 let terrain = *terrains.entry(value).or_insert_with(|| {
345 grid.palette
346 .get(usize::from(value) - 1)
347 .and_then(|proto| self.terrain_of(proto))
348 });
349 if let Some(slot) = window.get_mut(row * WINDOW + column) {
350 *slot = terrain;
351 }
352 }
353 }
354 }
355 }
356 window
357 }
358}