geometry.rs 12.3 KB
//
// Basic geometric things...
//
// Georg Hopp <georg@steffers.org>
//
// 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 <http://www.gnu.org/licenses/>.
//
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<T>
where T: Add + Sub + Neg + Mul + Div + Copy + Trig {
    corners :Vec<usize>,
    normal  :Option<Vector<T>>,
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct Point<T>(pub Vector<T>, T)
    where T: Add + Sub + Neg + Mul + Div + PartialEq + Copy + Trig;

impl<T> Point<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
       + Mul<Output = T> + Div<Output = T>
       + PartialEq + Trig + Copy + From<i32> {
    pub fn new(x :T, y :T, z :T) -> Self {
        Self(Vector(x, y, z), 1.into())
    }
}

impl<T> Add for Point<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
       + Mul<Output = T> + Div<Output = T>
       + 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<T> Neg for Point<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
       + Mul<Output = T> + Div<Output = T>
       + PartialEq + Trig + Copy {
    type Output = Self;

    fn neg(self) -> Self {
        let Point(v, w) = self;
        Self(-v, -w)
    }
}

impl<T> Sub for Point<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
       + Mul<Output = T> + Div<Output = T>
       + PartialEq + Trig + Copy {
    type Output = Self;

    fn sub(self, other :Self) -> Self {
        self + -other
    }
}

impl<T> Mul for Point<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
       + Mul<Output = T> + Div<Output = T>
       + PartialEq + Trig + Copy + From<i32> {
    type Output = Self;

    fn mul(self, other :Self) -> Self {
        let a :Vector<T> = self.into();
        let b :Vector<T> = other.into();

        Point(a * b, 1.into())
    }
}

impl<T> From<Vector<T>> for Point<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
       + Mul<Output = T> + Div<Output = T>
       + PartialEq + Trig + Copy + From<i32> {
    fn from(v :Vector<T>) -> Self {
        Point(v, 1.into())
    }
}

impl<T> Into<Vector<T>> for Point<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
       + Mul<Output = T> + Div<Output = T>
       + PartialEq + Trig + Copy + From<i32> {
    fn into(self) -> Vector<T> {
        let Point(v, w) = self;

        if w == 0.into() {
            v
        } else {
            v.mul(&w.recip())
        }
    }
}

impl<T> Transformable<T> for Point<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
       + Mul<Output = T> + Div<Output = T>
       + PartialEq + Debug + Trig + Copy + From<i32> {
    fn transform(&self, m :&TMatrix<T>) -> 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<T>
where T: Add + Sub + Neg + Mul + Div + PartialEq + Copy + Trig {
    points :Vec<Point<T>>,
    faces  :Vec<Face<T>>,
}

pub trait Primitives<T>
where T: Add + Sub + Neg + Mul + Div + Debug + Copy + Trig + From<i32> {
    fn transform(&self, m :&TMatrix<T>) -> Self;
    fn project( &self
              , camera :&Camera<T>
              , light :&DirectLight<T>
              , col :u32 ) -> Vec<(Polygon<T>, u32)>;
}

pub struct Camera<T>
where T: Add + Sub + Neg + Mul + Div + Debug + Copy + Trig + From<i32> {
    width    :T,
    height   :T,
    distance :T,
    project  :TMatrix<T>,
}

pub struct DirectLight<T>
where T: Add + Sub + Neg + Mul + Div + Debug + Copy + Trig + From<i32> {
    direction: Vector<T>,
}

impl<T> Camera<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
       + Mul<Output = T> + Div<Output = T>
       + PartialEq + Debug + Copy + Trig + From<i32> {
    // 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<T>, 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
               , distance: d
               , 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_distance(&self) -> T {
        self.distance
    }

    pub fn get_projection(&self) -> TMatrix<T> {
        self.project
    }

    pub fn project(&self, p :Point<T>) -> Point<T> {
        p.transform(&self.project)
    }
}

impl<T> DirectLight<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
       + Mul<Output = T> + Div<Output = T>
       + Debug + Copy + Trig + From<i32> {
    pub fn new(v :Vector<T>) -> Self {
        DirectLight{ direction: v }
    }

    pub fn dir(&self) -> Vector<T> {
        self.direction
    }
}

impl<T> Face<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
       + Mul<Output = T> + Div<Output = T>
       + PartialEq + Debug + Copy + Trig + From<i32> {
    fn new(corners :Vec<usize>, ps :&[Point<T>]) -> Self {
        let mut f = Face{ corners: corners, normal: None };
        f.update_normal(ps);
        f
    }

    fn update_normal(&mut self, ps :&[Point<T>]) {
        let edge10 :Vector<T> = (ps[self.corners[1]] - ps[self.corners[0]]).into();
        let edge12 :Vector<T> = (ps[self.corners[1]] - ps[self.corners[2]]).into();
        self.normal = Some(edge10 * edge12);
    }
}

impl<T> Polyeder<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
       + Mul<Output = T> + Div<Output = T>
       + PartialEq + Debug + Copy + Trig + From<i32> {
    fn update_normals(&mut self) {
        for f in self.faces.iter_mut() {
            f.update_normal(&self.points);
        }
    }

    // construct via cube, see polyhedra.pdf
    pub fn tetrahedron(a :T) -> Polyeder<T> {
        let f2 :T = 2.into();
        let ch    = a / (f2 * T::sqrt(f2).unwrap());

        let ps = vec!( Point::new(-ch, -ch,  ch)    // A
                     , Point::new(-ch,  ch, -ch)    // C
                     , Point::new( ch, -ch, -ch)    // E
                     , Point::new( ch,  ch,  ch) ); // G

        // bottom: 1, 2, 3
        let fs = vec!( Face::new(vec!(2, 1, 0), &ps) // bottom
                     , Face::new(vec!(3, 2, 0), &ps)
                     , Face::new(vec!(0, 1, 3), &ps)
                     , Face::new(vec!(1, 2, 3), &ps) );
        //let fs = vec!( Face::new(vec!(0, 1, 2), &ps) // bottom
        //             , Face::new(vec!(0, 2, 3), &ps)
        //             , Face::new(vec!(3, 1, 0), &ps)
        //             , Face::new(vec!(3, 2, 1), &ps) );

        Polyeder{ points: ps, faces: fs }
    }

    pub fn triangle(a :T) -> Polyeder<T> {
        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));

        Polyeder{ points: ps, faces: fs }
    }

    pub fn cube(a :T) -> Polyeder<T> {
        let ah :T = a / From::<i32>::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<T> Primitives<T> for Polyeder<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
       + Mul<Output = T> + Div<Output = T>
       + Debug + Copy + Trig + From<i32> + PartialOrd {
    // TODO Maybe this should also be an instance of Transformable…
    fn transform(&self, m :&TMatrix<T>) -> 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<T>
              , light :&DirectLight<T>
              , color :u32 ) -> Vec<(Polygon<T>, u32)> {
        // Helper to create a Polygon from Coordinates…
        // TODO probably there needs to be a Polygon constructor for this.
        fn polygon<I, T>(c :I) -> Polygon<T>
        where I: Iterator<Item = Coordinate<T>> {
            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]);
            Coordinate(T::round(&v.x()), T::round(&v.y()), v.z() - 1.into())
        };
        let to_poly  = |f :&Face<T>| {
                       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
                       }};

        self.faces.iter().filter_map(to_poly).collect()
    }
}