wheel/
wheel.rs

1use std::sync::Arc;
2use wassily::prelude::*;
3const WIDTH: u32 = 1080;
4const HEIGHT: u32 = 1080;
5
6fn main() {
7    let mut canvas = Canvas::new(WIDTH, HEIGHT);
8    let mut rng = SmallRng::seed_from_u64(23);
9    let mut color_wheel = ColorWheel::new(&mut rng, 7, 3);
10    for i in 0..WIDTH {
11        for j in 0..HEIGHT {
12            let x = i as f32 / WIDTH as f32;
13            let y = j as f32 / HEIGHT as f32;
14            let c = color_wheel.get_color(&mut rng, x, y);
15            canvas.dot(i as f32, j as f32, c);
16        }
17    }
18    canvas.save_png("./wheel4.png");
19}
20
21#[derive(Clone)]
22pub struct ColorWheel {
23    pub octaves: usize,
24    pub base_color: [f32; 3],
25    pub phases: Vec<[f32; 3]>,
26    pub frequencies: [f32; 7],
27    pub noise: Arc<Fbm<Perlin>>,
28    scale: f32,
29}
30
31impl ColorWheel {
32    pub fn new<R: Rng>(rng: &mut R, octaves: usize, noise_octaves: usize) -> Self {
33        assert!(octaves <= 7, "Maximum ocatves is 7");
34        let mut phases = ColorWheel::PHASES;
35        phases.shuffle(rng);
36        let phases: Vec<[f32; 3]> = phases.into_iter().map(|v| Self::shuffle3(rng, v)).collect();
37        let noise = Fbm::<Perlin>::default();
38        let seed: u32 = rng.gen();
39        let noise = noise.set_seed(seed).set_octaves(noise_octaves);
40        let x: f32 = 0.5 + rng.gen_range(-0.1..0.1);
41        let y: f32 = 0.3 + rng.gen_range(-0.1..0.1);
42        let z: f32 = 0.4 + rng.gen_range(-0.1..0.1);
43        let base_color = [x, y, z];
44        let mut scale: f32 = Self::AMPLITUDES[0..octaves].iter().sum();
45        scale += 0.4;
46        Self {
47            octaves,
48            phases,
49            frequencies: Self::FREQUENCIES,
50            noise: Arc::new(noise),
51            base_color,
52            scale,
53        }
54    }
55
56    const AMPLITUDES: [f32; 7] = [0.12, 0.11, 0.1, 0.09, 0.08, 0.07, 0.06];
57
58    const PHASES: [[f32; 3]; 9] = [
59        [0.0, 0.8, 1.0],
60        [0.3, 0.4, 0.1],
61        [0.1, 0.7, 1.1],
62        [0.2, 0.8, 1.4],
63        [0.2, 0.6, 0.7],
64        [0.1, 0.6, 0.7],
65        [0.0, 0.5, 0.8],
66        [0.1, 0.4, 0.7],
67        [1.1, 1.4, 2.7],
68    ];
69
70    const FREQUENCIES: [f32; 7] = [1.0, 3.1, 5.1, 9.1, 17.1, 31.1, 65.1];
71
72    fn shuffle3<R: Rng>(rng: &mut R, p: [f32; 3]) -> [f32; 3] {
73        let i: usize = rng.gen_range(0..=2);
74        let j = (i + rng.gen_range(1..=2)) % 3;
75        let x = p[i];
76        let y = p[j];
77        let z = p[3 - i - j];
78        [x, y, z]
79    }
80
81    fn term(&self, t: f32, amplitude: f32, freq: f32, phases: [f32; 3]) -> [f32; 3] {
82        phases.map(|p| amplitude * (TAU * t * freq + p).cos())
83    }
84
85    pub fn get_color<R: Rng>(&mut self, rng: &mut R, x: f32, y: f32) -> Color {
86        let mut rgb = self.base_color;
87        for i in 0..self.octaves {
88            let t = 0.5
89                + 0.5 * self.noise.get([3.0 * x as f64, 3.0 * y as f64]) as f32
90                + 0.02 * (rng.gen_range(0.0..1.0) + rng.gen_range(0.0..1.0));
91            let a = self.term(t, Self::AMPLITUDES[i], Self::FREQUENCIES[i], self.phases[i]);
92            for j in 0..3 {
93                rgb[j] += a[j];
94            }
95        }
96        Color::from_rgba(
97            (rgb[0] / self.scale).clamp(0.0, 1.0),
98            (rgb[1] / self.scale).clamp(0.2, 0.8),
99            (rgb[2] / self.scale).clamp(0.0, 1.0),
100            1.0,
101        )
102        .unwrap()
103    }
104}