polyeder.rs 7.03 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::fmt::Debug;
use std::ops::{Add, Div, Mul, Neg, Sub};

use crate::easel::polygon::Polygon;
use crate::easel::canvas::Vertex;

use crate::math::transform::{TMatrix, Transformable};
use crate::math::trigonometry::Trig;
use crate::math::vector::Vector;

use super::camera::Camera;
use super::face::Face;
use super::light::DirectLight;
use super::point::Point;
use super::primitives::Primitives;

#[derive(Debug)]
pub struct Polyeder<T>
where T: Add + Sub + Neg + Mul + Div + PartialEq + Copy + Trig {
    points :Vec<Point<T>>,
    faces  :Vec<Face<T>>,
}

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)
                     , Point::new(-ch,  ch, -ch)
                     , Point::new( ch, -ch, -ch)
                     , Point::new( ch,  ch,  ch) );

        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) );

        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> + From<f64> + 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 = Vertex<T>> {
            Polygon(c.collect())
        }

        // currently our cam has only one direction...
        let cam_dir = Vector(0.into(), 0.into(), 1.into());

        // 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_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()),
                       };
                       let view_f :T = match f.normal() {
                           None    => 1.into(),
                           Some(n) => n.dot(cam_dir)
                               / (n.mag() * cam_dir.mag()),
                       };

                       // this "if" represents a first simple backface culling
                       // approach. We only return face that face towards us.
                       if view_f >= 0.into() {
                           None
                       } else {
                           if lf < (-0.1).into() {
                               r = r * -lf;
                               g = g * -lf;
                               b = b * -lf;
                           } else {
                               r = r * 0.1.into();
                               g = g * 0.1.into();
                               b = b * 0.1.into();
                           }

                           let c :u32 = (r.round() as u32) << 16
                                      | (g.round() as u32) << 8
                                      | (b.round() as u32);

                           Some((pg, c))
                       }};

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