pub const MAX_LATTICE_RINGS: f32 = 128.0;
pub const MAX_LON_DENSITY: f32 = 256.0;
pub const MAX_ORBIT_N: f32 = 64.0;
pub const MAX_GHOST_N: f32 = 512.0;
pub const MAX_PARTICLES: f32 = 16.0;
pub const MAX_NODE_N: f32 = 128.0;
pub const MAX_SIGNALS: f32 = 64.0;
pub const MAX_STRAND_N: f32 = 256.0;
pub const MAX_LANES: f32 = 32.0;
pub const MAX_SEGS: f32 = 512.0;
pub const MAX_MOVE_COUNT: f32 = 64.0;
pub const MAX_ICON_D: f32 = 8.0;
pub const MAX_MORPH_DOTS: usize = 512;
pub const MIN_SIZE: f32 = 1.0;
pub const MAX_SIZE: f32 = 1024.0;
#[derive(Clone, Debug, Default)]
#[non_exhaustive]
pub struct ModeOpts {
pub lat_rings: Option<f32>,
pub lon_density: Option<f32>,
pub rings: Option<f32>,
pub r_base: Option<f32>,
pub r_depth: Option<f32>,
pub r_boost: Option<f32>,
pub r_active: Option<f32>,
pub ink_far: Option<f32>,
pub ink_span: Option<f32>,
pub rs_pow: Option<f32>,
pub r_min: Option<f32>,
pub move_count: Option<f32>,
pub scan_mul: Option<f32>,
pub dim_base: Option<f32>,
pub orbit_n: Option<f32>,
pub ghost_n: Option<f32>,
pub ghost_r: Option<f32>,
pub ghost_a: Option<f32>,
pub particles: Option<f32>,
pub part_r: Option<f32>,
pub part_r_depth: Option<f32>,
pub node_n: Option<f32>,
pub thr: Option<f32>,
pub signals: Option<f32>,
pub node_r: Option<f32>,
pub node_r_depth: Option<f32>,
pub line_w: Option<f32>,
pub strand_n: Option<f32>,
pub turns: Option<f32>,
pub lanes: Option<f32>,
pub segs: Option<f32>,
pub face_on: Option<f32>,
pub spin: Option<f32>,
pub band_mul: Option<f32>,
pub wob_mul: Option<f32>,
pub r_dot: Option<f32>,
pub icon_d: Option<f32>,
pub spread: Option<f32>,
pub r_size_mul: Option<f32>,
}
fn clamp_opt(v: Option<f32>, min: f32, max: f32) -> Option<f32> {
v.and_then(|x| {
if x.is_finite() {
Some(x.clamp(min, max))
} else {
None
}
})
}
pub fn sanitize_mode_opts(opts: &ModeOpts) -> ModeOpts {
ModeOpts {
lat_rings: clamp_opt(opts.lat_rings, 1.0, MAX_LATTICE_RINGS),
lon_density: clamp_opt(opts.lon_density, 1.0, MAX_LON_DENSITY),
rings: clamp_opt(opts.rings, 1.0, MAX_LATTICE_RINGS),
r_base: clamp_opt(opts.r_base, 0.0, 64.0),
r_depth: clamp_opt(opts.r_depth, 0.0, 64.0),
r_boost: clamp_opt(opts.r_boost, 0.0, 64.0),
r_active: clamp_opt(opts.r_active, 0.0, 64.0),
ink_far: clamp_opt(opts.ink_far, 0.0, 1.0),
ink_span: clamp_opt(opts.ink_span, 0.0, 2.0),
rs_pow: clamp_opt(opts.rs_pow, 0.05, 4.0),
r_min: clamp_opt(opts.r_min, 0.0, 32.0),
move_count: clamp_opt(opts.move_count, 0.0, MAX_MOVE_COUNT),
scan_mul: clamp_opt(opts.scan_mul, 0.0, 16.0),
dim_base: clamp_opt(opts.dim_base, 0.0, 1.0),
orbit_n: clamp_opt(opts.orbit_n, 0.0, MAX_ORBIT_N),
ghost_n: clamp_opt(opts.ghost_n, 0.0, MAX_GHOST_N),
ghost_r: clamp_opt(opts.ghost_r, 0.0, 64.0),
ghost_a: clamp_opt(opts.ghost_a, 0.0, 1.0),
particles: clamp_opt(opts.particles, 0.0, MAX_PARTICLES),
part_r: clamp_opt(opts.part_r, 0.0, 64.0),
part_r_depth: clamp_opt(opts.part_r_depth, 0.0, 64.0),
node_n: clamp_opt(opts.node_n, 0.0, MAX_NODE_N),
thr: clamp_opt(opts.thr, 0.0, 4.0),
signals: clamp_opt(opts.signals, 0.0, MAX_SIGNALS),
node_r: clamp_opt(opts.node_r, 0.0, 64.0),
node_r_depth: clamp_opt(opts.node_r_depth, 0.0, 64.0),
line_w: clamp_opt(opts.line_w, 0.0, 32.0),
strand_n: clamp_opt(opts.strand_n, 0.0, MAX_STRAND_N),
turns: clamp_opt(opts.turns, 0.0, 32.0),
lanes: clamp_opt(opts.lanes, 1.0, MAX_LANES),
segs: clamp_opt(opts.segs, 1.0, MAX_SEGS),
face_on: clamp_opt(opts.face_on, 0.0, 1.0),
spin: clamp_opt(opts.spin, -16.0, 16.0),
band_mul: clamp_opt(opts.band_mul, 0.05, 8.0),
wob_mul: clamp_opt(opts.wob_mul, 0.0, 8.0),
r_dot: clamp_opt(opts.r_dot, 0.0, 1.0),
icon_d: clamp_opt(opts.icon_d, 0.02, MAX_ICON_D),
spread: clamp_opt(opts.spread, 0.05, 4.0),
r_size_mul: clamp_opt(opts.r_size_mul, 0.0, 64.0),
}
}
pub fn sanitize_size(size: f32) -> f32 {
if size.is_finite() {
size.clamp(MIN_SIZE, MAX_SIZE)
} else {
64.0
}
}
#[inline]
pub fn count_usize(v: Option<f32>, default: f32, min: usize, max: usize) -> usize {
let x = match v {
Some(x) if x.is_finite() => x,
_ => default,
};
let lo = min as f32;
let hi = max as f32;
(x.round().clamp(lo, hi)) as usize
}
pub fn scale_counts(opts: &ModeOpts, scale: f32) -> ModeOpts {
let mut out = opts.clone();
let scale = if scale.is_finite() && scale > 0.0 {
scale
} else {
1.0
};
let rt = scale.sqrt();
if let (Some(va), Some(vb)) = (out.lat_rings, out.lon_density) {
out.lat_rings = Some((va * rt).round().clamp(2.0, MAX_LATTICE_RINGS));
out.lon_density = Some((vb * rt).round().clamp(2.0, MAX_LON_DENSITY));
} else if let (Some(va), Some(vb)) = (out.rings, out.lon_density) {
out.rings = Some((va * rt).round().clamp(2.0, MAX_LATTICE_RINGS));
out.lon_density = Some((vb * rt).round().clamp(2.0, MAX_LON_DENSITY));
}
if let (Some(va), Some(vb)) = (out.lanes, out.segs) {
out.lanes = Some((va * rt).round().clamp(2.0, MAX_LANES));
out.segs = Some((vb * rt).round().clamp(2.0, MAX_SEGS));
}
let scale_key = |v: Option<f32>, max: f32| match v {
Some(0.0) => Some(0.0),
Some(v) if v.is_finite() => Some((v * scale).round().clamp(1.0, max)),
Some(_) => None,
None => None,
};
out.orbit_n = scale_key(out.orbit_n, MAX_ORBIT_N);
out.ghost_n = scale_key(out.ghost_n, MAX_GHOST_N);
out.node_n = scale_key(out.node_n, MAX_NODE_N);
out.strand_n = scale_key(out.strand_n, MAX_STRAND_N);
out.signals = scale_key(out.signals, MAX_SIGNALS);
if let Some(v) = out.icon_d {
if v.is_finite() {
out.icon_d = Some((v * scale).clamp(0.02, MAX_ICON_D));
} else {
out.icon_d = None;
}
}
out
}
pub fn scale_radii(opts: &ModeOpts, scale: f32) -> ModeOpts {
let mut out = opts.clone();
let scale = if scale.is_finite() { scale } else { 1.0 };
let mul = |v: Option<f32>| v.and_then(|x| x.is_finite().then_some(x * scale));
out.r_base = mul(out.r_base);
out.r_depth = mul(out.r_depth);
out.r_active = mul(out.r_active);
out.r_dot = mul(out.r_dot);
out.ghost_r = mul(out.ghost_r);
out.part_r = mul(out.part_r);
out.part_r_depth = mul(out.part_r_depth);
out.node_r = mul(out.node_r);
out.node_r_depth = mul(out.node_r_depth);
out.r_size_mul = Some(out.r_size_mul.unwrap_or(1.0) * scale);
out
}
impl ModeOpts {
pub fn fill(mode: crate::orbs::types::ModeKey, f: impl FnOnce(&mut Self)) -> Self {
let mut opts = base_profile(mode);
f(&mut opts);
opts
}
}
pub fn base_profile(mode: crate::orbs::types::ModeKey) -> ModeOpts {
use crate::orbs::types::ModeKey::*;
match mode {
Globe => ModeOpts {
lat_rings: Some(17.0),
lon_density: Some(44.0),
r_base: Some(0.6),
r_depth: Some(1.7),
r_boost: Some(1.0),
ink_far: Some(0.62),
ink_span: Some(0.54),
rs_pow: Some(0.6),
r_min: Some(0.3),
..Default::default()
},
Orbits => ModeOpts {
orbit_n: Some(12.0),
ghost_n: Some(40.0),
ghost_r: Some(0.9),
ghost_a: Some(0.5),
particles: Some(3.0),
part_r: Some(1.2),
part_r_depth: Some(1.6),
rs_pow: Some(0.6),
r_min: Some(0.3),
..Default::default()
},
Rubik => ModeOpts {
lat_rings: Some(15.0),
lon_density: Some(40.0),
move_count: Some(14.0),
r_base: Some(0.6),
r_depth: Some(1.7),
r_active: Some(0.3),
ink_far: Some(0.62),
ink_span: Some(0.54),
rs_pow: Some(0.6),
r_min: Some(0.3),
..Default::default()
},
Wave => ModeOpts {
rings: Some(15.0),
lon_density: Some(40.0),
r_base: Some(0.6),
r_depth: Some(1.7),
rs_pow: Some(0.6),
r_min: Some(0.3),
..Default::default()
},
Web => ModeOpts {
node_n: Some(30.0),
thr: Some(0.72),
signals: Some(5.0),
node_r: Some(1.4),
node_r_depth: Some(1.8),
line_w: Some(0.8),
rs_pow: Some(0.6),
r_min: Some(0.3),
..Default::default()
},
Braid => ModeOpts {
strand_n: Some(52.0),
turns: Some(3.0),
ghost_n: Some(150.0),
r_base: Some(1.2),
r_depth: Some(1.8),
rs_pow: Some(0.6),
r_min: Some(0.3),
..Default::default()
},
Ribbon => ModeOpts {
lanes: Some(5.0),
segs: Some(88.0),
ghost_n: Some(150.0),
r_base: Some(1.1),
r_depth: Some(1.7),
rs_pow: Some(0.6),
r_min: Some(0.3),
..Default::default()
},
Ring => ModeOpts {
lanes: Some(5.0),
segs: Some(88.0),
ghost_n: Some(0.0),
face_on: Some(1.0),
r_base: Some(1.1),
r_depth: Some(1.7),
rs_pow: Some(0.6),
r_min: Some(0.3),
..Default::default()
},
Morph => ModeOpts {
r_dot: Some(0.021),
icon_d: Some(1.0),
r_min: Some(0.25),
..Default::default()
},
Focus => ModeOpts {
lanes: Some(6.0),
segs: Some(12.0),
particles: Some(5.0),
r_base: Some(0.9),
r_depth: Some(1.25),
rs_pow: Some(0.6),
r_min: Some(0.3),
..Default::default()
},
Gyroscope => ModeOpts {
lanes: Some(3.0),
segs: Some(28.0),
r_base: Some(0.8),
r_depth: Some(1.5),
rs_pow: Some(0.6),
r_min: Some(0.3),
..Default::default()
},
Echo => ModeOpts {
lanes: Some(4.0),
segs: Some(18.0),
particles: Some(3.0),
r_base: Some(0.85),
r_depth: Some(1.05),
rs_pow: Some(0.6),
r_min: Some(0.3),
..Default::default()
},
}
}