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.res::<crate::ecs::entity_registry::EntityRegistry>().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/// Bitmask of "culling layers" this entity belongs to. Independent
49/// from [`RenderLayer`] (which selects WORLD vs OVERLAY rendering).
50/// Cameras with a [`CameraCullingMask`] component skip entities
51/// whose `(entity_layers & camera_mask) == 0`. The default for
52/// entities without the component is bit 0 set (layer 0), and the
53/// default for cameras without the component is all bits set, so
54/// per-camera culling is purely opt-in.
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
56pub struct CullingMask(pub u32);
57
58impl Default for CullingMask {
59    fn default() -> Self {
60        Self(1)
61    }
62}
63
64impl CullingMask {
65    pub const DEFAULT_LAYER: u32 = 1;
66
67    pub fn new(mask: u32) -> Self {
68        Self(mask)
69    }
70
71    pub fn add_layer(&mut self, layer_index: u32) {
72        self.0 |= 1u32 << layer_index;
73    }
74
75    pub fn remove_layer(&mut self, layer_index: u32) {
76        self.0 &= !(1u32 << layer_index);
77    }
78
79    pub fn intersects(self, other: CullingMask) -> bool {
80        (self.0 & other.0) != 0
81    }
82}
83
84impl enum2schema::Schema for CullingMask {
85    fn schema() -> enum2schema::serde_json::Value {
86        enum2schema::serde_json::json!({ "type": "integer" })
87    }
88}
89
90/// Camera-side counterpart to [`CullingMask`]. The renderer
91/// resolves this into `EffectiveShading::culling_mask` per frame
92/// and the mesh / skinned-mesh prepare passes skip any entity whose
93/// own `CullingMask` doesn't intersect the camera's mask. Cameras
94/// without this component render every entity (mask = `!0`).
95#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
96pub struct CameraCullingMask(pub u32);
97
98impl Default for CameraCullingMask {
99    fn default() -> Self {
100        Self(!0)
101    }
102}
103
104impl CameraCullingMask {
105    pub fn new(mask: u32) -> Self {
106        Self(mask)
107    }
108
109    pub fn intersects(self, layer: CullingMask) -> bool {
110        (self.0 & layer.0) != 0
111    }
112}
113
114impl enum2schema::Schema for CameraCullingMask {
115    fn schema() -> enum2schema::serde_json::Value {
116        enum2schema::serde_json::json!({ "type": "integer" })
117    }
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
121pub enum EasingFunction {
122    #[default]
123    Linear,
124    QuadIn,
125    QuadOut,
126    QuadInOut,
127    CubicIn,
128    CubicOut,
129    CubicInOut,
130    QuartIn,
131    QuartOut,
132    QuartInOut,
133    QuintIn,
134    QuintOut,
135    QuintInOut,
136    SineIn,
137    SineOut,
138    SineInOut,
139    ExpoIn,
140    ExpoOut,
141    ExpoInOut,
142    CircIn,
143    CircOut,
144    CircInOut,
145    BackIn,
146    BackOut,
147    BackInOut,
148    ElasticIn,
149    ElasticOut,
150    ElasticInOut,
151    BounceIn,
152    BounceOut,
153    BounceInOut,
154}
155
156impl EasingFunction {
157    pub fn evaluate(&self, t: f32) -> f32 {
158        let t = t.clamp(0.0, 1.0);
159        match self {
160            Self::Linear => t,
161            Self::QuadIn => t * t,
162            Self::QuadOut => 1.0 - (1.0 - t) * (1.0 - t),
163            Self::QuadInOut => {
164                if t < 0.5 {
165                    2.0 * t * t
166                } else {
167                    1.0 - (-2.0 * t + 2.0).powi(2) / 2.0
168                }
169            }
170            Self::CubicIn => t * t * t,
171            Self::CubicOut => 1.0 - (1.0 - t).powi(3),
172            Self::CubicInOut => {
173                if t < 0.5 {
174                    4.0 * t * t * t
175                } else {
176                    1.0 - (-2.0 * t + 2.0).powi(3) / 2.0
177                }
178            }
179            Self::QuartIn => t * t * t * t,
180            Self::QuartOut => 1.0 - (1.0 - t).powi(4),
181            Self::QuartInOut => {
182                if t < 0.5 {
183                    8.0 * t * t * t * t
184                } else {
185                    1.0 - (-2.0 * t + 2.0).powi(4) / 2.0
186                }
187            }
188            Self::QuintIn => t * t * t * t * t,
189            Self::QuintOut => 1.0 - (1.0 - t).powi(5),
190            Self::QuintInOut => {
191                if t < 0.5 {
192                    16.0 * t * t * t * t * t
193                } else {
194                    1.0 - (-2.0 * t + 2.0).powi(5) / 2.0
195                }
196            }
197            Self::SineIn => 1.0 - (t * PI / 2.0).cos(),
198            Self::SineOut => (t * PI / 2.0).sin(),
199            Self::SineInOut => -(PI * t).cos() / 2.0 + 0.5,
200            Self::ExpoIn => {
201                if t == 0.0 {
202                    0.0
203                } else {
204                    (2.0f32).powf(10.0 * t - 10.0)
205                }
206            }
207            Self::ExpoOut => {
208                if t == 1.0 {
209                    1.0
210                } else {
211                    1.0 - (2.0f32).powf(-10.0 * t)
212                }
213            }
214            Self::ExpoInOut => {
215                if t == 0.0 {
216                    0.0
217                } else if t == 1.0 {
218                    1.0
219                } else if t < 0.5 {
220                    (2.0f32).powf(20.0 * t - 10.0) / 2.0
221                } else {
222                    (2.0 - (2.0f32).powf(-20.0 * t + 10.0)) / 2.0
223                }
224            }
225            Self::CircIn => 1.0 - (1.0 - t * t).sqrt(),
226            Self::CircOut => (1.0 - (t - 1.0).powi(2)).sqrt(),
227            Self::CircInOut => {
228                if t < 0.5 {
229                    (1.0 - (1.0 - (2.0 * t).powi(2)).sqrt()) / 2.0
230                } else {
231                    ((1.0 - (-2.0 * t + 2.0).powi(2)).sqrt() + 1.0) / 2.0
232                }
233            }
234            Self::BackIn => {
235                let c1 = 1.70158;
236                let c3 = c1 + 1.0;
237                c3 * t * t * t - c1 * t * t
238            }
239            Self::BackOut => {
240                let c1 = 1.70158;
241                let c3 = c1 + 1.0;
242                1.0 + c3 * (t - 1.0).powi(3) + c1 * (t - 1.0).powi(2)
243            }
244            Self::BackInOut => {
245                let c1 = 1.70158;
246                let c2 = c1 * 1.525;
247                if t < 0.5 {
248                    ((2.0 * t).powi(2) * ((c2 + 1.0) * 2.0 * t - c2)) / 2.0
249                } else {
250                    ((2.0 * t - 2.0).powi(2) * ((c2 + 1.0) * (2.0 * t - 2.0) + c2) + 2.0) / 2.0
251                }
252            }
253            Self::ElasticIn => {
254                if t == 0.0 {
255                    0.0
256                } else if t == 1.0 {
257                    1.0
258                } else {
259                    let c4 = 2.0 * PI / 3.0;
260                    -(2.0f32).powf(10.0 * t - 10.0) * ((10.0 * t - 10.75) * c4).sin()
261                }
262            }
263            Self::ElasticOut => {
264                if t == 0.0 {
265                    0.0
266                } else if t == 1.0 {
267                    1.0
268                } else {
269                    let c4 = 2.0 * PI / 3.0;
270                    (2.0f32).powf(-10.0 * t) * ((10.0 * t - 0.75) * c4).sin() + 1.0
271                }
272            }
273            Self::ElasticInOut => {
274                if t == 0.0 {
275                    0.0
276                } else if t == 1.0 {
277                    1.0
278                } else {
279                    let c5 = 2.0 * PI / 4.5;
280                    if t < 0.5 {
281                        -(2.0f32).powf(20.0 * t - 10.0) * ((20.0 * t - 11.125) * c5).sin() / 2.0
282                    } else {
283                        (2.0f32).powf(-20.0 * t + 10.0) * ((20.0 * t - 11.125) * c5).sin() / 2.0
284                            + 1.0
285                    }
286                }
287            }
288            Self::BounceIn => 1.0 - Self::BounceOut.evaluate(1.0 - t),
289            Self::BounceOut => bounce_out(t),
290            Self::BounceInOut => {
291                if t < 0.5 {
292                    (1.0 - bounce_out(1.0 - 2.0 * t)) / 2.0
293                } else {
294                    (1.0 + bounce_out(2.0 * t - 1.0)) / 2.0
295                }
296            }
297        }
298    }
299}
300
301fn bounce_out(t: f32) -> f32 {
302    let n1 = 7.5625;
303    let d1 = 2.75;
304    if t < 1.0 / d1 {
305        n1 * t * t
306    } else if t < 2.0 / d1 {
307        let t = t - 1.5 / d1;
308        n1 * t * t + 0.75
309    } else if t < 2.5 / d1 {
310        let t = t - 2.25 / d1;
311        n1 * t * t + 0.9375
312    } else {
313        let t = t - 2.625 / d1;
314        n1 * t * t + 0.984375
315    }
316}