1use std::collections::HashMap;
13
14use glam::{Mat4, Quat, Vec3};
15
16#[derive(Debug, Clone, Copy, PartialEq)]
23pub struct Instance {
24 pub model: Mat4,
25 pub color: [f32; 4],
26}
27
28const INTERP_DELAY_MS: u64 = 100;
30const MAX_EXTRAP_MS: u64 = 250;
32const MAX_BUFFER: usize = 16;
34
35#[derive(Debug, Clone, Copy)]
38pub struct PoseSample {
39 pub t_ms: u64,
40 pub pos: Vec3,
41 pub rot: Quat,
42 pub vel: Vec3,
43 pub anim: u8,
45}
46
47pub struct TravelerPose {
50 pub id: u32,
51 pub position: Vec3,
53 pub model: Mat4,
54 pub color: [f32; 4],
55 pub body: u32,
56 pub speed: f32,
57}
58
59pub struct Traveler {
61 pub id: u32,
62 pub name: String,
63 pub color: [f32; 4],
64 pub avatar: Option<infinite_avatar::AvatarSpec>,
68 buffer: Vec<PoseSample>,
69}
70
71impl Traveler {
72 fn new(id: u32, name: String, color: [f32; 4]) -> Self {
73 Self {
74 id,
75 name,
76 color,
77 avatar: None,
78 buffer: Vec::new(),
79 }
80 }
81
82 fn push(&mut self, sample: PoseSample) {
83 if self.buffer.last().is_some_and(|l| sample.t_ms < l.t_ms) {
85 return;
86 }
87 self.buffer.push(sample);
88 if self.buffer.len() > MAX_BUFFER {
89 self.buffer.remove(0);
90 }
91 }
92
93 fn sample_at(&self, render_t: u64) -> Option<(Vec3, Quat)> {
96 let first = self.buffer.first()?;
97 let last = self.buffer.last()?;
98 if render_t <= first.t_ms {
99 return Some((first.pos, first.rot));
100 }
101 if render_t >= last.t_ms {
102 let gap = (render_t - last.t_ms).min(MAX_EXTRAP_MS);
104 return Some((last.pos + last.vel * (gap as f32 / 1000.0), last.rot));
105 }
106 for pair in self.buffer.windows(2) {
108 let (a, b) = (&pair[0], &pair[1]);
109 if a.t_ms <= render_t && render_t < b.t_ms {
110 let span = (b.t_ms - a.t_ms).max(1) as f32;
111 let f = (render_t - a.t_ms) as f32 / span;
112 return Some((a.pos.lerp(b.pos, f), a.rot.slerp(b.rot, f)));
113 }
114 }
115 Some((last.pos, last.rot))
116 }
117}
118
119#[derive(Default)]
121pub struct PresenceState {
122 travelers: HashMap<u32, Traveler>,
123}
124
125impl PresenceState {
126 pub fn new() -> Self {
127 Self::default()
128 }
129
130 pub fn count(&self) -> usize {
131 self.travelers.len()
132 }
133
134 pub fn is_empty(&self) -> bool {
135 self.travelers.is_empty()
136 }
137
138 pub fn join(&mut self, id: u32, name: impl Into<String>, color: [f32; 4]) {
140 self.travelers
141 .entry(id)
142 .or_insert_with(|| Traveler::new(id, name.into(), color));
143 }
144
145 pub fn leave(&mut self, id: u32) {
147 self.travelers.remove(&id);
148 }
149
150 pub fn restyle(&mut self, id: u32, name: Option<String>, color: Option<[f32; 4]>) {
154 if let Some(t) = self.travelers.get_mut(&id) {
155 if let Some(n) = name {
156 t.name = n;
157 }
158 if let Some(c) = color {
159 t.color = c;
160 }
161 }
162 }
163
164 pub fn set_avatar(&mut self, id: u32, avatar: Option<infinite_avatar::AvatarSpec>) {
166 if let Some(t) = self.travelers.get_mut(&id) {
167 t.avatar = avatar;
168 }
169 }
170
171 pub fn pose(&mut self, id: u32, sample: PoseSample) {
173 self.travelers
174 .entry(id)
175 .or_insert_with(|| Traveler::new(id, format!("traveler {id}"), default_color(id)))
176 .push(sample);
177 }
178
179 pub fn entities(&self, now_ms: u64, foot_lift: f32) -> Vec<Instance> {
183 self.poses(now_ms, foot_lift)
184 .into_iter()
185 .map(|p| Instance { model: p.model, color: p.color })
186 .collect()
187 }
188
189 pub fn poses(&self, now_ms: u64, foot_lift: f32) -> Vec<TravelerPose> {
192 let render_t = now_ms.saturating_sub(INTERP_DELAY_MS);
193 self.travelers
194 .values()
195 .filter_map(|t| {
196 let (pos, rot) = t.sample_at(render_t)?;
197 let model =
198 Mat4::from_rotation_translation(rot, pos + Vec3::new(0.0, foot_lift, 0.0));
199 let body = t
200 .avatar
201 .as_ref()
202 .map(|s| s.get(infinite_avatar::AvatarSlot::Body))
203 .unwrap_or(0);
204 let speed = t
205 .buffer
206 .last()
207 .map(|s| Vec3::new(s.vel.x, 0.0, s.vel.z).length())
208 .unwrap_or(0.0);
209 Some(TravelerPose {
210 id: t.id,
211 position: pos,
212 model,
213 color: t.color,
214 body,
215 speed,
216 })
217 })
218 .collect()
219 }
220}
221
222fn default_color(id: u32) -> [f32; 4] {
224 let h = id.wrapping_mul(2654435761);
225 [
226 0.4 + ((h & 0xFF) as f32 / 255.0) * 0.5,
227 0.4 + (((h >> 8) & 0xFF) as f32 / 255.0) * 0.5,
228 0.4 + (((h >> 16) & 0xFF) as f32 / 255.0) * 0.5,
229 1.0,
230 ]
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236
237 fn s(t_ms: u64, pos: Vec3, vel: Vec3) -> PoseSample {
238 PoseSample {
239 t_ms,
240 pos,
241 rot: Quat::IDENTITY,
242 vel,
243 anim: 0,
244 }
245 }
246
247 #[test]
248 fn interpolates_between_poses() {
249 let mut t = Traveler::new(1, "x".into(), [1.0; 4]);
250 t.push(s(0, Vec3::ZERO, Vec3::ZERO));
251 t.push(s(100, Vec3::new(10.0, 0.0, 0.0), Vec3::ZERO));
252 let (p, _) = t.sample_at(50).unwrap();
253 assert!((p.x - 5.0).abs() < 1e-4, "midpoint lerp, got {}", p.x);
254 assert!((t.sample_at(0).unwrap().0.x - 0.0).abs() < 1e-4);
255 assert!((t.sample_at(100).unwrap().0.x - 10.0).abs() < 1e-4);
256 }
257
258 #[test]
259 fn extrapolates_along_velocity_then_freezes() {
260 let mut t = Traveler::new(1, "x".into(), [1.0; 4]);
261 t.push(s(0, Vec3::ZERO, Vec3::ZERO));
262 t.push(s(100, Vec3::ZERO, Vec3::new(10.0, 0.0, 0.0)));
263 assert!((t.sample_at(150).unwrap().0.x - 0.5).abs() < 1e-4);
265 assert!((t.sample_at(5000).unwrap().0.x - 2.5).abs() < 1e-4);
267 }
268
269 #[test]
270 fn restyle_upgrades_the_placeholder_look() {
271 let mut p = PresenceState::new();
272 p.pose(5, s(0, Vec3::ZERO, Vec3::ZERO)); p.restyle(5, Some("Anders".into()), Some([0.1, 0.2, 0.3, 1.0]));
274 let t = p.travelers.get(&5).unwrap();
275 assert_eq!(t.name, "Anders");
276 assert_eq!(t.color, [0.1, 0.2, 0.3, 1.0]);
277 p.restyle(99, Some("ghost".into()), None);
279 p.restyle(5, None, Some([1.0; 4]));
280 assert_eq!(p.travelers.get(&5).unwrap().name, "Anders");
281 assert_eq!(p.travelers.get(&5).unwrap().color, [1.0; 4]);
282 }
283
284 #[test]
285 fn produces_one_entity_per_traveler() {
286 let mut p = PresenceState::new();
287 p.pose(1, s(1000, Vec3::new(3.0, 0.0, 0.0), Vec3::ZERO));
288 p.pose(2, s(1000, Vec3::new(-3.0, 0.0, 0.0), Vec3::ZERO));
289 assert_eq!(p.count(), 2);
290 let ents = p.entities(1100, 0.9);
292 assert_eq!(ents.len(), 2);
293 p.leave(1);
294 assert_eq!(p.entities(1100, 0.9).len(), 1);
295 }
296}