| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
use crate::field::{unit, Dot, Field, ZOOM}; |
| 7 |
use crate::scene::Scene; |
| 8 |
use crate::sky::{hash, Colours}; |
| 9 |
use std::f32::consts::TAU; |
| 10 |
|
| 11 |
|
| 12 |
const DOTS: usize = 1400; |
| 13 |
|
| 14 |
const RIM_SPEED: f32 = 9.0; |
| 15 |
|
| 16 |
struct Bead { |
| 17 |
ring: usize, |
| 18 |
angle: f32, |
| 19 |
|
| 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 |
|
| 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) * ZOOM, 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 |
|
| 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 |
|
| 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 |
|
| 65 |
|
| 66 |
|
| 67 |
fn rings(&self) -> (f32, [(f32, f32); 2]) { |
| 68 |
let r = (self.w * 0.2).min(self.h * 0.42).clamp(110.0, 560.0); |
| 69 |
let (cx, cy) = (self.w / 2.0, self.h / 2.0); |
| 70 |
(r, [(cx - r * 1.1, cy), (cx + r * 1.1, cy)]) |
| 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 |
|
| 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 { |
| 118 |
self.field.easing() |
| 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 |
} |