OXYGENHUB
# 6246d1e51f4f oxygenhub / crates / backdrop / src / mark.rs
4.7 KiB · Rust · last changed feat(web): the mark, ripples, the swarm and adrift (closes #167) 23 days ago source blame history raw
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
6
use crate::field::{unit, Dot, Field};
7
use crate::scene::Scene;
8
use crate::sky::{hash, Colours};
9
use std::f32::consts::TAU;
10
11
/// Dots on the two rings.
12
const DOTS: usize = 560;
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 });
42
            let mut d = Dot::new(0.0, 0.0, 1.5 + unit(h, 40) * 1.6, if i % 11 == 0 { 1.0 } else { 0.0 });
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
64
    /// The rings' radius and centres for this view.
65
    fn rings(&self) -> (f32, [(f32, f32); 2]) {
66
        let r = (self.w.min(self.h * 1.6) * 0.17).clamp(110.0, 330.0);
67
        let (cx, cy) = (self.w / 2.0, self.h / 2.0);
68
        (r, [(cx - r, cy), (cx + r, cy)])
69
    }
70
71
    fn place(&mut self) {
72
        let (r, centres) = self.rings();
73
        let spin = RIM_SPEED / r * self.t;
74
        for (i, b) in self.beads.iter().enumerate() {
75
            // The left ring turns one way and the right the other.
76
            let a = b.angle + if b.ring == 0 { spin } else { -spin };
77
            let rr = r + b.off + 3.0 * (self.t * 0.4 + b.phase).sin();
78
            let (cx, cy) = centres[b.ring];
79
            self.field.dots[i].x = cx + a.cos() * rr;
80
            self.field.dots[i].y = cy + a.sin() * rr;
81
        }
82
        for (k, (cx, cy)) in centres.iter().enumerate() {
83
            let d = &mut self.field.dots[DOTS + k];
84
            d.x = *cx;
85
            d.y = *cy;
86
        }
87
    }
88
}
89
90
impl Scene for Mark {
91
    fn resize(&mut self, w: f32, h: f32, _avoid: Option<(f32, f32)>) {
92
        self.w = w;
93
        self.h = h;
94
        self.place();
95
    }
96
    fn point(&mut self, at: Option<(f32, f32)>) {
97
        self.field.point(at);
98
    }
99
    fn step(&mut self, dt: f32) {
100
        if !self.field.still {
101
            self.t += dt.clamp(0.0, 0.1);
102
        }
103
        self.place();
104
        self.field.light(dt);
105
    }
106
    fn instances(&self, c: &Colours, out: &mut Vec<f32>) {
107
        self.field.instances(c, out)
108
    }
109
    fn links(&self, c: &Colours, out: &mut Vec<f32>) {
110
        self.field.links(c, out)
111
    }
112
    fn label(&self) -> Option<(&str, f32, f32)> {
113
        None
114
    }
115
    fn busy(&self) -> bool {
116
        !self.field.still || self.field.easing()
117
    }
118
    fn still(&self) -> bool {
119
        self.field.still
120
    }
121
    fn size(&self) -> (f32, f32) {
122
        (self.w, self.h)
123
    }
124
}
125
126
#[cfg(test)]
127
mod tests {
128
    use super::*;
129
130
    #[test]
131
    fn two_rings_turn_opposite_ways_about_their_cores() {
132
        let mut m = Mark::new(false);
133
        m.resize(1600.0, 900.0, None);
134
        let (r, centres) = m.rings();
135
        assert!(centres[0].0 < 800.0 && centres[1].0 > 800.0);
136
        for (i, b) in m.beads.iter().enumerate() {
137
            let (cx, cy) = centres[b.ring];
138
            let d = (m.field.dots[i].x - cx).hypot(m.field.dots[i].y - cy);
139
            assert!((d - r).abs() <= 22.0, "on its ring: {d} against {r}");
140
        }
141
        let before = (m.field.dots[0].x, m.field.dots[0].y);
142
        m.step(0.1);
143
        let moved = (m.field.dots[0].x - before.0).hypot(m.field.dots[0].y - before.1);
144
        assert!(moved > 0.0 && moved < 2.0, "slow: {moved} px in a tenth of a second");
145
        assert!(m.label().is_none(), "brand only: nothing to name");
146
    }
147
}