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