Finalize marble parameters for scene/final.

Add pixel_scale parameter to noise_explorer to allow scaling of p vector
passed to value function.
This commit is contained in:
Bill Thiede 2018-10-15 12:42:22 -07:00
parent 5faba9cf26
commit ea0532fd6e
5 changed files with 157 additions and 50 deletions

View File

@ -1,6 +1,8 @@
#[macro_use] #[macro_use]
extern crate askama; extern crate askama;
extern crate actix_web; extern crate actix_web;
#[macro_use]
extern crate log;
extern crate image; extern crate image;
extern crate rand; extern crate rand;
#[macro_use] #[macro_use]
@ -14,7 +16,6 @@ extern crate structopt;
extern crate rtiow; extern crate rtiow;
use std::fmt; use std::fmt;
use std::path::PathBuf;
use std::time::SystemTime; use std::time::SystemTime;
use actix_web::http; use actix_web::http;
@ -23,7 +24,8 @@ use actix_web::server;
use actix_web::App; use actix_web::App;
use actix_web::HttpRequest; use actix_web::HttpRequest;
use actix_web::HttpResponse; use actix_web::HttpResponse;
use actix_web::Path as WebPath; use actix_web::Path;
use actix_web::Query;
use actix_web::Result; use actix_web::Result;
use askama::Template; use askama::Template;
use rand::SeedableRng; use rand::SeedableRng;
@ -44,10 +46,6 @@ use rtiow::vec3::Vec3;
about = "CLI for exploring Perlin noise" about = "CLI for exploring Perlin noise"
)] )]
struct Opt { struct Opt {
/// Output directory
#[structopt(parse(from_os_str), default_value = "/tmp/perlin")]
pub output: PathBuf,
/// HTTP listen address /// HTTP listen address
#[structopt(long = "addr", default_value = "0.0.0.0:8889")] #[structopt(long = "addr", default_value = "0.0.0.0:8889")]
pub addr: String, pub addr: String,
@ -77,6 +75,11 @@ impl fmt::Display for NoiseSource {
} }
} }
#[derive(Copy, Clone, Debug, Deserialize)]
struct OptionalParams {
pixel_scale: f32,
}
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
struct NoiseSourceParam { struct NoiseSourceParam {
noise_source: NoiseSource, noise_source: NoiseSource,
@ -88,6 +91,7 @@ struct NoiseParams {
height: u32, height: u32,
noise_source: NoiseSource, noise_source: NoiseSource,
noise_type: NoiseType, noise_type: NoiseType,
optional: Option<OptionalParams>,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@ -116,29 +120,41 @@ struct NoiseParamsMarble {
z: f32, z: f32,
power: f32, power: f32,
size: usize, size: usize,
scale: f32,
} }
impl NoiseParams { impl NoiseParams {
fn url(&self) -> String { fn url(&self) -> String {
format!( let mut s = format!(
"/noise/{}/{}/{}/{}", "/noise/{}/{}/{}/{}",
self.noise_source, self.noise_source,
self.width, self.width,
self.height, self.height,
self.noise_type.to_url(), self.noise_type.to_url(),
) );
if let Some(optional) = self.optional {
s += &format!("?pixel_scale={}", optional.pixel_scale);
};
s
} }
fn big_url(&self) -> String { fn big_url(&self) -> String {
format!( let mut s = format!(
"/noise/{}/1024/1024/{}", "/noise/{}/1024/1024/{}",
self.noise_source, self.noise_source,
self.noise_type.to_url() self.noise_type.to_url()
) );
if let Some(optional) = self.optional {
s += &format!("?pixel_scale={}", optional.pixel_scale);
};
s
} }
} }
fn render_noise(noise_params: &NoiseParams) -> image::GrayImage { fn render_noise(noise_params: NoiseParams) -> image::GrayImage {
let options = noise_params
.optional
.unwrap_or(OptionalParams { pixel_scale: 1.0 });
const SEED: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; const SEED: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
let rng: &mut XorShiftRng = &mut SeedableRng::from_seed(SEED); let rng: &mut XorShiftRng = &mut SeedableRng::from_seed(SEED);
let mut img = image::GrayImage::new(noise_params.width, noise_params.height); let mut img = image::GrayImage::new(noise_params.width, noise_params.height);
@ -148,11 +164,12 @@ fn render_noise(noise_params: &NoiseParams) -> image::GrayImage {
} }
NoiseSource::Lode => NoiseTexture::new(Box::new(Lode::new(rng)), noise_params.noise_type), NoiseSource::Lode => NoiseTexture::new(Box::new(Lode::new(rng)), noise_params.noise_type),
}; };
info!("{:?}", noise_params);
for x in 0..img.width() { for x in 0..img.width() {
for y in 0..img.height() { for y in 0..img.height() {
let u = x as f32 / 2.; let u = x as f32 / options.pixel_scale;
let v = y as f32 / 2.; let v = y as f32 / options.pixel_scale;
let p = Vec3::new(u, v, 1.); let p = Vec3::new(u, v, 1.);
let luma = tex.value(u, v, p).x; let luma = tex.value(u, v, p).x;
let luma = (luma * 255.) as u8; let luma = (luma * 255.) as u8;
@ -191,7 +208,12 @@ fn render_html(
index.render() index.render()
} }
fn build_specimens(noise_source: NoiseSource, width: u32, height: u32) -> Vec<Specimen> { fn build_specimens(
noise_source: NoiseSource,
width: u32,
height: u32,
optional: Option<OptionalParams>,
) -> Vec<Specimen> {
let mut specimens = Vec::new(); let mut specimens = Vec::new();
let mut specimen = Specimen { let mut specimen = Specimen {
title: "Unperturbed".into(), title: "Unperturbed".into(),
@ -203,6 +225,7 @@ fn build_specimens(noise_source: NoiseSource, width: u32, height: u32) -> Vec<Sp
height, height,
noise_source, noise_source,
noise_type: NoiseType::Scale(1.), noise_type: NoiseType::Scale(1.),
optional,
}; };
specimen.params.push(params); specimen.params.push(params);
} }
@ -220,6 +243,7 @@ fn build_specimens(noise_source: NoiseSource, width: u32, height: u32) -> Vec<Sp
height, height,
noise_source, noise_source,
noise_type: NoiseType::Scale(scale as f32 / 10.), noise_type: NoiseType::Scale(scale as f32 / 10.),
optional,
}; };
specimen.params.push(params); specimen.params.push(params);
} }
@ -235,6 +259,7 @@ fn build_specimens(noise_source: NoiseSource, width: u32, height: u32) -> Vec<Sp
height, height,
noise_source, noise_source,
noise_type: NoiseType::Scale(scale as f32), noise_type: NoiseType::Scale(scale as f32),
optional,
}; };
specimen.params.push(params); specimen.params.push(params);
} }
@ -250,6 +275,7 @@ fn build_specimens(noise_source: NoiseSource, width: u32, height: u32) -> Vec<Sp
height, height,
noise_source, noise_source,
noise_type: NoiseType::Turbulence(1 << turbulence), noise_type: NoiseType::Turbulence(1 << turbulence),
optional,
}; };
specimen.params.push(params); specimen.params.push(params);
} }
@ -274,7 +300,9 @@ fn build_specimens(noise_source: NoiseSource, width: u32, height: u32) -> Vec<Sp
period: Vec3::new(5., 10., 0.), period: Vec3::new(5., 10., 0.),
power: power as f32, power: power as f32,
size: 1 << size, size: 1 << size,
scale: 1.,
}, },
optional,
}; };
specimen.params.push(params); specimen.params.push(params);
} }
@ -294,7 +322,44 @@ fn build_specimens(noise_source: NoiseSource, width: u32, height: u32) -> Vec<Sp
period: Vec3::new(0., 1., 0.), period: Vec3::new(0., 1., 0.),
power: power as f32, power: power as f32,
size: 32, size: 32,
scale: 1.,
}, },
optional,
};
specimen.params.push(params);
}
specimens.push(specimen);
let mut specimen = Specimen {
title: "Marble varying scale, size = 32".into(),
params: Vec::new(),
};
for scale in (1..4).rev() {
let params = NoiseParams {
width,
height,
noise_source,
noise_type: NoiseType::Marble {
period: Vec3::new(0., 1., 0.),
power: 1.,
size: 32,
scale: 1. / (1 << scale) as f32,
},
optional,
};
specimen.params.push(params);
}
for scale in 1..4 {
let params = NoiseParams {
width,
height,
noise_source,
noise_type: NoiseType::Marble {
period: Vec3::new(0., 1., 0.),
power: 1.,
size: 32,
scale: (1 << scale) as f32,
},
optional,
}; };
specimen.params.push(params); specimen.params.push(params);
} }
@ -308,27 +373,25 @@ fn style(_req: &HttpRequest) -> Result<HttpResponse> {
Ok(HttpResponse::Ok().content_type("text/css").body(&bytes[..])) Ok(HttpResponse::Ok().content_type("text/css").body(&bytes[..]))
} }
fn index(source: NoiseSource, width: u32, height: u32) -> Result<HttpResponse> { fn index(source: NoiseSource, optional: Option<Query<OptionalParams>>) -> Result<HttpResponse> {
let specimens = build_specimens(source, width, height); let width = 128;
let height = 128;
let specimens = build_specimens(source, width, height, optional.map(|o| o.into_inner()));
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_type("text/html") .content_type("text/html")
.body(render_html(source, specimens).unwrap())) .body(render_html(source, specimens).unwrap()))
} }
fn index_perlin(_req: &HttpRequest) -> Result<HttpResponse> { fn index_perlin(optional: Option<Query<OptionalParams>>) -> Result<HttpResponse> {
let width = 128; index(NoiseSource::Perlin, optional)
let height = 128;
index(NoiseSource::Perlin, width, height)
} }
fn index_lode(_req: &HttpRequest) -> Result<HttpResponse> { fn index_lode(optional: Option<Query<OptionalParams>>) -> Result<HttpResponse> {
let width = 128; index(NoiseSource::Lode, optional)
let height = 128;
index(NoiseSource::Lode, width, height)
} }
fn noise(np: &NoiseParams) -> Result<HttpResponse> { fn noise(np: NoiseParams) -> Result<HttpResponse> {
let img = render_noise(&np); let img = render_noise(np);
let mut buf = Vec::new(); let mut buf = Vec::new();
let img = image::DynamicImage::ImageLuma8(img); let img = image::DynamicImage::ImageLuma8(img);
// TODO(wathiede): remove unwrap with proper error propagation. // TODO(wathiede): remove unwrap with proper error propagation.
@ -337,30 +400,41 @@ fn noise(np: &NoiseParams) -> Result<HttpResponse> {
Ok(HttpResponse::Ok().content_type("image/png").body(buf)) Ok(HttpResponse::Ok().content_type("image/png").body(buf))
} }
// Making parameter breaks WebPath::From magic.
#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] #[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
fn noise_scale(np: WebPath<NoiseParamsScale>) -> Result<HttpResponse> { fn noise_scale(
noise(&NoiseParams { np: Path<NoiseParamsScale>,
optional: Option<Query<OptionalParams>>,
) -> Result<HttpResponse> {
noise(NoiseParams {
width: np.width, width: np.width,
height: np.height, height: np.height,
noise_source: np.noise_source, noise_source: np.noise_source,
noise_type: NoiseType::Scale(np.scale), noise_type: NoiseType::Scale(np.scale),
optional: optional.map(|o| o.into_inner()),
}) })
} }
#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] #[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
fn noise_turbulence(np: WebPath<NoiseParamsTurbulence>) -> Result<HttpResponse> { fn noise_turbulence(
noise(&NoiseParams { np: Path<NoiseParamsTurbulence>,
optional: Option<Query<OptionalParams>>,
) -> Result<HttpResponse> {
noise(NoiseParams {
width: np.width, width: np.width,
height: np.height, height: np.height,
noise_source: np.noise_source, noise_source: np.noise_source,
noise_type: NoiseType::Turbulence(np.turbulence), noise_type: NoiseType::Turbulence(np.turbulence),
optional: optional.map(|o| o.into_inner()),
}) })
} }
#[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] #[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
fn noise_marble(np: WebPath<NoiseParamsMarble>) -> Result<HttpResponse> { fn noise_marble(
noise(&NoiseParams { np: Path<NoiseParamsMarble>,
optional: Option<Query<OptionalParams>>,
) -> Result<HttpResponse> {
info!("optional {:?}", optional);
noise(NoiseParams {
width: np.width, width: np.width,
height: np.height, height: np.height,
noise_source: np.noise_source, noise_source: np.noise_source,
@ -368,7 +442,9 @@ fn noise_marble(np: WebPath<NoiseParamsMarble>) -> Result<HttpResponse> {
period: Vec3::new(np.x, np.y, np.z), period: Vec3::new(np.x, np.y, np.z),
power: np.power, power: np.power,
size: np.size, size: np.size,
scale: np.scale,
}, },
optional: optional.map(|o| o.into_inner()),
}) })
} }
@ -387,13 +463,13 @@ fn main() -> Result<(), std::io::Error> {
}).resource("/noise/{noise_source}/{width}/{height}/turbulence/{turbulence}", |r| { }).resource("/noise/{noise_source}/{width}/{height}/turbulence/{turbulence}", |r| {
r.with(noise_turbulence); r.with(noise_turbulence);
}).resource( }).resource(
"/noise/{noise_source}/{width}/{height}/marble/period/{x},{y},{z}/power/{power}/size/{size}", "/noise/{noise_source}/{width}/{height}/marble/period/{x},{y},{z}/power/{power}/size/{size}/scale/{scale}",
|r| { |r| {
r.with(noise_marble); r.with(noise_marble);
}, },
).resource("/style.css", |r| r.f(style)) ).resource("/style.css", |r| r.f(style))
.resource("/lode", |r| r.f(index_lode)) .resource("/lode", |r| r.with(index_lode))
.resource("/perlin", |r| r.f(index_perlin)) .resource("/perlin", |r| r.with(index_perlin))
.resource("/", |r| r.f(|_| HttpResponse::Found().header(http::header::LOCATION, "/lode").finish())) .resource("/", |r| r.f(|_| HttpResponse::Found().header(http::header::LOCATION, "/lode").finish()))
.middleware(middleware::Logger::default()) .middleware(middleware::Logger::default())
}).bind(opt.addr) }).bind(opt.addr)
@ -410,22 +486,24 @@ mod tests {
use actix_web::test::TestServer; use actix_web::test::TestServer;
use actix_web::HttpMessage; use actix_web::HttpMessage;
use actix_web::Path; use actix_web::Path;
use actix_web::Query;
use super::NoiseParamsMarble; use super::NoiseParamsMarble;
use super::NoiseParamsScale; use super::NoiseParamsScale;
use super::NoiseParamsTurbulence; use super::NoiseParamsTurbulence;
use super::OptionalParams;
#[test] #[test]
fn noise_param_from_req() { fn noise_param_from_req() {
let mut srv = TestServer::build().start(|app| { let mut srv = TestServer::build().start(|app| {
app.resource("/noise/{noise_source}/{width}/{height}/scale/{scale}", |r| { app.resource("/noise/{noise_source}/{width}/{height}/scale/{scale}", |r| {
r.with(|p: Path<NoiseParamsScale>| format!("{:?}", p)); r.with(|p: Path<NoiseParamsScale>, q:Option<Query<OptionalParams>>| format!("{:?} {:?}", p,q));
}); });
app.resource("/noise/{noise_source}/{width}/{height}/turbulence/{turbulence}", |r| { app.resource("/noise/{noise_source}/{width}/{height}/turbulence/{turbulence}", |r| {
r.with(|p: Path<NoiseParamsTurbulence>| format!("{:?}", p)); r.with(|p: Path<NoiseParamsTurbulence>| format!("{:?}", p));
}); });
app.resource( app.resource(
"/noise/{noise_source}/{width}/{height}/marble/period/{x},{y},{z}/power/{power}/size/{size}", "/noise/{noise_source}/{width}/{height}/marble/period/{x},{y},{z}/power/{power}/size/{size}/scale/{scale}",
|r| { |r| {
r.with(|p: Path<NoiseParamsMarble>| format!("{:?}", p)); r.with(|p: Path<NoiseParamsMarble>| format!("{:?}", p));
}, },
@ -443,7 +521,21 @@ mod tests {
let body = str::from_utf8(&bytes).unwrap(); let body = str::from_utf8(&bytes).unwrap();
assert_eq!( assert_eq!(
body, body,
"NoiseParamsScale { width: 32, height: 32, noise_source: Perlin, scale: 1.0 }" "NoiseParamsScale { width: 32, height: 32, noise_source: Perlin, scale: 1.0 } None"
);
let request = srv
.client(Method::GET, "/noise/perlin/32/32/scale/1?pixel_scale=1")
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert!(response.status().is_success(), "Response {:?}", response);
let bytes = srv.execute(response.body()).unwrap();
let body = str::from_utf8(&bytes).unwrap();
assert_eq!(
body,
"NoiseParamsScale { width: 32, height: 32, noise_source: Perlin, scale: 1.0 } Some(OptionalParams { pixel_scale: 1.0 })"
); );
let request = srv let request = srv
@ -457,7 +549,7 @@ mod tests {
let body = str::from_utf8(&bytes).unwrap(); let body = str::from_utf8(&bytes).unwrap();
assert_eq!( assert_eq!(
body, body,
"NoiseParamsScale { width: 32, height: 32, noise_source: Lode, scale: 1.0 }" "NoiseParamsScale { width: 32, height: 32, noise_source: Lode, scale: 1.0 } None"
); );
let request = srv let request = srv
@ -474,11 +566,10 @@ mod tests {
"NoiseParamsTurbulence { width: 32, height: 32, noise_source: Lode, turbulence: 5 }" "NoiseParamsTurbulence { width: 32, height: 32, noise_source: Lode, turbulence: 5 }"
); );
// TODO(wathiede): this isn't working, probably because Vec3 Deserialize is not working.
let request = srv let request = srv
.client( .client(
Method::GET, Method::GET,
"/noise/lode/32/32/marble/period/1.,2.,3./power/32./size/4", "/noise/lode/32/32/marble/period/1.,2.,3./power/32./size/4/scale/255.",
).finish() ).finish()
.unwrap(); .unwrap();
let response = srv.execute(request.send()).unwrap(); let response = srv.execute(request.send()).unwrap();
@ -488,7 +579,7 @@ mod tests {
let body = str::from_utf8(&bytes).unwrap(); let body = str::from_utf8(&bytes).unwrap();
assert_eq!( assert_eq!(
body, body,
"NoiseParamsMarble { width: 32, height: 32, noise_source: Lode, x: 1.0, y: 2.0, z: 3.0, power: 32.0, size: 4 }" "NoiseParamsMarble { width: 32, height: 32, noise_source: Lode, x: 1.0, y: 2.0, z: 3.0, power: 32.0, size: 4, scale: 255.0 }"
); );
} }
} }

View File

@ -9,7 +9,8 @@ pub trait NoiseSource: Send + Sync {
/// value returns noise on the interval [0., 1.). /// value returns noise on the interval [0., 1.).
fn value(&self, p: Vec3) -> f32; fn value(&self, p: Vec3) -> f32;
fn marble(&self, p: Vec3, period: Vec3, power: f32, size: usize) -> f32 { fn marble(&self, p: Vec3, period: Vec3, power: f32, size: usize, scale: f32) -> f32 {
let p = p / scale;
// TODO(wathiede): can't understand why 255 works for perlin and lode, maybe it's near 360 // TODO(wathiede): can't understand why 255 works for perlin and lode, maybe it's near 360
// degrees and interacts with the sine function? // degrees and interacts with the sine function?
let xyz_value = p.x * period.x / 255. let xyz_value = p.x * period.x / 255.
@ -52,6 +53,7 @@ pub enum NoiseType {
period: Vec3, period: Vec3,
power: f32, power: f32,
size: usize, size: usize,
scale: f32,
}, },
} }
@ -64,9 +66,10 @@ impl NoiseType {
period, period,
power, power,
size, size,
scale,
} => format!( } => format!(
"marble/period/{},{},{}/power/{}/size/{}", "marble/period/{},{},{}/power/{}/size/{}/scale/{}",
period.x, period.y, period.z, power, size, period.x, period.y, period.z, power, size, scale
), ),
} }
} }
@ -79,10 +82,12 @@ impl NoiseType {
period, period,
power, power,
size, size,
scale,
} => vec![ } => vec![
("Period", period.to_string()), ("Period", period.to_string()),
("Power", power.to_string()), ("Power", power.to_string()),
("Size", size.to_string()), ("Size", size.to_string()),
("Scale", scale.to_string()),
], ],
} }
} }

View File

@ -130,7 +130,12 @@ pub fn new(opt: &Opt) -> Scene {
// Perlin noise sphere // Perlin noise sphere
let noise_source = Perlin::new(rng); let noise_source = Perlin::new(rng);
let noise_type = NoiseType::Scale(0.1); let noise_type = NoiseType::Marble {
period: Vec3::new(0., 1., 0.),
power: 4.,
size: 32,
scale: 0.5,
};
let pertext = NoiseTexture::new(noise_source, noise_type); let pertext = NoiseTexture::new(noise_source, noise_type);
list.push(Box::new(Sphere::new( list.push(Box::new(Sphere::new(
[220., 280., 300.], [220., 280., 300.],

View File

@ -7,6 +7,7 @@ use hitable::Hit;
use hitable_list::HitableList; use hitable_list::HitableList;
use kdtree::KDTree; use kdtree::KDTree;
use material::Lambertian; use material::Lambertian;
use noise::lode::Lode;
use noise::perlin::Perlin; use noise::perlin::Perlin;
use noise::NoiseType; use noise::NoiseType;
use renderer::Opt; use renderer::Opt;
@ -34,10 +35,14 @@ pub fn new(opt: &Opt) -> Scene {
time_min, time_min,
time_max, time_max,
); );
// TODO(wathiede): Use XOR rng for predictability?
let rng = &mut rand::thread_rng(); let rng = &mut rand::thread_rng();
let noise_source = Perlin::new(rng); let noise_source = Perlin::new(rng);
let noise_type = NoiseType::Scale(10.); let noise_type = NoiseType::Marble {
period: Vec3::new(0., 1., 0.),
power: 4.,
size: 32,
scale: 0.05,
};
let pertext: Arc<Texture> = Arc::new(NoiseTexture::new(noise_source, noise_type)); let pertext: Arc<Texture> = Arc::new(NoiseTexture::new(noise_source, noise_type));
let objects: Vec<Box<Hit>> = vec![ let objects: Vec<Box<Hit>> = vec![

View File

@ -36,7 +36,8 @@ where
period, period,
power, power,
size, size,
} => self.noise_source.marble(p, period, power, size), scale,
} => self.noise_source.marble(p, period, power, size, scale),
}; };
debug_assert!(v >= 0., "Cold pixel @ {}: {}", p, v); debug_assert!(v >= 0., "Cold pixel @ {}: {}", p, v);
debug_assert!(v <= 1., "Hot pixel @ {}: {}", p, v); debug_assert!(v <= 1., "Hot pixel @ {}: {}", p, v);