Last active
November 29, 2020 15:16
-
-
Save paulgb/30be364651b1974916c56aa9bf425d27 to your computer and use it in GitHub Desktop.
moire plot
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
mod plot; | |
mod noise; | |
use plot::{Plot, CoordinatePair, PointString}; | |
use std::f64::consts::PI; | |
use noise::NoiseMaker; | |
fn generate_circle(center: CoordinatePair, radius: f64, divisions: usize) -> PointString { | |
let mut points = Vec::with_capacity(divisions); | |
for i in 0..=divisions { | |
let theta = 2. * PI * (i as f64 / divisions as f64); | |
let v = CoordinatePair::new(theta.cos(), theta.sin()) * radius + center; | |
points.push(v); | |
} | |
points | |
} | |
fn v(x: f64, y: f64) -> CoordinatePair { | |
CoordinatePair::new(x, y) | |
} | |
fn main() { | |
let mut plot = Plot::new(); | |
/* | |
for i in 4..100 { | |
plot.add_path( | |
generate_circle(v(50. + 4. * i as f64, 50. + 4. * i as f64), 40., i) | |
); | |
} | |
*/ | |
let noise_maker = NoiseMaker::new(51.); | |
for i in 1..300 { | |
let mut p = Vec::new(); | |
for j in 1..300 { | |
let noise = noise_maker.noise((i as f64 + 0.5) / 50., (j as f64 + 5.) / 50.); | |
p.push(CoordinatePair::new(j as f64 * 3., i as f64 * 3. + noise * 5.)); | |
} | |
plot.add_path(p); | |
} | |
plot.save("output/plot.svg"); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! Deterministic implementation of two dimensional | |
//! [Perlin Noise](https://en.wikipedia.org/wiki/Perlin_noise). | |
//! Allows noise to optionally be looped in the x and y | |
//! directions, or both, for creating smooth cyclic patterns. | |
use crate::plot::{CoordinatePair}; | |
use std::f64::consts::PI; | |
use cgmath::dot; | |
/// Return a pseudo-random value from a given float. | |
pub fn pseudo_random(seed: f64) -> f64 { | |
(seed.sin() * 1e10).fract() | |
} | |
/// Perlin noise generator. Control points are constructed on an | |
/// integer grids, and samples can be taken at arbitrary resolution | |
/// between them. | |
pub struct NoiseMaker { | |
// Periodicity of noise. | |
x_period: Option<usize>, | |
y_period: Option<usize>, | |
// Seeds. | |
x_seed: f64, | |
y_seed: f64, | |
} | |
impl NoiseMaker { | |
/// Construct a new noise generator with an initial seed. | |
pub fn new(seed: f64) -> NoiseMaker { | |
let x_seed = pseudo_random(seed) * 1e6; | |
let y_seed = pseudo_random(seed + 1.) * 1e6; | |
NoiseMaker { | |
x_period: None, | |
y_period: None, | |
x_seed, | |
y_seed, | |
} | |
} | |
/// Set the x period of repetition. | |
pub fn x_period(&mut self, x_period: usize) -> &mut NoiseMaker { | |
self.x_period = Some(x_period); | |
self | |
} | |
/// Set the y period of repetition. | |
pub fn y_period(&mut self, y_period: usize) -> &mut NoiseMaker { | |
self.y_period = Some(y_period); | |
self | |
} | |
fn random(&self, mut x: usize, mut y: usize) -> f64 { | |
if let Some(xp) = self.x_period { | |
x = x % xp; | |
} | |
if let Some(yp) = self.y_period { | |
y = y % yp; | |
} | |
pseudo_random(x as f64 * self.x_seed + y as f64 * self.y_seed) | |
} | |
fn random_unit(&self, x: usize, y: usize) -> CoordinatePair { | |
let r = PI * 2. * self.random(x, y); | |
CoordinatePair::new(r.cos(), r.sin()) | |
} | |
fn smooth_step(v1: f64, v2: f64, w: f64) -> f64 { | |
let mut w = w.max(0.).min(1.); | |
w = 6. * w.powi(5) - 15. * w.powi(4) + 10. * w.powi(3); | |
(1. - w) * v1 + w * v2 | |
} | |
/// Generate a noise value for the given x and y. | |
pub fn noise(&self, x: f64, y: f64) -> f64 { | |
let xi = x.floor(); | |
let xf = x - xi; | |
let yi = y.floor(); | |
let yf = y - yi; | |
let v00 = CoordinatePair::new(xf, yf); | |
let v01 = CoordinatePair::new(xf, yf - 1.); | |
let v10 = CoordinatePair::new(xf - 1., yf); | |
let v11 = CoordinatePair::new(xf - 1., yf - 1.); | |
let n00 = dot(v00, self.random_unit(xi as usize, yi as usize)); | |
let n01 = dot(v01, self.random_unit(xi as usize, yi as usize + 1)); | |
let n10 = dot(v10, self.random_unit(xi as usize + 1, yi as usize)); | |
let n11 = dot(v11, self.random_unit(xi as usize + 1, yi as usize + 1)); | |
NoiseMaker::smooth_step( | |
NoiseMaker::smooth_step(n00, n01, yf), | |
NoiseMaker::smooth_step(n10, n11, yf), | |
xf, | |
) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use svg::Document; | |
use svg::node::element::path::Data; | |
use svg::node::element::Path; | |
use cgmath::Vector2; | |
pub type CoordinatePair = Vector2<f64>; | |
pub type PointString = Vec<CoordinatePair>; | |
fn coords_to_pair(coords: &CoordinatePair) -> (f64, f64) { | |
(coords.x, coords.y) | |
} | |
pub struct Plot { | |
paths: Vec<PointString> | |
} | |
impl Plot { | |
pub fn new() -> Self { | |
Plot { | |
paths: Vec::new() | |
} | |
} | |
pub fn add_path(&mut self, points: PointString) { | |
self.paths.push(points) | |
} | |
pub fn save(self, filename: &str) { | |
let mut doc = Document::new(); | |
for path in self.paths { | |
if let Some(first_coord) = path.first() { | |
let mut data = Data::new() | |
.move_to(coords_to_pair(&first_coord)); | |
for coord in &path[1..] { | |
data = data.line_to(coords_to_pair(coord)) | |
} | |
let path = Path::new() | |
.set("stroke", "black") | |
.set("fill", "none") | |
.set("d", data); | |
doc = doc.add(path); | |
} | |
} | |
svg::save(filename, &doc).unwrap(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment