Commit 1e6d133ffe8d5cfda673e848b95f872dab65d621

Authored by Georg Hopp
1 parent 1cfd7918

Add geometric primitives

  1 +//
  2 +// Basic geometric things...
  3 +//
  4 +// Georg Hopp <georg@steffers.org>
  5 +//
  6 +// Copyright © 2019 Georg Hopp
  7 +//
  8 +// This program is free software: you can redistribute it and/or modify
  9 +// it under the terms of the GNU General Public License as published by
  10 +// the Free Software Foundation, either version 3 of the License, or
  11 +// (at your option) any later version.
  12 +//
  13 +// This program is distributed in the hope that it will be useful,
  14 +// but WITHOUT ANY WARRANTY; without even the implied warranty of
  15 +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16 +// GNU General Public License for more details.
  17 +//
  18 +// You should have received a copy of the GNU General Public License
  19 +// along with this program. If not, see <http://www.gnu.org/licenses/>.
  20 +//
  21 +use std::convert::From;
  22 +use std::ops::{Add,Sub,Neg,Mul,Div};
  23 +use std::fmt::Debug;
  24 +
  25 +use crate::easel::{Canvas,Coordinate,Coordinates,Polygon};
  26 +use crate::transform::TMatrix;
  27 +use crate::trigonometry::Trig;
  28 +use crate::vector::Vector;
  29 +
  30 +#[derive(Debug)]
  31 +pub struct Polyeder<T>
  32 +where T: Add + Sub + Neg + Mul + Div + Copy + Trig {
  33 + points :Vec<Vector<T>>,
  34 + faces :Vec<Vec<usize>>,
  35 +}
  36 +
  37 +pub trait Primitives<T>
  38 +where T: Add + Sub + Neg + Mul + Div + Copy + Trig + From<i32> {
  39 + fn transform(&self, m :&TMatrix<T>) -> Self;
  40 + fn project(&self, camera :&Camera<T>) -> Vec<Polygon>;
  41 +}
  42 +
  43 +pub struct Camera<T>
  44 +where T: Add + Sub + Neg + Mul + Div + Copy + Trig {
  45 + width :T,
  46 + height :T,
  47 + fovx :T,
  48 + fovy :T,
  49 +}
  50 +
  51 +impl<T> Camera<T>
  52 +where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
  53 + + Mul<Output = T> + Div<Output = T>
  54 + + Copy + Trig + From<i32> {
  55 + pub fn new(c :&dyn Canvas, angle :i32) -> Self {
  56 + let width = <T as From<i32>>::from(c.width() as i32);
  57 + let height = <T as From<i32>>::from(c.height() as i32);
  58 +
  59 + // The calculations for fovx and fovy are taken from a book, but I
  60 + // have the impression, coming from my limited algebra knowledge,
  61 + // that they are always equal…
  62 + Camera { width: width
  63 + , height: height
  64 + , fovx: T::cot(angle) * width
  65 + , fovy: width / height * T::cot(angle) * height }
  66 + }
  67 +
  68 + pub fn project(&self, v :Vector<T>) -> Coordinate {
  69 + let f2 = From::<i32>::from(2);
  70 + let xs = v.x() / v.z() * self.fovx + self.width / f2;
  71 + let ys = v.y() / v.z() * self.fovy + self.height / f2;
  72 +
  73 + Coordinate(T::round(&xs), T::round(&ys))
  74 + }
  75 +}
  76 +
  77 +impl<T> Polyeder<T>
  78 +where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
  79 + + Mul<Output = T> + Div<Output = T>
  80 + + Copy + Trig + From<i32> {
  81 + // https://rechneronline.de/pi/tetrahedron.php
  82 + pub fn tetrahedron(a :T) -> Polyeder<T> {
  83 + let f0 :T = From::<i32>::from(0);
  84 + let f3 :T = From::<i32>::from(3);
  85 + let f4 :T = From::<i32>::from(4);
  86 + let f6 :T = From::<i32>::from(6);
  87 + let f12 :T = From::<i32>::from(12);
  88 +
  89 + let yi :T = a / f12 * T::sqrt(f6).unwrap();
  90 + let yc :T = a / f4 * T::sqrt(f6).unwrap();
  91 + let zi :T = T::sqrt(f3).unwrap() / f6 * a;
  92 + let zc :T = T::sqrt(f3).unwrap() / f3 * a;
  93 + let ah :T = a / From::<i32>::from(2);
  94 +
  95 + // half the height in y
  96 + let _yh :T = a / f6 * T::sqrt(f6).unwrap();
  97 + // half the deeps in z
  98 + let _zh :T = T::sqrt(f3).unwrap() / f4 * a;
  99 +
  100 + Polyeder{ points: vec!( Vector( f0, yc, f0)
  101 + , Vector(-ah, -yi, -zi)
  102 + , Vector( ah, -yi, -zi)
  103 + , Vector( f0, -yi, zc) )
  104 + , faces: vec!( vec!(1, 2, 3)
  105 + , vec!(1, 0, 2)
  106 + , vec!(3, 0, 1)
  107 + , vec!(2, 0, 3) )}
  108 + }
  109 +
  110 + pub fn cube(a :T) -> Polyeder<T> {
  111 + let ah :T = a / From::<i32>::from(2);
  112 +
  113 + Polyeder{ points: vec!( Vector(-ah, ah, -ah) // 0 => front 1
  114 + , Vector(-ah, -ah, -ah) // 1 => front 2
  115 + , Vector( ah, -ah, -ah) // 2 => front 3
  116 + , Vector( ah, ah, -ah) // 3 => front 4
  117 + , Vector(-ah, ah, ah) // 4 => back 1
  118 + , Vector(-ah, -ah, ah) // 5 => back 2
  119 + , Vector( ah, -ah, ah) // 6 => back 3
  120 + , Vector( ah, ah, ah) ) // 7 => back 4
  121 + , faces: vec!( vec!(0, 1, 2, 3) // front
  122 + , vec!(7, 6, 5, 4) // back
  123 + , vec!(1, 5, 6, 2) // top
  124 + , vec!(0, 3, 7, 4) // bottom
  125 + , vec!(0, 4, 5, 1) // left
  126 + , vec!(2, 6, 7, 3) )} // right
  127 + }
  128 +}
  129 +
  130 +impl<T> Primitives<T> for Polyeder<T>
  131 +where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
  132 + + Mul<Output = T> + Div<Output = T>
  133 + + Copy + Trig + From<i32> + From<i32> {
  134 + fn transform(&self, m :&TMatrix<T>) -> Self {
  135 + Polyeder{ points: self.points.iter().map(|p| m.apply(p)).collect()
  136 + , faces: self.faces.to_vec() }
  137 + }
  138 +
  139 + fn project(&self, camera :&Camera<T>) -> Vec<Polygon> {
  140 + fn polygon<I>(c :I) -> Polygon
  141 + where I: Iterator<Item = Coordinate> {
  142 + Polygon(Coordinates(c.collect()))
  143 + }
  144 +
  145 + let to_coord = |p :&usize| camera.project(self.points[*p]);
  146 + let to_poly = |f :&Vec<usize>| polygon(f.iter().map(to_coord));
  147 +
  148 + self.faces.iter().map(to_poly).collect()
  149 + }
  150 +}
@@ -29,6 +29,7 @@ pub mod transform; @@ -29,6 +29,7 @@ pub mod transform;
29 pub mod trigonometry; 29 pub mod trigonometry;
30 pub mod vector; 30 pub mod vector;
31 pub mod xcb; 31 pub mod xcb;
  32 +pub mod geometry;
32 33
33 use fractional::Fractional; 34 use fractional::Fractional;
34 use vector::Vector; 35 use vector::Vector;
@@ -38,6 +38,8 @@ use fractional::transform::{TMatrix, translate, rotate_x, rotate_y, rotate_z, ro @@ -38,6 +38,8 @@ use fractional::transform::{TMatrix, translate, rotate_x, rotate_y, rotate_z, ro
38 use fractional::xcb::XcbEasel; 38 use fractional::xcb::XcbEasel;
39 use fractional::easel::Canvas; 39 use fractional::easel::Canvas;
40 40
  41 +use fractional::geometry::{Camera,Polyeder,Primitives};
  42 +
41 fn mean(v: &Vec<i64>) -> Result<Fractional, TryFromIntError> { 43 fn mean(v: &Vec<i64>) -> Result<Fractional, TryFromIntError> {
42 let r = v.iter().fold(0, |acc, x| acc + x); 44 let r = v.iter().fold(0, |acc, x| acc + x);
43 let l = i64::try_from(v.len())?; 45 let l = i64::try_from(v.len())?;
@@ -340,48 +342,10 @@ fn main() { @@ -340,48 +342,10 @@ fn main() {
340 342
341 let (tx, rx) = mpsc::channel(); 343 let (tx, rx) = mpsc::channel();
342 344
343 - // TODO I ran into overflow issues using fractionals so for now  
344 - // use floating point values.  
345 - // https://rechneronline.de/pi/tetrahedron.php  
346 - // yi = a / 12 * √6  
347 - let yi = 60.0 / 12.0 * 6.0.sqrt().unwrap();  
348 - // yc = a / 4 * √6  
349 - let yc = 60.0 / 4.0 * 6.0.sqrt().unwrap();  
350 - // zi = √3 / 6 * a  
351 - let zi = 3.0.sqrt().unwrap() / 6.0 * 60.0;  
352 - // zc = √3 / 3 * a  
353 - let zc = 3.0.sqrt().unwrap() / 3.0 * 60.0;  
354 -  
355 - let i = Vector( 0.0, yc, 0.0);  
356 - let j = Vector(-30.0, -yi, -zi);  
357 - let k = Vector( 30.0, -yi, -zi);  
358 - let l = Vector( 0.0, -yi, zc);  
359 -  
360 - let cf1 = Vector(-30.0, 30.0, -30.0);  
361 - let cf2 = Vector(-30.0, -30.0, -30.0);  
362 - let cf3 = Vector( 30.0, -30.0, -30.0);  
363 - let cf4 = Vector( 30.0, 30.0, -30.0);  
364 -  
365 - let cb1 = Vector(-30.0, 30.0, 30.0);  
366 - let cb2 = Vector(-30.0, -30.0, 30.0);  
367 - let cb3 = Vector( 30.0, -30.0, 30.0);  
368 - let cb4 = Vector( 30.0, 30.0, 30.0);  
369 -  
370 - fn to_screen(c: &dyn Canvas, v :Vector<f64>) -> Coordinate {  
371 - // TODO .. these are in fact constants that should be stored once  
372 - // somewhere… Rust doesn't let me make this static here.  
373 - // In a way they are part of the canvas and they should change as the  
374 - // canvas is changing…  
375 - let fovx :f64 = 1.0 / <f64 as Trig>::tan(50);  
376 - let fovy :f64 = c.width() as f64 / c.height() as f64 * fovx;  
377 -  
378 - let xs = ( v.x() / v.z() * fovx * c.width() as f64 ).round() as i32  
379 - + c.width() as i32 / 2;  
380 - let ys = ( -v.y() / v.z() * fovy * c.height() as f64 ).round() as i32  
381 - + c.height() as i32 / 2;  
382 -  
383 - Coordinate(xs, ys)  
384 - } 345 + let tetrahedron = Polyeder::tetrahedron(60.0);
  346 + let cube = Polyeder::cube(60.0);
  347 + let camera = Camera::<f64>::new(&canvas, 40); // the orig. view angle
  348 + // was 50.
385 349
386 canvas.start_events(tx); 350 canvas.start_events(tx);
387 351
@@ -389,88 +353,24 @@ fn main() { @@ -389,88 +353,24 @@ fn main() {
389 let step = Duration::from_millis(25); 353 let step = Duration::from_millis(25);
390 let mut last = Instant::now(); 354 let mut last = Instant::now();
391 thread::spawn(move || { 355 thread::spawn(move || {
392 - //const DWC :f64 = 10.0;  
393 -  
394 loop { 356 loop {
395 - let deg = ((start.elapsed() / 20).as_millis() % 360) as i32; 357 + let deg = ((start.elapsed() / 25).as_millis() % 360) as i32;
396 let rot1 :TMatrix<f64> = rotate_z(deg) 358 let rot1 :TMatrix<f64> = rotate_z(deg)
397 * rotate_x(-deg*2) 359 * rotate_x(-deg*2)
398 * translate(Vector(0.0, 0.0, 150.0)); 360 * translate(Vector(0.0, 0.0, 150.0));
399 361
400 - let rot2 :TMatrix<f64> = rotate_z(deg)  
401 - * rotate_y(-deg*2) 362 + let rot2 :TMatrix<f64> = rotate_z(-deg*2)
  363 + * rotate_y(deg)
402 * translate(Vector(0.0, 0.0, 150.0)); 364 * translate(Vector(0.0, 0.0, 150.0));
403 365
404 - let ia = rot1.apply(&i);  
405 - let ja = rot1.apply(&j);  
406 - let ka = rot1.apply(&k);  
407 - let la = rot1.apply(&l);  
408 -  
409 - let cf1a = rot2.apply(&cf1);  
410 - let cf2a = rot2.apply(&cf2);  
411 - let cf3a = rot2.apply(&cf3);  
412 - let cf4a = rot2.apply(&cf4);  
413 -  
414 - let cb1a = rot2.apply(&cb1);  
415 - let cb2a = rot2.apply(&cb2);  
416 - let cb3a = rot2.apply(&cb3);  
417 - let cb4a = rot2.apply(&cb4);  
418 -  
419 - let pg1 = Polygon(Coordinates(vec!( to_screen(&canvas, ja)  
420 - , to_screen(&canvas, ka)  
421 - , to_screen(&canvas, la) )));  
422 - let pg2 = Polygon(Coordinates(vec!( to_screen(&canvas, ja)  
423 - , to_screen(&canvas, ia)  
424 - , to_screen(&canvas, ka) )));  
425 - let pg3 = Polygon(Coordinates(vec!( to_screen(&canvas, la)  
426 - , to_screen(&canvas, ia)  
427 - , to_screen(&canvas, ja) )));  
428 - let pg4 = Polygon(Coordinates(vec!( to_screen(&canvas, ka)  
429 - , to_screen(&canvas, ia)  
430 - , to_screen(&canvas, la) )));  
431 -  
432 - // front: cf1 cf2 cf3 cf4  
433 - let cf = Polygon(Coordinates(vec!( to_screen(&canvas, cf1a)  
434 - , to_screen(&canvas, cf2a)  
435 - , to_screen(&canvas, cf3a)  
436 - , to_screen(&canvas, cf4a) )));  
437 - // back: cb4 cb3 cb2 cb1  
438 - let cb = Polygon(Coordinates(vec!( to_screen(&canvas, cb4a)  
439 - , to_screen(&canvas, cb3a)  
440 - , to_screen(&canvas, cb2a)  
441 - , to_screen(&canvas, cb1a) )));  
442 - // top: cf2 cb2 cb3 cf3  
443 - let ct = Polygon(Coordinates(vec!( to_screen(&canvas, cf2a)  
444 - , to_screen(&canvas, cb2a)  
445 - , to_screen(&canvas, cb3a)  
446 - , to_screen(&canvas, cf3a) )));  
447 - // bottom: cf1 cf4 cb4 cb1  
448 - let co = Polygon(Coordinates(vec!( to_screen(&canvas, cf1a)  
449 - , to_screen(&canvas, cf4a)  
450 - , to_screen(&canvas, cb4a)  
451 - , to_screen(&canvas, cb1a) )));  
452 - // left: cf1 cb1 cb2 cf2  
453 - let cl = Polygon(Coordinates(vec!( to_screen(&canvas, cf1a)  
454 - , to_screen(&canvas, cb1a)  
455 - , to_screen(&canvas, cb2a)  
456 - , to_screen(&canvas, cf2a) )));  
457 - // right: cf3 cb3 cb4 cf4  
458 - let cr = Polygon(Coordinates(vec!( to_screen(&canvas, cf3a)  
459 - , to_screen(&canvas, cb3a)  
460 - , to_screen(&canvas, cb4a)  
461 - , to_screen(&canvas, cf4a) )));  
462 -  
463 canvas.clear(); 366 canvas.clear();
464 - canvas.draw(&pg1, Coordinate(0,0), 0xFFFF00);  
465 - canvas.draw(&pg2, Coordinate(0,0), 0xFFFF00);  
466 - canvas.draw(&pg3, Coordinate(0,0), 0xFFFF00);  
467 - canvas.draw(&pg4, Coordinate(0,0), 0xFFFF00);  
468 - canvas.draw( &cf, Coordinate(0,0), 0x0000FF);  
469 - canvas.draw( &cb, Coordinate(0,0), 0x0000FF);  
470 - canvas.draw( &ct, Coordinate(0,0), 0x0000FF);  
471 - canvas.draw( &co, Coordinate(0,0), 0x0000FF);  
472 - canvas.draw( &cl, Coordinate(0,0), 0x0000FF);  
473 - canvas.draw( &cr, Coordinate(0,0), 0x0000FF); 367 +
  368 + for pg in tetrahedron.transform(&rot1).project(&camera) {
  369 + canvas.draw(&pg, Coordinate(0,0), 0xFFFF00);
  370 + }
  371 + for pg in cube.transform(&rot2).project(&camera) {
  372 + canvas.draw(&pg, Coordinate(0,0), 0x0000FF);
  373 + }
474 374
475 let passed = Instant::now() - last; 375 let passed = Instant::now() - last;
476 let f = (passed.as_nanos() / step.as_nanos()) as u32; 376 let f = (passed.as_nanos() / step.as_nanos()) as u32;
@@ -26,17 +26,19 @@ @@ -26,17 +26,19 @@
26 // along with this program. If not, see <http://www.gnu.org/licenses/>. 26 // along with this program. If not, see <http://www.gnu.org/licenses/>.
27 // 27 //
28 use std::cmp::Ordering; 28 use std::cmp::Ordering;
  29 +use std::ops::Div;
29 use std::ops::Neg; 30 use std::ops::Neg;
30 use std::marker::Sized; 31 use std::marker::Sized;
31 use crate::{Fractional, Error}; 32 use crate::{Fractional, Error};
32 use crate::continuous::Continuous; 33 use crate::continuous::Continuous;
33 34
34 pub trait Trig { 35 pub trait Trig {
35 - fn pi() -> Self;  
36 - fn recip(self) -> Self;  
37 - fn sqrt(self) -> Result<Self, Error> where Self: Sized;  
38 - fn sintab() -> Vec<Self> where Self: Sized;  
39 - fn tantab() -> Vec<Self> where Self: Sized; 36 + fn pi() -> Self;
  37 + fn recip(self) -> Self;
  38 + fn round(&self) -> i32;
  39 + fn sqrt(self) -> Result<Self, Error> where Self: Sized;
  40 + fn sintab() -> Vec<Self> where Self: Sized;
  41 + fn tantab() -> Vec<Self> where Self: Sized;
40 42
41 fn sin(d :i32) -> Self 43 fn sin(d :i32) -> Self
42 where Self: Sized + Neg<Output = Self> + Copy { 44 where Self: Sized + Neg<Output = Self> + Copy {
@@ -73,6 +75,11 @@ pub trait Trig { @@ -73,6 +75,11 @@ pub trait Trig {
73 }, 75 },
74 } 76 }
75 } 77 }
  78 +
  79 + fn cot(d :i32) -> Self
  80 + where Self: Sized + Copy + From<i32> + Div<Output = Self> {
  81 + Into::<Self>::into(1) / Self::tan(d)
  82 + }
76 } 83 }
77 84
78 // Try to keep precision as high as possible while having a denominator 85 // Try to keep precision as high as possible while having a denominator
@@ -91,6 +98,11 @@ impl Trig for Fractional { @@ -91,6 +98,11 @@ impl Trig for Fractional {
91 Fractional(d, n) 98 Fractional(d, n)
92 } 99 }
93 100
  101 + fn round(&self) -> i32 {
  102 + let Fractional(n, d) = self;
  103 + (n / d) as i32
  104 + }
  105 +
94 // This is a really bad approximation of sqrt for a fractional... 106 // This is a really bad approximation of sqrt for a fractional...
95 // for (9/3) it will result 3 which if way to far from the truth, 107 // for (9/3) it will result 3 which if way to far from the truth,
96 // which is ~1.7320508075 108 // which is ~1.7320508075
@@ -198,6 +210,10 @@ impl Trig for f64 { @@ -198,6 +210,10 @@ impl Trig for f64 {
198 self.recip() 210 self.recip()
199 } 211 }
200 212
  213 + fn round(&self) -> i32 {
  214 + f64::round(*self) as i32
  215 + }
  216 +
201 fn sqrt(self) -> Result<Self, Error> { 217 fn sqrt(self) -> Result<Self, Error> {
202 let x = self.sqrt(); 218 let x = self.sqrt();
203 match x.is_nan() { 219 match x.is_nan() {
Please register or login to post a comment