Skip to main content

nightshade/ecs/
primitives.rs

1//! Small primitive component types and shared helpers that don't justify
2//! their own module. Grouped here to keep the top-level `ecs/` tree
3//! shallow.
4
5use serde::{Deserialize, Serialize};
6use std::f32::consts::PI;
7
8#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
9#[serde(default)]
10pub struct Name(pub String);
11
12impl enum2schema::Schema for Name {
13    fn schema() -> enum2schema::serde_json::Value {
14        enum2schema::serde_json::json!({ "type": "string" })
15    }
16}
17
18#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, enum2schema::Schema)]
19#[serde(default)]
20pub struct Visibility {
21    pub visible: bool,
22}
23
24impl Default for Visibility {
25    fn default() -> Self {
26        Self { visible: true }
27    }
28}
29
30#[derive(Default, Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
31pub struct CastsShadow;
32
33impl enum2schema::Schema for CastsShadow {
34    fn schema() -> enum2schema::serde_json::Value {
35        enum2schema::serde_json::json!({ "type": "null" })
36    }
37}
38
39/// Stable engine-side identity for entities that round-trip through a
40/// save file or that other entities cross-reference. Opt-in: omit on
41/// transient entities (particles, projectiles, popups). Engine mints
42/// values from `world.resources.entities.next_guid`, which is itself
43/// persisted in the scene file so freshly-loaded scenes don't collide
44/// with the writer's ids.
45#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
46pub struct Guid(pub u64);
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
49pub struct RenderLayer(pub u32);
50
51impl enum2schema::Schema for RenderLayer {
52    fn schema() -> enum2schema::serde_json::Value {
53        enum2schema::serde_json::json!({ "type": "integer" })
54    }
55}
56
57impl Default for RenderLayer {
58    fn default() -> Self {
59        Self(Self::WORLD)
60    }
61}
62
63impl RenderLayer {
64    pub const WORLD: u32 = 0;
65    pub const OVERLAY: u32 = 1;
66
67    pub fn new(layer: u32) -> Self {
68        Self(layer)
69    }
70
71    pub fn is_in_layer(&self, layer: u32) -> bool {
72        self.0 == layer
73    }
74
75    pub fn is_overlay(&self) -> bool {
76        self.0 == Self::OVERLAY
77    }
78}
79
80/// Bitmask of "culling layers" this entity belongs to. Independent
81/// from [`RenderLayer`] (which selects WORLD vs OVERLAY rendering).
82/// Cameras with a [`CameraCullingMask`] component skip entities
83/// whose `(entity_layers & camera_mask) == 0`. The default for
84/// entities without the component is bit 0 set (layer 0), and the
85/// default for cameras without the component is all bits set, so
86/// per-camera culling is purely opt-in.
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
88pub struct CullingMask(pub u32);
89
90impl Default for CullingMask {
91    fn default() -> Self {
92        Self(1)
93    }
94}
95
96impl CullingMask {
97    pub const DEFAULT_LAYER: u32 = 1;
98
99    pub fn new(mask: u32) -> Self {
100        Self(mask)
101    }
102
103    pub fn add_layer(&mut self, layer_index: u32) {
104        self.0 |= 1u32 << layer_index;
105    }
106
107    pub fn remove_layer(&mut self, layer_index: u32) {
108        self.0 &= !(1u32 << layer_index);
109    }
110
111    pub fn intersects(self, other: CullingMask) -> bool {
112        (self.0 & other.0) != 0
113    }
114}
115
116/// Camera-side counterpart to [`CullingMask`]. The renderer
117/// resolves this into `EffectiveShading::culling_mask` per frame
118/// and the mesh / skinned-mesh prepare passes skip any entity whose
119/// own `CullingMask` doesn't intersect the camera's mask. Cameras
120/// without this component render every entity (mask = `!0`).
121#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
122pub struct CameraCullingMask(pub u32);
123
124impl Default for CameraCullingMask {
125    fn default() -> Self {
126        Self(!0)
127    }
128}
129
130impl CameraCullingMask {
131    pub fn new(mask: u32) -> Self {
132        Self(mask)
133    }
134
135    pub fn intersects(self, layer: CullingMask) -> bool {
136        (self.0 & layer.0) != 0
137    }
138}
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
141pub enum EasingFunction {
142    #[default]
143    Linear,
144    QuadIn,
145    QuadOut,
146    QuadInOut,
147    CubicIn,
148    CubicOut,
149    CubicInOut,
150    QuartIn,
151    QuartOut,
152    QuartInOut,
153    QuintIn,
154    QuintOut,
155    QuintInOut,
156    SineIn,
157    SineOut,
158    SineInOut,
159    ExpoIn,
160    ExpoOut,
161    ExpoInOut,
162    CircIn,
163    CircOut,
164    CircInOut,
165    BackIn,
166    BackOut,
167    BackInOut,
168    ElasticIn,
169    ElasticOut,
170    ElasticInOut,
171    BounceIn,
172    BounceOut,
173    BounceInOut,
174}
175
176impl EasingFunction {
177    pub fn evaluate(&self, t: f32) -> f32 {
178        let t = t.clamp(0.0, 1.0);
179        match self {
180            Self::Linear => t,
181            Self::QuadIn => t * t,
182            Self::QuadOut => 1.0 - (1.0 - t) * (1.0 - t),
183            Self::QuadInOut => {
184                if t < 0.5 {
185                    2.0 * t * t
186                } else {
187                    1.0 - (-2.0 * t + 2.0).powi(2) / 2.0
188                }
189            }
190            Self::CubicIn => t * t * t,
191            Self::CubicOut => 1.0 - (1.0 - t).powi(3),
192            Self::CubicInOut => {
193                if t < 0.5 {
194                    4.0 * t * t * t
195                } else {
196                    1.0 - (-2.0 * t + 2.0).powi(3) / 2.0
197                }
198            }
199            Self::QuartIn => t * t * t * t,
200            Self::QuartOut => 1.0 - (1.0 - t).powi(4),
201            Self::QuartInOut => {
202                if t < 0.5 {
203                    8.0 * t * t * t * t
204                } else {
205                    1.0 - (-2.0 * t + 2.0).powi(4) / 2.0
206                }
207            }
208            Self::QuintIn => t * t * t * t * t,
209            Self::QuintOut => 1.0 - (1.0 - t).powi(5),
210            Self::QuintInOut => {
211                if t < 0.5 {
212                    16.0 * t * t * t * t * t
213                } else {
214                    1.0 - (-2.0 * t + 2.0).powi(5) / 2.0
215                }
216            }
217            Self::SineIn => 1.0 - (t * PI / 2.0).cos(),
218            Self::SineOut => (t * PI / 2.0).sin(),
219            Self::SineInOut => -(PI * t).cos() / 2.0 + 0.5,
220            Self::ExpoIn => {
221                if t == 0.0 {
222                    0.0
223                } else {
224                    (2.0f32).powf(10.0 * t - 10.0)
225                }
226            }
227            Self::ExpoOut => {
228                if t == 1.0 {
229                    1.0
230                } else {
231                    1.0 - (2.0f32).powf(-10.0 * t)
232                }
233            }
234            Self::ExpoInOut => {
235                if t == 0.0 {
236                    0.0
237                } else if t == 1.0 {
238                    1.0
239                } else if t < 0.5 {
240                    (2.0f32).powf(20.0 * t - 10.0) / 2.0
241                } else {
242                    (2.0 - (2.0f32).powf(-20.0 * t + 10.0)) / 2.0
243                }
244            }
245            Self::CircIn => 1.0 - (1.0 - t * t).sqrt(),
246            Self::CircOut => (1.0 - (t - 1.0).powi(2)).sqrt(),
247            Self::CircInOut => {
248                if t < 0.5 {
249                    (1.0 - (1.0 - (2.0 * t).powi(2)).sqrt()) / 2.0
250                } else {
251                    ((1.0 - (-2.0 * t + 2.0).powi(2)).sqrt() + 1.0) / 2.0
252                }
253            }
254            Self::BackIn => {
255                let c1 = 1.70158;
256                let c3 = c1 + 1.0;
257                c3 * t * t * t - c1 * t * t
258            }
259            Self::BackOut => {
260                let c1 = 1.70158;
261                let c3 = c1 + 1.0;
262                1.0 + c3 * (t - 1.0).powi(3) + c1 * (t - 1.0).powi(2)
263            }
264            Self::BackInOut => {
265                let c1 = 1.70158;
266                let c2 = c1 * 1.525;
267                if t < 0.5 {
268                    ((2.0 * t).powi(2) * ((c2 + 1.0) * 2.0 * t - c2)) / 2.0
269                } else {
270                    ((2.0 * t - 2.0).powi(2) * ((c2 + 1.0) * (2.0 * t - 2.0) + c2) + 2.0) / 2.0
271                }
272            }
273            Self::ElasticIn => {
274                if t == 0.0 {
275                    0.0
276                } else if t == 1.0 {
277                    1.0
278                } else {
279                    let c4 = 2.0 * PI / 3.0;
280                    -(2.0f32).powf(10.0 * t - 10.0) * ((10.0 * t - 10.75) * c4).sin()
281                }
282            }
283            Self::ElasticOut => {
284                if t == 0.0 {
285                    0.0
286                } else if t == 1.0 {
287                    1.0
288                } else {
289                    let c4 = 2.0 * PI / 3.0;
290                    (2.0f32).powf(-10.0 * t) * ((10.0 * t - 0.75) * c4).sin() + 1.0
291                }
292            }
293            Self::ElasticInOut => {
294                if t == 0.0 {
295                    0.0
296                } else if t == 1.0 {
297                    1.0
298                } else {
299                    let c5 = 2.0 * PI / 4.5;
300                    if t < 0.5 {
301                        -(2.0f32).powf(20.0 * t - 10.0) * ((20.0 * t - 11.125) * c5).sin() / 2.0
302                    } else {
303                        (2.0f32).powf(-20.0 * t + 10.0) * ((20.0 * t - 11.125) * c5).sin() / 2.0
304                            + 1.0
305                    }
306                }
307            }
308            Self::BounceIn => 1.0 - Self::BounceOut.evaluate(1.0 - t),
309            Self::BounceOut => bounce_out(t),
310            Self::BounceInOut => {
311                if t < 0.5 {
312                    (1.0 - bounce_out(1.0 - 2.0 * t)) / 2.0
313                } else {
314                    (1.0 + bounce_out(2.0 * t - 1.0)) / 2.0
315                }
316            }
317        }
318    }
319}
320
321fn bounce_out(t: f32) -> f32 {
322    let n1 = 7.5625;
323    let d1 = 2.75;
324    if t < 1.0 / d1 {
325        n1 * t * t
326    } else if t < 2.0 / d1 {
327        let t = t - 1.5 / d1;
328        n1 * t * t + 0.75
329    } else if t < 2.5 / d1 {
330        let t = t - 2.25 / d1;
331        n1 * t * t + 0.9375
332    } else {
333        let t = t - 2.625 / d1;
334        n1 * t * t + 0.984375
335    }
336}