Compare commits

...

5 Commits

4 changed files with 137 additions and 13 deletions

View File

@ -0,0 +1,51 @@
use std::f32::consts::PI;
use anyhow::Result;
use rtchallenge::{
canvas::Canvas,
matrices::Matrix4x4,
tuples::{Color, Tuple},
};
fn draw_dot(c: &mut Canvas, x: usize, y: usize) {
let red = Color::new(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 w = 200;
let h = w;
let mut c = Canvas::new(w, h);
let t = Matrix4x4::translate(0., 0.4, 0.);
let p = Tuple::point(0., 0., 0.);
let rot_hour = Matrix4x4::rotation_z(-PI / 6.);
let mut p = t * p;
let w = w as f32;
let h = h as f32;
let h_w = w / 2.0;
let h_h = h / 2.0;
// The 'world' exists between -0.5 - 0.5 in X-Y plane.
// To convert to screen space, we translate by 0.5, scale to canvas size,
// and invert the Y-axis.
let world_to_screen =
Matrix4x4::scaling(w as f32, -h as f32, 1.0) * Matrix4x4::translate(0.5, -0.5, 0.);
for _ in 0..12 {
let canvas_pixel = world_to_screen * p;
draw_dot(&mut c, canvas_pixel.x as usize, canvas_pixel.y as usize);
p = rot_hour * p;
}
let path = "/tmp/eoc4.png";
println!("saving output to {}", path);
c.write_to_file(path)?;
Ok(())
}

View File

@ -1,3 +1,6 @@
pub mod canvas;
pub mod matrices;
pub mod tuples;
/// Value considered close enough for PartialEq implementations.
pub const EPSILON: f32 = 0.00001;

View File

@ -1,13 +1,7 @@
use std::fmt;
use std::ops::{Index, IndexMut, Mul};
// Implement a PartialEq that does approx_eq internally.
//use float_cmp::{ApproxEq, F32Margin};
use crate::tuples::Tuple;
/// Value considered close enough for PartialEq implementations.
const EPSILON: f32 = 0.00001;
use crate::{tuples::Tuple, EPSILON};
#[derive(Debug)]
pub struct Matrix2x2 {
@ -170,6 +164,36 @@ impl PartialEq for Matrix3x3 {
#[derive(Copy, Clone, Default)]
/// Matrix4x4 represents a 4x4 matrix in row-major form. So, element `m[i][j]` corresponds to m<sub>i,j</sub>
/// where `i` is the row number and `j` is the column number.
///
/// # Examples
/// ```
/// use std::f32::consts::PI;
///
/// use rtchallenge::{matrices::Matrix4x4, tuples::Tuple};
///
/// // Individual transformations are applied in sequence.
/// let p = Tuple::point(1., 0., 1.);
/// let a = Matrix4x4::rotation_x(PI / 2.);
/// let b = Matrix4x4::scaling(5., 5., 5.);
/// let c = Matrix4x4::translate(10., 5., 7.);
/// // Apply rotation first.
/// let p2 = a * p;
/// assert_eq!(p2, Tuple::point(1., -1., 0.));
/// // Then apply scaling.
/// let p3 = b * p2;
/// assert_eq!(p3, Tuple::point(5., -5., 0.));
/// // Then apply translation.
/// let p4 = c * p3;
/// assert_eq!(p4, Tuple::point(15., 0., 7.));
///
/// // Chained transformations must be applied in reverse order.
/// let p = Tuple::point(1., 0., 1.);
/// let a = Matrix4x4::rotation_x(PI / 2.);
/// let b = Matrix4x4::scaling(5., 5., 5.);
/// let c = Matrix4x4::translate(10., 5., 7.);
/// let t = c * b * a;
/// assert_eq!(t * p, Tuple::point(15., 0., 7.));
/// ```
pub struct Matrix4x4 {
m: [[f32; 4]; 4],
}
@ -402,6 +426,50 @@ impl Matrix4x4 {
],
}
}
/// Create a transform matrix that will shear (skew) points.
/// # Examples
///
/// ```
/// use rtchallenge::{matrices::Matrix4x4, tuples::Tuple};
///
/// // A shearing transform moves x in proportion to y.
/// let transform = Matrix4x4::shearing(1.,0.,0.,0.,0.,0.);
/// let p = Tuple::point(2.,3.,4.);
/// assert_eq!(transform * p, Tuple::point(5.,3.,4.));
///
/// // A shearing transform moves x in proportion to z.
/// let transform = Matrix4x4::shearing(0.,1.,0.,0.,0.,0.);
/// let p = Tuple::point(2.,3.,4.);
/// assert_eq!(transform * p, Tuple::point(6.,3.,4.));
///
/// // A shearing transform moves y in proportion to x.
/// let transform = Matrix4x4::shearing(0.,0.,1.,0.,0.,0.);
/// let p = Tuple::point(2.,3.,4.);
/// assert_eq!(transform * p, Tuple::point(2.,5.,4.));
///
/// // A shearing transform moves y in proportion to z.
/// let transform = Matrix4x4::shearing(0.,0.,0.,1.,0.,0.);
/// let p = Tuple::point(2.,3.,4.);
/// assert_eq!(transform * p, Tuple::point(2.,7.,4.));
///
/// // A shearing transform moves z in proportion to x.
/// let transform = Matrix4x4::shearing(0.,0.,0.,0.,1.,0.);
/// let p = Tuple::point(2.,3.,4.);
/// assert_eq!(transform * p, Tuple::point(2.,3.,6.));
///
/// // A shearing transform moves z in proportion to y.
/// let transform = Matrix4x4::shearing(0.,0.,0.,0.,0.,1.);
/// let p = Tuple::point(2.,3.,4.);
/// assert_eq!(transform * p, Tuple::point(2.,3.,7.));
pub fn shearing(xy: f32, xz: f32, yx: f32, yz: f32, zx: f32, zy: f32) -> Matrix4x4 {
Matrix4x4::new(
[1., xy, xz, 0.],
[yx, 1., yz, 0.],
[zx, zy, 1., 0.],
[0., 0., 0., 1.],
)
}
/// Returns a new matrix that is the inverse of self. If self is A, inverse returns A<sup>-1</sup>, where
/// AA<sup>-1</sup> = I.

View File

@ -1,5 +1,7 @@
use std::ops::{Add, Div, Mul, Neg, Sub};
use crate::EPSILON;
#[derive(Debug, Copy, Clone)]
pub struct Tuple {
pub x: f32,
@ -116,10 +118,10 @@ impl Sub for Tuple {
impl PartialEq for Tuple {
fn eq(&self, rhs: &Tuple) -> bool {
((self.x - rhs.x).abs() < f32::EPSILON)
&& ((self.y - rhs.y).abs() < f32::EPSILON)
&& ((self.z - rhs.z).abs() < f32::EPSILON)
&& ((self.w - rhs.w).abs() < f32::EPSILON)
((self.x - rhs.x).abs() < EPSILON)
&& ((self.y - rhs.y).abs() < EPSILON)
&& ((self.z - rhs.z).abs() < EPSILON)
&& ((self.w - rhs.w).abs() < EPSILON)
}
}
pub fn dot(a: Tuple, b: Tuple) -> f32 {
@ -221,7 +223,7 @@ impl Sub for Color {
#[cfg(test)]
mod tests {
use super::{cross, dot, Color, Tuple};
use super::{cross, dot, Color, Tuple, EPSILON};
#[test]
fn is_point() {
// A tuple with w = 1 is a point
@ -326,7 +328,7 @@ mod tests {
#[test]
fn vector_normalize_magnitude() {
let len = Tuple::vector(1., 2., 3.).normalize().magnitude();
assert!((1. - len).abs() < f32::EPSILON);
assert!((1. - len).abs() < EPSILON);
}
#[test]
fn dot_two_tuples() {