diff --git a/fractional/src/geometry.rs b/fractional/src/geometry.rs new file mode 100644 index 0000000..8e3e0e0 --- /dev/null +++ b/fractional/src/geometry.rs @@ -0,0 +1,150 @@ +// +// 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; +use std::ops::{Add,Sub,Neg,Mul,Div}; +use std::fmt::Debug; + +use crate::easel::{Canvas,Coordinate,Coordinates,Polygon}; +use crate::transform::TMatrix; +use crate::trigonometry::Trig; +use crate::vector::Vector; + +#[derive(Debug)] +pub struct Polyeder +where T: Add + Sub + Neg + Mul + Div + Copy + Trig { + points :Vec>, + faces :Vec>, +} + +pub trait Primitives +where T: Add + Sub + Neg + Mul + Div + Copy + Trig + From { + fn transform(&self, m :&TMatrix) -> Self; + fn project(&self, camera :&Camera) -> Vec; +} + +pub struct Camera +where T: Add + Sub + Neg + Mul + Div + Copy + Trig { + width :T, + height :T, + fovx :T, + fovy :T, +} + +impl Camera +where T: Add + Sub + Neg + + Mul + Div + + Copy + Trig + From { + pub fn new(c :&dyn Canvas, angle :i32) -> Self { + let width = >::from(c.width() as i32); + let height = >::from(c.height() as i32); + + // The calculations for fovx and fovy are taken from a book, but I + // have the impression, coming from my limited algebra knowledge, + // that they are always equal… + Camera { width: width + , height: height + , fovx: T::cot(angle) * width + , fovy: width / height * T::cot(angle) * height } + } + + pub fn project(&self, v :Vector) -> Coordinate { + let f2 = From::::from(2); + let xs = v.x() / v.z() * self.fovx + self.width / f2; + let ys = v.y() / v.z() * self.fovy + self.height / f2; + + Coordinate(T::round(&xs), T::round(&ys)) + } +} + +impl Polyeder +where T: Add + Sub + Neg + + Mul + Div + + Copy + Trig + From { + // https://rechneronline.de/pi/tetrahedron.php + pub fn tetrahedron(a :T) -> Polyeder { + let f0 :T = From::::from(0); + let f3 :T = From::::from(3); + let f4 :T = From::::from(4); + let f6 :T = From::::from(6); + let f12 :T = From::::from(12); + + 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 / From::::from(2); + + // 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; + + Polyeder{ points: vec!( Vector( f0, yc, f0) + , Vector(-ah, -yi, -zi) + , Vector( ah, -yi, -zi) + , Vector( f0, -yi, zc) ) + , faces: vec!( vec!(1, 2, 3) + , vec!(1, 0, 2) + , vec!(3, 0, 1) + , vec!(2, 0, 3) )} + } + + pub fn cube(a :T) -> Polyeder { + let ah :T = a / From::::from(2); + + Polyeder{ points: vec!( Vector(-ah, ah, -ah) // 0 => front 1 + , Vector(-ah, -ah, -ah) // 1 => front 2 + , Vector( ah, -ah, -ah) // 2 => front 3 + , Vector( ah, ah, -ah) // 3 => front 4 + , Vector(-ah, ah, ah) // 4 => back 1 + , Vector(-ah, -ah, ah) // 5 => back 2 + , Vector( ah, -ah, ah) // 6 => back 3 + , Vector( ah, ah, ah) ) // 7 => back 4 + , faces: vec!( vec!(0, 1, 2, 3) // front + , vec!(7, 6, 5, 4) // back + , vec!(1, 5, 6, 2) // top + , vec!(0, 3, 7, 4) // bottom + , vec!(0, 4, 5, 1) // left + , vec!(2, 6, 7, 3) )} // right + } +} + +impl Primitives for Polyeder +where T: Add + Sub + Neg + + Mul + Div + + Copy + Trig + From + From { + fn transform(&self, m :&TMatrix) -> Self { + Polyeder{ points: self.points.iter().map(|p| m.apply(p)).collect() + , faces: self.faces.to_vec() } + } + + fn project(&self, camera :&Camera) -> Vec { + fn polygon(c :I) -> Polygon + where I: Iterator { + Polygon(Coordinates(c.collect())) + } + + let to_coord = |p :&usize| camera.project(self.points[*p]); + let to_poly = |f :&Vec| polygon(f.iter().map(to_coord)); + + self.faces.iter().map(to_poly).collect() + } +} diff --git a/fractional/src/lib.rs b/fractional/src/lib.rs index e2c5a8a..592e570 100644 --- a/fractional/src/lib.rs +++ b/fractional/src/lib.rs @@ -29,6 +29,7 @@ pub mod transform; pub mod trigonometry; pub mod vector; pub mod xcb; +pub mod geometry; use fractional::Fractional; use vector::Vector; diff --git a/fractional/src/main.rs b/fractional/src/main.rs index c49edfe..5df381c 100644 --- a/fractional/src/main.rs +++ b/fractional/src/main.rs @@ -38,6 +38,8 @@ use fractional::transform::{TMatrix, translate, rotate_x, rotate_y, rotate_z, ro use fractional::xcb::XcbEasel; use fractional::easel::Canvas; +use fractional::geometry::{Camera,Polyeder,Primitives}; + fn mean(v: &Vec) -> Result { let r = v.iter().fold(0, |acc, x| acc + x); let l = i64::try_from(v.len())?; @@ -340,48 +342,10 @@ fn main() { let (tx, rx) = mpsc::channel(); - // TODO I ran into overflow issues using fractionals so for now - // use floating point values. - // https://rechneronline.de/pi/tetrahedron.php - // yi = a / 12 * √6 - let yi = 60.0 / 12.0 * 6.0.sqrt().unwrap(); - // yc = a / 4 * √6 - let yc = 60.0 / 4.0 * 6.0.sqrt().unwrap(); - // zi = √3 / 6 * a - let zi = 3.0.sqrt().unwrap() / 6.0 * 60.0; - // zc = √3 / 3 * a - let zc = 3.0.sqrt().unwrap() / 3.0 * 60.0; - - let i = Vector( 0.0, yc, 0.0); - let j = Vector(-30.0, -yi, -zi); - let k = Vector( 30.0, -yi, -zi); - let l = Vector( 0.0, -yi, zc); - - let cf1 = Vector(-30.0, 30.0, -30.0); - let cf2 = Vector(-30.0, -30.0, -30.0); - let cf3 = Vector( 30.0, -30.0, -30.0); - let cf4 = Vector( 30.0, 30.0, -30.0); - - let cb1 = Vector(-30.0, 30.0, 30.0); - let cb2 = Vector(-30.0, -30.0, 30.0); - let cb3 = Vector( 30.0, -30.0, 30.0); - let cb4 = Vector( 30.0, 30.0, 30.0); - - fn to_screen(c: &dyn Canvas, v :Vector) -> Coordinate { - // TODO .. these are in fact constants that should be stored once - // somewhere… Rust doesn't let me make this static here. - // In a way they are part of the canvas and they should change as the - // canvas is changing… - let fovx :f64 = 1.0 / ::tan(50); - let fovy :f64 = c.width() as f64 / c.height() as f64 * fovx; - - let xs = ( v.x() / v.z() * fovx * c.width() as f64 ).round() as i32 - + c.width() as i32 / 2; - let ys = ( -v.y() / v.z() * fovy * c.height() as f64 ).round() as i32 - + c.height() as i32 / 2; - - Coordinate(xs, ys) - } + let tetrahedron = Polyeder::tetrahedron(60.0); + let cube = Polyeder::cube(60.0); + let camera = Camera::::new(&canvas, 40); // the orig. view angle + // was 50. canvas.start_events(tx); @@ -389,88 +353,24 @@ fn main() { let step = Duration::from_millis(25); let mut last = Instant::now(); thread::spawn(move || { - //const DWC :f64 = 10.0; - loop { - let deg = ((start.elapsed() / 20).as_millis() % 360) as i32; + let deg = ((start.elapsed() / 25).as_millis() % 360) as i32; let rot1 :TMatrix = rotate_z(deg) * rotate_x(-deg*2) * translate(Vector(0.0, 0.0, 150.0)); - let rot2 :TMatrix = rotate_z(deg) - * rotate_y(-deg*2) + let rot2 :TMatrix = rotate_z(-deg*2) + * rotate_y(deg) * translate(Vector(0.0, 0.0, 150.0)); - let ia = rot1.apply(&i); - let ja = rot1.apply(&j); - let ka = rot1.apply(&k); - let la = rot1.apply(&l); - - let cf1a = rot2.apply(&cf1); - let cf2a = rot2.apply(&cf2); - let cf3a = rot2.apply(&cf3); - let cf4a = rot2.apply(&cf4); - - let cb1a = rot2.apply(&cb1); - let cb2a = rot2.apply(&cb2); - let cb3a = rot2.apply(&cb3); - let cb4a = rot2.apply(&cb4); - - let pg1 = Polygon(Coordinates(vec!( to_screen(&canvas, ja) - , to_screen(&canvas, ka) - , to_screen(&canvas, la) ))); - let pg2 = Polygon(Coordinates(vec!( to_screen(&canvas, ja) - , to_screen(&canvas, ia) - , to_screen(&canvas, ka) ))); - let pg3 = Polygon(Coordinates(vec!( to_screen(&canvas, la) - , to_screen(&canvas, ia) - , to_screen(&canvas, ja) ))); - let pg4 = Polygon(Coordinates(vec!( to_screen(&canvas, ka) - , to_screen(&canvas, ia) - , to_screen(&canvas, la) ))); - - // front: cf1 cf2 cf3 cf4 - let cf = Polygon(Coordinates(vec!( to_screen(&canvas, cf1a) - , to_screen(&canvas, cf2a) - , to_screen(&canvas, cf3a) - , to_screen(&canvas, cf4a) ))); - // back: cb4 cb3 cb2 cb1 - let cb = Polygon(Coordinates(vec!( to_screen(&canvas, cb4a) - , to_screen(&canvas, cb3a) - , to_screen(&canvas, cb2a) - , to_screen(&canvas, cb1a) ))); - // top: cf2 cb2 cb3 cf3 - let ct = Polygon(Coordinates(vec!( to_screen(&canvas, cf2a) - , to_screen(&canvas, cb2a) - , to_screen(&canvas, cb3a) - , to_screen(&canvas, cf3a) ))); - // bottom: cf1 cf4 cb4 cb1 - let co = Polygon(Coordinates(vec!( to_screen(&canvas, cf1a) - , to_screen(&canvas, cf4a) - , to_screen(&canvas, cb4a) - , to_screen(&canvas, cb1a) ))); - // left: cf1 cb1 cb2 cf2 - let cl = Polygon(Coordinates(vec!( to_screen(&canvas, cf1a) - , to_screen(&canvas, cb1a) - , to_screen(&canvas, cb2a) - , to_screen(&canvas, cf2a) ))); - // right: cf3 cb3 cb4 cf4 - let cr = Polygon(Coordinates(vec!( to_screen(&canvas, cf3a) - , to_screen(&canvas, cb3a) - , to_screen(&canvas, cb4a) - , to_screen(&canvas, cf4a) ))); - canvas.clear(); - canvas.draw(&pg1, Coordinate(0,0), 0xFFFF00); - canvas.draw(&pg2, Coordinate(0,0), 0xFFFF00); - canvas.draw(&pg3, Coordinate(0,0), 0xFFFF00); - canvas.draw(&pg4, Coordinate(0,0), 0xFFFF00); - canvas.draw( &cf, Coordinate(0,0), 0x0000FF); - canvas.draw( &cb, Coordinate(0,0), 0x0000FF); - canvas.draw( &ct, Coordinate(0,0), 0x0000FF); - canvas.draw( &co, Coordinate(0,0), 0x0000FF); - canvas.draw( &cl, Coordinate(0,0), 0x0000FF); - canvas.draw( &cr, Coordinate(0,0), 0x0000FF); + + for pg in tetrahedron.transform(&rot1).project(&camera) { + canvas.draw(&pg, Coordinate(0,0), 0xFFFF00); + } + for pg in cube.transform(&rot2).project(&camera) { + canvas.draw(&pg, Coordinate(0,0), 0x0000FF); + } let passed = Instant::now() - last; let f = (passed.as_nanos() / step.as_nanos()) as u32; diff --git a/fractional/src/trigonometry.rs b/fractional/src/trigonometry.rs index f43ed38..8b41d3a 100644 --- a/fractional/src/trigonometry.rs +++ b/fractional/src/trigonometry.rs @@ -26,17 +26,19 @@ // along with this program. If not, see . // use std::cmp::Ordering; +use std::ops::Div; use std::ops::Neg; use std::marker::Sized; use crate::{Fractional, Error}; use crate::continuous::Continuous; pub trait Trig { - fn pi() -> Self; - fn recip(self) -> Self; - fn sqrt(self) -> Result where Self: Sized; - fn sintab() -> Vec where Self: Sized; - fn tantab() -> Vec where Self: Sized; + fn pi() -> Self; + fn recip(self) -> Self; + fn round(&self) -> i32; + fn sqrt(self) -> Result where Self: Sized; + fn sintab() -> Vec where Self: Sized; + fn tantab() -> Vec where Self: Sized; fn sin(d :i32) -> Self where Self: Sized + Neg + Copy { @@ -73,6 +75,11 @@ pub trait Trig { }, } } + + fn cot(d :i32) -> Self + where Self: Sized + Copy + From + Div { + Into::::into(1) / Self::tan(d) + } } // Try to keep precision as high as possible while having a denominator @@ -91,6 +98,11 @@ impl Trig for Fractional { Fractional(d, n) } + fn round(&self) -> i32 { + let Fractional(n, d) = self; + (n / d) as i32 + } + // This is a really bad approximation of sqrt for a fractional... // for (9/3) it will result 3 which if way to far from the truth, // which is ~1.7320508075 @@ -198,6 +210,10 @@ impl Trig for f64 { self.recip() } + fn round(&self) -> i32 { + f64::round(*self) as i32 + } + fn sqrt(self) -> Result { let x = self.sqrt(); match x.is_nan() {