diff --git a/rtiow/src/bin/noise_explorer.rs b/rtiow/src/bin/noise_explorer.rs index f866943..448c0d3 100644 --- a/rtiow/src/bin/noise_explorer.rs +++ b/rtiow/src/bin/noise_explorer.rs @@ -1,6 +1,8 @@ #[macro_use] extern crate askama; extern crate actix_web; +#[macro_use] +extern crate log; extern crate image; extern crate rand; #[macro_use] @@ -14,7 +16,6 @@ extern crate structopt; extern crate rtiow; use std::fmt; -use std::path::PathBuf; use std::time::SystemTime; use actix_web::http; @@ -23,7 +24,8 @@ use actix_web::server; use actix_web::App; use actix_web::HttpRequest; use actix_web::HttpResponse; -use actix_web::Path as WebPath; +use actix_web::Path; +use actix_web::Query; use actix_web::Result; use askama::Template; use rand::SeedableRng; @@ -44,10 +46,6 @@ use rtiow::vec3::Vec3; about = "CLI for exploring Perlin noise" )] struct Opt { - /// Output directory - #[structopt(parse(from_os_str), default_value = "/tmp/perlin")] - pub output: PathBuf, - /// HTTP listen address #[structopt(long = "addr", default_value = "0.0.0.0:8889")] 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)] struct NoiseSourceParam { noise_source: NoiseSource, @@ -88,6 +91,7 @@ struct NoiseParams { height: u32, noise_source: NoiseSource, noise_type: NoiseType, + optional: Option, } #[derive(Debug, Deserialize)] @@ -116,29 +120,41 @@ struct NoiseParamsMarble { z: f32, power: f32, size: usize, + scale: f32, } impl NoiseParams { fn url(&self) -> String { - format!( + let mut s = format!( "/noise/{}/{}/{}/{}", self.noise_source, self.width, self.height, self.noise_type.to_url(), - ) + ); + if let Some(optional) = self.optional { + s += &format!("?pixel_scale={}", optional.pixel_scale); + }; + s } fn big_url(&self) -> String { - format!( + let mut s = format!( "/noise/{}/1024/1024/{}", self.noise_source, 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]; let rng: &mut XorShiftRng = &mut SeedableRng::from_seed(SEED); 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), }; + info!("{:?}", noise_params); for x in 0..img.width() { for y in 0..img.height() { - let u = x as f32 / 2.; - let v = y as f32 / 2.; + let u = x as f32 / options.pixel_scale; + let v = y as f32 / options.pixel_scale; let p = Vec3::new(u, v, 1.); let luma = tex.value(u, v, p).x; let luma = (luma * 255.) as u8; @@ -191,7 +208,12 @@ fn render_html( index.render() } -fn build_specimens(noise_source: NoiseSource, width: u32, height: u32) -> Vec { +fn build_specimens( + noise_source: NoiseSource, + width: u32, + height: u32, + optional: Option, +) -> Vec { let mut specimens = Vec::new(); let mut specimen = Specimen { title: "Unperturbed".into(), @@ -203,6 +225,7 @@ fn build_specimens(noise_source: NoiseSource, width: u32, height: u32) -> Vec Vec Vec Vec Vec Vec Result { Ok(HttpResponse::Ok().content_type("text/css").body(&bytes[..])) } -fn index(source: NoiseSource, width: u32, height: u32) -> Result { - let specimens = build_specimens(source, width, height); +fn index(source: NoiseSource, optional: Option>) -> Result { + let width = 128; + let height = 128; + let specimens = build_specimens(source, width, height, optional.map(|o| o.into_inner())); Ok(HttpResponse::Ok() .content_type("text/html") .body(render_html(source, specimens).unwrap())) } -fn index_perlin(_req: &HttpRequest) -> Result { - let width = 128; - let height = 128; - index(NoiseSource::Perlin, width, height) +fn index_perlin(optional: Option>) -> Result { + index(NoiseSource::Perlin, optional) } -fn index_lode(_req: &HttpRequest) -> Result { - let width = 128; - let height = 128; - index(NoiseSource::Lode, width, height) +fn index_lode(optional: Option>) -> Result { + index(NoiseSource::Lode, optional) } -fn noise(np: &NoiseParams) -> Result { - let img = render_noise(&np); +fn noise(np: NoiseParams) -> Result { + let img = render_noise(np); let mut buf = Vec::new(); let img = image::DynamicImage::ImageLuma8(img); // TODO(wathiede): remove unwrap with proper error propagation. @@ -337,30 +400,41 @@ fn noise(np: &NoiseParams) -> Result { 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))] -fn noise_scale(np: WebPath) -> Result { - noise(&NoiseParams { +fn noise_scale( + np: Path, + optional: Option>, +) -> Result { + noise(NoiseParams { width: np.width, height: np.height, noise_source: np.noise_source, noise_type: NoiseType::Scale(np.scale), + optional: optional.map(|o| o.into_inner()), }) } #[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] -fn noise_turbulence(np: WebPath) -> Result { - noise(&NoiseParams { +fn noise_turbulence( + np: Path, + optional: Option>, +) -> Result { + noise(NoiseParams { width: np.width, height: np.height, noise_source: np.noise_source, noise_type: NoiseType::Turbulence(np.turbulence), + optional: optional.map(|o| o.into_inner()), }) } #[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] -fn noise_marble(np: WebPath) -> Result { - noise(&NoiseParams { +fn noise_marble( + np: Path, + optional: Option>, +) -> Result { + info!("optional {:?}", optional); + noise(NoiseParams { width: np.width, height: np.height, noise_source: np.noise_source, @@ -368,7 +442,9 @@ fn noise_marble(np: WebPath) -> Result { period: Vec3::new(np.x, np.y, np.z), power: np.power, 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| { r.with(noise_turbulence); }).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.with(noise_marble); }, ).resource("/style.css", |r| r.f(style)) - .resource("/lode", |r| r.f(index_lode)) - .resource("/perlin", |r| r.f(index_perlin)) + .resource("/lode", |r| r.with(index_lode)) + .resource("/perlin", |r| r.with(index_perlin)) .resource("/", |r| r.f(|_| HttpResponse::Found().header(http::header::LOCATION, "/lode").finish())) .middleware(middleware::Logger::default()) }).bind(opt.addr) @@ -410,22 +486,24 @@ mod tests { use actix_web::test::TestServer; use actix_web::HttpMessage; use actix_web::Path; + use actix_web::Query; use super::NoiseParamsMarble; use super::NoiseParamsScale; use super::NoiseParamsTurbulence; + use super::OptionalParams; #[test] fn noise_param_from_req() { let mut srv = TestServer::build().start(|app| { app.resource("/noise/{noise_source}/{width}/{height}/scale/{scale}", |r| { - r.with(|p: Path| format!("{:?}", p)); + r.with(|p: Path, q:Option>| format!("{:?} {:?}", p,q)); }); app.resource("/noise/{noise_source}/{width}/{height}/turbulence/{turbulence}", |r| { r.with(|p: Path| format!("{:?}", p)); }); 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.with(|p: Path| format!("{:?}", p)); }, @@ -443,7 +521,21 @@ mod tests { let body = str::from_utf8(&bytes).unwrap(); assert_eq!( 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 @@ -457,7 +549,7 @@ mod tests { let body = str::from_utf8(&bytes).unwrap(); assert_eq!( 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 @@ -474,11 +566,10 @@ mod tests { "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 .client( 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() .unwrap(); let response = srv.execute(request.send()).unwrap(); @@ -488,7 +579,7 @@ mod tests { let body = str::from_utf8(&bytes).unwrap(); assert_eq!( 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 }" ); } } diff --git a/rtiow/src/noise/mod.rs b/rtiow/src/noise/mod.rs index 477c79e..5c3d342 100644 --- a/rtiow/src/noise/mod.rs +++ b/rtiow/src/noise/mod.rs @@ -9,7 +9,8 @@ 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 { + 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 // degrees and interacts with the sine function? let xyz_value = p.x * period.x / 255. @@ -52,6 +53,7 @@ pub enum NoiseType { period: Vec3, power: f32, size: usize, + scale: f32, }, } @@ -64,9 +66,10 @@ impl NoiseType { period, power, size, + scale, } => format!( - "marble/period/{},{},{}/power/{}/size/{}", - period.x, period.y, period.z, power, size, + "marble/period/{},{},{}/power/{}/size/{}/scale/{}", + period.x, period.y, period.z, power, size, scale ), } } @@ -79,10 +82,12 @@ impl NoiseType { period, power, size, + scale, } => vec![ ("Period", period.to_string()), ("Power", power.to_string()), ("Size", size.to_string()), + ("Scale", scale.to_string()), ], } } diff --git a/rtiow/src/scenes/final_scene.rs b/rtiow/src/scenes/final_scene.rs index a751eff..2f6799d 100644 --- a/rtiow/src/scenes/final_scene.rs +++ b/rtiow/src/scenes/final_scene.rs @@ -130,7 +130,12 @@ pub fn new(opt: &Opt) -> Scene { // Perlin noise sphere 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); list.push(Box::new(Sphere::new( [220., 280., 300.], diff --git a/rtiow/src/scenes/perlin_debug.rs b/rtiow/src/scenes/perlin_debug.rs index 9c90c2e..4ee84f3 100644 --- a/rtiow/src/scenes/perlin_debug.rs +++ b/rtiow/src/scenes/perlin_debug.rs @@ -7,6 +7,7 @@ use hitable::Hit; use hitable_list::HitableList; use kdtree::KDTree; use material::Lambertian; +use noise::lode::Lode; use noise::perlin::Perlin; use noise::NoiseType; use renderer::Opt; @@ -34,10 +35,14 @@ pub fn new(opt: &Opt) -> Scene { time_min, time_max, ); - // TODO(wathiede): Use XOR rng for predictability? let rng = &mut rand::thread_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 = Arc::new(NoiseTexture::new(noise_source, noise_type)); let objects: Vec> = vec![ diff --git a/rtiow/src/texture/noise.rs b/rtiow/src/texture/noise.rs index bb1717b..eeabc05 100644 --- a/rtiow/src/texture/noise.rs +++ b/rtiow/src/texture/noise.rs @@ -36,7 +36,8 @@ where period, power, 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 <= 1., "Hot pixel @ {}: {}", p, v);