1use crate::components::{Component, MeshRenderer, Transform};
5use crate::noise::{Noise, NoiseKind};
6use crate::reflect::Field;
7use glam::Vec3;
8use kvas_ecs::World;
9use serde::{Deserialize, Serialize};
10use std::f32::consts::TAU;
11
12#[derive(Clone, Serialize, Deserialize)]
14#[serde(default)]
15pub struct Spin {
16 pub speed: Vec3,
18}
19
20impl Default for Spin {
21 fn default() -> Self {
22 Self { speed: Vec3::new(0.0, 45.0, 0.0) }
23 }
24}
25
26impl Component for Spin {
27 fn fields(&mut self) -> Vec<Field<'_>> {
28 vec![Field::vec3("speed", &mut self.speed)]
29 }
30}
31
32#[derive(Clone, Serialize, Deserialize)]
34#[serde(default)]
35pub struct Bob {
36 pub axis: Vec3,
38 pub amplitude: f32,
40 pub speed: f32,
42 pub phase: f32,
44 #[serde(skip)]
45 #[doc(hidden)]
46 pub origin: Option<Vec3>,
47}
48
49impl Default for Bob {
50 fn default() -> Self {
51 Self { axis: Vec3::Y, amplitude: 0.5, speed: 0.5, phase: 0.0, origin: None }
52 }
53}
54
55impl Component for Bob {
56 fn fields(&mut self) -> Vec<Field<'_>> {
57 vec![
58 Field::vec3("axis", &mut self.axis),
59 Field::f32("amplitude", &mut self.amplitude),
60 Field::f32("speed", &mut self.speed),
61 Field::f32("phase", &mut self.phase),
62 ]
63 }
64}
65
66#[derive(Clone, Serialize, Deserialize)]
68#[serde(default)]
69pub struct Pulse {
70 pub amount: f32,
72 pub speed: f32,
74 #[serde(skip)]
75 #[doc(hidden)]
76 pub origin: Option<Vec3>,
77}
78
79impl Default for Pulse {
80 fn default() -> Self {
81 Self { amount: 0.15, speed: 1.0, origin: None }
82 }
83}
84
85impl Component for Pulse {
86 fn fields(&mut self) -> Vec<Field<'_>> {
87 vec![Field::f32("amount", &mut self.amount), Field::f32("speed", &mut self.speed)]
88 }
89}
90
91#[derive(Clone, Serialize, Deserialize)]
93#[serde(default)]
94pub struct Wobble {
95 pub amount: Vec3,
97 pub frequency: f32,
99 pub kind: NoiseKind,
101 pub seed: i32,
103 #[serde(skip)]
104 #[doc(hidden)]
105 pub origin: Option<Vec3>,
106}
107
108impl Default for Wobble {
109 fn default() -> Self {
110 Self { amount: Vec3::splat(0.3), frequency: 0.7, kind: NoiseKind::Perlin, seed: 1, origin: None }
111 }
112}
113
114impl Component for Wobble {
115 fn fields(&mut self) -> Vec<Field<'_>> {
116 vec![
117 Field::vec3("amount", &mut self.amount),
118 Field::f32("frequency", &mut self.frequency),
119 Field::choice("noise", &mut self.kind),
120 Field::i32("seed", &mut self.seed),
121 ]
122 }
123}
124
125#[derive(Clone, Serialize, Deserialize)]
127#[serde(default)]
128pub struct ColorCycle {
129 pub speed: f32,
131 pub saturation: f32,
133 pub value: f32,
135}
136
137impl Default for ColorCycle {
138 fn default() -> Self {
139 Self { speed: 0.15, saturation: 0.75, value: 1.0 }
140 }
141}
142
143impl Component for ColorCycle {
144 fn fields(&mut self) -> Vec<Field<'_>> {
145 vec![Field::f32("speed", &mut self.speed), Field::f32("saturation", &mut self.saturation), Field::f32("value", &mut self.value)]
146 }
147}
148
149pub fn hsv_to_rgb(h: f32, s: f32, v: f32) -> [f32; 3] {
151 let k = |n: f32| (n + h.rem_euclid(1.0) * 6.0).rem_euclid(6.0);
152 let f = |n: f32| v - v * s * k(n).min(4.0 - k(n)).clamp(0.0, 1.0);
153 [f(5.0), f(3.0), f(1.0)]
154}
155
156pub fn update(world: &mut World, time: f32, dt: f32) {
159 if !time.is_finite() || !dt.is_finite() || dt < 0.0 { return; }
160 use kvas_ecs::Entity;
161 let spinning: Vec<(Entity, Vec3)> = world.iter::<Spin>().map(|(e, s)| (e, s.speed)).collect();
162 for (e, speed) in spinning {
163 if let Some(t) = world.get_mut::<Transform>(e) {
164 t.rotation += speed * dt;
165 }
166 }
167
168 for e in world.entities_with::<Bob>() {
169 let Some(pos) = world.get::<Transform>(e).map(|t| t.position) else { continue };
170 let b = world.get_mut::<Bob>(e).unwrap();
171 let origin = *b.origin.get_or_insert(pos);
172 let offset = b.axis.normalize_or_zero() * b.amplitude * ((time * b.speed + b.phase) * TAU).sin();
173 let target = origin + offset;
174 world.get_mut::<Transform>(e).unwrap().position = target;
175 }
176
177 for e in world.entities_with::<Pulse>() {
178 let Some(scale) = world.get::<Transform>(e).map(|t| t.scale) else { continue };
179 let p = world.get_mut::<Pulse>(e).unwrap();
180 let origin = *p.origin.get_or_insert(scale);
181 let k = 1.0 + p.amount * (time * p.speed * TAU).sin();
182 world.get_mut::<Transform>(e).unwrap().scale = origin * k;
183 }
184
185 for e in world.entities_with::<Wobble>() {
186 let Some(pos) = world.get::<Transform>(e).map(|t| t.position) else { continue };
187 let w = world.get_mut::<Wobble>(e).unwrap();
188 let origin = *w.origin.get_or_insert(pos);
189 let n = Noise::new(w.kind).with_seed(w.seed as u32).with_octaves(2);
190 let t = time * w.frequency;
191 let offset = Vec3::new(n.sample3(t, 0.0, 11.0), n.sample3(0.0, t, 23.0), n.sample3(37.0, 0.0, t)) * w.amount;
192 world.get_mut::<Transform>(e).unwrap().position = origin + offset;
193 }
194
195 for e in world.entities_with::<ColorCycle>() {
196 let c = world.get::<ColorCycle>(e).unwrap().clone();
197 if let Some(m) = world.get_mut::<MeshRenderer>(e) {
198 let rgb = hsv_to_rgb((time * c.speed).rem_euclid(1.0), c.saturation, c.value);
199 m.color = [rgb[0], rgb[1], rgb[2], m.color[3]];
200 }
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 #[test]
209 fn hsv_primaries() {
210 let near = |a: [f32; 3], b: [f32; 3]| a.iter().zip(b).all(|(x, y)| (x - y).abs() < 1e-4);
211 assert!(near(hsv_to_rgb(0.0, 1.0, 1.0), [1.0, 0.0, 0.0]));
212 assert!(near(hsv_to_rgb(1.0 / 3.0, 1.0, 1.0), [0.0, 1.0, 0.0]));
213 assert!(near(hsv_to_rgb(2.0 / 3.0, 1.0, 1.0), [0.0, 0.0, 1.0]));
214 }
215}