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