Skip to main content

mirage_engine/mesh/
slot.rs

1use crate::assets::Unresolved;
2use crate::{Material, ReliefData, ShadingData, TextureData};
3
4/// What one part of a mesh draws with: a material and its maps, over some
5/// of the mesh's triangles.
6///
7/// A slot is a length, not a range.
8#[derive(Clone, Debug)]
9pub struct Slot {
10    index_count: u32,
11    material: Material,
12    texture: Option<TextureData>,
13    relief: Option<ReliefData>,
14    shading: Option<ShadingData>,
15    emissive: Option<TextureData>,
16    /// The index of the part naming this slot, absent while it is
17    /// anonymous.
18    part: Option<u32>,
19}
20
21impl Slot {
22    /// A slot drawn with `material` over a white default, for the next
23    /// `index_count` indices.
24    pub fn new(index_count: u32, material: Material) -> Self {
25        Self {
26            index_count,
27            material,
28            texture: None,
29            relief: None,
30            shading: None,
31            emissive: None,
32            part: None,
33        }
34    }
35
36    /// Samples this slot from `texture`, in place of the white default.
37    #[must_use]
38    pub fn textured(mut self, texture: TextureData) -> Self {
39        self.texture = Some(texture);
40        self
41    }
42
43    /// The relief is a texture map: `RGB` holds each texel's normal, `+Y` up
44    /// the map, and `A` its depth where the relief declares one, `1.0` one
45    /// sprite width, half in front of the sprite and half behind.
46    ///
47    /// Required if you want a surface lit by normals its own corners do not
48    /// hold. A billboarded or upright draw reads the normal in the axes the
49    /// camera turned; every other draw reads it through the derivative
50    /// basis — the basis its own position and UV derivatives span — and
51    /// ignores the depth. The relief is windowed like the color texture, so
52    /// each sheet cell lines up with its own relief cell. A depth also
53    /// decides what a billboarded or upright draw casts: a texel with no
54    /// depth casts nothing, and depth under the sprite's own texels, down to
55    /// its foot, produces a shadow that starts at the ground it is drawn on.
56    #[must_use]
57    pub fn relief(mut self, relief: ReliefData) -> Self {
58        self.relief = Some(relief);
59        self
60    }
61
62    /// The shading map holds each texel's occlusion in `R`, its roughness in
63    /// `G` and its metallic in `B`. The roughness and metallic each scale the
64    /// slot's own lane; the occlusion instead darkens what the texel takes
65    /// of the frame's sky.
66    ///
67    /// Required if you want the roughness, the metallic or the sky's own
68    /// light on one material to differ across the surface. Every light of
69    /// the frame lands whole, whatever the occlusion holds.
70    #[must_use]
71    pub fn shading(mut self, shading: ShadingData) -> Self {
72        self.shading = Some(shading);
73        self
74    }
75
76    /// The emissive map is the light this slot casts per texel, scaled by the
77    /// material's emissive color.
78    ///
79    /// Required if you want part of a surface to cast light while the rest of
80    /// it does not. A material casts none by default, so the map alone casts
81    /// nothing.
82    #[must_use]
83    pub fn emissive_map(mut self, emissive: TextureData) -> Self {
84        self.emissive = Some(emissive);
85        self
86    }
87
88    /// The number of the mesh's indices this slot covers.
89    pub fn index_count(&self) -> u32 {
90        self.index_count
91    }
92
93    /// The material used unless a draw overrides this slot.
94    pub fn material(&self) -> Material {
95        self.material
96    }
97
98    /// The pixels this slot samples, absent while it is drawn over white.
99    pub fn texture(&self) -> Option<&TextureData> {
100        self.texture.as_ref()
101    }
102
103    /// The same slot named by the part at `part`.
104    pub(crate) fn named(mut self, part: u32) -> Self {
105        self.part = Some(part);
106        self
107    }
108
109    /// The index of the part naming this slot, absent while it is
110    /// anonymous.
111    pub(crate) fn part(&self) -> Option<u32> {
112        self.part
113    }
114
115    /// The relief this slot reads, absent where it has none.
116    pub(crate) fn relief_map(&self) -> Option<&ReliefData> {
117        self.relief.as_ref()
118    }
119
120    /// The shading map this slot reads, absent where it has none.
121    pub(crate) fn shading_map(&self) -> Option<&ShadingData> {
122        self.shading.as_ref()
123    }
124
125    /// The emissive map this slot reads, absent where it has none.
126    pub(crate) fn emissive(&self) -> Option<&TextureData> {
127        self.emissive.as_ref()
128    }
129
130    /// Sets the material, in place of the one it has.
131    pub(crate) fn set_material(&mut self, material: Material) {
132        self.material = material;
133    }
134
135    /// Sets the texture, in place of any it has, and returns what the pulls
136    /// of the one it displaced did not get.
137    #[must_use]
138    pub(crate) fn set_texture(&mut self, texture: TextureData) -> Unresolved {
139        displaced(self.texture.replace(texture).as_mut())
140    }
141
142    /// Sets the relief, in place of any it has, and returns what the pulls
143    /// of the one it displaced did not get.
144    #[must_use]
145    pub(crate) fn set_relief(&mut self, relief: ReliefData) -> Unresolved {
146        displaced(
147            self.relief
148                .replace(relief)
149                .as_mut()
150                .map(ReliefData::map_mut),
151        )
152    }
153
154    /// Sets the shading map, in place of any it has, and returns what the
155    /// pulls of the one it displaced did not get.
156    #[must_use]
157    pub(crate) fn set_shading(&mut self, shading: ShadingData) -> Unresolved {
158        displaced(
159            self.shading
160                .replace(shading)
161                .as_mut()
162                .map(ShadingData::map_mut),
163        )
164    }
165
166    /// Sets the emissive map, in place of any it has, and returns what the
167    /// pulls of the one it displaced did not get.
168    #[must_use]
169    pub(crate) fn set_emissive_map(&mut self, emissive: TextureData) -> Unresolved {
170        displaced(self.emissive.replace(emissive).as_mut())
171    }
172
173    /// Memory the pixels this slot samples hold.
174    pub(crate) fn bytes(&self) -> usize {
175        [
176            self.texture.as_ref(),
177            self.relief.as_ref().map(ReliefData::map),
178            self.shading.as_ref().map(ShadingData::map),
179            self.emissive.as_ref(),
180        ]
181        .into_iter()
182        .flatten()
183        .map(|texture| texture.pixels().len())
184        .sum()
185    }
186
187    /// What the pulls of the maps this slot samples did not get, taken out
188    /// of all four of them.
189    pub(crate) fn take_unresolved(&mut self) -> Unresolved {
190        let maps = [
191            self.texture.as_mut(),
192            self.relief.as_mut().map(ReliefData::map_mut),
193            self.shading.as_mut().map(ShadingData::map_mut),
194            self.emissive.as_mut(),
195        ];
196
197        maps.into_iter()
198            .flatten()
199            .fold(Unresolved::default(), |mut unresolved, map| {
200                unresolved.record(map.take_unresolved());
201                unresolved
202            })
203    }
204}
205
206/// What the pulls of a map a slot displaced did not get; nothing where it
207/// displaced none.
208fn displaced(map: Option<&mut TextureData>) -> Unresolved {
209    map.map(TextureData::take_unresolved).unwrap_or_default()
210}