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
116impl enum2schema::Schema for CullingMask {
117    fn schema() -> enum2schema::serde_json::Value {
118        enum2schema::serde_json::json!({ "type": "integer" })
119    }
120}
121
122/// Camera-side counterpart to [`CullingMask`]. The renderer
123/// resolves this into `EffectiveShading::culling_mask` per frame
124/// and the mesh / skinned-mesh prepare passes skip any entity whose
125/// own `CullingMask` doesn't intersect the camera's mask. Cameras
126/// without this component render every entity (mask = `!0`).
127#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
128pub struct CameraCullingMask(pub u32);
129
130impl Default for CameraCullingMask {
131    fn default() -> Self {
132        Self(!0)
133    }
134}
135
136impl CameraCullingMask {
137    pub fn new(mask: u32) -> Self {
138        Self(mask)
139    }
140
141    pub fn intersects(self, layer: CullingMask) -> bool {
142        (self.0 & layer.0) != 0
143    }
144}
145
146impl enum2schema::Schema for CameraCullingMask {
147    fn schema() -> enum2schema::serde_json::Value {
148        enum2schema::serde_json::json!({ "type": "integer" })
149    }
150}
151
152#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
153pub enum EasingFunction {
154    #[default]
155    Linear,
156    QuadIn,
157    QuadOut,
158    QuadInOut,
159    CubicIn,
160    CubicOut,
161    CubicInOut,
162    QuartIn,
163    QuartOut,
164    QuartInOut,
165    QuintIn,
166    QuintOut,
167    QuintInOut,
168    SineIn,
169    SineOut,
170    SineInOut,
171    ExpoIn,
172    ExpoOut,
173    ExpoInOut,
174    CircIn,
175    CircOut,
176    CircInOut,
177    BackIn,
178    BackOut,
179    BackInOut,
180    ElasticIn,
181    ElasticOut,
182    ElasticInOut,
183    BounceIn,
184    BounceOut,
185    BounceInOut,
186}
187
188impl EasingFunction {
189    pub fn evaluate(&self, t: f32) -> f32 {
190        let t = t.clamp(0.0, 1.0);
191        match self {
192            Self::Linear => t,
193            Self::QuadIn => t * t,
194            Self::QuadOut => 1.0 - (1.0 - t) * (1.0 - t),
195            Self::QuadInOut => {
196                if t < 0.5 {
197                    2.0 * t * t
198                } else {
199                    1.0 - (-2.0 * t + 2.0).powi(2) / 2.0
200                }
201            }
202            Self::CubicIn => t * t * t,
203            Self::CubicOut => 1.0 - (1.0 - t).powi(3),
204            Self::CubicInOut => {
205                if t < 0.5 {
206                    4.0 * t * t * t
207                } else {
208                    1.0 - (-2.0 * t + 2.0).powi(3) / 2.0
209                }
210            }
211            Self::QuartIn => t * t * t * t,
212            Self::QuartOut => 1.0 - (1.0 - t).powi(4),
213            Self::QuartInOut => {
214                if t < 0.5 {
215                    8.0 * t * t * t * t
216                } else {
217                    1.0 - (-2.0 * t + 2.0).powi(4) / 2.0
218                }
219            }
220            Self::QuintIn => t * t * t * t * t,
221            Self::QuintOut => 1.0 - (1.0 - t).powi(5),
222            Self::QuintInOut => {
223                if t < 0.5 {
224                    16.0 * t * t * t * t * t
225                } else {
226                    1.0 - (-2.0 * t + 2.0).powi(5) / 2.0
227                }
228            }
229            Self::SineIn => 1.0 - (t * PI / 2.0).cos(),
230            Self::SineOut => (t * PI / 2.0).sin(),
231            Self::SineInOut => -(PI * t).cos() / 2.0 + 0.5,
232            Self::ExpoIn => {
233                if t == 0.0 {
234                    0.0
235                } else {
236                    (2.0f32).powf(10.0 * t - 10.0)
237                }
238            }
239            Self::ExpoOut => {
240                if t == 1.0 {
241                    1.0
242                } else {
243                    1.0 - (2.0f32).powf(-10.0 * t)
244                }
245            }
246            Self::ExpoInOut => {
247                if t == 0.0 {
248                    0.0
249                } else if t == 1.0 {
250                    1.0
251                } else if t < 0.5 {
252                    (2.0f32).powf(20.0 * t - 10.0) / 2.0
253                } else {
254                    (2.0 - (2.0f32).powf(-20.0 * t + 10.0)) / 2.0
255                }
256            }
257            Self::CircIn => 1.0 - (1.0 - t * t).sqrt(),
258            Self::CircOut => (1.0 - (t - 1.0).powi(2)).sqrt(),
259            Self::CircInOut => {
260                if t < 0.5 {
261                    (1.0 - (1.0 - (2.0 * t).powi(2)).sqrt()) / 2.0
262                } else {
263                    ((1.0 - (-2.0 * t + 2.0).powi(2)).sqrt() + 1.0) / 2.0
264                }
265            }
266            Self::BackIn => {
267                let c1 = 1.70158;
268                let c3 = c1 + 1.0;
269                c3 * t * t * t - c1 * t * t
270            }
271            Self::BackOut => {
272                let c1 = 1.70158;
273                let c3 = c1 + 1.0;
274                1.0 + c3 * (t - 1.0).powi(3) + c1 * (t - 1.0).powi(2)
275            }
276            Self::BackInOut => {
277                let c1 = 1.70158;
278                let c2 = c1 * 1.525;
279                if t < 0.5 {
280                    ((2.0 * t).powi(2) * ((c2 + 1.0) * 2.0 * t - c2)) / 2.0
281                } else {
282                    ((2.0 * t - 2.0).powi(2) * ((c2 + 1.0) * (2.0 * t - 2.0) + c2) + 2.0) / 2.0
283                }
284            }
285            Self::ElasticIn => {
286                if t == 0.0 {
287                    0.0
288                } else if t == 1.0 {
289                    1.0
290                } else {
291                    let c4 = 2.0 * PI / 3.0;
292                    -(2.0f32).powf(10.0 * t - 10.0) * ((10.0 * t - 10.75) * c4).sin()
293                }
294            }
295            Self::ElasticOut => {
296                if t == 0.0 {
297                    0.0
298                } else if t == 1.0 {
299                    1.0
300                } else {
301                    let c4 = 2.0 * PI / 3.0;
302                    (2.0f32).powf(-10.0 * t) * ((10.0 * t - 0.75) * c4).sin() + 1.0
303                }
304            }
305            Self::ElasticInOut => {
306                if t == 0.0 {
307                    0.0
308                } else if t == 1.0 {
309                    1.0
310                } else {
311                    let c5 = 2.0 * PI / 4.5;
312                    if t < 0.5 {
313                        -(2.0f32).powf(20.0 * t - 10.0) * ((20.0 * t - 11.125) * c5).sin() / 2.0
314                    } else {
315                        (2.0f32).powf(-20.0 * t + 10.0) * ((20.0 * t - 11.125) * c5).sin() / 2.0
316                            + 1.0
317                    }
318                }
319            }
320            Self::BounceIn => 1.0 - Self::BounceOut.evaluate(1.0 - t),
321            Self::BounceOut => bounce_out(t),
322            Self::BounceInOut => {
323                if t < 0.5 {
324                    (1.0 - bounce_out(1.0 - 2.0 * t)) / 2.0
325                } else {
326                    (1.0 + bounce_out(2.0 * t - 1.0)) / 2.0
327                }
328            }
329        }
330    }
331}
332
333fn bounce_out(t: f32) -> f32 {
334    let n1 = 7.5625;
335    let d1 = 2.75;
336    if t < 1.0 / d1 {
337        n1 * t * t
338    } else if t < 2.0 / d1 {
339        let t = t - 1.5 / d1;
340        n1 * t * t + 0.75
341    } else if t < 2.5 / d1 {
342        let t = t - 2.25 / d1;
343        n1 * t * t + 0.9375
344    } else {
345        let t = t - 2.625 / d1;
346        n1 * t * t + 0.984375
347    }
348}