Browse Source

Add geometric primitives

master
Georg Hopp 6 years ago
parent
commit
1e6d133ffe
Signed by: ghopp GPG Key ID: 4C5D226768784538
  1. 150
      fractional/src/geometry.rs
  2. 1
      fractional/src/lib.rs
  3. 132
      fractional/src/main.rs
  4. 26
      fractional/src/trigonometry.rs

150
fractional/src/geometry.rs

@ -0,0 +1,150 @@
//
// 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;
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<T>
where T: Add + Sub + Neg + Mul + Div + Copy + Trig {
points :Vec<Vector<T>>,
faces :Vec<Vec<usize>>,
}
pub trait Primitives<T>
where T: Add + Sub + Neg + Mul + Div + Copy + Trig + From<i32> {
fn transform(&self, m :&TMatrix<T>) -> Self;
fn project(&self, camera :&Camera<T>) -> Vec<Polygon>;
}
pub struct Camera<T>
where T: Add + Sub + Neg + Mul + Div + Copy + Trig {
width :T,
height :T,
fovx :T,
fovy :T,
}
impl<T> Camera<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
+ Mul<Output = T> + Div<Output = T>
+ Copy + Trig + From<i32> {
pub fn new(c :&dyn Canvas, angle :i32) -> Self {
let width = <T as From<i32>>::from(c.width() as i32);
let height = <T as From<i32>>::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<T>) -> Coordinate {
let f2 = From::<i32>::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<T> Polyeder<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
+ Mul<Output = T> + Div<Output = T>
+ Copy + Trig + From<i32> {
// https://rechneronline.de/pi/tetrahedron.php
pub fn tetrahedron(a :T) -> Polyeder<T> {
let f0 :T = From::<i32>::from(0);
let f3 :T = From::<i32>::from(3);
let f4 :T = From::<i32>::from(4);
let f6 :T = From::<i32>::from(6);
let f12 :T = From::<i32>::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::<i32>::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<T> {
let ah :T = a / From::<i32>::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<T> Primitives<T> for Polyeder<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
+ Mul<Output = T> + Div<Output = T>
+ Copy + Trig + From<i32> + From<i32> {
fn transform(&self, m :&TMatrix<T>) -> Self {
Polyeder{ points: self.points.iter().map(|p| m.apply(p)).collect()
, faces: self.faces.to_vec() }
}
fn project(&self, camera :&Camera<T>) -> Vec<Polygon> {
fn polygon<I>(c :I) -> Polygon
where I: Iterator<Item = Coordinate> {
Polygon(Coordinates(c.collect()))
}
let to_coord = |p :&usize| camera.project(self.points[*p]);
let to_poly = |f :&Vec<usize>| polygon(f.iter().map(to_coord));
self.faces.iter().map(to_poly).collect()
}
}

1
fractional/src/lib.rs

@ -29,6 +29,7 @@ pub mod transform;
pub mod trigonometry; pub mod trigonometry;
pub mod vector; pub mod vector;
pub mod xcb; pub mod xcb;
pub mod geometry;
use fractional::Fractional; use fractional::Fractional;
use vector::Vector; use vector::Vector;

132
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::xcb::XcbEasel;
use fractional::easel::Canvas; use fractional::easel::Canvas;
use fractional::geometry::{Camera,Polyeder,Primitives};
fn mean(v: &Vec<i64>) -> Result<Fractional, TryFromIntError> { fn mean(v: &Vec<i64>) -> Result<Fractional, TryFromIntError> {
let r = v.iter().fold(0, |acc, x| acc + x); let r = v.iter().fold(0, |acc, x| acc + x);
let l = i64::try_from(v.len())?; let l = i64::try_from(v.len())?;
@ -340,48 +342,10 @@ fn main() {
let (tx, rx) = mpsc::channel(); 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<f64>) -> 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 / <f64 as Trig>::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::<f64>::new(&canvas, 40); // the orig. view angle
// was 50.
canvas.start_events(tx); canvas.start_events(tx);
@ -389,88 +353,24 @@ fn main() {
let step = Duration::from_millis(25); let step = Duration::from_millis(25);
let mut last = Instant::now(); let mut last = Instant::now();
thread::spawn(move || { thread::spawn(move || {
//const DWC :f64 = 10.0;
loop { loop {
let deg = ((start.elapsed() / 20).as_millis() % 360) as i32;
let deg = ((start.elapsed() / 25).as_millis() % 360) as i32;
let rot1 :TMatrix<f64> = rotate_z(deg) let rot1 :TMatrix<f64> = rotate_z(deg)
* rotate_x(-deg*2) * rotate_x(-deg*2)
* translate(Vector(0.0, 0.0, 150.0)); * translate(Vector(0.0, 0.0, 150.0));
let rot2 :TMatrix<f64> = rotate_z(deg)
* rotate_y(-deg*2)
let rot2 :TMatrix<f64> = rotate_z(-deg*2)
* rotate_y(deg)
* translate(Vector(0.0, 0.0, 150.0)); * 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.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 passed = Instant::now() - last;
let f = (passed.as_nanos() / step.as_nanos()) as u32; let f = (passed.as_nanos() / step.as_nanos()) as u32;

26
fractional/src/trigonometry.rs

@ -26,17 +26,19 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>. // along with this program. If not, see <http://www.gnu.org/licenses/>.
// //
use std::cmp::Ordering; use std::cmp::Ordering;
use std::ops::Div;
use std::ops::Neg; use std::ops::Neg;
use std::marker::Sized; use std::marker::Sized;
use crate::{Fractional, Error}; use crate::{Fractional, Error};
use crate::continuous::Continuous; use crate::continuous::Continuous;
pub trait Trig { pub trait Trig {
fn pi() -> Self;
fn recip(self) -> Self;
fn sqrt(self) -> Result<Self, Error> where Self: Sized;
fn sintab() -> Vec<Self> where Self: Sized;
fn tantab() -> Vec<Self> where Self: Sized;
fn pi() -> Self;
fn recip(self) -> Self;
fn round(&self) -> i32;
fn sqrt(self) -> Result<Self, Error> where Self: Sized;
fn sintab() -> Vec<Self> where Self: Sized;
fn tantab() -> Vec<Self> where Self: Sized;
fn sin(d :i32) -> Self fn sin(d :i32) -> Self
where Self: Sized + Neg<Output = Self> + Copy { where Self: Sized + Neg<Output = Self> + Copy {
@ -73,6 +75,11 @@ pub trait Trig {
}, },
} }
} }
fn cot(d :i32) -> Self
where Self: Sized + Copy + From<i32> + Div<Output = Self> {
Into::<Self>::into(1) / Self::tan(d)
}
} }
// Try to keep precision as high as possible while having a denominator // Try to keep precision as high as possible while having a denominator
@ -91,6 +98,11 @@ impl Trig for Fractional {
Fractional(d, n) 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... // 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, // for (9/3) it will result 3 which if way to far from the truth,
// which is ~1.7320508075 // which is ~1.7320508075
@ -198,6 +210,10 @@ impl Trig for f64 {
self.recip() self.recip()
} }
fn round(&self) -> i32 {
f64::round(*self) as i32
}
fn sqrt(self) -> Result<Self, Error> { fn sqrt(self) -> Result<Self, Error> {
let x = self.sqrt(); let x = self.sqrt();
match x.is_nan() { match x.is_nan() {

Loading…
Cancel
Save