Compare commits

..

12 Commits

7 changed files with 684 additions and 13 deletions

155
rtchallenge/Cargo.lock generated
View File

@ -1,5 +1,160 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
[[package]]
name = "adler32"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
[[package]]
name = "anyhow"
version = "1.0.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15af2628f6890fe2609a3b91bef4c83450512802e59489f9c1cb1fa5df064a61"
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "byteorder"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "crc32fast"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
dependencies = [
"cfg-if",
]
[[package]]
name = "deflate"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174"
dependencies = [
"adler32",
"byteorder",
]
[[package]]
name = "float-cmp"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4"
dependencies = [
"num-traits",
]
[[package]]
name = "miniz_oxide"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
dependencies = [
"adler32",
]
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
]
[[package]]
name = "png"
version = "0.16.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6"
dependencies = [
"bitflags",
"crc32fast",
"deflate",
"miniz_oxide",
]
[[package]]
name = "proc-macro2"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
dependencies = [
"proc-macro2",
]
[[package]] [[package]]
name = "rtchallenge" name = "rtchallenge"
version = "0.1.0" version = "0.1.0"
dependencies = [
"anyhow",
"float-cmp",
"png",
"thiserror",
]
[[package]]
name = "syn"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "thiserror"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa6f76457f59514c7eeb4e59d891395fab0b2fd1d40723ae737d64153392e9c6"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"

View File

@ -7,3 +7,7 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
anyhow = "1.0.41"
float-cmp = "0.8.0"
png = "0.16.8"
thiserror = "1.0.25"

View File

@ -0,0 +1,39 @@
use rtchallenge::tuples::{point, vector, Tuple};
#[derive(Debug)]
struct Environment {
gravity: Tuple,
wind: Tuple,
}
#[derive(Debug)]
struct Projectile {
position: Tuple,
velocity: Tuple,
}
fn tick(env: &Environment, proj: &Projectile) -> Projectile {
let position = proj.position + proj.velocity;
let velocity = proj.velocity + env.gravity + env.wind;
Projectile { position, velocity }
}
fn main() {
let mut p = Projectile {
position: point(0., 1., 0.),
velocity: 4. * vector(1., 1., 0.).normalize(),
};
let e = Environment {
gravity: vector(0., -0.1, 0.).normalize(),
wind: vector(-0.01, 0., 0.),
};
let mut i = 0;
while p.position.y > 0. {
p = tick(&e, &p);
println!("tick {}: position {:?}", i, p.position);
i += 1;
if i > 100 {
eprintln!("too many iterations");
return;
}
}
}

View File

@ -0,0 +1,71 @@
use anyhow::{bail, Result};
use rtchallenge::{
canvas::{canvas, Canvas},
tuples::{color, point, vector, Tuple},
};
#[derive(Debug)]
struct Environment {
gravity: Tuple,
wind: Tuple,
}
#[derive(Debug)]
struct Projectile {
position: Tuple,
velocity: Tuple,
}
fn tick(env: &Environment, proj: &Projectile) -> Projectile {
let position = proj.position + proj.velocity;
let velocity = proj.velocity + env.gravity + env.wind;
Projectile { position, velocity }
}
fn draw_dot(c: &mut Canvas, x: usize, y: usize) {
let red = color(1., 0., 0.);
c.set(x.saturating_sub(1), y.saturating_sub(1), red);
c.set(x, y.saturating_sub(1), red);
c.set(x + 1, y.saturating_sub(1), red);
c.set(x.saturating_sub(1), y, red);
c.set(x, y, red);
c.set(x + 1, y, red);
c.set(x.saturating_sub(1), y + 1, red);
c.set(x, y + 1, red);
c.set(x + 1, y + 1, red);
}
fn main() -> Result<()> {
let position = point(0., 1., 0.);
let velocity = vector(1., 1.8, 0.).normalize() * 11.25;
let mut p = Projectile { position, velocity };
let gravity = vector(0., -0.1, 0.);
let wind = vector(-0.01, 0., 0.);
let e = Environment { gravity, wind };
let w = 800;
let h = 600;
let mut c = canvas(w, h);
let mut i = 0;
let w = w as f32;
let h = h as f32;
while p.position.y > 0. {
p = tick(&e, &p);
println!("tick {}: proj {:?}", i, p);
let x = p.position.x;
let y = p.position.y;
if x > 0. && x < w && y > 0. && y < h {
let x = x as usize;
let y = (h - y) as usize;
draw_dot(&mut c, x, y)
}
i += 1;
if i > 1000 {
bail!("too many iterations");
}
}
let path = "/tmp/output.png";
println!("saving output to {}", path);
c.write_to_file(path)?;
Ok(())
}

97
rtchallenge/src/canvas.rs Normal file
View File

@ -0,0 +1,97 @@
use std::fs::File;
use std::io::BufWriter;
use std::path::Path;
use png;
use thiserror::Error;
use crate::tuples::{color, Color};
#[derive(Error, Debug)]
pub enum CanvasError {
#[error("faile to write canvas")]
IOError(#[from] std::io::Error),
#[error("faile to encode canvas to png")]
EncodingError(#[from] png::EncodingError),
}
pub struct Canvas {
pub height: usize,
pub width: usize,
pub pixels: Vec<Color>,
}
pub fn canvas(width: usize, height: usize) -> Canvas {
let pixels = vec![color(0., 0., 0.); width * height];
Canvas {
width,
height,
pixels,
}
}
impl Canvas {
pub fn set(&mut self, x: usize, y: usize, c: Color) {
if x > self.width {
return;
}
if y > self.height {
return;
}
self.pixels[x + y * self.width] = c;
}
pub fn get(&mut self, x: usize, y: usize) -> Color {
self.pixels[x + y * self.width]
}
pub fn write_to_file<P>(&self, path: P) -> Result<(), CanvasError>
where
P: AsRef<Path>,
{
let path = Path::new(path.as_ref());
let file = File::create(path)?;
let ref mut w = BufWriter::new(file);
let mut encoder = png::Encoder::new(w, self.width as u32, self.height as u32);
encoder.set_color(png::ColorType::RGB);
encoder.set_depth(png::BitDepth::Eight);
let mut writer = encoder.write_header()?;
let data: Vec<u8> = self
.pixels
.iter()
.flat_map(|p| {
vec![
(p.red.clamp(0., 1.) * 255.) as u8,
(p.green.clamp(0., 1.) * 255.) as u8,
(p.blue.clamp(0., 1.) * 255.) as u8,
]
})
.collect();
writer.write_image_data(&data)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::canvas;
use crate::tuples::color;
#[test]
fn create_canvas() {
let c = canvas(10, 20);
assert_eq!(c.width, 10);
assert_eq!(c.height, 20);
let black = color(0., 0., 0.);
for (i, p) in c.pixels.iter().enumerate() {
assert_eq!(p, &black, "pixel {} not {:?}: {:?}", i, &black, p);
}
}
#[test]
fn write_to_canvas() {
let mut c = canvas(10, 20);
let red = color(1., 0., 0.);
c.set(2, 3, red);
assert_eq!(c.get(2, 3), red);
}
}

View File

@ -1 +1,2 @@
mod tuples; pub mod canvas;
pub mod tuples;

View File

@ -1,36 +1,218 @@
#[derive(Debug, PartialEq)] use std::ops::{Add, Div, Mul, Neg, Sub};
struct Tuple { #[derive(Debug, PartialEq, Copy, Clone)]
x: f32, pub struct Tuple {
y: f32, pub x: f32,
z: f32, pub y: f32,
w: f32, pub z: f32,
pub w: f32,
} }
impl Tuple { impl Tuple {
fn is_point(&self) -> bool { pub fn is_point(&self) -> bool {
self.w == 1.0 self.w == 1.0
} }
fn is_vector(&self) -> bool { pub fn is_vector(&self) -> bool {
self.w == 0.0 self.w == 0.0
} }
pub fn magnitude(&self) -> f32 {
(self.x * self.x + self.y * self.y + self.z * self.z + self.w * self.w).sqrt()
}
pub fn normalize(&self) -> Tuple {
let m = self.magnitude();
Tuple {
x: self.x / m,
y: self.y / m,
z: self.z / m,
w: self.w / m,
}
}
} }
fn point(x: f32, y: f32, z: f32) -> Tuple { impl Add for Tuple {
type Output = Self;
fn add(self, other: Self) -> Self {
Self {
x: self.x + other.x,
y: self.y + other.y,
z: self.z + other.z,
w: self.w + other.w,
}
}
}
impl Div<f32> for Tuple {
type Output = Self;
fn div(self, rhs: f32) -> Self::Output {
Self::Output {
x: self.x / rhs,
y: self.y / rhs,
z: self.z / rhs,
w: self.w / rhs,
}
}
}
impl Mul<f32> for Tuple {
type Output = Self;
fn mul(self, rhs: f32) -> Self::Output {
Self::Output {
x: self.x * rhs,
y: self.y * rhs,
z: self.z * rhs,
w: self.w * rhs,
}
}
}
impl Mul<Tuple> for f32 {
type Output = Tuple;
fn mul(self, rhs: Tuple) -> Self::Output {
Self::Output {
x: self * rhs.x,
y: self * rhs.y,
z: self * rhs.z,
w: self * rhs.w,
}
}
}
impl Neg for Tuple {
type Output = Self;
fn neg(self) -> Self::Output {
Self {
x: -self.x,
y: -self.y,
z: -self.z,
w: -self.w,
}
}
}
impl Sub for Tuple {
type Output = Self;
fn sub(self, other: Self) -> Self {
Self {
x: self.x - other.x,
y: self.y - other.y,
z: self.z - other.z,
w: self.w - other.w,
}
}
}
pub fn point(x: f32, y: f32, z: f32) -> Tuple {
tuple(x, y, z, 1.0) tuple(x, y, z, 1.0)
} }
fn vector(x: f32, y: f32, z: f32) -> Tuple { pub fn vector(x: f32, y: f32, z: f32) -> Tuple {
tuple(x, y, z, 0.0) tuple(x, y, z, 0.0)
} }
fn tuple(x: f32, y: f32, z: f32, w: f32) -> Tuple { pub fn tuple(x: f32, y: f32, z: f32, w: f32) -> Tuple {
Tuple { x, y, z, w } Tuple { x, y, z, w }
} }
pub fn dot(a: Tuple, b: Tuple) -> f32 {
a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w
}
pub fn cross(a: Tuple, b: Tuple) -> Tuple {
vector(
a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x,
)
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Color {
pub red: f32,
pub green: f32,
pub blue: f32,
}
pub fn color(red: f32, green: f32, blue: f32) -> Color {
Color { red, green, blue }
}
impl Add for Color {
type Output = Self;
fn add(self, other: Self) -> Self {
Self {
red: self.red + other.red,
green: self.green + other.green,
blue: self.blue + other.blue,
}
}
}
impl Div<f32> for Color {
type Output = Self;
fn div(self, rhs: f32) -> Self::Output {
Self::Output {
red: self.red / rhs,
green: self.green / rhs,
blue: self.blue / rhs,
}
}
}
impl Mul<f32> for Color {
type Output = Self;
fn mul(self, rhs: f32) -> Self::Output {
Self::Output {
red: self.red * rhs,
green: self.green * rhs,
blue: self.blue * rhs,
}
}
}
impl Mul<Color> for f32 {
type Output = Color;
fn mul(self, rhs: Color) -> Self::Output {
Self::Output {
red: self * rhs.red,
green: self * rhs.green,
blue: self * rhs.blue,
}
}
}
impl Mul<Color> for Color {
type Output = Color;
fn mul(self, rhs: Color) -> Self::Output {
Self::Output {
red: self.red * rhs.red,
green: self.green * rhs.green,
blue: self.blue * rhs.blue,
}
}
}
impl Neg for Color {
type Output = Self;
fn neg(self) -> Self::Output {
Self {
red: -self.red,
green: -self.green,
blue: -self.blue,
}
}
}
impl Sub for Color {
type Output = Self;
fn sub(self, other: Self) -> Self {
Self {
red: self.red - other.red,
green: self.green - other.green,
blue: self.blue - other.blue,
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{point, tuple, vector}; use float_cmp::approx_eq;
use super::{color, cross, dot, point, tuple, vector};
#[test] #[test]
fn is_point() { fn is_point() {
// A tuple with w = 1 is a point // A tuple with w = 1 is a point
@ -61,4 +243,126 @@ mod tests {
fn vector_tuple() { fn vector_tuple() {
assert_eq!(vector(4., -4., 3.), tuple(4., -4., 3., 0.)) assert_eq!(vector(4., -4., 3.), tuple(4., -4., 3., 0.))
} }
#[test]
fn add_two_tuples() {
let a1 = tuple(3., -2., 5., 1.);
let a2 = tuple(-2., 3., 1., 0.);
assert_eq!(a1 + a2, tuple(1., 1., 6., 1.));
}
#[test]
fn sub_two_points() {
let p1 = point(3., 2., 1.);
let p2 = point(5., 6., 7.);
assert_eq!(p1 - p2, vector(-2., -4., -6.));
}
#[test]
fn sub_vector_point() {
let p = point(3., 2., 1.);
let v = vector(5., 6., 7.);
assert_eq!(p - v, point(-2., -4., -6.));
}
#[test]
fn sub_two_vectors() {
let v1 = vector(3., 2., 1.);
let v2 = vector(5., 6., 7.);
assert_eq!(v1 - v2, vector(-2., -4., -6.));
}
#[test]
fn sub_zero_vector() {
let zero = vector(0., 0., 0.);
let v = vector(1., -2., 3.);
assert_eq!(zero - v, vector(-1., 2., -3.));
}
#[test]
fn negate_tuple() {
let a = tuple(1., -2., 3., -4.);
assert_eq!(-a, tuple(-1., 2., -3., 4.));
}
#[test]
fn mul_tuple_scalar() {
let a = tuple(1., -2., 3., -4.);
assert_eq!(a * 3.5, tuple(3.5, -7., 10.5, -14.));
assert_eq!(3.5 * a, tuple(3.5, -7., 10.5, -14.));
}
#[test]
fn mul_tuple_fraction() {
let a = tuple(1., -2., 3., -4.);
assert_eq!(a * 0.5, tuple(0.5, -1., 1.5, -2.));
assert_eq!(0.5 * a, tuple(0.5, -1., 1.5, -2.));
}
#[test]
fn div_tuple_scalar() {
let a = tuple(1., -2., 3., -4.);
assert_eq!(a / 2., tuple(0.5, -1., 1.5, -2.));
}
#[test]
fn vector_magnitude() {
assert_eq!(1., vector(1., 0., 0.).magnitude());
assert_eq!(1., vector(0., 1., 0.).magnitude());
assert_eq!(1., vector(0., 0., 1.).magnitude());
assert_eq!(14_f32.sqrt(), vector(1., 2., 3.).magnitude());
assert_eq!(14_f32.sqrt(), vector(-1., -2., -3.).magnitude());
}
#[test]
fn vector_normalize() {
assert_eq!(vector(1., 0., 0.), vector(4., 0., 0.).normalize());
assert_eq!(
vector(1. / 14_f32.sqrt(), 2. / 14_f32.sqrt(), 3. / 14_f32.sqrt()),
vector(1., 2., 3.).normalize()
);
}
#[test]
fn vector_normalize_magnitude() {
assert!(approx_eq!(
f32,
1.,
vector(1., 2., 3.).normalize().magnitude(),
ulps = 1
));
}
#[test]
fn dot_two_tuples() {
let a = vector(1., 2., 3.);
let b = vector(2., 3., 4.);
assert_eq!(20., dot(a, b));
}
#[test]
fn cross_two_tuples() {
let a = vector(1., 2., 3.);
let b = vector(2., 3., 4.);
assert_eq!(vector(-1., 2., -1.), cross(a, b));
assert_eq!(vector(1., -2., 1.), cross(b, a));
}
#[test]
fn color_rgb() {
let c = color(-0.5, 0.4, 1.7);
assert_eq!(c.red, -0.5);
assert_eq!(c.green, 0.4);
assert_eq!(c.blue, 1.7);
}
#[test]
fn add_color() {
let c1 = color(0.9, 0.6, 0.75);
let c2 = color(0.7, 0.1, 0.25);
assert_eq!(c1 + c2, color(0.9 + 0.7, 0.6 + 0.1, 0.75 + 0.25));
}
#[test]
fn sub_color() {
let c1 = color(0.9, 0.6, 0.75);
let c2 = color(0.7, 0.1, 0.25);
assert_eq!(c1 - c2, color(0.9 - 0.7, 0.6 - 0.1, 0.75 - 0.25));
}
#[test]
fn mul_color_scalar() {
let c = color(0.2, 0.3, 0.4);
assert_eq!(c * 2., color(0.2 * 2., 0.3 * 2., 0.4 * 2.));
assert_eq!(2. * c, color(0.2 * 2., 0.3 * 2., 0.4 * 2.));
}
#[test]
fn mul_colors() {
let c1 = color(1., 0.2, 0.4);
let c2 = color(0.9, 1., 0.1);
assert_eq!(c1 * c2, color(1.0 * 0.9, 0.2 * 1., 0.4 * 0.1));
}
} }