Repository navigation
Expand file tree
/
Copy pathbehavior.rs
More file actions
204 lines (186 loc) · 7.89 KB
/
Copy pathbehavior.rs
File metadata and controls
204 lines (186 loc) · 7.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
//! Procedural behaviors a host switches on: blinking, breathing, gaze toward a target and lip sync.
//! Each drives the parameters of a role the rig declares (EyeBlink, Breath, AngleX, ..., LipSync), on
//! top of whatever clips set, before physics runs.
use crate::rig::Rig;
pub const BLINK: u32 = 1;
pub const BREATH: u32 = 2;
pub const LOOK: u32 = 4;
pub const LIP_SYNC: u32 = 8;
/// A sway on one role: offset and peak as fractions of the range (half range for the angles), and the
/// cycle in seconds.
const SWAYS: [(&str, f32, f32, f32); 5] = [
("AngleX", 0.0, 0.15, 6.5345),
("AngleY", 0.0, 0.08, 3.5345),
("AngleZ", 0.0, 0.10, 5.5345),
("BodyAngleX", 0.0, 0.04, 15.5345),
("Breath", 0.5, 0.5, 3.2345),
];
/// How far gaze turns each role, as a fraction of the parameter's half range; AngleZ follows x times y.
const LOOKS: [(&str, f32, f32); 5] = [("AngleX", 1.0, 0.0), ("AngleY", 0.0, 1.0), ("BodyAngleX", 0.33, 0.0), ("EyeBallX", 1.0, 0.0), ("EyeBallY", 0.0, 1.0)];
/// How the behaviors move: blink timing in seconds and how strongly breathing and gaze move the head.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Settings {
/// The time between blinks, drawn evenly between these.
pub blink_interval: (f32, f32),
/// How long the eyes take to close, stay closed and open again.
pub blink_closing: f32,
pub blink_closed: f32,
pub blink_opening: f32,
/// Multiplies the breathing sways (1 as they are).
pub sway: f32,
/// Multiplies how far gaze turns the head, body and eyes.
pub look: f32,
/// Multiplies how quickly gaze follows its target.
pub look_speed: f32,
}
impl Default for Settings {
fn default() -> Self {
Settings { blink_interval: (2.0, 6.0), blink_closing: CLOSING, blink_closed: CLOSED, blink_opening: OPENING, sway: 1.0, look: 1.0, look_speed: 1.0 }
}
}
#[derive(Debug, Clone)]
pub struct Behaviors {
pub enabled: u32,
pub settings: Settings,
time: f32,
blink_at: f32,
blink_start: Option<f32>,
seed: u32,
look: (f32, f32),
look_velocity: (f32, f32),
target: (f32, f32),
lip: f32,
}
impl Default for Behaviors {
fn default() -> Self {
Behaviors { enabled: BLINK | BREATH, settings: Settings::default(), time: 0.0, blink_at: 2.0, blink_start: None, seed: 0x2545_f491, look: (0.0, 0.0), look_velocity: (0.0, 0.0), target: (0.0, 0.0), lip: 0.0 }
}
}
const CLOSING: f32 = 0.1;
const CLOSED: f32 = 0.05;
const OPENING: f32 = 0.15;
impl Behaviors {
/// Reseeds the blink timing, so rigs side by side do not blink together; the next blink comes in 2..6
/// seconds. Zero takes the default seed.
pub fn seed(&mut self, seed: u32) {
self.seed = if seed == 0 { Behaviors::default().seed } else { seed };
if self.blink_start.is_none() {
self.blink_at = self.next_blink();
}
}
/// When the next blink comes, from now.
fn next_blink(&mut self) -> f32 {
let (min, max) = self.settings.blink_interval;
self.time + min + self.random() * (max - min)
}
/// Sets the mouth opening from a block of audio [samples] (-1..1): their root mean square times [gain],
/// clamped to 0..1; returns it.
pub fn lip_sync_samples(&mut self, samples: &[f32], gain: f32) -> f32 {
let finite = samples.iter().filter(|s| s.is_finite());
let (sum, n) = finite.fold((0.0f64, 0usize), |(sum, n), s| (sum + (*s as f64) * (*s as f64), n + 1));
let rms = if n == 0 { 0.0 } else { (sum / n as f64).sqrt() as f32 };
self.lip_sync(rms * if gain.is_finite() { gain } else { 1.0 });
self.lip
}
/// Where to look, each axis -1..1 (x right, y up).
pub fn look_at(&mut self, x: f32, y: f32) {
self.target = (x.clamp(-1.0, 1.0), y.clamp(-1.0, 1.0));
}
/// The mouth opening 0..1, from the host's audio.
pub fn lip_sync(&mut self, level: f32) {
self.lip = if level.is_finite() { level.clamp(0.0, 1.0) } else { 0.0 };
}
fn random(&mut self) -> f32 {
self.seed ^= self.seed << 13;
self.seed ^= self.seed >> 17;
self.seed ^= self.seed << 5;
(self.seed >> 8) as f32 / (1u32 << 24) as f32
}
/// How open the eyes are now, 0..1.
fn eyes(&mut self) -> f32 {
if self.blink_start.is_none() && self.time >= self.blink_at {
self.blink_start = Some(self.time);
}
let Some(start) = self.blink_start else { return 1.0 };
let t = self.time - start;
let (closing, closed, opening) = (self.settings.blink_closing, self.settings.blink_closed, self.settings.blink_opening);
if t < closing {
1.0 - t / closing
} else if t < closing + closed {
0.0
} else if t < closing + closed + opening {
(t - closing - closed) / opening
} else {
self.blink_start = None;
// The next blink in 2..6 seconds unless the settings say otherwise.
self.blink_at = self.next_blink();
1.0
}
}
pub fn update(&mut self, rig: &Rig, dt: f32, values: &mut [f32]) {
let dt = if dt.is_finite() { dt.clamp(0.0, 1.0) } else { 0.0 };
self.time += dt;
fn roles<'a>(rig: &'a Rig, name: &'a str) -> impl Iterator<Item = usize> + 'a {
rig.roles.iter().filter(move |r| r.role == name).flat_map(|r| r.parameters.iter().copied())
}
if self.enabled & BLINK != 0 {
let open = self.eyes();
for p in roles(rig, "EyeBlink") {
values[p] *= open;
}
}
if self.enabled & BREATH != 0 {
for (role, offset, peak, cycle) in SWAYS {
let wave = (self.time * std::f32::consts::TAU / cycle).sin() * self.settings.sway;
for p in roles(rig, role) {
let param = &rig.parameters[p];
let span = param.max - param.min;
if role == "Breath" {
// Breathing sets its parameter across the range; the sways add to the pose.
values[p] = param.min + (offset + peak * wave) * span;
} else {
values[p] += (offset + peak * wave) * span * 0.5;
}
}
}
}
if self.enabled & LOOK != 0 {
// A critically damped follow, settling in about a third of a second.
let k = 120.0f32 * self.settings.look_speed * self.settings.look_speed;
let d = 2.0 * k.sqrt();
for (pos, vel, target) in [(&mut self.look.0, &mut self.look_velocity.0, self.target.0), (&mut self.look.1, &mut self.look_velocity.1, self.target.1)] {
let mut remaining = dt;
while remaining > 0.0 {
let h = remaining.min(1.0 / 120.0);
*vel += ((target - *pos) * k - *vel * d) * h;
*pos += *vel * h;
remaining -= h;
}
}
let (x, y) = (self.look.0 * self.settings.look, self.look.1 * self.settings.look);
for (role, gx, gy) in LOOKS {
for p in roles(rig, role) {
values[p] += half_range(rig, p, gx * x + gy * y);
}
}
for p in roles(rig, "AngleZ") {
values[p] += half_range(rig, p, -x * y);
}
}
if self.enabled & LIP_SYNC != 0 {
for p in roles(rig, "LipSync") {
let param = &rig.parameters[p];
values[p] = values[p].max(param.min + (param.max - param.min) * self.lip);
}
}
}
}
/// [fraction] of the way from the default toward the parameter's end in that direction.
fn half_range(rig: &Rig, p: usize, fraction: f32) -> f32 {
let param = &rig.parameters[p];
if fraction >= 0.0 {
(param.max - param.default) * fraction
} else {
(param.default - param.min) * fraction
}
}