// // Basic geometric things... // // Georg Hopp // // Copyright © 2019 Georg Hopp // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // use std::convert::{From, Into}; use std::ops::{Add,Sub,Neg,Mul,Div}; use std::fmt::Debug; use crate::easel::{Canvas, Coordinate, Coordinates, Polygon}; use crate::transform::{TMatrix, Transformable}; 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, PartialEq, Eq, Clone, Copy)] pub struct Point(pub Vector, T) where T: Add + Sub + Neg + Mul + Div + PartialEq + Copy + Trig; impl Point where T: Add + Sub + Neg + Mul + Div + PartialEq + Trig + Copy + From { pub fn new(x :T, y :T, z :T) -> Self { Self(Vector(x, y, z), 1.into()) } } impl Add for Point where T: Add + Sub + Neg + Mul + Div + PartialEq + Trig + Copy { type Output = Self; fn add(self, other :Self) -> Self { let Point(v1, w1) = self; let Point(v2, w2) = other; Self(v1 + v2, w1 + w2) } } impl Neg for Point where T: Add + Sub + Neg + Mul + Div + PartialEq + Trig + Copy { type Output = Self; fn neg(self) -> Self { let Point(v, w) = self; Self(-v, -w) } } impl Sub for Point where T: Add + Sub + Neg + Mul + Div + PartialEq + Trig + Copy { type Output = Self; fn sub(self, other :Self) -> Self { self + -other } } impl Mul for Point where T: Add + Sub + Neg + Mul + Div + PartialEq + Trig + Copy + From { type Output = Self; fn mul(self, other :Self) -> Self { let a :Vector = self.into(); let b :Vector = other.into(); Point(a * b, 1.into()) } } impl From> for Point where T: Add + Sub + Neg + Mul + Div + PartialEq + Trig + Copy + From { fn from(v :Vector) -> Self { Point(v, 1.into()) } } impl Into> for Point where T: Add + Sub + Neg + Mul + Div + PartialEq + Trig + Copy + From { fn into(self) -> Vector { let Point(v, w) = self; if w == 0.into() { v } else { v.mul(&w.recip()) } } } impl Transformable for Point where T: Add + Sub + Neg + Mul + Div + PartialEq + Debug + Trig + Copy + From { fn transform(&self, m :&TMatrix) -> Self { let Point(v, w) = *self; let (v, w) = m.apply(&v, w); if w == 0.into() { v.into() } else { v.mul(&w.recip()).into() } } } #[derive(Debug)] pub struct Polyeder where T: Add + Sub + Neg + Mul + Div + PartialEq + 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 , light :&DirectLight , col :u32 ) -> Vec<(Polygon, u32)>; } pub struct Camera where T: Add + Sub + Neg + Mul + Div + Debug + Copy + Trig + From { width :T, height :T, 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 + PartialEq + Debug + Copy + Trig + From { // This code assumes that the size of the viewport is always // equal to the size of the physical screen… e.g. window/canvas thus some // effects can't be done. See book for examples with different viewport // and screen sizes. pub fn new(c :&dyn Canvas, angle :i32) -> Self { let width :T = (c.width() as i32).into(); let height :T = (c.height() as i32).into(); let d :T = 1.into(); let fov = T::cot(angle) * width; let wh = width / 2.into(); let hh = height / 2.into(); Camera { width: width , height: height , project: TMatrix::new( ( fov, 0.into(), wh, 0.into()) , (0.into(), fov, hh, 0.into()) , (0.into(), 0.into(), d, 1.into()) , (0.into(), 0.into(), 1.into(), 0.into()) ) } } pub fn get_projection(&self) -> TMatrix { self.project } pub fn project(&self, p :Point) -> Point { p.transform(&self.project) } } 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 + PartialEq + Debug + Copy + Trig + From { fn new(corners :Vec, ps :&[Point]) -> Self { let mut f = Face{ corners: corners, normal: None }; f.update_normal(ps); f } fn update_normal(&mut self, ps :&[Point]) { let edge10 :Vector = (ps[self.corners[1]] - ps[self.corners[0]]).into(); let edge12 :Vector = (ps[self.corners[1]] - ps[self.corners[2]]).into(); self.normal = Some(edge10 * edge12); } } impl Polyeder where T: Add + Sub + Neg + Mul + Div + PartialEq + 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(); let f3 :T = 3.into(); let f4 :T = 4.into(); let f6 :T = 6.into(); let f12 :T = 12.into(); let yi :T = a / f12 * T::sqrt(f6).unwrap(); let yc :T = a / f4 * T::sqrt(f6).unwrap(); let zi :T = T::sqrt(f3).unwrap() / f6 * a; let zc :T = T::sqrt(f3).unwrap() / f3 * a; let ah :T = a / 2.into(); // half the height in y let _yh :T = a / f6 * T::sqrt(f6).unwrap(); // half the deeps in z let _zh :T = T::sqrt(f3).unwrap() / f4 * a; let ps = vec!( Point::new( f0, yc, f0) , Point::new(-ah, -yi, -zi) , Point::new( ah, -yi, -zi) , Point::new( 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!( Point::new(-ah, f0, -zi) , Point::new( f0, f0, zc) , Point::new( 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); let ps = vec!( Point::new(-ah, ah, -ah) // 0 => front 1 , Point::new(-ah, -ah, -ah) // 1 => front 2 , Point::new( ah, -ah, -ah) // 2 => front 3 , Point::new( ah, ah, -ah) // 3 => front 4 , Point::new(-ah, ah, ah) // 4 => back 1 , Point::new(-ah, -ah, ah) // 5 => back 2 , Point::new( ah, -ah, ah) // 6 => back 3 , Point::new( 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 + PartialOrd { // TODO Maybe this should also be an instance of Transformable… fn transform(&self, m :&TMatrix) -> Self { let Polyeder{ points: ps, faces: fs } = self; let mut p = Polyeder{ points: ps.iter().map(|p| p.transform(m)).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 } 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())) } // 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| { let Point(v, _) = camera.project(self.points[*p]); // println!("== {:?} / {:?}", self.points[*p], (v.z() - 1.into()).recip()); Coordinate(T::round(&v.x()), T::round(&v.y()), v.z() - 1.into()) }; 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()), }; // this "if" represents a first simple backface culling // approach. We only return face that face towards us. 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 }}; let mut ps :Vec<(Polygon, u32)> = self.faces.iter() . filter_map(to_poly).collect(); // this sorts by the color value which is no longer neccessary as soon // as the z-buffer is complete. ps.sort_by(|a, b| a.1.cmp(&b.1)); ps } }