diff --git a/fractional/notes/math.url b/fractional/notes/math.url new file mode 100644 index 0000000..b2b55d3 --- /dev/null +++ b/fractional/notes/math.url @@ -0,0 +1 @@ +https://www.themathpage.com/Alg/reciprocals.htm diff --git a/fractional/notes/texture-mapping.url b/fractional/notes/texture-mapping.url index fd55dc7..88914ef 100644 --- a/fractional/notes/texture-mapping.url +++ b/fractional/notes/texture-mapping.url @@ -11,3 +11,5 @@ http://www.lysator.liu.se/~mikaelk/doc/perspectivetexture/ # Shader... This also describes z-Buffer... :) https://people.ece.cornell.edu/land/OldStudentProjects/cs490-95to96/GUO/report.html +https://software.intel.com/en-us/articles/the-basics-of-the-art-of-lighting-part-3-lighting-and-shaders/ +https://docs.unity3d.com/Manual/Lighting.html diff --git a/fractional/src/geometry.rs b/fractional/src/geometry.rs index 7fa883c..ede5968 100644 --- a/fractional/src/geometry.rs +++ b/fractional/src/geometry.rs @@ -27,18 +27,27 @@ use crate::transform::TMatrix; use crate::trigonometry::Trig; use crate::vector::Vector; +#[derive(Debug, Clone)] +pub struct Face +where T: Add + Sub + Neg + Mul + Div + Copy + Trig { + corners :Vec, + normal :Option>, +} + #[derive(Debug)] pub struct Polyeder -where T: Add + Sub + Neg + Mul + Div + Debug + Copy + Trig { - points :Vec>, - faces :Vec>, - normals :Vec>, +where T: Add + Sub + Neg + Mul + Div + Copy + Trig { + points :Vec>, + faces :Vec>, } pub trait Primitives where T: Add + Sub + Neg + Mul + Div + Debug + Copy + Trig + From { fn transform(&self, m :&TMatrix) -> Self; - fn project(&self, camera :&Camera) -> Vec; + fn project( &self + , camera :&Camera + , light :&DirectLight + , col :u32 ) -> Vec<(Polygon, u32)>; } pub struct Camera @@ -48,6 +57,11 @@ where T: Add + Sub + Neg + Mul + Div + Debug + Copy + Trig + From { project :TMatrix, } +pub struct DirectLight +where T: Add + Sub + Neg + Mul + Div + Debug + Copy + Trig + From { + direction: Vector, +} + impl Camera where T: Add + Sub + Neg + Mul + Div @@ -83,10 +97,46 @@ where T: Add + Sub + Neg } } +impl DirectLight +where T: Add + Sub + Neg + + Mul + Div + + Debug + Copy + Trig + From { + pub fn new(v :Vector) -> Self { + DirectLight{ direction: v } + } + + pub fn dir(&self) -> Vector { + self.direction + } +} + +impl Face +where T: Add + Sub + Neg + + Mul + Div + + Debug + Copy + Trig + From { + fn new(corners :Vec, ps :&[Vector]) -> Self { + let mut f = Face{ corners: corners, normal: None }; + f.update_normal(ps); + f + } + + fn update_normal(&mut self, ps :&[Vector]) { + let edge10 = ps[self.corners[1]] - ps[self.corners[0]]; + let edge12 = ps[self.corners[1]] - ps[self.corners[2]]; + self.normal = Some(edge10 * edge12); + } +} + impl Polyeder where T: Add + Sub + Neg + Mul + Div + Debug + Copy + Trig + From { + fn update_normals(&mut self) { + for f in self.faces.iter_mut() { + f.update_normal(&self.points); + } + } + // https://rechneronline.de/pi/tetrahedron.php pub fn tetrahedron(a :T) -> Polyeder { let f0 :T = 0.into(); @@ -106,49 +156,86 @@ where T: Add + Sub + Neg // half the deeps in z let _zh :T = T::sqrt(f3).unwrap() / f4 * a; - Polyeder{ points: vec!( Vector( f0, yc, f0) - , Vector(-ah, -yi, -zi) - , Vector( ah, -yi, -zi) - , Vector( f0, -yi, zc) ) - , faces: vec!( vec!(1, 2, 3) - , vec!(1, 0, 2) - , vec!(3, 0, 1) - , vec!(2, 0, 3) )} + let ps = vec!( Vector( f0, yc, f0) + , Vector(-ah, -yi, -zi) + , Vector( ah, -yi, -zi) + , Vector( f0, -yi, zc) ); + + let fs = vec!( Face::new(vec!(1, 2, 3), &ps) + , Face::new(vec!(1, 0, 2), &ps) + , Face::new(vec!(3, 0, 1), &ps) + , Face::new(vec!(2, 0, 3), &ps) ); + + Polyeder{ points: ps, faces: fs } + } + + pub fn triangle(a :T) -> Polyeder { + let f0 :T = 0.into(); + let f3 :T = 3.into(); + let f6 :T = 6.into(); + let zi :T = T::sqrt(f3).unwrap() / f6 * a; + let zc :T = T::sqrt(f3).unwrap() / f3 * a; + let ah :T = a / 2.into(); + + let ps = vec!( Vector(-ah, f0, -zi) + , Vector( f0, f0, zc) + , Vector( ah, f0, -zi) ); + + let fs = vec!(Face::new(vec!(0, 1, 2), &ps)); + + println!("== {:?}", fs); + + Polyeder{ points: ps, faces: fs } } pub fn cube(a :T) -> Polyeder { let ah :T = a / From::::from(2); - Polyeder{ points: vec!( Vector(-ah, ah, -ah) // 0 => front 1 - , Vector(-ah, -ah, -ah) // 1 => front 2 - , Vector( ah, -ah, -ah) // 2 => front 3 - , Vector( ah, ah, -ah) // 3 => front 4 - , Vector(-ah, ah, ah) // 4 => back 1 - , Vector(-ah, -ah, ah) // 5 => back 2 - , Vector( ah, -ah, ah) // 6 => back 3 - , Vector( ah, ah, ah) ) // 7 => back 4 - , faces: vec!( vec!(0, 1, 2, 3) // front - , vec!(7, 6, 5, 4) // back - , vec!(1, 5, 6, 2) // top - , vec!(0, 3, 7, 4) // bottom - , vec!(0, 4, 5, 1) // left - , vec!(2, 6, 7, 3) )} // right + let ps = vec!( Vector(-ah, ah, -ah) // 0 => front 1 + , Vector(-ah, -ah, -ah) // 1 => front 2 + , Vector( ah, -ah, -ah) // 2 => front 3 + , Vector( ah, ah, -ah) // 3 => front 4 + , Vector(-ah, ah, ah) // 4 => back 1 + , Vector(-ah, -ah, ah) // 5 => back 2 + , Vector( ah, -ah, ah) // 6 => back 3 + , Vector( ah, ah, ah) ); // 7 => back 4 + + let fs = vec!( Face::new(vec!(0, 1, 2, 3), &ps) // front + , Face::new(vec!(7, 6, 5, 4), &ps) // back + , Face::new(vec!(1, 5, 6, 2), &ps) // top + , Face::new(vec!(0, 3, 7, 4), &ps) // bottom + , Face::new(vec!(0, 4, 5, 1), &ps) // left + , Face::new(vec!(2, 6, 7, 3), &ps) ); // right + + Polyeder{ points: ps, faces: fs } } } impl Primitives for Polyeder where T: Add + Sub + Neg + Mul + Div - + Debug + Copy + Trig + From + From { + + Debug + Copy + Trig + From + PartialOrd { fn transform(&self, m :&TMatrix) -> Self { - Polyeder{ points: self.points.iter().map(|p| m.apply(p)).collect() - , faces: self.faces.to_vec() } + let Polyeder{ points: ps, faces: fs } = self; + + let mut p = Polyeder{ points: ps.iter().map(|p| m.apply(p)).collect() + , faces: fs.to_vec() }; + + // TODO alternatively we could rotate the normals too, but this cannot + // done with the original matrix… the question is, what is faster. + p.update_normals(); + p } // TODO for now we assume already prejected vertices (points) // in future we need to distinguish more clear between vertex and point // and projected_point. - fn project(&self, _camera :&Camera) -> Vec { + fn project( &self + , camera :&Camera + , light :&DirectLight + , color :u32 ) -> Vec<(Polygon, u32)> { + // Helper to create a Polygon from Coordinates… + // TODO probably there needs to be a Polygon constructor for this. fn polygon(c :I) -> Polygon where I: Iterator { Polygon(Coordinates(c.collect())) @@ -156,12 +243,34 @@ where T: Add + Sub + Neg // this one does the projection... as the projection was the last // matrix we do not need to do it here. - // let to_coord = |p :&usize| _camera.project(self.points[*p]); - let to_coord = |p :&usize| { - let v = self.points[*p]; - Coordinate(v.x().round(), v.y().round()) }; - let to_poly = |f :&Vec| polygon(f.iter().map(to_coord)); + let to_coord = |p :&usize| camera.project(self.points[*p]); + let to_poly = |f :&Face| { + let pg = polygon(f.corners.iter().map(to_coord)); + let mut r :T = (((color >> 16) & 0xFF) as i32).into(); + let mut g :T = (((color >> 8) & 0xFF) as i32).into(); + let mut b :T = (((color ) & 0xFF) as i32).into(); + let lf :T = match f.normal { + None => 1.into(), + Some(n) => n.dot(light.dir()) + / (n.mag() * light.dir().mag()), + }; + + if lf < 0.into() { + r = r * -lf; + g = g * -lf; + b = b * -lf; + + let c :u32 = (r.round() as u32) << 16 + | (g.round() as u32) << 8 + | (b.round() as u32); + + Some((pg, c)) + } else { + None + }}; - self.faces.iter().map(to_poly).collect() + let mut ps :Vec<(Polygon, u32)> = self.faces.iter().filter_map(to_poly).collect(); + ps.sort_by(|a, b| a.1.cmp(&b.1)); + ps } } diff --git a/fractional/src/main.rs b/fractional/src/main.rs index 028732e..1a24aef 100644 --- a/fractional/src/main.rs +++ b/fractional/src/main.rs @@ -39,7 +39,7 @@ use fractional::transform::{TMatrix, translate, rotate_x, rotate_y, rotate_z, ro use fractional::xcb::XcbEasel; use fractional::easel::Canvas; -use fractional::geometry::{Camera,Polyeder,Primitives}; +use fractional::geometry::{Camera,DirectLight,Polyeder,Primitives}; fn mean(v: &Vec) -> Result { let r = v.iter().fold(0, |acc, x| acc + x); @@ -162,16 +162,16 @@ fn _vector(v1 :Vector, v2 :Vector, s :T) + Mul + Div + Trig + Copy + Display { println!("{:>14} : {}", "Vector v1", v1); println!("{:>14} : {}", "Vector v2", v2); - println!("{:>14} : {}", "abs v1", v1.abs()); + println!("{:>14} : {}", "magnitude v1", v1.mag()); println!("{:>14} : {}", "-v1", -v1); println!("{:>14} : {}", "v1 + v1", v1 + v1); println!("{:>14} : {}", "v1 - v1", v1 - v1); println!("{:>14} : {}", "v2 - v1", v2 - v1); println!("{:>14} : {}", format!("v1 * {}", s), v1.mul(&s)); println!("{:>14} : {}", "norm v1", v1.norm()); - println!("{:>14} : {}", "abs norm v1", v1.norm().abs()); - println!("{:>14} : {}", "abs v1", v1.abs()); - println!("{:>14} : {}", "norm * abs", v1.norm().mul(&v1.abs())); + println!("{:>14} : {}", "magnitude norm v1", v1.norm().mag()); + println!("{:>14} : {}", "magnitude v1", v1.mag()); + println!("{:>14} : {}", "norm * magnitude", v1.norm().mul(&v1.mag())); println!("{:>14} : {}", "distance v1 v2", v1.distance(v2)); println!("{:>14} : {}", "distance v2 v1", v2.distance(v1)); println!("{:>14} : {}", "v1 dot v2", v1.dot(v2)); @@ -313,15 +313,17 @@ fn _line() { fn _democanvas( xcb :&XcbEasel , title :&'static str , tx :mpsc::Sender + , _triangle :Polyeder , tetrahedron :Polyeder - , cube :Polyeder ) + , cube :Polyeder + , light :DirectLight ) where T: 'static + Add + Sub + Neg + Mul + Div - + Debug + Copy + Trig + Send + From { + + Debug + Copy + Trig + Send + From + PartialOrd { let mut canvas = xcb.canvas(151, 151).unwrap(); - let camera = Camera::::new(&canvas, 45); // the orig. view angle - // was 50. + let camera = Camera::::new(&canvas, 45); // the orig. view angle + // was 50. canvas.set_title(title); canvas.init_events(); @@ -333,24 +335,31 @@ fn _democanvas( xcb :&XcbEasel let mut last = Instant::now(); let t :TMatrix = translate(Vector(0.into(), 0.into(), 150.into())); - let p :TMatrix = camera.get_projection(); + // We do not need this here… it is used within projection… + // let p :TMatrix = camera.get_projection(); loop { let deg = ((start.elapsed() / 25).as_millis() % 360) as i32; let rz :TMatrix = rotate_z(deg); - let rot1 = TMatrix::combine(vec!(rz, rotate_x(-deg*2), t, p)); - let rot2 = TMatrix::combine(vec!(rz, rotate_y(-deg*2), t, p)); + // I can not apply the projection in one turn, as I generate the + // normals always… and this is no longer possible after the + // projection… + // let rot1 = TMatrix::combine(vec!(rz, rotate_x(-deg*2), t, p)); + // let rot2 = TMatrix::combine(vec!(rz, rotate_y(-deg*2), t, p)); + let rot1 = TMatrix::combine(vec!(rz, rotate_x(-deg*2), t)); + let rot2 = TMatrix::combine(vec!(rz, rotate_y(-deg*2), t)); let objects = vec!( (tetrahedron.transform(&rot1), 0xFFFF00) , ( cube.transform(&rot2), 0x0000FF) ); + //let objects = vec!( (triangle.transform(&rot1), 0xFFFF00) ); canvas.clear(); for (o, color) in objects { - for pg in o.project(&camera) { - canvas.draw(&pg, Coordinate(0,0), color); + for (pg, c) in o.project(&camera, &light, color) { + canvas.draw(&pg, Coordinate(0,0), c); } } @@ -400,11 +409,17 @@ fn main() { let (tx, rx) = mpsc::channel(); _democanvas( &xcb, "Something...(f64)", tx.clone() + , Polyeder::triangle(60.0) , Polyeder::tetrahedron(60.0) - , Polyeder::cube(60.0) ); + , Polyeder::cube(60.0) + , DirectLight::new(Vector(0.0, 0.0, 1.0)) ); _democanvas( &xcb, "Something...(Fractional)", tx.clone() + , Polyeder::triangle(Fractional(60,1)) , Polyeder::tetrahedron(Fractional(60,1)) - , Polyeder::cube(Fractional(60,1)) ); + , Polyeder::cube(Fractional(60,1)) + , DirectLight::new(Vector( Fractional(0,1) + , Fractional(0,1) + , Fractional(1,1) )) ); for x in rx { match x { diff --git a/fractional/src/vector.rs b/fractional/src/vector.rs index e56afae..30d126a 100644 --- a/fractional/src/vector.rs +++ b/fractional/src/vector.rs @@ -34,7 +34,7 @@ where T: Add + Sub + Neg pub fn y(self) -> T { self.1 } pub fn z(self) -> T { self.2 } - pub fn abs(self) -> T { + pub fn mag(self) -> T { let Vector(x, y, z) = self; (x * x + y * y + z * z).sqrt().unwrap() } @@ -53,12 +53,12 @@ where T: Add + Sub + Neg pub fn norm(self) -> Self { // TODO This can result in 0 or inf Vectors… - // Maybe we need to handle zero and inf abs here… - self.mul(&self.abs()) + // Maybe we need to handle zero and inf magnitude here… + self.mul(&self.mag().recip()) } pub fn distance(self, other :Self) -> T { - (self - other).abs() + (self - other).mag() } }