13 changed files with 612 additions and 383 deletions
-
373src/geometry.rs
-
8src/lib.rs
-
28src/math/mod.rs
-
4src/math/transform.rs
-
0src/math/trigonometry.rs
-
4src/math/vector.rs
-
81src/space/camera.rs
-
62src/space/face.rs
-
50src/space/light.rs
-
27src/space/mod.rs
-
130src/space/point.rs
-
189src/space/polyeder.rs
-
39src/space/primitives.rs
@ -1,373 +0,0 @@ |
|||||
//
|
|
||||
// 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::{Canvas, Vertex};
|
|
||||
use crate::easel::polygon::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)>;
|
|
||||
}
|
|
||||
|
|
||||
#[derive(Debug, Clone, Copy)]
|
|
||||
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)
|
|
||||
, 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> + 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())
|
|
||||
}
|
|
||||
|
|
||||
// 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]);
|
|
||||
Vertex::new(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()
|
|
||||
}
|
|
||||
}
|
|
||||
@ -0,0 +1,28 @@ |
|||||
|
//
|
||||
|
// Math needed for 3D transformations and projection.
|
||||
|
//
|
||||
|
// Georg Hopp <georg@steffers.org>
|
||||
|
//
|
||||
|
// Copyright © 2020 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/>.
|
||||
|
//
|
||||
|
|
||||
|
// generics for needed trigonometric stuff. As well as an implementation for
|
||||
|
// f64.
|
||||
|
pub mod trigonometry;
|
||||
|
|
||||
|
// generics for 3D math stuff.
|
||||
|
pub mod vector;
|
||||
|
pub mod transform;
|
||||
@ -0,0 +1,81 @@ |
|||||
|
//
|
||||
|
// 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::canvas::{Canvas, Vertex};
|
||||
|
use crate::math::transform::{TMatrix, Transformable};
|
||||
|
use crate::math::trigonometry::Trig;
|
||||
|
use crate::math::vector::Vector;
|
||||
|
|
||||
|
use super::point::Point;
|
||||
|
|
||||
|
#[derive(Debug, Clone, Copy)]
|
||||
|
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>,
|
||||
|
}
|
||||
|
|
||||
|
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>) -> Vertex<T> {
|
||||
|
let v :Vector<T> = p.transform(&self.project).into();
|
||||
|
Vertex::new( T::round(&v.x())
|
||||
|
, T::round(&v.y())
|
||||
|
, v.z() - self.distance )
|
||||
|
}
|
||||
|
}
|
||||
@ -0,0 +1,62 @@ |
|||||
|
//
|
||||
|
// 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::math::trigonometry::Trig;
|
||||
|
use crate::math::vector::Vector;
|
||||
|
|
||||
|
use super::point::Point;
|
||||
|
|
||||
|
#[derive(Debug, Clone)]
|
||||
|
pub struct Face<T>
|
||||
|
where T: Add + Sub + Neg + Mul + Div + Copy + Trig {
|
||||
|
corners :Vec<usize>,
|
||||
|
normal :Option<Vector<T>>,
|
||||
|
}
|
||||
|
|
||||
|
impl<'a, 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> {
|
||||
|
pub fn new(corners :Vec<usize>, ps :&[Point<T>]) -> Self {
|
||||
|
let mut f = Face{ corners: corners, normal: None };
|
||||
|
f.update_normal(ps);
|
||||
|
f
|
||||
|
}
|
||||
|
|
||||
|
#[inline]
|
||||
|
pub fn corners(&self) -> &[usize] {
|
||||
|
&self.corners
|
||||
|
}
|
||||
|
|
||||
|
#[inline]
|
||||
|
pub fn normal(&self) -> Option<&Vector<T>> {
|
||||
|
(&self.normal).as_ref()
|
||||
|
}
|
||||
|
|
||||
|
pub 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);
|
||||
|
}
|
||||
|
}
|
||||
@ -0,0 +1,50 @@ |
|||||
|
//
|
||||
|
// 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::math::transform::{TMatrix, Transformable};
|
||||
|
use crate::math::trigonometry::Trig;
|
||||
|
use crate::math::vector::Vector;
|
||||
|
|
||||
|
pub struct DirectLight<T>
|
||||
|
where T: Add + Sub + Neg + Mul + Div + Debug + Copy + Trig + From<i32> {
|
||||
|
direction: Vector<T>,
|
||||
|
}
|
||||
|
|
||||
|
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 transform(&self, m :&TMatrix<T>) -> Self {
|
||||
|
let DirectLight{ direction: v } = self;
|
||||
|
DirectLight { direction: v.transform(m) }
|
||||
|
}
|
||||
|
|
||||
|
pub fn dir(&self) -> Vector<T> {
|
||||
|
self.direction
|
||||
|
}
|
||||
|
}
|
||||
@ -0,0 +1,27 @@ |
|||||
|
//
|
||||
|
// This holds 3D primitives.
|
||||
|
//
|
||||
|
// Georg Hopp <georg@steffers.org>
|
||||
|
//
|
||||
|
// Copyright © 2020 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/>.
|
||||
|
//
|
||||
|
|
||||
|
pub mod face;
|
||||
|
pub mod point;
|
||||
|
pub mod polyeder;
|
||||
|
pub mod primitives;
|
||||
|
pub mod camera;
|
||||
|
pub mod light;
|
||||
@ -0,0 +1,130 @@ |
|||||
|
//
|
||||
|
// 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::math::transform::{TMatrix, Transformable};
|
||||
|
use crate::math::trigonometry::Trig;
|
||||
|
use crate::math::vector::Vector;
|
||||
|
|
||||
|
#[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()
|
||||
|
}
|
||||
|
}
|
||||
|
}
|
||||
@ -0,0 +1,189 @@ |
|||||
|
//
|
||||
|
// 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()
|
||||
|
}
|
||||
|
}
|
||||
@ -0,0 +1,39 @@ |
|||||
|
//
|
||||
|
// 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::math::transform::TMatrix;
|
||||
|
use crate::math::trigonometry::Trig;
|
||||
|
|
||||
|
use super::camera::Camera;
|
||||
|
use super::light::DirectLight;
|
||||
|
|
||||
|
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)>;
|
||||
|
}
|
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue