OXYGENHUB
◆ v0.5.0 oxygenhub / crates / backdrop / src / adrift.rs
4.1 KiB · Rust · last changed Live arrivals on the sky 22 days ago source blame history raw
1
//! Adrift (docs/backgrounds.md §2): the not-found and server-error pages.
2
//! A quiet cluster in the margin, and one mark drifting away from it, as
3
//! the page drifted from what was asked for. It drifts for a minute, fades
4
//! and sets out again. No data.
5
6
use crate::field::{knot, margins, unit, Dot, Field, Orbit, ZOOM};
7
use crate::scene::Scene;
8
use crate::sky::{hash, Colours};
9
10
const CLUSTER: usize = 160;
11
/// The lost mark's speed, and how long one drift lasts, fade included.
12
const DRIFT_SPEED: f32 = 7.0;
13
const DRIFT_S: f32 = 70.0;
14
const FADE_S: f32 = 5.0;
15
16
pub struct Adrift {
17
    field: Field,
18
    orbits: Vec<Orbit>,
19
    centre: (f32, f32),
20
    w: f32,
21
    h: f32,
22
    t: f32,
23
}
24
25
impl Adrift {
26
    pub fn new(still: bool) -> Adrift {
27
        let mut field = Field::new(still);
28
        let orbits = knot("adrift", CLUSTER);
29
        for k in 0..CLUSTER {
30
            let h = hash(&format!("adrift@{k}"));
31
            let mut d = Dot::new(0.0, 0.0, 2.0 * ZOOM, if k % 7 == 0 { 2.0 } else { 0.0 });
32
            d.bright = 0.5 + 0.5 * unit(h, 48);
33
            d.group = 0;
34
            d.order = k as u32;
35
            field.dots.push(d);
36
        }
37
        // The cluster's core, and the lost mark, a little larger.
38
        field.dots.push(Dot::new(0.0, 0.0, 8.0, 4.0));
39
        let mut lost = Dot::new(0.0, 0.0, 3.4 * ZOOM, 0.0);
40
        lost.bright = 1.0;
41
        field.dots.push(lost);
42
        let mut a = Adrift { field, orbits, centre: (0.0, 0.0), w: 0.0, h: 0.0, t: 12.0 };
43
        a.place();
44
        a
45
    }
46
47
    fn place(&mut self) {
48
        let (cx, cy) = self.centre;
49
        for (i, o) in self.orbits.iter().enumerate() {
50
            let (x, y) = o.at((cx, cy), self.t);
51
            self.field.dots[i].x = x;
52
            self.field.dots[i].y = y;
53
        }
54
        let core = &mut self.field.dots[CLUSTER];
55
        core.x = cx;
56
        core.y = cy;
57
        // The lost mark leaves up and to the right, fading at the end of
58
        // each drift and setting out again from the cluster's edge.
59
        let p = self.t % DRIFT_S;
60
        let dir: f32 = -0.35;
61
        let from = self.orbits.iter().map(|o| o.radius).fold(0.0, f32::max) + 14.0;
62
        let lost = &mut self.field.dots[CLUSTER + 1];
63
        lost.x = cx + dir.cos() * (from + p * DRIFT_SPEED);
64
        lost.y = cy + dir.sin() * (from + p * DRIFT_SPEED);
65
        lost.bright = ((DRIFT_S - p) / FADE_S).min(p / FADE_S).clamp(0.0, 1.0);
66
    }
67
}
68
69
impl Scene for Adrift {
70
    fn resize(&mut self, w: f32, h: f32, avoid: Option<(f32, f32)>) {
71
        self.w = w;
72
        self.h = h;
73
        self.centre = match margins(w, avoid) {
74
            Some((l, _)) => (l * 0.45, h * 0.62),
75
            None => (w * 0.15, h * 0.7),
76
        };
77
        self.place();
78
    }
79
    fn point(&mut self, at: Option<(f32, f32)>) {
80
        self.field.point(at);
81
    }
82
    fn step(&mut self, dt: f32) {
83
        if !self.field.still {
84
            self.t += dt.clamp(0.0, 0.1);
85
        }
86
        self.place();
87
        self.field.light(dt);
88
    }
89
    fn instances(&self, c: &Colours, out: &mut Vec<f32>) {
90
        self.field.instances(c, out)
91
    }
92
    fn links(&self, c: &Colours, out: &mut Vec<f32>) {
93
        self.field.links(c, out)
94
    }
95
    fn label(&self) -> Option<(&str, f32, f32)> {
96
        None
97
    }
98
    fn busy(&self) -> bool {
99
        self.field.easing()
100
    }
101
    fn still(&self) -> bool {
102
        self.field.still
103
    }
104
    fn size(&self) -> (f32, f32) {
105
        (self.w, self.h)
106
    }
107
}
108
109
#[cfg(test)]
110
mod tests {
111
    use super::*;
112
113
    #[test]
114
    fn one_mark_drifts_away_from_the_cluster_and_sets_out_again() {
115
        let mut a = Adrift::new(false);
116
        a.resize(2560.0, 1270.0, Some((707.0, 1838.0)));
117
        assert!(a.centre.0 < 707.0, "the cluster sits in the margin");
118
        let dist = |a: &Adrift| {
119
            let l = &a.field.dots[CLUSTER + 1];
120
            (l.x - a.centre.0).hypot(l.y - a.centre.1)
121
        };
122
        let early = dist(&a);
123
        for _ in 0..200 {
124
            a.step(0.1);
125
        }
126
        assert!(dist(&a) > early + 100.0, "it drifts away");
127
        for _ in 0..500 {
128
            a.step(0.1);
129
        }
130
        assert!(dist(&a) < early + 100.0, "and sets out again");
131
    }
132
}