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:
2018-10-14 15:09:57 -07:00
parent accfd09ce4
commit 5faba9cf26
12 changed files with 286 additions and 161 deletions

67
rtiow/src/noise/lode.rs Normal file
View 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
View 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
View 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
}
}