Skip to content

Instantly share code, notes, and snippets.

@paulgb
Last active November 29, 2020 15:16
Show Gist options
  • Save paulgb/30be364651b1974916c56aa9bf425d27 to your computer and use it in GitHub Desktop.
Save paulgb/30be364651b1974916c56aa9bf425d27 to your computer and use it in GitHub Desktop.
moire plot
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");
}
//! 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,
)
}
}
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