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