Moved lode/perlin code to noise module.
Changed how NoiseTexture is created to allow noise function and parameters to be passed in as options. Allowed setting of noise source from URL parameters.
This commit is contained in:
67
rtiow/src/noise/lode.rs
Normal file
67
rtiow/src/noise/lode.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
/// Implements the concepts from https://lodev.org/cgtutor/randomnoise.html
|
||||
use rand;
|
||||
|
||||
use noise::NoiseSource;
|
||||
use vec3::Vec3;
|
||||
|
||||
const NOISE_SIZE: usize = 128;
|
||||
pub struct Lode {
|
||||
// Using fixed array causes stack overflow.
|
||||
noise: Vec<Vec<Vec<f32>>>, //[[[f32; NOISE_SIZE]; NOISE_SIZE]; NOISE_SIZE],
|
||||
}
|
||||
|
||||
impl Lode {
|
||||
pub fn new<R>(rng: &mut R) -> Lode
|
||||
where
|
||||
R: rand::Rng,
|
||||
{
|
||||
let mut noise = vec![vec![vec![0.; NOISE_SIZE]; NOISE_SIZE]; NOISE_SIZE];
|
||||
// Squelch warning about only being used to index, as this way reads more consistently.
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(needless_range_loop))]
|
||||
for x in 0..NOISE_SIZE {
|
||||
for y in 0..NOISE_SIZE {
|
||||
for z in 0..NOISE_SIZE {
|
||||
noise[x][y][z] = rng.gen_range::<f32>(0., 1.);
|
||||
}
|
||||
}
|
||||
}
|
||||
Lode { noise }
|
||||
}
|
||||
}
|
||||
|
||||
impl NoiseSource for Lode {
|
||||
fn value(&self, p: Vec3) -> f32 {
|
||||
// Fractional part of vector components
|
||||
let x = p.x - p.x.floor();
|
||||
let y = p.y - p.y.floor();
|
||||
let z = p.z - p.z.floor();
|
||||
|
||||
// Wrap around the arrays
|
||||
let x1 = (p.x.floor() as usize + NOISE_SIZE) % NOISE_SIZE;
|
||||
let y1 = (p.y.floor() as usize + NOISE_SIZE) % NOISE_SIZE;
|
||||
let z1 = (p.z.floor() as usize + NOISE_SIZE) % NOISE_SIZE;
|
||||
|
||||
// Neighbor values
|
||||
let x2 = (x1 as usize + NOISE_SIZE - 1) % NOISE_SIZE;
|
||||
let y2 = (y1 as usize + NOISE_SIZE - 1) % NOISE_SIZE;
|
||||
let z2 = (z1 as usize + NOISE_SIZE - 1) % NOISE_SIZE;
|
||||
|
||||
trace!(target: "noise", "p {}", p);
|
||||
trace!(target: "noise", "x {:.2} y {:.2} z {:.2}", x, y, z);
|
||||
trace!(target: "noise", "x1 {:.2} y1 {:.2} z1 {:.2}", x1, y1, z1);
|
||||
trace!(target: "noise", "x2 {:.2} y2 {:.2} z2 {:.2}", x2, y2, z2);
|
||||
let mut value = 0.;
|
||||
// Smooth the noise with bilinear interpolation. Completely untested
|
||||
value += x * y * z * self.noise[x1][y1][z1];
|
||||
value += (1. - x) * y * z * self.noise[x2][y1][z1];
|
||||
value += x * (1. - y) * z * self.noise[x1][y2][z1];
|
||||
value += x * y * (1. - z) * self.noise[x1][y1][z2];
|
||||
value += (1. - x) * (1. - y) * z * self.noise[x2][y2][z1];
|
||||
value += x * (1. - y) * (1. - z) * self.noise[x1][y2][z2];
|
||||
value += (1. - x) * y * (1. - z) * self.noise[x2][y1][z2];
|
||||
value += (1. - x) * (1. - y) * (1. - z) * self.noise[x2][y2][z2];
|
||||
|
||||
trace!(target: "noise", "luma {}", value);
|
||||
value
|
||||
}
|
||||
}
|
||||
89
rtiow/src/noise/mod.rs
Normal file
89
rtiow/src/noise/mod.rs
Normal file
@@ -0,0 +1,89 @@
|
||||
pub mod lode;
|
||||
pub mod perlin;
|
||||
|
||||
use std::f32::consts::PI;
|
||||
|
||||
use vec3::Vec3;
|
||||
|
||||
pub trait NoiseSource: Send + Sync {
|
||||
/// value returns noise on the interval [0., 1.).
|
||||
fn value(&self, p: Vec3) -> f32;
|
||||
|
||||
fn marble(&self, p: Vec3, period: Vec3, power: f32, size: usize) -> f32 {
|
||||
// TODO(wathiede): can't understand why 255 works for perlin and lode, maybe it's near 360
|
||||
// degrees and interacts with the sine function?
|
||||
let xyz_value = p.x * period.x / 255.
|
||||
+ p.y * period.y / 255.
|
||||
+ p.z * period.z / 255.
|
||||
+ power * self.turbulence(p, size);
|
||||
(xyz_value * PI).sin().abs()
|
||||
}
|
||||
|
||||
fn turbulence(&self, p: Vec3, factor: usize) -> f32 {
|
||||
let mut value = 0.;
|
||||
let initial_factor = factor;
|
||||
let mut factor = factor;
|
||||
|
||||
while factor >= 1 {
|
||||
value += self.value(p / factor as f32) * factor as f32;
|
||||
factor /= 2;
|
||||
}
|
||||
0.5 * value / initial_factor as f32
|
||||
}
|
||||
|
||||
fn scaled(&self, p: Vec3, scale: f32) -> f32 {
|
||||
let p = p / scale;
|
||||
self.value(p)
|
||||
}
|
||||
}
|
||||
|
||||
impl NoiseSource for Box<NoiseSource> {
|
||||
fn value(&self, p: Vec3) -> f32 {
|
||||
(**self).value(p)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum NoiseType {
|
||||
Scale(f32),
|
||||
Turbulence(usize),
|
||||
Marble {
|
||||
period: Vec3,
|
||||
power: f32,
|
||||
size: usize,
|
||||
},
|
||||
}
|
||||
|
||||
impl NoiseType {
|
||||
pub fn to_url(&self) -> String {
|
||||
match &self {
|
||||
NoiseType::Scale(scale) => format!("scale/{}", scale),
|
||||
NoiseType::Turbulence(turbulence) => format!("turbulence/{}", turbulence),
|
||||
NoiseType::Marble {
|
||||
period,
|
||||
power,
|
||||
size,
|
||||
} => format!(
|
||||
"marble/period/{},{},{}/power/{}/size/{}",
|
||||
period.x, period.y, period.z, power, size,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parameters(&self) -> Vec<(&str, String)> {
|
||||
match &self {
|
||||
NoiseType::Scale(scale) => vec![("Scale", scale.to_string())],
|
||||
NoiseType::Turbulence(turbulence) => vec![("Turbulence", turbulence.to_string())],
|
||||
NoiseType::Marble {
|
||||
period,
|
||||
power,
|
||||
size,
|
||||
} => vec![
|
||||
("Period", period.to_string()),
|
||||
("Power", power.to_string()),
|
||||
("Size", size.to_string()),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
149
rtiow/src/noise/perlin.rs
Normal file
149
rtiow/src/noise/perlin.rs
Normal file
@@ -0,0 +1,149 @@
|
||||
// There are many math functions in this file, so we allow single letter variable names.
|
||||
#![cfg_attr(feature = "cargo-clippy", allow(many_single_char_names))]
|
||||
use rand::Rng;
|
||||
|
||||
use noise::NoiseSource;
|
||||
use vec3::dot;
|
||||
use vec3::Vec3;
|
||||
|
||||
pub struct Perlin {
|
||||
ran_vec: Vec<Vec3>,
|
||||
perm_x: Vec<usize>,
|
||||
perm_y: Vec<usize>,
|
||||
perm_z: Vec<usize>,
|
||||
}
|
||||
|
||||
fn perlin_generate<R>(rng: &mut R) -> Vec<Vec3>
|
||||
where
|
||||
R: Rng,
|
||||
{
|
||||
(0..256)
|
||||
.map(|_| {
|
||||
Vec3::new(
|
||||
rng.gen_range::<f32>(-1., 1.),
|
||||
rng.gen_range::<f32>(-1., 1.),
|
||||
rng.gen_range::<f32>(-1., 1.),
|
||||
).unit_vector()
|
||||
}).collect()
|
||||
}
|
||||
|
||||
fn perlin_generate_perm<R>(rng: &mut R) -> Vec<usize>
|
||||
where
|
||||
R: Rng,
|
||||
{
|
||||
let mut p: Vec<usize> = (0..256).map(|i| i).collect();
|
||||
rng.shuffle(&mut p);
|
||||
p
|
||||
}
|
||||
|
||||
fn perlin_interp(c: [[[Vec3; 2]; 2]; 2], u: f32, v: f32, w: f32) -> f32 {
|
||||
// Hermite cubic to round off interpolation and attempt to address 'mach bands'.
|
||||
let uu = u * u * (3. - 2. * u);
|
||||
let vv = v * v * (3. - 2. * v);
|
||||
let ww = w * w * (3. - 2. * w);
|
||||
|
||||
let mut accum = 0.;
|
||||
for i in 0..2 {
|
||||
for j in 0..2 {
|
||||
for k in 0..2 {
|
||||
let weight_v = Vec3::new(u - i as f32, v - j as f32, w - k as f32);
|
||||
|
||||
let i = i as f32;
|
||||
let j = j as f32;
|
||||
let k = k as f32;
|
||||
accum += (i * uu + (1. - i) * (1. - uu))
|
||||
* (j * vv + (1. - j) * (1. - vv))
|
||||
* (k * ww + (1. - k) * (1. - ww))
|
||||
* dot(c[i as usize][j as usize][k as usize], weight_v);
|
||||
}
|
||||
}
|
||||
}
|
||||
//info!("u {} v {} accum {}", u, v, accum);
|
||||
accum
|
||||
}
|
||||
|
||||
impl Perlin {
|
||||
pub fn new<R>(rng: &mut R) -> Perlin
|
||||
where
|
||||
R: Rng,
|
||||
{
|
||||
let p = Perlin {
|
||||
ran_vec: perlin_generate(rng),
|
||||
perm_x: perlin_generate_perm(rng),
|
||||
perm_y: perlin_generate_perm(rng),
|
||||
perm_z: perlin_generate_perm(rng),
|
||||
};
|
||||
trace!(target: "perlin",
|
||||
"ran_vec: {}",
|
||||
p.ran_vec
|
||||
.iter()
|
||||
.map(|v| v.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
);
|
||||
trace!(target: "perlin",
|
||||
"perm_x: {}",
|
||||
p.perm_x
|
||||
.iter()
|
||||
.map(|v| v.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
);
|
||||
trace!(target: "perlin",
|
||||
"perm_y: {}",
|
||||
p.perm_y
|
||||
.iter()
|
||||
.map(|v| v.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
);
|
||||
trace!(target: "perlin",
|
||||
"perm_z: {}",
|
||||
p.perm_z
|
||||
.iter()
|
||||
.map(|v| v.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
);
|
||||
p
|
||||
}
|
||||
|
||||
pub fn turb(&self, p: Vec3, depth: usize) -> f32 {
|
||||
let mut accum = 0.;
|
||||
let mut temp_p = p;
|
||||
let mut weight = 1.;
|
||||
for _ in 0..depth {
|
||||
accum += weight * self.value(temp_p);
|
||||
weight *= 0.5;
|
||||
temp_p = temp_p * 0.2;
|
||||
}
|
||||
accum.abs()
|
||||
}
|
||||
}
|
||||
|
||||
impl NoiseSource for Perlin {
|
||||
fn value(&self, p: Vec3) -> f32 {
|
||||
let u = p.x - p.x.floor();
|
||||
let v = p.y - p.y.floor();
|
||||
let w = p.z - p.z.floor();
|
||||
|
||||
let i = p.x.floor() as usize;
|
||||
let j = p.y.floor() as usize;
|
||||
let k = p.z.floor() as usize;
|
||||
|
||||
let mut c: [[[Vec3; 2]; 2]; 2] = Default::default();
|
||||
|
||||
// Squelch warning about di only being used to index, as this way reads more consistently.
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(needless_range_loop))]
|
||||
for di in 0..2 {
|
||||
for dj in 0..2 {
|
||||
for dk in 0..2 {
|
||||
c[di][dj][dk] = self.ran_vec[self.perm_x[(i + di) & 255]
|
||||
^ self.perm_y[(j + dj) & 255]
|
||||
^ self.perm_z[(k + dk) & 255]]
|
||||
}
|
||||
}
|
||||
}
|
||||
(1. + perlin_interp(c, u, v, w)) * 0.5
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user