1use std::{
5 f32::consts::{FRAC_PI_2, FRAC_PI_4, PI, TAU},
6 sync::atomic::{AtomicU64, Ordering},
7};
8use web_time::{SystemTime, UNIX_EPOCH};
9
10pub const SAMPLES: usize = 64;
13
14const FLOOR: f32 = 0.45;
17
18#[derive(Clone, Copy, Debug, Default, PartialEq)]
20pub struct Lobe {
21 pub k: u8,
22 pub amp: f32,
23 pub phase: f32,
24}
25
26const NONE: Lobe = Lobe {
27 k: 0,
28 amp: 0.0,
29 phase: 0.0,
30};
31
32const fn lobe(k: u8, amp: f32, phase: f32) -> Lobe {
33 Lobe { k, amp, phase }
34}
35
36#[derive(Clone, Copy, Debug, PartialEq)]
37pub struct Shape {
38 pub lobes: [Lobe; 3],
39 pub sides: u8,
41 pub corner: f32,
43 pub stretch: f32,
45 pub taper: f32,
47 pub rot: f32,
48}
49
50impl Default for Shape {
51 fn default() -> Self {
52 Self::ROUND
53 }
54}
55
56impl Shape {
57 pub const ROUND: Self = Self {
58 lobes: [NONE; 3],
59 sides: 0,
60 corner: 0.0,
61 stretch: 1.0,
62 taper: 0.0,
63 rot: 0.0,
64 };
65 pub const EGG: Self = Self {
66 stretch: 0.93,
67 taper: 0.2,
68 ..Self::ROUND
69 };
70 pub const BEAN: Self = Self {
71 lobes: [lobe(2, 0.13, 0.9), NONE, NONE],
72 stretch: 1.1,
73 rot: 0.5,
74 ..Self::ROUND
75 };
76 pub const DROP: Self = Self {
77 stretch: 0.92,
78 taper: 0.44,
79 ..Self::ROUND
80 };
81 pub const BLOB: Self = Self {
82 lobes: [lobe(3, 0.09, 0.6), lobe(5, 0.035, 2.1), NONE],
83 rot: 0.4,
84 ..Self::ROUND
85 };
86 pub const CLOUD: Self = Self {
87 lobes: [lobe(5, 0.12, 1.2), lobe(2, 0.05, 0.3), NONE],
88 stretch: 1.1,
89 ..Self::ROUND
90 };
91 pub const TILE: Self = Self {
94 sides: 4,
95 corner: 0.82,
96 rot: FRAC_PI_4,
97 ..Self::ROUND
98 };
99 pub const GEM: Self = Self {
100 sides: 6,
101 corner: 0.72,
102 rot: 0.26,
103 ..Self::ROUND
104 };
105 pub const SHARD: Self = Self {
106 sides: 3,
107 corner: 0.62,
108 stretch: 1.04,
109 ..Self::ROUND
110 };
111 pub const SUN: Self = Self {
112 lobes: [lobe(8, 0.11, 0.0), NONE, NONE],
113 ..Self::ROUND
114 };
115
116 pub const PRESETS: [(&'static str, Self); 10] = [
118 ("round", Self::ROUND),
119 ("egg", Self::EGG),
120 ("bean", Self::BEAN),
121 ("blob", Self::BLOB),
122 ("cloud", Self::CLOUD),
123 ("drop", Self::DROP),
124 ("tile", Self::TILE),
125 ("gem", Self::GEM),
126 ("shard", Self::SHARD),
127 ("sun", Self::SUN),
128 ];
129
130 pub fn random() -> Self {
132 static COUNT: AtomicU64 = AtomicU64::new(0);
133 let nanos = SystemTime::now()
134 .duration_since(UNIX_EPOCH)
135 .map(|d| d.as_nanos() as u64)
136 .unwrap_or(0);
137 Self::from(nanos ^ COUNT.fetch_add(0x9e37_79b9_7f4a_7c15, Ordering::Relaxed))
138 }
139
140 pub fn radius(&self, theta: f32) -> f32 {
142 let a = theta + self.rot;
143 let mut r = 1.0;
144 if self.sides >= 3 {
145 let seg = TAU / self.sides as f32;
148 let phi = ((a + FRAC_PI_2) % seg + seg) % seg - seg / 2.0;
149 r += ((seg / 2.0).cos() / phi.cos() - 1.0) * self.corner;
150 }
151 for l in &self.lobes {
152 if l.k >= 2 {
153 r += l.amp * (l.k as f32 * a + l.phase).cos();
154 }
155 }
156 let up = (1.0 - theta.sin()) * 0.5;
160 r += self.taper * up.powi(6);
161 r.max(FLOOR)
162 }
163
164 pub fn outline(&self) -> [(f32, f32); SAMPLES] {
166 std::array::from_fn(|i| {
167 let a = TAU * i as f32 / SAMPLES as f32;
168 let r = self.radius(a);
169 (r * a.cos() * self.stretch, r * a.sin() / self.stretch)
170 })
171 }
172
173 pub fn reach(&self, x: f32, y: f32) -> f32 {
176 let a = y.atan2(x);
177 let r = self.radius(a);
178 (r * a.cos() * self.stretch).hypot(r * a.sin() / self.stretch)
179 }
180}
181
182struct Rng(u64);
185
186impl Rng {
187 fn next(&mut self) -> u64 {
188 self.0 = self.0.wrapping_add(0x9e37_79b9_7f4a_7c15);
189 let mut z = self.0;
190 z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
191 z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
192 z ^ (z >> 31)
193 }
194
195 fn unit(&mut self) -> f32 {
196 (self.next() >> 40) as f32 / 16_777_216.0
197 }
198
199 fn range(&mut self, lo: f32, hi: f32) -> f32 {
200 lo + self.unit() * (hi - lo)
201 }
202
203 fn int(&mut self, lo: u8, hi: u8) -> u8 {
204 lo + (self.unit() * (hi - lo + 1) as f32) as u8
205 }
206}
207
208impl From<u64> for Shape {
209 fn from(seed: u64) -> Self {
210 let mut rng = Rng(seed);
211 let sides = if rng.unit() < 0.34 { rng.int(3, 8) } else { 0 };
212 let corner = if sides >= 3 { rng.range(0.2, 0.8) } else { 0.0 };
213
214 let waist = if sides >= 3 {
217 1.0 + corner * ((PI / sides as f32).cos() - 1.0)
218 } else {
219 1.0
220 };
221 let mut budget = (waist - FLOOR).clamp(0.0, 0.26);
222
223 let count = rng.int(1, 3);
224 let lobes = std::array::from_fn(|i| {
225 if i as u8 >= count || budget <= 0.01 {
226 return Lobe::default();
227 }
228 let k = 2 + (rng.unit().powi(2) * 7.0) as u8;
231 let amp = budget * rng.range(0.35, 0.9) * (3.0 / k as f32).min(1.0);
234 budget -= amp;
235 lobe(k, amp, rng.range(0.0, TAU))
236 });
237
238 Self {
239 lobes,
240 sides,
241 corner,
242 stretch: rng.range(0.88, 1.14),
243 taper: if rng.unit() < 0.28 {
244 rng.range(0.08, 0.3)
245 } else {
246 0.0
247 },
248 rot: rng.range(0.0, TAU),
249 }
250 }
251}
252
253impl From<&str> for Shape {
254 fn from(name: &str) -> Self {
255 Self::from(seed(name))
256 }
257}
258
259pub fn seed(name: &str) -> u64 {
268 let feed = |h: u64, b: u8| (h ^ b as u64).wrapping_mul(0x0000_0100_0000_01b3);
269 let mut h: u64 = 0xcbf2_9ce4_8422_2325;
270 let (mut started, mut gap) = (false, false);
271 let mut buf = [0u8; 4];
272 for c in name.chars().flat_map(char::to_lowercase) {
273 if c.is_whitespace() {
274 gap = started;
276 continue;
277 }
278 if gap {
279 h = feed(h, b' ');
280 gap = false;
281 }
282 for b in c.encode_utf8(&mut buf).as_bytes() {
283 h = feed(h, *b);
284 }
285 started = true;
286 }
287 h
288}