//! Adrift (docs/backgrounds.md ยง2): the not-found and server-error pages. //! A quiet cluster in the margin, and one mark drifting away from it, as //! the page drifted from what was asked for. It drifts for a minute, fades //! and sets out again. No data. use crate::field::{knot, margins, unit, Dot, Field, Orbit, ZOOM}; use crate::scene::Scene; use crate::sky::{hash, Colours}; const CLUSTER: usize = 160; /// The lost mark's speed, and how long one drift lasts, fade included. const DRIFT_SPEED: f32 = 7.0; const DRIFT_S: f32 = 70.0; const FADE_S: f32 = 5.0; pub struct Adrift { field: Field, orbits: Vec, centre: (f32, f32), w: f32, h: f32, t: f32, } impl Adrift { pub fn new(still: bool) -> Adrift { let mut field = Field::new(still); let orbits = knot("adrift", CLUSTER); for k in 0..CLUSTER { let h = hash(&format!("adrift@{k}")); let mut d = Dot::new(0.0, 0.0, 2.0 * ZOOM, if k % 7 == 0 { 2.0 } else { 0.0 }); d.bright = 0.5 + 0.5 * unit(h, 48); d.group = 0; d.order = k as u32; field.dots.push(d); } // The cluster's core, and the lost mark, a little larger. field.dots.push(Dot::new(0.0, 0.0, 8.0, 4.0)); let mut lost = Dot::new(0.0, 0.0, 3.4 * ZOOM, 0.0); lost.bright = 1.0; field.dots.push(lost); let mut a = Adrift { field, orbits, centre: (0.0, 0.0), w: 0.0, h: 0.0, t: 12.0 }; a.place(); a } fn place(&mut self) { let (cx, cy) = self.centre; for (i, o) in self.orbits.iter().enumerate() { let (x, y) = o.at((cx, cy), self.t); self.field.dots[i].x = x; self.field.dots[i].y = y; } let core = &mut self.field.dots[CLUSTER]; core.x = cx; core.y = cy; // The lost mark leaves up and to the right, fading at the end of // each drift and setting out again from the cluster's edge. let p = self.t % DRIFT_S; let dir: f32 = -0.35; let from = self.orbits.iter().map(|o| o.radius).fold(0.0, f32::max) + 14.0; let lost = &mut self.field.dots[CLUSTER + 1]; lost.x = cx + dir.cos() * (from + p * DRIFT_SPEED); lost.y = cy + dir.sin() * (from + p * DRIFT_SPEED); lost.bright = ((DRIFT_S - p) / FADE_S).min(p / FADE_S).clamp(0.0, 1.0); } } impl Scene for Adrift { fn resize(&mut self, w: f32, h: f32, avoid: Option<(f32, f32)>) { self.w = w; self.h = h; self.centre = match margins(w, avoid) { Some((l, _)) => (l * 0.45, h * 0.62), None => (w * 0.15, h * 0.7), }; self.place(); } fn point(&mut self, at: Option<(f32, f32)>) { self.field.point(at); } fn step(&mut self, dt: f32) { if !self.field.still { self.t += dt.clamp(0.0, 0.1); } self.place(); self.field.light(dt); } fn instances(&self, c: &Colours, out: &mut Vec) { self.field.instances(c, out) } fn links(&self, c: &Colours, out: &mut Vec) { self.field.links(c, out) } fn label(&self) -> Option<(&str, f32, f32)> { None } fn busy(&self) -> bool { self.field.easing() } fn still(&self) -> bool { self.field.still } fn size(&self) -> (f32, f32) { (self.w, self.h) } } #[cfg(test)] mod tests { use super::*; #[test] fn one_mark_drifts_away_from_the_cluster_and_sets_out_again() { let mut a = Adrift::new(false); a.resize(2560.0, 1270.0, Some((707.0, 1838.0))); assert!(a.centre.0 < 707.0, "the cluster sits in the margin"); let dist = |a: &Adrift| { let l = &a.field.dots[CLUSTER + 1]; (l.x - a.centre.0).hypot(l.y - a.centre.1) }; let early = dist(&a); for _ in 0..200 { a.step(0.1); } assert!(dist(&a) > early + 100.0, "it drifts away"); for _ in 0..500 { a.step(0.1); } assert!(dist(&a) < early + 100.0, "and sets out again"); } }