1pub mod cell;
2pub mod mat_cell;
3pub use mat_cell::Material;
4
5pub use self::{
7 cell::{Cell, CellSurface},
8 mat_cell::MatCell,
9};
10
11use crate::{
12 terrain::{Block, BlockKind, SpriteKind},
13 vol::{FilledVox, IntoFullPosIterator, IntoFullVolIterator, ReadVol, SizedVol, WriteVol},
14 volumes::dyna::Dyna,
15};
16use dot_vox::DotVoxData;
17use hashbrown::HashMap;
18use vek::*;
19
20pub type TerrainSegment = Dyna<Block, ()>;
21
22impl From<Segment> for TerrainSegment {
23 fn from(value: Segment) -> Self {
24 TerrainSegment::from_fn(value.sz, (), |pos| match value.get(pos) {
25 Err(_) => Block::air(SpriteKind::Empty),
26 Ok(cell) if !cell.is_filled() => Block::air(SpriteKind::Empty),
27 Ok(cell) => {
28 let kind = match cell.get_surf().unwrap_or(CellSurface::Matte) {
29 CellSurface::Glowy => BlockKind::GlowingRock,
30 CellSurface::Fire => BlockKind::Lava,
31 CellSurface::Water => BlockKind::Water,
32 CellSurface::Matte | CellSurface::Shiny => BlockKind::Misc,
33 };
34 Block::new(kind, cell.get_color().unwrap_or_default())
35 },
36 })
37 }
38}
39
40pub type Segment = Dyna<Cell, ()>;
44
45impl Segment {
46 pub fn from_voxes(data: &[(&DotVoxData, Vec3<i32>, bool)]) -> (Self, Vec3<i32>) {
49 let mut union = DynaUnionizer::new();
50 for (datum, offset, xmirror) in data.iter() {
51 union = union.add(Segment::from_vox(datum, *xmirror, 0, None), *offset);
52 }
53 union.unify()
54 }
55
56 pub fn from_vox_model_index(
57 dot_vox_data: &DotVoxData,
58 model_index: usize,
59 custom_indices: Option<&HashMap<u8, Cell>>,
60 ) -> Self {
61 Self::from_vox(dot_vox_data, false, model_index, custom_indices)
62 }
63
64 pub fn from_vox(
65 dot_vox_data: &DotVoxData,
66 flipped: bool,
67 model_index: usize,
68 custom_indices: Option<&HashMap<u8, Cell>>,
69 ) -> Self {
70 if let Some(model) = dot_vox_data.models.get(model_index) {
71 let palette = dot_vox_data
72 .palette
73 .iter()
74 .map(|col| Rgb::new(col.r, col.g, col.b))
75 .collect::<Vec<_>>();
76
77 let mut segment = Segment::filled(
78 Vec3::new(model.size.x, model.size.y, model.size.z),
79 Cell::empty(),
80 (),
81 );
82
83 let mut indices = [Cell::empty(); 256];
85 for i in 0..=255 {
86 indices[i as usize] = Cell::from_index(i, Rgb::zero());
87 }
88 for (i, cell) in custom_indices.iter().flat_map(|x| x.iter()) {
89 indices[*i as usize] = *cell;
90 }
91
92 for voxel in &model.voxels {
93 if let Some(&color) = palette.get(voxel.i as usize) {
94 segment
95 .set(
96 Vec3::new(
97 if flipped {
98 model.size.x as u8 - 1 - voxel.x
99 } else {
100 voxel.x
101 },
102 voxel.y,
103 voxel.z,
104 )
105 .map(i32::from),
106 indices[voxel.i as usize].map_rgb(|_| color),
107 )
108 .unwrap();
109 };
110 }
111
112 segment
113 } else {
114 Segment::filled(Vec3::zero(), Cell::empty(), ())
115 }
116 }
117
118 #[must_use]
120 pub fn map(mut self, transform: impl Fn(Cell) -> Option<Cell>) -> Self {
121 for pos in self.full_pos_iter() {
122 if let Some(new) = transform(*self.get(pos).unwrap()) {
123 self.set(pos, new).unwrap();
124 }
125 }
126
127 self
128 }
129
130 #[must_use]
132 pub fn map_rgb(self, transform: impl Fn(Rgb<u8>) -> Rgb<u8>) -> Self {
133 self.map(|cell| Some(cell.map_rgb(&transform)))
134 }
135}
136
137pub struct DynaUnionizer<V: FilledVox>(Vec<(Dyna<V, ()>, Vec3<i32>)>);
140
141impl<V: FilledVox + Copy> DynaUnionizer<V> {
142 #[expect(clippy::new_without_default)]
143 pub fn new() -> Self { DynaUnionizer(Vec::new()) }
144
145 #[must_use]
146 pub fn add(mut self, dyna: Dyna<V, ()>, offset: Vec3<i32>) -> Self {
147 self.0.push((dyna, offset));
148 self
149 }
150
151 #[must_use]
152 pub fn maybe_add(self, maybe: Option<(Dyna<V, ()>, Vec3<i32>)>) -> Self {
153 match maybe {
154 Some((dyna, offset)) => self.add(dyna, offset),
155 None => self,
156 }
157 }
158
159 pub fn unify(self) -> (Dyna<V, ()>, Vec3<i32>) { self.unify_with(|v, _| v) }
160
161 pub fn unify_with(self, mut f: impl FnMut(V, V) -> V) -> (Dyna<V, ()>, Vec3<i32>) {
164 if self.0.is_empty() {
165 return (
166 Dyna::filled(Vec3::zero(), V::default_non_filled(), ()),
167 Vec3::zero(),
168 );
169 }
170
171 let mut min_point = self.0[0].1;
173 let mut max_point = self.0[0].1 + self.0[0].0.size().map(|e| e as i32);
174 for (dyna, offset) in self.0.iter().skip(1) {
175 let size = dyna.size().map(|e| e as i32);
176 min_point = min_point.map2(*offset, std::cmp::min);
177 max_point = max_point.map2(offset + size, std::cmp::max);
178 }
179 let new_size = (max_point - min_point).map(|e| e as u32);
180 let mut combined = Dyna::filled(new_size, V::default_non_filled(), ());
182 let origin = min_point.map(|e| -e);
184 for (dyna, offset) in self.0 {
185 for (pos, vox) in dyna.full_vol_iter() {
186 let cell_pos = origin + offset + pos;
187 let old_vox = *combined.get(cell_pos).unwrap();
188 let new_vox = f(*vox, old_vox);
189 combined.set(cell_pos, new_vox).unwrap();
190 }
191 }
192
193 (combined, origin)
194 }
195}
196
197pub type MatSegment = Dyna<MatCell, ()>;
198
199impl MatSegment {
200 pub fn to_segment(&self, map: impl Fn(Material) -> Rgb<u8>) -> Segment {
201 let mut vol = Dyna::filled(self.size(), Cell::empty(), ());
202 for (pos, vox) in self.full_vol_iter() {
203 let cell = match vox {
204 MatCell::Mat(mat) => Cell::filled(map(*mat), CellSurface::Matte),
205 MatCell::Normal(cell) => *cell,
206 };
207 vol.set(pos, cell).unwrap();
208 }
209 vol
210 }
211
212 #[must_use]
214 pub fn map(mut self, transform: impl Fn(MatCell) -> Option<MatCell>) -> Self {
215 for pos in self.full_pos_iter() {
216 if let Some(new) = transform(*self.get(pos).unwrap()) {
217 self.set(pos, new).unwrap();
218 }
219 }
220
221 self
222 }
223
224 #[must_use]
226 pub fn map_rgb(self, transform: impl Fn(Rgb<u8>) -> Rgb<u8>) -> Self {
227 self.map(|cell| match cell {
228 MatCell::Normal(cell) => Some(MatCell::Normal(cell.map_rgb(&transform))),
229 _ => None,
230 })
231 }
232
233 pub fn from_vox_model_index(dot_vox_data: &DotVoxData, model_index: usize) -> Self {
234 Self::from_vox(dot_vox_data, false, model_index)
235 }
236
237 pub fn from_vox(dot_vox_data: &DotVoxData, flipped: bool, model_index: usize) -> Self {
238 if let Some(model) = dot_vox_data.models.get(model_index) {
239 let palette = dot_vox_data
240 .palette
241 .iter()
242 .map(|col| Rgb::new(col.r, col.g, col.b))
243 .collect::<Vec<_>>();
244
245 let mut vol = Dyna::filled(
246 Vec3::new(model.size.x, model.size.y, model.size.z),
247 MatCell::Normal(Cell::empty()),
248 (),
249 );
250
251 for voxel in &model.voxels {
252 let block = match voxel.i {
253 0 => MatCell::Mat(Material::Skin),
254 1 => MatCell::Mat(Material::Hair),
255 2 => MatCell::Mat(Material::EyeDark),
256 3 => MatCell::Mat(Material::EyeLight),
257 4 => MatCell::Mat(Material::SkinDark),
258 5 => MatCell::Mat(Material::SkinLight),
259 7 => MatCell::Mat(Material::EyeWhite),
260 index => {
262 let color = palette
263 .get(index as usize)
264 .copied()
265 .unwrap_or_else(|| Rgb::broadcast(0));
266 MatCell::Normal(Cell::from_index(index, color))
267 },
268 };
269
270 vol.set(
271 Vec3::new(
272 if flipped {
273 model.size.x as u8 - 1 - voxel.x
274 } else {
275 voxel.x
276 },
277 voxel.y,
278 voxel.z,
279 )
280 .map(i32::from),
281 block,
282 )
283 .unwrap();
284 }
285
286 vol
287 } else {
288 Dyna::filled(Vec3::zero(), MatCell::Normal(Cell::empty()), ())
289 }
290 }
291}