1pub const MAX_LATTICE_RINGS: f32 = 128.0;
6pub const MAX_LON_DENSITY: f32 = 256.0;
7pub const MAX_ORBIT_N: f32 = 64.0;
8pub const MAX_GHOST_N: f32 = 512.0;
9pub const MAX_PARTICLES: f32 = 16.0;
10pub const MAX_NODE_N: f32 = 128.0;
11pub const MAX_SIGNALS: f32 = 64.0;
12pub const MAX_STRAND_N: f32 = 256.0;
13pub const MAX_LANES: f32 = 32.0;
14pub const MAX_SEGS: f32 = 512.0;
15pub const MAX_MOVE_COUNT: f32 = 64.0;
16pub const MAX_ICON_D: f32 = 8.0;
17pub const MAX_MORPH_DOTS: usize = 512;
19pub const MIN_SIZE: f32 = 1.0;
21pub const MAX_SIZE: f32 = 1024.0;
22
23#[derive(Clone, Debug, Default)]
44#[non_exhaustive]
45pub struct ModeOpts {
46 pub lat_rings: Option<f32>,
48 pub lon_density: Option<f32>,
49 pub rings: Option<f32>,
50 pub r_base: Option<f32>,
51 pub r_depth: Option<f32>,
52 pub r_boost: Option<f32>,
53 pub r_active: Option<f32>,
54 pub ink_far: Option<f32>,
55 pub ink_span: Option<f32>,
56 pub rs_pow: Option<f32>,
57 pub r_min: Option<f32>,
58 pub move_count: Option<f32>,
59 pub scan_mul: Option<f32>,
60 pub dim_base: Option<f32>,
61 pub orbit_n: Option<f32>,
63 pub ghost_n: Option<f32>,
64 pub ghost_r: Option<f32>,
65 pub ghost_a: Option<f32>,
66 pub particles: Option<f32>,
67 pub part_r: Option<f32>,
68 pub part_r_depth: Option<f32>,
69 pub node_n: Option<f32>,
71 pub thr: Option<f32>,
72 pub signals: Option<f32>,
73 pub node_r: Option<f32>,
74 pub node_r_depth: Option<f32>,
75 pub line_w: Option<f32>,
76 pub strand_n: Option<f32>,
78 pub turns: Option<f32>,
79 pub lanes: Option<f32>,
81 pub segs: Option<f32>,
82 pub face_on: Option<f32>,
83 pub spin: Option<f32>,
84 pub band_mul: Option<f32>,
85 pub wob_mul: Option<f32>,
86 pub r_dot: Option<f32>,
88 pub icon_d: Option<f32>,
89 pub spread: Option<f32>,
90 pub r_size_mul: Option<f32>,
91}
92
93fn clamp_opt(v: Option<f32>, min: f32, max: f32) -> Option<f32> {
95 v.and_then(|x| {
96 if x.is_finite() {
97 Some(x.clamp(min, max))
98 } else {
99 None
100 }
101 })
102}
103
104pub fn sanitize_mode_opts(opts: &ModeOpts) -> ModeOpts {
110 ModeOpts {
111 lat_rings: clamp_opt(opts.lat_rings, 1.0, MAX_LATTICE_RINGS),
112 lon_density: clamp_opt(opts.lon_density, 1.0, MAX_LON_DENSITY),
113 rings: clamp_opt(opts.rings, 1.0, MAX_LATTICE_RINGS),
114 r_base: clamp_opt(opts.r_base, 0.0, 64.0),
115 r_depth: clamp_opt(opts.r_depth, 0.0, 64.0),
116 r_boost: clamp_opt(opts.r_boost, 0.0, 64.0),
117 r_active: clamp_opt(opts.r_active, 0.0, 64.0),
118 ink_far: clamp_opt(opts.ink_far, 0.0, 1.0),
119 ink_span: clamp_opt(opts.ink_span, 0.0, 2.0),
120 rs_pow: clamp_opt(opts.rs_pow, 0.05, 4.0),
121 r_min: clamp_opt(opts.r_min, 0.0, 32.0),
122 move_count: clamp_opt(opts.move_count, 0.0, MAX_MOVE_COUNT),
123 scan_mul: clamp_opt(opts.scan_mul, 0.0, 16.0),
124 dim_base: clamp_opt(opts.dim_base, 0.0, 1.0),
125 orbit_n: clamp_opt(opts.orbit_n, 0.0, MAX_ORBIT_N),
126 ghost_n: clamp_opt(opts.ghost_n, 0.0, MAX_GHOST_N),
127 ghost_r: clamp_opt(opts.ghost_r, 0.0, 64.0),
128 ghost_a: clamp_opt(opts.ghost_a, 0.0, 1.0),
129 particles: clamp_opt(opts.particles, 0.0, MAX_PARTICLES),
130 part_r: clamp_opt(opts.part_r, 0.0, 64.0),
131 part_r_depth: clamp_opt(opts.part_r_depth, 0.0, 64.0),
132 node_n: clamp_opt(opts.node_n, 0.0, MAX_NODE_N),
133 thr: clamp_opt(opts.thr, 0.0, 4.0),
134 signals: clamp_opt(opts.signals, 0.0, MAX_SIGNALS),
135 node_r: clamp_opt(opts.node_r, 0.0, 64.0),
136 node_r_depth: clamp_opt(opts.node_r_depth, 0.0, 64.0),
137 line_w: clamp_opt(opts.line_w, 0.0, 32.0),
138 strand_n: clamp_opt(opts.strand_n, 0.0, MAX_STRAND_N),
139 turns: clamp_opt(opts.turns, 0.0, 32.0),
140 lanes: clamp_opt(opts.lanes, 1.0, MAX_LANES),
141 segs: clamp_opt(opts.segs, 1.0, MAX_SEGS),
142 face_on: clamp_opt(opts.face_on, 0.0, 1.0),
143 spin: clamp_opt(opts.spin, -16.0, 16.0),
144 band_mul: clamp_opt(opts.band_mul, 0.05, 8.0),
145 wob_mul: clamp_opt(opts.wob_mul, 0.0, 8.0),
146 r_dot: clamp_opt(opts.r_dot, 0.0, 1.0),
147 icon_d: clamp_opt(opts.icon_d, 0.02, MAX_ICON_D),
148 spread: clamp_opt(opts.spread, 0.05, 4.0),
149 r_size_mul: clamp_opt(opts.r_size_mul, 0.0, 64.0),
150 }
151}
152
153pub fn sanitize_size(size: f32) -> f32 {
155 if size.is_finite() {
156 size.clamp(MIN_SIZE, MAX_SIZE)
157 } else {
158 64.0
159 }
160}
161
162#[inline]
167pub fn count_usize(v: Option<f32>, default: f32, min: usize, max: usize) -> usize {
168 let x = match v {
169 Some(x) if x.is_finite() => x,
170 _ => default,
171 };
172 let lo = min as f32;
173 let hi = max as f32;
174 (x.round().clamp(lo, hi)) as usize
175}
176
177pub fn scale_counts(opts: &ModeOpts, scale: f32) -> ModeOpts {
180 let mut out = opts.clone();
181 let scale = if scale.is_finite() && scale > 0.0 {
182 scale
183 } else {
184 1.0
185 };
186 let rt = scale.sqrt();
187
188 if let (Some(va), Some(vb)) = (out.lat_rings, out.lon_density) {
191 out.lat_rings = Some((va * rt).round().clamp(2.0, MAX_LATTICE_RINGS));
192 out.lon_density = Some((vb * rt).round().clamp(2.0, MAX_LON_DENSITY));
193 } else if let (Some(va), Some(vb)) = (out.rings, out.lon_density) {
194 out.rings = Some((va * rt).round().clamp(2.0, MAX_LATTICE_RINGS));
195 out.lon_density = Some((vb * rt).round().clamp(2.0, MAX_LON_DENSITY));
196 }
197 if let (Some(va), Some(vb)) = (out.lanes, out.segs) {
199 out.lanes = Some((va * rt).round().clamp(2.0, MAX_LANES));
200 out.segs = Some((vb * rt).round().clamp(2.0, MAX_SEGS));
201 }
202
203 let scale_key = |v: Option<f32>, max: f32| match v {
205 Some(0.0) => Some(0.0),
206 Some(v) if v.is_finite() => Some((v * scale).round().clamp(1.0, max)),
207 Some(_) => None,
208 None => None,
209 };
210 out.orbit_n = scale_key(out.orbit_n, MAX_ORBIT_N);
211 out.ghost_n = scale_key(out.ghost_n, MAX_GHOST_N);
212 out.node_n = scale_key(out.node_n, MAX_NODE_N);
213 out.strand_n = scale_key(out.strand_n, MAX_STRAND_N);
214 out.signals = scale_key(out.signals, MAX_SIGNALS);
215
216 if let Some(v) = out.icon_d {
217 if v.is_finite() {
218 out.icon_d = Some((v * scale).clamp(0.02, MAX_ICON_D));
219 } else {
220 out.icon_d = None;
221 }
222 }
223 out
224}
225
226pub fn scale_radii(opts: &ModeOpts, scale: f32) -> ModeOpts {
228 let mut out = opts.clone();
229 let scale = if scale.is_finite() { scale } else { 1.0 };
230 let mul = |v: Option<f32>| v.and_then(|x| x.is_finite().then_some(x * scale));
231 out.r_base = mul(out.r_base);
232 out.r_depth = mul(out.r_depth);
233 out.r_active = mul(out.r_active);
234 out.r_dot = mul(out.r_dot);
235 out.ghost_r = mul(out.ghost_r);
236 out.part_r = mul(out.part_r);
237 out.part_r_depth = mul(out.part_r_depth);
238 out.node_r = mul(out.node_r);
239 out.node_r_depth = mul(out.node_r_depth);
240 out.r_size_mul = Some(out.r_size_mul.unwrap_or(1.0) * scale);
241 out
242}
243
244impl ModeOpts {
245 pub fn fill(mode: crate::orbs::types::ModeKey, f: impl FnOnce(&mut Self)) -> Self {
250 let mut opts = base_profile(mode);
251 f(&mut opts);
252 opts
253 }
254}
255
256pub fn base_profile(mode: crate::orbs::types::ModeKey) -> ModeOpts {
258 use crate::orbs::types::ModeKey::*;
259 match mode {
260 Globe => ModeOpts {
261 lat_rings: Some(17.0),
262 lon_density: Some(44.0),
263 r_base: Some(0.6),
264 r_depth: Some(1.7),
265 r_boost: Some(1.0),
266 ink_far: Some(0.62),
267 ink_span: Some(0.54),
268 rs_pow: Some(0.6),
269 r_min: Some(0.3),
270 ..Default::default()
271 },
272 Orbits => ModeOpts {
273 orbit_n: Some(12.0),
274 ghost_n: Some(40.0),
275 ghost_r: Some(0.9),
276 ghost_a: Some(0.5),
277 particles: Some(3.0),
278 part_r: Some(1.2),
279 part_r_depth: Some(1.6),
280 rs_pow: Some(0.6),
281 r_min: Some(0.3),
282 ..Default::default()
283 },
284 Rubik => ModeOpts {
285 lat_rings: Some(15.0),
286 lon_density: Some(40.0),
287 move_count: Some(14.0),
288 r_base: Some(0.6),
289 r_depth: Some(1.7),
290 r_active: Some(0.3),
291 ink_far: Some(0.62),
292 ink_span: Some(0.54),
293 rs_pow: Some(0.6),
294 r_min: Some(0.3),
295 ..Default::default()
296 },
297 Wave => ModeOpts {
298 rings: Some(15.0),
299 lon_density: Some(40.0),
300 r_base: Some(0.6),
301 r_depth: Some(1.7),
302 rs_pow: Some(0.6),
303 r_min: Some(0.3),
304 ..Default::default()
305 },
306 Web => ModeOpts {
307 node_n: Some(30.0),
308 thr: Some(0.72),
309 signals: Some(5.0),
310 node_r: Some(1.4),
311 node_r_depth: Some(1.8),
312 line_w: Some(0.8),
313 rs_pow: Some(0.6),
314 r_min: Some(0.3),
315 ..Default::default()
316 },
317 Braid => ModeOpts {
318 strand_n: Some(52.0),
319 turns: Some(3.0),
320 ghost_n: Some(150.0),
321 r_base: Some(1.2),
322 r_depth: Some(1.8),
323 rs_pow: Some(0.6),
324 r_min: Some(0.3),
325 ..Default::default()
326 },
327 Ribbon => ModeOpts {
328 lanes: Some(5.0),
329 segs: Some(88.0),
330 ghost_n: Some(150.0),
331 r_base: Some(1.1),
332 r_depth: Some(1.7),
333 rs_pow: Some(0.6),
334 r_min: Some(0.3),
335 ..Default::default()
336 },
337 Ring => ModeOpts {
338 lanes: Some(5.0),
339 segs: Some(88.0),
340 ghost_n: Some(0.0),
341 face_on: Some(1.0),
342 r_base: Some(1.1),
343 r_depth: Some(1.7),
344 rs_pow: Some(0.6),
345 r_min: Some(0.3),
346 ..Default::default()
347 },
348 Morph => ModeOpts {
349 r_dot: Some(0.021),
350 icon_d: Some(1.0),
351 r_min: Some(0.25),
352 ..Default::default()
353 },
354 Focus => ModeOpts {
355 lanes: Some(6.0),
356 segs: Some(12.0),
357 particles: Some(5.0),
358 r_base: Some(0.9),
359 r_depth: Some(1.25),
360 rs_pow: Some(0.6),
361 r_min: Some(0.3),
362 ..Default::default()
363 },
364 Gyroscope => ModeOpts {
365 lanes: Some(3.0),
366 segs: Some(28.0),
367 r_base: Some(0.8),
368 r_depth: Some(1.5),
369 rs_pow: Some(0.6),
370 r_min: Some(0.3),
371 ..Default::default()
372 },
373 Echo => ModeOpts {
374 lanes: Some(4.0),
375 segs: Some(18.0),
376 particles: Some(3.0),
377 r_base: Some(0.85),
378 r_depth: Some(1.05),
379 rs_pow: Some(0.6),
380 r_min: Some(0.3),
381 ..Default::default()
382 },
383 }
384}