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]
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<OptionalParams>,
}
#[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<Specimen> {
fn build_specimens(
noise_source: NoiseSource,
width: u32,
height: u32,
optional: Option<OptionalParams>,
) -> Vec<Specimen> {
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<Sp
height,
noise_source,
noise_type: NoiseType::Scale(1.),
optional,
};
specimen.params.push(params);
}
@ -220,6 +243,7 @@ fn build_specimens(noise_source: NoiseSource, width: u32, height: u32) -> Vec<Sp
height,
noise_source,
noise_type: NoiseType::Scale(scale as f32 / 10.),
optional,
};
specimen.params.push(params);
}
@ -235,6 +259,7 @@ fn build_specimens(noise_source: NoiseSource, width: u32, height: u32) -> Vec<Sp
height,
noise_source,
noise_type: NoiseType::Scale(scale as f32),
optional,
};
specimen.params.push(params);
}
@ -250,6 +275,7 @@ fn build_specimens(noise_source: NoiseSource, width: u32, height: u32) -> Vec<Sp
height,
noise_source,
noise_type: NoiseType::Turbulence(1 << turbulence),
optional,
};
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.),
power: power as f32,
size: 1 << size,
scale: 1.,
},
optional,
};
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.),
power: power as f32,
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);
}
@ -308,27 +373,25 @@ fn style(_req: &HttpRequest) -> Result<HttpResponse> {
Ok(HttpResponse::Ok().content_type("text/css").body(&bytes[..]))
}
fn index(source: NoiseSource, width: u32, height: u32) -> Result<HttpResponse> {
let specimens = build_specimens(source, width, height);
fn index(source: NoiseSource, optional: Option<Query<OptionalParams>>) -> Result<HttpResponse> {
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<HttpResponse> {
let width = 128;
let height = 128;
index(NoiseSource::Perlin, width, height)
fn index_perlin(optional: Option<Query<OptionalParams>>) -> Result<HttpResponse> {
index(NoiseSource::Perlin, optional)
}
fn index_lode(_req: &HttpRequest) -> Result<HttpResponse> {
let width = 128;
let height = 128;
index(NoiseSource::Lode, width, height)
fn index_lode(optional: Option<Query<OptionalParams>>) -> Result<HttpResponse> {
index(NoiseSource::Lode, optional)
}
fn noise(np: &NoiseParams) -> Result<HttpResponse> {
let img = render_noise(&np);
fn noise(np: NoiseParams) -> Result<HttpResponse> {
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<HttpResponse> {
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<NoiseParamsScale>) -> Result<HttpResponse> {
noise(&NoiseParams {
fn noise_scale(
np: Path<NoiseParamsScale>,
optional: Option<Query<OptionalParams>>,
) -> Result<HttpResponse> {
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<NoiseParamsTurbulence>) -> Result<HttpResponse> {
noise(&NoiseParams {
fn noise_turbulence(
np: Path<NoiseParamsTurbulence>,
optional: Option<Query<OptionalParams>>,
) -> Result<HttpResponse> {
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<NoiseParamsMarble>) -> Result<HttpResponse> {
noise(&NoiseParams {
fn noise_marble(
np: Path<NoiseParamsMarble>,
optional: Option<Query<OptionalParams>>,
) -> Result<HttpResponse> {
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<NoiseParamsMarble>) -> Result<HttpResponse> {
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<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| {
r.with(|p: Path<NoiseParamsTurbulence>| 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<NoiseParamsMarble>| 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 }"
);
}
}

View File

@ -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()),
],
}
}

View File

@ -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.],

View File

@ -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<Texture> = Arc::new(NoiseTexture::new(noise_source, noise_type));
let objects: Vec<Box<Hit>> = vec![

View File

@ -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);