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:
parent
5faba9cf26
commit
ea0532fd6e
@ -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 }"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.],
|
||||
|
||||
@ -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![
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user