veloren_common/figure/
mod.rs

1pub mod cell;
2pub mod mat_cell;
3pub use mat_cell::Material;
4
5// Reexport
6pub 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
40/// A type representing a volume that may be part of an animated figure.
41///
42/// Figures are used to represent things like characters, NPCs, mobs, etc.
43pub type Segment = Dyna<Cell, ()>;
44
45impl Segment {
46    /// Take a list of voxel data, offsets, and x-mirror flags, and assembled
47    /// them into a combined segment
48    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            // Set up the index lookup table
84            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    /// Transform cells
119    #[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    /// Transform cell colors
131    #[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
137// TODO: move
138/// A `Dyna` builder that combines Dynas
139pub 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    /// Unify dynamic volumes, with a function that takes (cell, old_cell) and
162    /// returns the cell to use.
163    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        // Determine size of the new Dyna
172        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        // Allocate new segment
181        let mut combined = Dyna::filled(new_size, V::default_non_filled(), ());
182        // Copy segments into combined
183        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    /// Transform cells
213    #[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    /// Transform cell colors
225    #[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                    //6 => MatCell::Mat(Material::Clothing),
261                    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}