OXYGENHUB
# fac6853c4e46 oxygenhub / crates / backdrop / src / mark.rs
4.9 KiB · Rust · last changed Live arrivals on the sky 22 days ago source blame history raw
6246d1e5 Andy McCall ↶ 1
//! The mark (docs/backgrounds.md §2): sign in, register and device sign-in.
2
//! The Oxygen mark's two rings drawn in dots, turning opposite ways so the
3
//! stream runs through the middle like the mark's infinity, each with its
4
//! core. Brand only: no data.
5
f993f792 Andy McCall ↶ 6
use crate::field::{unit, Dot, Field, ZOOM};
6246d1e5 Andy McCall ↶ 7
use crate::scene::Scene;
8
use crate::sky::{hash, Colours};
9
use std::f32::consts::TAU;
10
11
/// Dots on the two rings.
5501b1c6 Andy McCall ↶ 12
const DOTS: usize = 1400;
6246d1e5 Andy McCall ↶ 13
/// How fast the rings turn, at their rim, in CSS pixels a second.
14
const RIM_SPEED: f32 = 9.0;
15
16
struct Bead {
17
    ring: usize,
18
    angle: f32,
19
    /// Its distance off the ring's line, and its breathing's phase.
20
    off: f32,
21
    phase: f32,
22
}
23
24
pub struct Mark {
25
    field: Field,
26
    beads: Vec<Bead>,
27
    w: f32,
28
    h: f32,
29
    t: f32,
30
}
31
32
impl Mark {
33
    pub fn new(still: bool) -> Mark {
34
        let mut field = Field::new(still);
35
        let mut beads = Vec::new();
36
        for i in 0..DOTS {
37
            let h = hash(&format!("mark#{i}"));
38
            let ring = i % 2;
39
            // Off the ring's line by a bell-ish spread, most near it.
40
            let off = (unit(h, 16) + unit(h, 32) - 1.0) * 18.0;
41
            beads.push(Bead { ring, angle: unit(h, 0) * TAU, off, phase: unit(h, 48) * TAU });
f993f792 Andy McCall ↶ 42
            let mut d = Dot::new(0.0, 0.0, (1.5 + unit(h, 40) * 1.6) * ZOOM, if i % 11 == 0 { 1.0 } else { 0.0 });
6246d1e5 Andy McCall ↶ 43
            d.bright = 0.55 + 0.45 * unit(h, 24);
44
            d.group = ring as u32;
45
            field.dots.push(d);
46
        }
47
        // Join each ring's beads in the order round it.
48
        for ring in 0..2 {
49
            let mut idx: Vec<usize> = (0..DOTS).filter(|&i| beads[i].ring == ring).collect();
50
            idx.sort_by(|&a, &b| beads[a].angle.total_cmp(&beads[b].angle));
51
            for (k, i) in idx.into_iter().enumerate() {
52
                field.dots[i].order = k as u32;
53
            }
54
        }
55
        // The two cores.
56
        for _ in 0..2 {
57
            field.dots.push(Dot::new(0.0, 0.0, 7.0, 0.0));
58
        }
59
        let mut m = Mark { field, beads, w: 0.0, h: 0.0, t: 0.0 };
60
        m.place();
61
        m
62
    }
63
5501b1c6 Andy McCall ↶ 64
    /// The rings' radius and centres for this view: large enough to frame
65
    /// the page, out into its margins, their centres 1.1 radii either side
66
    /// of the middle, as the mark draws them.
6246d1e5 Andy McCall ↶ 67
    fn rings(&self) -> (f32, [(f32, f32); 2]) {
5501b1c6 Andy McCall ↶ 68
        let r = (self.w * 0.2).min(self.h * 0.42).clamp(110.0, 560.0);
6246d1e5 Andy McCall ↶ 69
        let (cx, cy) = (self.w / 2.0, self.h / 2.0);
5501b1c6 Andy McCall ↶ 70
        (r, [(cx - r * 1.1, cy), (cx + r * 1.1, cy)])
6246d1e5 Andy McCall ↶ 71
    }
72
73
    fn place(&mut self) {
74
        let (r, centres) = self.rings();
75
        let spin = RIM_SPEED / r * self.t;
76
        for (i, b) in self.beads.iter().enumerate() {
77
            // The left ring turns one way and the right the other.
78
            let a = b.angle + if b.ring == 0 { spin } else { -spin };
79
            let rr = r + b.off + 3.0 * (self.t * 0.4 + b.phase).sin();
80
            let (cx, cy) = centres[b.ring];
81
            self.field.dots[i].x = cx + a.cos() * rr;
82
            self.field.dots[i].y = cy + a.sin() * rr;
83
        }
84
        for (k, (cx, cy)) in centres.iter().enumerate() {
85
            let d = &mut self.field.dots[DOTS + k];
86
            d.x = *cx;
87
            d.y = *cy;
88
        }
89
    }
90
}
91
92
impl Scene for Mark {
93
    fn resize(&mut self, w: f32, h: f32, _avoid: Option<(f32, f32)>) {
94
        self.w = w;
95
        self.h = h;
96
        self.place();
97
    }
98
    fn point(&mut self, at: Option<(f32, f32)>) {
99
        self.field.point(at);
100
    }
101
    fn step(&mut self, dt: f32) {
102
        if !self.field.still {
103
            self.t += dt.clamp(0.0, 0.1);
104
        }
105
        self.place();
106
        self.field.light(dt);
107
    }
108
    fn instances(&self, c: &Colours, out: &mut Vec<f32>) {
109
        self.field.instances(c, out)
110
    }
111
    fn links(&self, c: &Colours, out: &mut Vec<f32>) {
112
        self.field.links(c, out)
113
    }
114
    fn label(&self) -> Option<(&str, f32, f32)> {
115
        None
116
    }
117
    fn busy(&self) -> bool {
da8f7826 Andy McCall ↶ 118
        self.field.easing()
6246d1e5 Andy McCall ↶ 119
    }
120
    fn still(&self) -> bool {
121
        self.field.still
122
    }
123
    fn size(&self) -> (f32, f32) {
124
        (self.w, self.h)
125
    }
126
}
127
128
#[cfg(test)]
129
mod tests {
130
    use super::*;
131
132
    #[test]
133
    fn two_rings_turn_opposite_ways_about_their_cores() {
134
        let mut m = Mark::new(false);
135
        m.resize(1600.0, 900.0, None);
136
        let (r, centres) = m.rings();
137
        assert!(centres[0].0 < 800.0 && centres[1].0 > 800.0);
138
        for (i, b) in m.beads.iter().enumerate() {
139
            let (cx, cy) = centres[b.ring];
140
            let d = (m.field.dots[i].x - cx).hypot(m.field.dots[i].y - cy);
141
            assert!((d - r).abs() <= 22.0, "on its ring: {d} against {r}");
142
        }
143
        let before = (m.field.dots[0].x, m.field.dots[0].y);
144
        m.step(0.1);
145
        let moved = (m.field.dots[0].x - before.0).hypot(m.field.dots[0].y - before.1);
146
        assert!(moved > 0.0 && moved < 2.0, "slow: {moved} px in a tenth of a second");
147
        assert!(m.label().is_none(), "brand only: nothing to name");
148
    }
149
}