rtiow: break project into multiple workspaces.
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
8
rtiow/noise_explorer/src/Dockerfile
Normal file
8
rtiow/noise_explorer/src/Dockerfile
Normal file
@@ -0,0 +1,8 @@
|
||||
FROM rust:latest AS build-env
|
||||
WORKDIR /src
|
||||
RUN git clone https://git.z.xinu.tv/wathiede/raytracers.git
|
||||
WORKDIR /src/raytracers/rtiow/
|
||||
RUN cargo install --bin noise_explorer
|
||||
|
||||
FROM rust:slim
|
||||
COPY --from=build-env /src/raytracers/rtiow/target/release/noise_explorer /usr/bin/noise_explorer
|
||||
1
rtiow/noise_explorer/src/config.dbuild
Normal file
1
rtiow/noise_explorer/src/config.dbuild
Normal file
@@ -0,0 +1 @@
|
||||
package="apps/rtiow"
|
||||
571
rtiow/noise_explorer/src/main.rs
Normal file
571
rtiow/noise_explorer/src/main.rs
Normal file
@@ -0,0 +1,571 @@
|
||||
use std::fmt;
|
||||
use std::time::SystemTime;
|
||||
|
||||
use actix_web::http;
|
||||
use actix_web::middleware;
|
||||
use actix_web::server;
|
||||
use actix_web::App;
|
||||
use actix_web::HttpRequest;
|
||||
use actix_web::HttpResponse;
|
||||
use actix_web::Path;
|
||||
use actix_web::Query;
|
||||
use actix_web::Result;
|
||||
use askama::Template;
|
||||
use image;
|
||||
use log::info;
|
||||
use rand::SeedableRng;
|
||||
use rand::XorShiftRng;
|
||||
use serde_derive::Deserialize;
|
||||
use structopt::StructOpt;
|
||||
|
||||
use renderer::noise;
|
||||
use renderer::noise::lode::Lode;
|
||||
use renderer::noise::perlin::Perlin;
|
||||
use renderer::noise::NoiseType;
|
||||
use renderer::texture::NoiseTexture;
|
||||
use renderer::texture::Texture;
|
||||
use renderer::vec3::Vec3;
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[structopt(name = "noise_explorer", about = "CLI for exploring Perlin noise")]
|
||||
struct Opt {
|
||||
/// HTTP listen address
|
||||
#[structopt(long = "addr", default_value = "0.0.0.0:8889")]
|
||||
pub addr: String,
|
||||
|
||||
/// Width of noise images
|
||||
#[structopt(short = "w", long = "width", default_value = "128")]
|
||||
pub width: u32,
|
||||
/// Height of noise images
|
||||
#[structopt(short = "h", long = "height", default_value = "128")]
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Deserialize, PartialEq)]
|
||||
enum NoiseSource {
|
||||
#[serde(rename = "perlin")]
|
||||
Perlin,
|
||||
#[serde(rename = "lode")]
|
||||
Lode,
|
||||
}
|
||||
|
||||
impl fmt::Display for NoiseSource {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
NoiseSource::Perlin => write!(f, "perlin"),
|
||||
NoiseSource::Lode => write!(f, "lode"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Deserialize)]
|
||||
struct OptionalParams {
|
||||
pixel_scale: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct NoiseSourceParam {
|
||||
noise_source: NoiseSource,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct NoiseParams {
|
||||
width: u32,
|
||||
height: u32,
|
||||
noise_source: NoiseSource,
|
||||
noise_type: NoiseType,
|
||||
optional: Option<OptionalParams>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct NoiseParamsScale {
|
||||
width: u32,
|
||||
height: u32,
|
||||
noise_source: NoiseSource,
|
||||
scale: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct NoiseParamsTurbulence {
|
||||
width: u32,
|
||||
height: u32,
|
||||
noise_source: NoiseSource,
|
||||
turbulence: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct NoiseParamsMarble {
|
||||
width: u32,
|
||||
height: u32,
|
||||
noise_source: NoiseSource,
|
||||
x: f32,
|
||||
y: f32,
|
||||
z: f32,
|
||||
power: f32,
|
||||
size: usize,
|
||||
scale: f32,
|
||||
}
|
||||
|
||||
impl NoiseParams {
|
||||
fn url(&self) -> String {
|
||||
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 {
|
||||
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 {
|
||||
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 mut rng: XorShiftRng = SeedableRng::from_seed(SEED);
|
||||
let mut img = image::GrayImage::new(noise_params.width, noise_params.height);
|
||||
let tex: NoiseTexture<Box<dyn noise::NoiseSource>> = match noise_params.noise_source {
|
||||
NoiseSource::Perlin => {
|
||||
NoiseTexture::new(Box::new(Perlin::new(&mut rng)), noise_params.noise_type)
|
||||
}
|
||||
NoiseSource::Lode => {
|
||||
NoiseTexture::new(Box::new(Lode::new(&mut 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 / 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;
|
||||
img.put_pixel(x, y, image::Luma([luma]));
|
||||
}
|
||||
}
|
||||
|
||||
img
|
||||
}
|
||||
|
||||
struct Specimen {
|
||||
title: String,
|
||||
params: Vec<NoiseParams>,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "index.html")]
|
||||
struct IndexTemplate {
|
||||
render_time: u64,
|
||||
noise_source: NoiseSource,
|
||||
specimens: Vec<Specimen>,
|
||||
}
|
||||
|
||||
fn render_html(
|
||||
noise_source: NoiseSource,
|
||||
specimens: Vec<Specimen>,
|
||||
) -> Result<String, askama::Error> {
|
||||
let index = IndexTemplate {
|
||||
render_time: SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs(),
|
||||
noise_source,
|
||||
specimens,
|
||||
};
|
||||
index.render()
|
||||
}
|
||||
|
||||
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(),
|
||||
params: Vec::new(),
|
||||
};
|
||||
{
|
||||
let params = NoiseParams {
|
||||
width,
|
||||
height,
|
||||
noise_source,
|
||||
noise_type: NoiseType::Scale(1.),
|
||||
optional,
|
||||
};
|
||||
specimen.params.push(params);
|
||||
}
|
||||
specimens.push(specimen);
|
||||
|
||||
let mut specimen = Specimen {
|
||||
title: "Varying Scale Factor <1".into(),
|
||||
params: Vec::new(),
|
||||
};
|
||||
let debug = false;
|
||||
if !debug {
|
||||
for scale in 1..10 {
|
||||
let params = NoiseParams {
|
||||
width,
|
||||
height,
|
||||
noise_source,
|
||||
noise_type: NoiseType::Scale(scale as f32 / 10.),
|
||||
optional,
|
||||
};
|
||||
specimen.params.push(params);
|
||||
}
|
||||
specimens.push(specimen);
|
||||
|
||||
let mut specimen = Specimen {
|
||||
title: "Varying Scale Factor >1".into(),
|
||||
params: Vec::new(),
|
||||
};
|
||||
for scale in 1..10 {
|
||||
let params = NoiseParams {
|
||||
width,
|
||||
height,
|
||||
noise_source,
|
||||
noise_type: NoiseType::Scale(scale as f32),
|
||||
optional,
|
||||
};
|
||||
specimen.params.push(params);
|
||||
}
|
||||
specimens.push(specimen);
|
||||
|
||||
let mut specimen = Specimen {
|
||||
title: "Varying Turbulence Factor".into(),
|
||||
params: Vec::new(),
|
||||
};
|
||||
for turbulence in 0..9 {
|
||||
let params = NoiseParams {
|
||||
width,
|
||||
height,
|
||||
noise_source,
|
||||
noise_type: NoiseType::Turbulence(1 << turbulence),
|
||||
optional,
|
||||
};
|
||||
specimen.params.push(params);
|
||||
}
|
||||
specimens.push(specimen);
|
||||
|
||||
let max_size = 8;
|
||||
for power in 1..12 {
|
||||
let mut specimen = Specimen {
|
||||
title: format!(
|
||||
"Marble power {}, varying size 2-{}",
|
||||
power,
|
||||
1 << (max_size - 1)
|
||||
),
|
||||
params: Vec::new(),
|
||||
};
|
||||
for size in 1..max_size {
|
||||
let params = NoiseParams {
|
||||
width,
|
||||
height,
|
||||
noise_source,
|
||||
noise_type: NoiseType::Marble {
|
||||
period: Vec3::new(5., 10., 0.),
|
||||
power: power as f32,
|
||||
size: 1 << size,
|
||||
scale: 1.,
|
||||
},
|
||||
optional,
|
||||
};
|
||||
specimen.params.push(params);
|
||||
}
|
||||
specimens.push(specimen);
|
||||
}
|
||||
|
||||
let mut specimen = Specimen {
|
||||
title: "Marble varying power, size = 32".into(),
|
||||
params: Vec::new(),
|
||||
};
|
||||
for power in 1..10 {
|
||||
let params = NoiseParams {
|
||||
width,
|
||||
height,
|
||||
noise_source,
|
||||
noise_type: NoiseType::Marble {
|
||||
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);
|
||||
}
|
||||
specimens.push(specimen);
|
||||
}
|
||||
specimens
|
||||
}
|
||||
|
||||
fn style(_req: &HttpRequest) -> Result<HttpResponse> {
|
||||
let bytes = include_bytes!("../templates/style.css");
|
||||
Ok(HttpResponse::Ok().content_type("text/css").body(&bytes[..]))
|
||||
}
|
||||
|
||||
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(optional: Option<Query<OptionalParams>>) -> Result<HttpResponse> {
|
||||
index(NoiseSource::Perlin, optional)
|
||||
}
|
||||
|
||||
fn index_lode(optional: Option<Query<OptionalParams>>) -> Result<HttpResponse> {
|
||||
index(NoiseSource::Lode, optional)
|
||||
}
|
||||
|
||||
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.
|
||||
img.write_to(&mut buf, image::ImageOutputFormat::PNG)
|
||||
.unwrap();
|
||||
Ok(HttpResponse::Ok().content_type("image/png").body(buf))
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
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()),
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
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()),
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::needless_pass_by_value)]
|
||||
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,
|
||||
noise_type: NoiseType::Marble {
|
||||
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()),
|
||||
})
|
||||
}
|
||||
|
||||
fn main() -> Result<(), std::io::Error> {
|
||||
stderrlog::new()
|
||||
.verbosity(2)
|
||||
.timestamp(stderrlog::Timestamp::Millisecond)
|
||||
.init()
|
||||
.unwrap();
|
||||
let opt = Opt::from_args();
|
||||
|
||||
server::new(|| {
|
||||
App::new()
|
||||
.resource("/noise/{noise_source}/{width}/{height}/scale/{scale}", |r| {
|
||||
r.with(noise_scale);
|
||||
}).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}/scale/{scale}",
|
||||
|r| {
|
||||
r.with(noise_marble);
|
||||
},
|
||||
).resource("/style.css", |r| r.f(style))
|
||||
.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)
|
||||
.unwrap()
|
||||
.run();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::str;
|
||||
|
||||
use actix_web::http::Method;
|
||||
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>, 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}/scale/{scale}",
|
||||
|r| {
|
||||
r.with(|p: Path<NoiseParamsMarble>| format!("{:?}", p));
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
let request = srv
|
||||
.client(Method::GET, "/noise/perlin/32/32/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 } 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
|
||||
.client(Method::GET, "/noise/lode/32/32/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: Lode, scale: 1.0 } None"
|
||||
);
|
||||
|
||||
let request = srv
|
||||
.client(Method::GET, "/noise/lode/32/32/turbulence/5")
|
||||
.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,
|
||||
"NoiseParamsTurbulence { width: 32, height: 32, noise_source: Lode, turbulence: 5 }"
|
||||
);
|
||||
|
||||
let request = srv
|
||||
.client(
|
||||
Method::GET,
|
||||
"/noise/lode/32/32/marble/period/1.,2.,3./power/32./size/4/scale/255.",
|
||||
)
|
||||
.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,
|
||||
"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 }"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user