1use std::{
2 cmp::Ordering,
3 io::{Read, Seek, Write},
4};
5
6use crate::{
7 Block, BlockArchetype, CCoord, Chunk, HeightMode, JavaChunk, LoaderError, LoaderResult, RCoord,
8 RegionLoader,
9};
10
11use super::biome::Biome;
12
13pub type Rgba = [u8; 4];
14
15pub trait Palette {
18 fn pick(&self, block: &Block, biome: Option<Biome>) -> Rgba;
19}
20
21pub struct TopShadeRenderer<'a, P: Palette> {
22 palette: &'a P,
23 height_mode: HeightMode,
24}
25
26impl<'a, P: Palette> TopShadeRenderer<'a, P> {
27 pub fn new(palette: &'a P, mode: HeightMode) -> Self {
28 Self {
29 palette,
30 height_mode: mode,
31 }
32 }
33
34 pub fn render<C: Chunk + ?Sized>(&self, chunk: &C, north: Option<&C>) -> [Rgba; 16 * 16] {
35 let mut data = [[0, 0, 0, 0]; 16 * 16];
36
37 let status = chunk.status();
38 const OK_STATUSES: [&str; 8] = ["full", "spawn", "postprocessed", "fullchunk", "minecraft:full", "minecraft:spawn", "minecraft:postprocessed", "minecraft:fullchunk"];
39 if !OK_STATUSES.contains(&status.as_str()) {
40 return data;
43 }
44
45 let y_range = chunk.y_range();
46
47 for z in 0..16 {
48 for x in 0..16 {
49 let air_height = chunk.surface_height(x, z, self.height_mode);
50 let block_height = (air_height - 1).max(y_range.start);
51
52 let colour = self.drill_for_colour(x, block_height, z, chunk, y_range.start);
53
54 let north_air_height = match z {
55 0 => north
57 .map(|c| c.surface_height(x, 15, self.height_mode))
58 .unwrap_or(block_height),
59 z => chunk.surface_height(x, z - 1, self.height_mode),
60 };
61 let colour = top_shade_colour(colour, air_height, north_air_height);
62
63 data[z * 16 + x] = colour;
64 }
65 }
66
67 data
68 }
69
70 fn drill_for_colour<C: Chunk + ?Sized>(
73 &self,
74 x: usize,
75 y_start: isize,
76 z: usize,
77 chunk: &C,
78 y_min: isize,
79 ) -> Rgba {
80 let mut y = y_start;
81 let mut colour = [0, 0, 0, 0];
82
83 while colour[3] != 255 && y >= y_min {
84 let current_biome = chunk.biome(x, y, z);
85 let current_block = chunk.block(x, y, z);
86
87 if let Some(current_block) = current_block {
88 match current_block.archetype {
89 BlockArchetype::Airy => {
90 y -= 1;
91 }
92 BlockArchetype::Watery => {
95 let mut block_colour = self.palette.pick(current_block, current_biome);
96 let water_depth = water_depth(x, y, z, chunk, y_min);
97 let alpha = water_depth_to_alpha(water_depth);
98
99 block_colour[3] = alpha;
100
101 colour = a_over_b_colour(colour, block_colour);
102 y -= water_depth;
103 }
104 _ => {
105 let block_colour = self.palette.pick(current_block, current_biome);
106 colour = a_over_b_colour(colour, block_colour);
107 y -= 1;
108 }
109 }
110 } else {
111 return colour;
112 }
113 }
114
115 colour
116 }
117}
118
119fn water_depth_to_alpha(water_depth: isize) -> u8 {
121 (180 + 2 * water_depth).min(250) as u8
136}
137
138fn water_depth<C: Chunk + ?Sized>(
139 x: usize,
140 mut y: isize,
141 z: usize,
142 chunk: &C,
143 y_min: isize,
144) -> isize {
145 let mut depth = 1;
146 while y > y_min {
147 let block = match chunk.block(x, y, z) {
148 Some(b) => b,
149 None => return depth,
150 };
151
152 if block.archetype == BlockArchetype::Watery {
153 depth += 1;
154 } else {
155 return depth;
156 }
157 y -= 1;
158 }
159 depth
160}
161
162fn a_over_b_colour(colour: [u8; 4], below_colour: [u8; 4]) -> [u8; 4] {
167 let linear = |c: u8| (((c as usize).pow(2)) as f32) / ((255 * 255) as f32);
168 let colour = colour.map(linear);
169 let below_colour = below_colour.map(linear);
170
171 let over_component = |ca: f32, aa: f32, cb: f32, ab: f32| {
172 let a_out = aa + ab * (1. - aa);
173 let linear_out = (ca * aa + cb * ab * (1. - aa)) / a_out;
174 (linear_out * 255. * 255.).sqrt() as u8
175 };
176
177 let over_alpha = |aa: f32, ab: f32| {
178 let a_out = aa + ab * (1. - aa);
179 (a_out * 255. * 255.).sqrt() as u8
180 };
181
182 [
183 over_component(colour[0], colour[3], below_colour[0], below_colour[3]),
184 over_component(colour[1], colour[3], below_colour[1], below_colour[3]),
185 over_component(colour[2], colour[3], below_colour[2], below_colour[3]),
186 over_alpha(colour[3], below_colour[3]),
187 ]
188}
189
190pub struct RegionMap<T> {
191 pub data: Vec<T>,
192 pub x: RCoord,
193 pub z: RCoord,
194}
195
196impl<T: Clone> RegionMap<T> {
197 pub fn new(x: RCoord, z: RCoord, default: T) -> Self {
198 let mut data: Vec<T> = Vec::new();
199 data.resize(16 * 16 * 32 * 32, default);
200 Self { data, x, z }
201 }
202
203 pub fn chunk_mut(&mut self, x: CCoord, z: CCoord) -> &mut [T] {
204 debug_assert!(x.0 >= 0 && z.0 >= 0);
205
206 let len = 16 * 16;
207 let begin = (z.0 * 32 + x.0) as usize * len;
208 &mut self.data[begin..begin + len]
209 }
210
211 pub fn chunk(&self, x: CCoord, z: CCoord) -> &[T] {
212 debug_assert!(x.0 >= 0 && z.0 >= 0);
213
214 let len = 16 * 16;
215 let begin = (z.0 * 32 + x.0) as usize * len;
216 &self.data[begin..begin + len]
217 }
218}
219
220pub fn render_region<P: Palette, S>(
221 x: RCoord,
222 z: RCoord,
223 loader: &dyn RegionLoader<S>,
224 renderer: TopShadeRenderer<P>,
225) -> LoaderResult<Option<RegionMap<Rgba>>>
226where
227 S: Seek + Read + Write,
228{
229 let mut map = RegionMap::new(x, z, [0u8; 4]);
230
231 let mut region = match loader.region(x, z)? {
232 Some(r) => r,
233 None => return Ok(None),
234 };
235
236 let mut cache: [Option<JavaChunk>; 32] = Default::default();
237
238 if let Some(mut r) = loader.region(x, RCoord(z.0 - 1))? {
241 for (x, entry) in cache.iter_mut().enumerate() {
242 *entry = r
243 .read_chunk(x, 31)
244 .ok()
245 .flatten()
246 .and_then(|b| JavaChunk::from_bytes(&b).ok())
247 }
248 }
249
250 for z in 0usize..32 {
251 for (x, cache) in cache.iter_mut().enumerate() {
252 let data = map.chunk_mut(CCoord(x as isize), CCoord(z as isize));
253
254 let chunk_data = region
255 .read_chunk(x, z)
256 .map_err(|e| LoaderError(e.to_string()))?;
257 let chunk_data = match chunk_data {
258 Some(data) => data,
259 None => {
260 *cache = None;
264 continue;
265 }
266 };
267
268 let chunk =
269 JavaChunk::from_bytes(&chunk_data).map_err(|e| LoaderError(e.to_string()))?;
270
271 let north = cache.as_ref();
280
281 let res = renderer.render(&chunk, north);
282 *cache = Some(chunk);
283
284 data[..].clone_from_slice(&res);
285 }
286 }
287
288 Ok(Some(map))
289}
290
291fn top_shade_colour(colour: Rgba, height: isize, shade_height: isize) -> Rgba {
298 let shade = match height.cmp(&shade_height) {
299 Ordering::Less => 180usize,
300 Ordering::Equal => 220,
301 Ordering::Greater => 255,
302 };
303 [
304 (colour[0] as usize * shade / 255) as u8,
305 (colour[1] as usize * shade / 255) as u8,
306 (colour[2] as usize * shade / 255) as u8,
307 colour[3],
308 ]
309}