Blame view

fractional/src/geometry.rs 12.3 KB
Georg Hopp authored
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
//
// 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/>.
//
Georg Hopp authored
21
use std::convert::{From, Into};
Georg Hopp authored
22 23 24
use std::ops::{Add,Sub,Neg,Mul,Div};
use std::fmt::Debug;
25
use crate::easel::{Canvas, Coordinate, Coordinates, Polygon};
Georg Hopp authored
26
use crate::transform::{TMatrix, Transformable};
Georg Hopp authored
27 28 29
use crate::trigonometry::Trig;
use crate::vector::Vector;
30 31 32 33 34 35 36
#[derive(Debug, Clone)]
pub struct Face<T>
where T: Add + Sub + Neg + Mul + Div + Copy + Trig {
    corners :Vec<usize>,
    normal  :Option<Vector<T>>,
}
Georg Hopp authored
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
#[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()
        }
    }
}
Georg Hopp authored
140 141
#[derive(Debug)]
pub struct Polyeder<T>
Georg Hopp authored
142 143
where T: Add + Sub + Neg + Mul + Div + PartialEq + Copy + Trig {
    points :Vec<Point<T>>,
144
    faces  :Vec<Face<T>>,
Georg Hopp authored
145 146 147
}

pub trait Primitives<T>
Georg Hopp authored
148
where T: Add + Sub + Neg + Mul + Div + Debug + Copy + Trig + From<i32> {
Georg Hopp authored
149
    fn transform(&self, m :&TMatrix<T>) -> Self;
150 151 152
    fn project( &self
              , camera :&Camera<T>
              , light :&DirectLight<T>
153
              , col :u32 ) -> Vec<(Polygon<T>, u32)>;
Georg Hopp authored
154 155 156
}

pub struct Camera<T>
Georg Hopp authored
157
where T: Add + Sub + Neg + Mul + Div + Debug + Copy + Trig + From<i32> {
Georg Hopp authored
158 159 160 161
    width    :T,
    height   :T,
    distance :T,
    project  :TMatrix<T>,
Georg Hopp authored
162 163
}
164 165 166 167 168
pub struct DirectLight<T>
where T: Add + Sub + Neg + Mul + Div + Debug + Copy + Trig + From<i32> {
    direction: Vector<T>,
}
Georg Hopp authored
169 170 171
impl<T> Camera<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
       + Mul<Output = T> + Div<Output = T>
Georg Hopp authored
172
       + PartialEq + Debug + Copy + Trig + From<i32> {
Georg Hopp authored
173 174 175 176
    // 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.
177
    pub fn new(c :&dyn Canvas<T>, angle :i32) -> Self {
Georg Hopp authored
178 179 180 181 182 183 184
        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();
Georg Hopp authored
185 186 187 188
        Camera { width:    width
               , height:   height
               , distance: d
               , project:  TMatrix::new(
Georg Hopp authored
189 190 191 192
                     (     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()) ) }
Georg Hopp authored
193 194
    }
Georg Hopp authored
195 196 197 198
    pub fn get_distance(&self) -> T {
        self.distance
    }
Georg Hopp authored
199 200 201
    pub fn get_projection(&self) -> TMatrix<T> {
        self.project
    }
Georg Hopp authored
202
203 204
    pub fn project(&self, p :Point<T>) -> Point<T> {
        p.transform(&self.project)
Georg Hopp authored
205 206 207
    }
}
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
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>
Georg Hopp authored
224 225
       + PartialEq + Debug + Copy + Trig + From<i32> {
    fn new(corners :Vec<usize>, ps :&[Point<T>]) -> Self {
226 227 228 229 230
        let mut f = Face{ corners: corners, normal: None };
        f.update_normal(ps);
        f
    }
Georg Hopp authored
231 232 233
    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();
234 235 236 237
        self.normal = Some(edge10 * edge12);
    }
}
Georg Hopp authored
238 239 240
impl<T> Polyeder<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
       + Mul<Output = T> + Div<Output = T>
Georg Hopp authored
241
       + PartialEq + Debug + Copy + Trig + From<i32> {
242 243 244 245 246 247
    fn update_normals(&mut self) {
        for f in self.faces.iter_mut() {
            f.update_normal(&self.points);
        }
    }
248
    // construct via cube, see polyhedra.pdf
Georg Hopp authored
249
    pub fn tetrahedron(a :T) -> Polyeder<T> {
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
        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) );
267 268 269 270 271 272 273 274 275 276 277 278

        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();
Georg Hopp authored
279 280 281
        let ps = vec!( Point::new(-ah, f0, -zi)
                     , Point::new( f0, f0,  zc)
                     , Point::new( ah, f0, -zi) );
282 283 284 285

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

        Polyeder{ points: ps, faces: fs }
Georg Hopp authored
286 287 288 289 290
    }

    pub fn cube(a :T) -> Polyeder<T> {
        let ah :T = a / From::<i32>::from(2);
Georg Hopp authored
291 292 293 294 295 296 297 298
        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
299 300 301 302 303 304 305 306 307

        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 }
Georg Hopp authored
308 309 310 311 312 313
    }
}

impl<T> Primitives<T> for Polyeder<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
       + Mul<Output = T> + Div<Output = T>
314
       + Debug + Copy + Trig + From<i32> + PartialOrd {
315
    // TODO Maybe this should also be an instance of Transformable…
Georg Hopp authored
316
    fn transform(&self, m :&TMatrix<T>) -> Self {
317 318
        let Polyeder{ points: ps, faces: fs } = self;
Georg Hopp authored
319 320 321 322
        let mut p = Polyeder{
              points: ps.iter().map(|p| p.transform(m)).collect()
            , faces:  fs.to_vec()
        };
323 324 325 326 327

        // 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
Georg Hopp authored
328 329
    }
330 331 332
    fn project( &self
              , camera :&Camera<T>
              , light :&DirectLight<T>
333
              , color :u32 ) -> Vec<(Polygon<T>, u32)> {
334 335
        // Helper to create a Polygon from Coordinates…
        // TODO probably there needs to be a Polygon constructor for this.
336 337
        fn polygon<I, T>(c :I) -> Polygon<T>
        where I: Iterator<Item = Coordinate<T>> {
Georg Hopp authored
338 339 340
            Polygon(Coordinates(c.collect()))
        }
Georg Hopp authored
341 342
        // this one does the projection... as the projection was the last
        // matrix we do not need to do it here.
343 344 345 346
        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())
        };
347 348 349 350 351 352 353 354 355 356 357
        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()),
                       };
358
                       // this "if" represents a first simple backface culling
Georg Hopp authored
359
                       // approach. We only return face that face towards us.
360 361 362 363 364 365 366 367 368 369 370 371 372
                       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
                       }};
Georg Hopp authored
373
374
        self.faces.iter().filter_map(to_poly).collect()
Georg Hopp authored
375 376
    }
}