1use crate::avatar::shape::Shape;
6use std::f32::consts::PI;
7
8const BLINK_EVERY: f32 = 4.4;
10const BLINK_FOR: f32 = 0.16;
11const SCAN_EVERY: f32 = 1.2;
13
14#[derive(Clone, Copy, Debug, PartialEq)]
15pub struct Motion {
16 pub wobble: f32,
18 pub breathe: f32,
20 pub drift: f32,
22 pub blink: bool,
23 pub scan: f32,
28 pub lid: f32,
31}
32
33impl Default for Motion {
34 fn default() -> Self {
35 Self::ALIVE
36 }
37}
38
39impl Motion {
40 pub const STILL: Self = Self {
41 wobble: 0.0,
42 breathe: 0.0,
43 drift: 0.0,
44 blink: false,
45 scan: 0.0,
46 lid: 0.0,
47 };
48 pub const ALIVE: Self = Self {
49 wobble: 0.34,
50 breathe: 0.014,
51 drift: 0.03,
52 blink: true,
53 scan: 0.0,
54 lid: 0.0,
55 };
56 pub const BUSY: Self = Self {
58 scan: 0.3,
59 ..Self::ALIVE
60 };
61 pub const ASLEEP: Self = Self {
63 wobble: 0.0,
64 breathe: 0.014,
65 drift: 0.0,
66 blink: false,
67 scan: 0.0,
68 lid: 1.0,
69 };
70
71 pub fn reach(&self) -> f32 {
73 self.drift + self.scan
74 }
75
76 pub fn sweep(&self, t: f32) -> f32 {
78 self.scan * (2.0 * PI * t / SCAN_EVERY).sin()
79 }
80
81 pub fn shape(&self, mut shape: Shape, t: f32) -> Shape {
84 if self.wobble == 0.0 {
85 return shape;
86 }
87 for l in &mut shape.lobes {
88 l.phase += t * self.wobble * (0.7 + 0.13 * l.k as f32);
89 }
90 shape
91 }
92
93 pub fn beat(&self, t: f32) -> Beat {
95 let blink = if self.blink {
96 let p = (t / BLINK_EVERY).fract() * BLINK_EVERY;
97 if p < BLINK_FOR {
98 (PI * p / BLINK_FOR).sin()
99 } else {
100 0.0
101 }
102 } else {
103 0.0
104 };
105 let lid = blink.max(self.lid.clamp(0.0, 1.0));
106 Beat {
107 scale: 1.0 + self.breathe * (0.9 * t).sin(),
108 gaze: (
109 self.drift * (0.6 * (0.31 * t).sin() + 0.4 * (0.17 * t + 1.3).sin())
110 + self.sweep(t),
111 self.drift * (0.5 * (0.23 * t + 0.7).sin() + 0.5 * (0.13 * t).sin()),
112 ),
113 lid,
114 }
115 }
116}
117
118#[derive(Clone, Copy, Debug, PartialEq)]
120pub struct Beat {
121 pub scale: f32,
122 pub gaze: (f32, f32),
123 pub lid: f32,
125}