refactor into modular for future
This commit is contained in:
+5
-116
@@ -25,13 +25,13 @@
|
||||
|
||||
use crate::audio::{Bands, WAVE_N};
|
||||
use crate::viz::curve::{Rng, flow};
|
||||
use crate::viz::geometry::Figure;
|
||||
use crate::viz::math::smoothstep;
|
||||
use crate::viz::palette::Palette;
|
||||
use nannou::prelude::*;
|
||||
|
||||
const FIELD: f32 = 960.0; // design-space extent (matches sigil/post)
|
||||
const N: usize = 1600; // beam samples per figure
|
||||
const KINDS: u32 = 5;
|
||||
const PARAMS: usize = 7;
|
||||
const MORPH_SECS: f32 = 0.85; // figure cross-fade time
|
||||
|
||||
/// Stateless hash -> 0..1 (ordered dither + grain; deterministic per frame).
|
||||
@@ -43,120 +43,9 @@ fn h01(a: u32, b: u32) -> f32 {
|
||||
(x >> 9) as f32 / (1u32 << 23) as f32
|
||||
}
|
||||
|
||||
fn smoothstep(t: f32) -> f32 {
|
||||
let t = t.clamp(0.0, 1.0);
|
||||
t * t * (3.0 - 2.0 * t)
|
||||
}
|
||||
|
||||
/// One figure: a kind tag + its numeric parameters. Sampled into a Vec3 path.
|
||||
#[derive(Clone, Copy)]
|
||||
struct Figure {
|
||||
kind: u32,
|
||||
p: [f32; PARAMS],
|
||||
}
|
||||
|
||||
impl Figure {
|
||||
/// Seed a fresh figure. Ratios/petals are small integers so the curves
|
||||
/// close cleanly (the oscilloscope-art look); free exponents add variety.
|
||||
fn random(rng: &mut Rng) -> Self {
|
||||
let kind = (rng.idx(KINDS as usize)) as u32;
|
||||
let mut p = [0.0f32; PARAMS];
|
||||
match kind {
|
||||
// torus knot (p,q): coprime-ish small ints, tube radius
|
||||
0 => {
|
||||
p[0] = (2 + rng.idx(6)) as f32;
|
||||
p[1] = (1 + rng.idx(7)) as f32;
|
||||
p[2] = rng.range(0.25, 0.6);
|
||||
p[3] = rng.range(2.0, 4.0); // winds (path loops)
|
||||
}
|
||||
// 3D supershape (Gielis): two superformulas, spherical product
|
||||
1 => {
|
||||
p[0] = (rng.idx(12) as f32) + 1.0; // m
|
||||
p[1] = rng.range(0.3, 3.0); // n1
|
||||
p[2] = rng.range(0.3, 4.0); // n2
|
||||
p[3] = rng.range(0.3, 4.0); // n3
|
||||
p[4] = (1 + rng.idx(8)) as f32; // surface-spiral turns
|
||||
}
|
||||
// 3D Lissajous: integer freqs + phase offsets
|
||||
2 => {
|
||||
p[0] = (1 + rng.idx(7)) as f32;
|
||||
p[1] = (1 + rng.idx(7)) as f32;
|
||||
p[2] = (1 + rng.idx(7)) as f32;
|
||||
p[3] = rng.range(0.0, std::f32::consts::TAU);
|
||||
p[4] = rng.range(0.0, std::f32::consts::TAU);
|
||||
}
|
||||
// harmonograph: damped sum of sinusoids
|
||||
3 => {
|
||||
for s in p.iter_mut().take(4) {
|
||||
*s = (1 + rng.idx(5)) as f32;
|
||||
}
|
||||
p[4] = rng.range(0.0, std::f32::consts::TAU);
|
||||
p[5] = rng.range(0.0, std::f32::consts::TAU);
|
||||
p[6] = rng.range(0.6, 2.4); // decay
|
||||
}
|
||||
// rose-helix: k-petal rose climbing in z
|
||||
_ => {
|
||||
p[0] = (2 + rng.idx(9)) as f32; // petals
|
||||
p[1] = rng.range(3.0, 9.0); // turns
|
||||
p[2] = rng.range(0.4, 1.1); // height
|
||||
}
|
||||
}
|
||||
Figure { kind, p }
|
||||
}
|
||||
|
||||
/// Sample at `u` in 0..1, returned roughly within a unit-ish box.
|
||||
fn at(&self, u: f32) -> Vec3 {
|
||||
let tau = std::f32::consts::TAU;
|
||||
let p = &self.p;
|
||||
match self.kind {
|
||||
0 => {
|
||||
let t = tau * p[3].max(1.0) * u;
|
||||
let (pn, qn) = (p[0], p[1]);
|
||||
let r = 1.0 + p[2] * (qn * t).cos();
|
||||
vec3(r * (pn * t).cos(), r * (pn * t).sin(), p[2] * (qn * t).sin()) * 0.85
|
||||
}
|
||||
1 => {
|
||||
let sf = |ang: f32, m: f32, n1: f32, n2: f32, n3: f32| -> f32 {
|
||||
let a = ((m * ang / 4.0).cos().abs()).powf(n2);
|
||||
let b = ((m * ang / 4.0).sin().abs()).powf(n3);
|
||||
(a + b).powf(-1.0 / n1.max(0.05)).min(3.0)
|
||||
};
|
||||
// wind a spiral over the supershape surface
|
||||
let lon = (u * p[4].max(1.0) * tau).rem_euclid(tau) - std::f32::consts::PI;
|
||||
let lat = (u - 0.5) * std::f32::consts::PI;
|
||||
let r1 = sf(lon, p[0], p[1], p[2], p[3]);
|
||||
let r2 = sf(lat, p[0], p[1], p[2], p[3]);
|
||||
vec3(
|
||||
r1 * lon.cos() * r2 * lat.cos(),
|
||||
r1 * lon.sin() * r2 * lat.cos(),
|
||||
r2 * lat.sin(),
|
||||
) * 0.7
|
||||
}
|
||||
2 => {
|
||||
let t = tau * u;
|
||||
vec3(
|
||||
(p[0] * t + p[3]).sin(),
|
||||
(p[1] * t + p[4]).sin(),
|
||||
(p[2] * t).sin(),
|
||||
)
|
||||
}
|
||||
3 => {
|
||||
let t = u * tau * 4.0;
|
||||
let d = (-p[6] * u).exp();
|
||||
vec3(
|
||||
d * ((p[0] * t).sin() + 0.6 * (p[2] * t + p[4]).sin()),
|
||||
d * ((p[1] * t + p[5]).sin() + 0.6 * (p[3] * t).sin()),
|
||||
d * (0.5 * ((p[0] + p[1]) * 0.5 * t).sin()),
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
let th = u * tau * p[1].max(1.0);
|
||||
let r = (p[0] * th).cos();
|
||||
vec3(r * th.cos(), r * th.sin(), p[2] * (u - 0.5) * 2.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// `Figure` (torus knot / Gielis supershape / 3D Lissajous / harmonograph /
|
||||
// rose-helix) is shared geometry — see `crate::viz::geometry` (imported
|
||||
// above). The scope only seeds (`Figure::random`) and samples (`.at`) it.
|
||||
|
||||
pub struct Scope {
|
||||
pub seed: u64,
|
||||
|
||||
Reference in New Issue
Block a user