veloren_common/figure/
mod.rs

1pub mod cell;
2pub mod mat_cell;
3use cell::CellAttr;
4pub use mat_cell::Material;
5
6// Reexport
7pub use self::{
8    cell::{Cell, CellData},
9    mat_cell::MatCell,
10};
11
12use crate::{
13    terrain::{Block, BlockKind, SpriteKind},
14    vol::{FilledVox, IntoFullPosIterator, IntoFullVolIterator, ReadVol, SizedVol, WriteVol},
15    volumes::dyna::Dyna,
16};
17use dot_vox::DotVoxData;
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(_) | Ok(Cell::Empty) => Block::air(SpriteKind::Empty),
26            Ok(cell) => {
27                if cell.attr().is_hollow() {
28                    Block::air(SpriteKind::Empty)
29                } else if cell.attr().is_glowy() {
30                    Block::new(BlockKind::GlowingRock, cell.get_color().unwrap())
31                } else {
32                    Block::new(BlockKind::Misc, cell.get_color().unwrap())
33                }
34            },
35        })
36    }
37}
38
39/// A type representing a volume that may be part of an animated figure.
40///
41/// Figures are used to represent things like characters, NPCs, mobs, etc.
42pub type Segment = Dyna<Cell, ()>;
43
44impl Segment {
45    /// Take a list of voxel data, offsets, and x-mirror flags, and assembled
46    /// them into a combined segment
47    pub fn from_voxes(data: &[(&DotVoxData, Vec3<i32>, bool)]) -> (Self, Vec3<i32>) {
48        let mut union = DynaUnionizer::new();
49        for (datum, offset, xmirror) in data.iter() {
50            union = union.add(Segment::from_vox(datum, *xmirror, 0), *offset);
51        }
52        union.unify()
53    }
54
55    pub fn from_vox_model_index(dot_vox_data: &DotVoxData, model_index: usize) -> Self {
56        Self::from_vox(dot_vox_data, false, model_index)
57    }
58
59    pub fn from_vox(dot_vox_data: &DotVoxData, flipped: bool, model_index: usize) -> Self {
60        if let Some(model) = dot_vox_data.models.get(model_index) {
61            let palette = dot_vox_data
62                .palette
63                .iter()
64                .map(|col| Rgb::new(col.r, col.g, col.b))
65                .collect::<Vec<_>>();
66
67            let mut segment = Segment::filled(
68                Vec3::new(model.size.x, model.size.y, model.size.z),
69                Cell::Empty,
70                (),
71            );
72
73            for voxel in &model.voxels {
74                if let Some(&color) = palette.get(voxel.i as usize) {
75                    segment
76                        .set(
77                            Vec3::new(
78                                if flipped {
79                                    model.size.x as u8 - 1 - voxel.x
80                                } else {
81                                    voxel.x
82                                },
83                                voxel.y,
84                                voxel.z,
85                            )
86                            .map(i32::from),
87                            Cell::new(color, CellAttr::from_index(voxel.i)),
88                        )
89                        .unwrap();
90                };
91            }
92
93            segment
94        } else {
95            Segment::filled(Vec3::zero(), Cell::Empty, ())
96        }
97    }
98
99    /// Transform cells
100    #[must_use]
101    pub fn map(mut self, transform: impl Fn(Cell) -> Option<Cell>) -> Self {
102        for pos in self.full_pos_iter() {
103            if let Some(new) = transform(*self.get(pos).unwrap()) {
104                self.set(pos, new).unwrap();
105            }
106        }
107
108        self
109    }
110
111    /// Transform cell colors
112    #[must_use]
113    pub fn map_rgb(self, transform: impl Fn(Rgb<u8>) -> Rgb<u8>) -> Self {
114        self.map(|cell| {
115            cell.get_color()
116                .map(|rgb| Cell::new(transform(rgb), cell.attr()))
117        })
118    }
119}
120
121// TODO: move
122/// A `Dyna` builder that combines Dynas
123pub struct DynaUnionizer<V: FilledVox>(Vec<(Dyna<V, ()>, Vec3<i32>)>);
124
125impl<V: FilledVox + Copy> DynaUnionizer<V> {
126    #[expect(clippy::new_without_default)]
127    pub fn new() -> Self { DynaUnionizer(Vec::new()) }
128
129    #[must_use]
130    pub fn add(mut self, dyna: Dyna<V, ()>, offset: Vec3<i32>) -> Self {
131        self.0.push((dyna, offset));
132        self
133    }
134
135    #[must_use]
136    pub fn maybe_add(self, maybe: Option<(Dyna<V, ()>, Vec3<i32>)>) -> Self {
137        match maybe {
138            Some((dyna, offset)) => self.add(dyna, offset),
139            None => self,
140        }
141    }
142
143    pub fn unify(self) -> (Dyna<V, ()>, Vec3<i32>) { self.unify_with(|v, _| v) }
144
145    /// Unify dynamic volumes, with a function that takes (cell, old_cell) and
146    /// returns the cell to use.
147    pub fn unify_with(self, mut f: impl FnMut(V, V) -> V) -> (Dyna<V, ()>, Vec3<i32>) {
148        if self.0.is_empty() {
149            return (
150                Dyna::filled(Vec3::zero(), V::default_non_filled(), ()),
151                Vec3::zero(),
152            );
153        }
154
155        // Determine size of the new Dyna
156        let mut min_point = self.0[0].1;
157        let mut max_point = self.0[0].1 + self.0[0].0.size().map(|e| e as i32);
158        for (dyna, offset) in self.0.iter().skip(1) {
159            let size = dyna.size().map(|e| e as i32);
160            min_point = min_point.map2(*offset, std::cmp::min);
161            max_point = max_point.map2(offset + size, std::cmp::max);
162        }
163        let new_size = (max_point - min_point).map(|e| e as u32);
164        // Allocate new segment
165        let mut combined = Dyna::filled(new_size, V::default_non_filled(), ());
166        // Copy segments into combined
167        let origin = min_point.map(|e| -e);
168        for (dyna, offset) in self.0 {
169            for (pos, vox) in dyna.full_vol_iter() {
170                if vox.is_filled() {
171                    let cell_pos = origin + offset + pos;
172                    let old_vox = *combined.get(cell_pos).unwrap();
173                    let new_vox = f(*vox, old_vox);
174                    combined.set(cell_pos, new_vox).unwrap();
175                }
176            }
177        }
178
179        (combined, origin)
180    }
181}
182
183pub type MatSegment = Dyna<MatCell, ()>;
184
185impl MatSegment {
186    pub fn to_segment(&self, map: impl Fn(Material) -> Rgb<u8>) -> Segment {
187        let mut vol = Dyna::filled(self.size(), Cell::Empty, ());
188        for (pos, vox) in self.full_vol_iter() {
189            let data = match vox {
190                MatCell::None => continue,
191                MatCell::Mat(mat) => CellData::new(map(*mat), CellAttr::empty()),
192                MatCell::Normal(data) => *data,
193            };
194            vol.set(pos, Cell::Filled(data)).unwrap();
195        }
196        vol
197    }
198
199    /// Transform cells
200    #[must_use]
201    pub fn map(mut self, transform: impl Fn(MatCell) -> Option<MatCell>) -> Self {
202        for pos in self.full_pos_iter() {
203            if let Some(new) = transform(*self.get(pos).unwrap()) {
204                self.set(pos, new).unwrap();
205            }
206        }
207
208        self
209    }
210
211    /// Transform cell colors
212    #[must_use]
213    pub fn map_rgb(self, transform: impl Fn(Rgb<u8>) -> Rgb<u8>) -> Self {
214        self.map(|cell| match cell {
215            MatCell::Normal(data) => Some(MatCell::Normal(CellData {
216                col: transform(data.col),
217                ..data
218            })),
219            _ => None,
220        })
221    }
222
223    pub fn from_vox_model_index(dot_vox_data: &DotVoxData, model_index: usize) -> Self {
224        Self::from_vox(dot_vox_data, false, model_index)
225    }
226
227    pub fn from_vox(dot_vox_data: &DotVoxData, flipped: bool, model_index: usize) -> Self {
228        if let Some(model) = dot_vox_data.models.get(model_index) {
229            let palette = dot_vox_data
230                .palette
231                .iter()
232                .map(|col| Rgb::new(col.r, col.g, col.b))
233                .collect::<Vec<_>>();
234
235            let mut vol = Dyna::filled(
236                Vec3::new(model.size.x, model.size.y, model.size.z),
237                MatCell::None,
238                (),
239            );
240
241            for voxel in &model.voxels {
242                let block = match voxel.i {
243                    0 => MatCell::Mat(Material::Skin),
244                    1 => MatCell::Mat(Material::Hair),
245                    2 => MatCell::Mat(Material::EyeDark),
246                    3 => MatCell::Mat(Material::EyeLight),
247                    4 => MatCell::Mat(Material::SkinDark),
248                    5 => MatCell::Mat(Material::SkinLight),
249                    7 => MatCell::Mat(Material::EyeWhite),
250                    //6 => MatCell::Mat(Material::Clothing),
251                    index => {
252                        let color = palette
253                            .get(index as usize)
254                            .copied()
255                            .unwrap_or_else(|| Rgb::broadcast(0));
256                        MatCell::Normal(CellData::new(color, CellAttr::from_index(index)))
257                    },
258                };
259
260                vol.set(
261                    Vec3::new(
262                        if flipped {
263                            model.size.x as u8 - 1 - voxel.x
264                        } else {
265                            voxel.x
266                        },
267                        voxel.y,
268                        voxel.z,
269                    )
270                    .map(i32::from),
271                    block,
272                )
273                .unwrap();
274            }
275
276            vol
277        } else {
278            Dyna::filled(Vec3::zero(), MatCell::None, ())
279        }
280    }
281}