blinksy/patterns/
noise.rs

1//! # Noise Patterns
2//!
3//! The noise pattern creates flowing animations based on a noise function.
4//!
5//! # What is a noise function?
6//!
7//! A noise function is given a position in 1d, 2d, 3d, or 4d space and returns
8//! a random value between -1.0 and 1.0, where values between nearbly positions are
9//! smoothly interpolated.
10//!
11//! For example, a common use of noise functions is to procedurally generate terrain.
12//! You could give a 2d noise function an (x, y) position and use the resulting value
13//! as an elevation.
14//!
15//! In our case, we will use noise functions to generate `hue` and `value` for [Okhsv]
16//! colors. To animate through time, rather than adding time to our position, we will
17//! input the time to the noise function as an additonal dimension. So a 1d layout will
18//! use a 2d noise function, a 2d layout a 3d noise function, and so on.
19//!
20//! This pattern is the same concept as what you see on [mikey.nz](https://mikey.nz/).
21//!
22//! ## Example
23//!
24//! ```rust,ignore
25//! use blinksy::{
26//!     ControlBuilder,
27//!     layout2d,
28//!     layout::{Layout2d, Shape2d, Vec2},
29//!     patterns::noise::{Noise2d, noise_fns, NoiseParams}
30//! };
31//!
32//! // Define a 2D layout
33//! layout2d!(
34//!     Layout,
35//!     [Shape2d::Grid {
36//!         start: Vec2::new(-1., -1.),
37//!         horizontal_end: Vec2::new(1., -1.),
38//!         vertical_end: Vec2::new(-1., 1.),
39//!         horizontal_pixel_count: 16,
40//!         vertical_pixel_count: 16,
41//!         serpentine: true,
42//!     }]
43//! );
44//!
45//! // Create a 2D noise pattern with Perlin noise
46//! let control = ControlBuilder::new_2d()
47//!     .with_layout::<Layout, { Layout::PIXEL_COUNT }>()
48//!     .with_pattern::<Noise2d<noise_fns::Perlin>>(NoiseParams {
49//!         time_scalar: 0.001,
50//!         position_scalar: 0.1,
51//!     })
52//!     .with_driver(/* Your driver */)
53//!     .with_frame_buffer_size::</* Length of frame buffer */>()
54//!     .build();
55//! ```
56//!
57//! [`Okhsv`]: crate::color::Okhsv
58//! [mikey.nz]: https://mikey.nz
59
60use noise_functions::{modifiers::Seeded, Noise as NoiseTrait, Sample};
61
62use crate::{
63    color::Okhsv,
64    layout::{Layout1d, Layout2d, Layout3d},
65    markers::{Dim1d, Dim2d, Dim3d},
66    pattern::Pattern,
67};
68
69/// Re-exports of noise functions from the noise crate.
70pub mod noise_fns {
71    pub use noise_functions::{OpenSimplex2, Perlin, Simplex};
72}
73
74/// Configuration parameters for noise patterns.
75#[derive(Debug)]
76#[cfg_attr(feature = "defmt", derive(defmt::Format))]
77pub struct NoiseParams {
78    /// Controls the speed of animation (higher = faster)
79    pub time_scalar: f32,
80    /// Controls the spatial scale of the noise (higher = more compressed)
81    pub position_scalar: f32,
82}
83
84impl Default for NoiseParams {
85    fn default() -> Self {
86        const MILLISECONDS_PER_SECOND: f32 = 1e3;
87        Self {
88            time_scalar: 0.75 / MILLISECONDS_PER_SECOND,
89            position_scalar: 0.5,
90        }
91    }
92}
93
94/// One-dimensional noise pattern implementation.
95///
96/// Creates flowing animations based on a 2D noise function, using
97/// time and the 1D position for the input coordinates.
98#[derive(Debug)]
99#[cfg_attr(feature = "defmt", derive(defmt::Format))]
100pub struct Noise1d<Noise> {
101    /// The noise function used to get hue
102    hue_noise: Seeded<Noise>,
103    /// The noise function used to get value
104    value_noise: Seeded<Noise>,
105    /// Configuration parameters
106    params: NoiseParams,
107}
108
109impl<Layout, Noise> Pattern<Dim1d, Layout> for Noise1d<Noise>
110where
111    Layout: Layout1d,
112    Noise: NoiseTrait + Sample<2> + Default,
113{
114    type Params = NoiseParams;
115    type Color = Okhsv;
116
117    /// Creates a new Noise1d pattern with the specified parameters.
118    fn new(params: Self::Params) -> Self {
119        Self {
120            hue_noise: Noise::default().seed(0),
121            value_noise: Noise::default().seed(1),
122            params,
123        }
124    }
125
126    /// Generates colors for a 1D layout using noise.
127    ///
128    /// The pattern uses the LED position and time as inputs to a 2D noise function,
129    /// mapping the noise value to a hue in the Okhsv color space.
130    fn tick(&self, time_in_ms: u64) -> impl Iterator<Item = Self::Color> {
131        let Self {
132            hue_noise,
133            value_noise,
134            params,
135        } = self;
136
137        let NoiseParams {
138            time_scalar,
139            position_scalar,
140        } = params;
141
142        let noise_time = time_in_ms as f32 * time_scalar;
143
144        Layout::points().map(move |x| {
145            let noise_args = [position_scalar * x, noise_time];
146            let hue = hue_noise.sample2(noise_args);
147            let saturation = 1.;
148            let value = 0.75 + 0.25 * value_noise.sample2(noise_args);
149            Okhsv::new(hue, saturation, value)
150        })
151    }
152}
153
154/// Two-dimensional noise pattern implementation.
155///
156/// Creates flowing animations based on a 3D noise function, using
157/// time and the 2D position for the input coordinates.
158#[derive(Debug)]
159#[cfg_attr(feature = "defmt", derive(defmt::Format))]
160pub struct Noise2d<Noise> {
161    /// The noise function used to get hue
162    hue_noise: Seeded<Noise>,
163    /// The noise function used to get value
164    value_noise: Seeded<Noise>,
165    /// Configuration parameters
166    params: NoiseParams,
167}
168
169impl<Layout, Noise> Pattern<Dim2d, Layout> for Noise2d<Noise>
170where
171    Layout: Layout2d,
172    Noise: NoiseTrait + Sample<3> + Default,
173{
174    type Params = NoiseParams;
175    type Color = Okhsv;
176
177    /// Creates a new Noise2d pattern with the specified parameters.
178    fn new(params: Self::Params) -> Self {
179        Self {
180            hue_noise: Noise::default().seed(0),
181            value_noise: Noise::default().seed(1),
182            params,
183        }
184    }
185
186    /// Generates colors for a 2D layout using noise.
187    ///
188    /// The pattern uses the LED x,y position and time as inputs to a 3D noise function,
189    /// mapping the noise value to a hue in the Okhsv color space.
190    fn tick(&self, time_in_ms: u64) -> impl Iterator<Item = Self::Color> {
191        let Self {
192            hue_noise,
193            value_noise,
194            params,
195        } = self;
196
197        let NoiseParams {
198            time_scalar,
199            position_scalar,
200        } = params;
201
202        let noise_time = time_in_ms as f32 * time_scalar;
203
204        Layout::points().map(move |point| {
205            let noise_args = [
206                position_scalar * point.x,
207                position_scalar * point.y,
208                noise_time,
209            ];
210            let hue = hue_noise.sample3(noise_args);
211            let saturation = 1.;
212            let value = 0.75 + 0.25 * value_noise.sample3(noise_args);
213            Okhsv::new(hue, saturation, value)
214        })
215    }
216}
217
218/// Three-dimensional noise pattern implementation.
219///
220/// Creates flowing animations based on a 4D noise function, using
221/// time and the 3D position for the input coordinates.
222#[derive(Debug)]
223#[cfg_attr(feature = "defmt", derive(defmt::Format))]
224pub struct Noise3d<Noise>
225where
226    Noise: NoiseTrait,
227{
228    /// The noise function used to get hue
229    hue_noise: Seeded<Noise>,
230    /// The noise function used to get value
231    value_noise: Seeded<Noise>,
232    /// Configuration parameters
233    params: NoiseParams,
234}
235
236impl<Layout, Noise> Pattern<Dim3d, Layout> for Noise3d<Noise>
237where
238    Layout: Layout3d,
239    Noise: NoiseTrait + Sample<4> + Default,
240{
241    type Params = NoiseParams;
242    type Color = Okhsv;
243
244    /// Creates a new Noise2d pattern with the specified parameters.
245    fn new(params: Self::Params) -> Self {
246        Self {
247            hue_noise: Noise::default().seed(0),
248            value_noise: Noise::default().seed(1),
249            params,
250        }
251    }
252
253    /// Generates colors for a 3D layout using noise.
254    ///
255    /// The pattern uses the LED x,y,z position and time as inputs to a 4D noise function,
256    /// mapping the noise value to a hue in the HSV color space.
257    fn tick(&self, time_in_ms: u64) -> impl Iterator<Item = Self::Color> {
258        let Self {
259            hue_noise,
260            value_noise,
261            params,
262        } = self;
263
264        let NoiseParams {
265            time_scalar,
266            position_scalar,
267        } = params;
268
269        let noise_time = time_in_ms as f32 * time_scalar;
270
271        Layout::points().map(move |point| {
272            let noise_args = [
273                position_scalar * point.x,
274                position_scalar * point.y,
275                position_scalar * point.z,
276                noise_time,
277            ];
278            let hue = hue_noise.sample4(noise_args);
279            let saturation = 1.;
280            let value = 0.75 + 0.25 * value_noise.sample4(noise_args);
281            Okhsv::new(hue, saturation, value)
282        })
283    }
284}