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 29 pub mod trigonometry;
30 30 pub mod vector;
31 31 pub mod xcb;
  32 +pub mod geometry;
32 33
33 34 use fractional::Fractional;
34 35 use vector::Vector;
... ...
... ... @@ -38,6 +38,8 @@ use fractional::transform::{TMatrix, translate, rotate_x, rotate_y, rotate_z, ro
38 38 use fractional::xcb::XcbEasel;
39 39 use fractional::easel::Canvas;
40 40
  41 +use fractional::geometry::{Camera,Polyeder,Primitives};
  42 +
41 43 fn mean(v: &Vec<i64>) -> Result<Fractional, TryFromIntError> {
42 44 let r = v.iter().fold(0, |acc, x| acc + x);
43 45 let l = i64::try_from(v.len())?;
... ... @@ -340,48 +342,10 @@ fn main() {
340 342
341 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 350 canvas.start_events(tx);
387 351
... ... @@ -389,88 +353,24 @@ fn main() {
389 353 let step = Duration::from_millis(25);
390 354 let mut last = Instant::now();
391 355 thread::spawn(move || {
392   - //const DWC :f64 = 10.0;
393   -
394 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 358 let rot1 :TMatrix<f64> = rotate_z(deg)
397 359 * rotate_x(-deg*2)
398 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 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 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 375 let passed = Instant::now() - last;
476 376 let f = (passed.as_nanos() / step.as_nanos()) as u32;
... ...
... ... @@ -26,17 +26,19 @@
26 26 // along with this program. If not, see <http://www.gnu.org/licenses/>.
27 27 //
28 28 use std::cmp::Ordering;
  29 +use std::ops::Div;
29 30 use std::ops::Neg;
30 31 use std::marker::Sized;
31 32 use crate::{Fractional, Error};
32 33 use crate::continuous::Continuous;
33 34
34 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 43 fn sin(d :i32) -> Self
42 44 where Self: Sized + Neg<Output = Self> + Copy {
... ... @@ -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 85 // Try to keep precision as high as possible while having a denominator
... ... @@ -91,6 +98,11 @@ impl Trig for Fractional {
91 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 106 // This is a really bad approximation of sqrt for a fractional...
95 107 // for (9/3) it will result 3 which if way to far from the truth,
96 108 // which is ~1.7320508075
... ... @@ -198,6 +210,10 @@ impl Trig for f64 {
198 210 self.recip()
199 211 }
200 212
  213 + fn round(&self) -> i32 {
  214 + f64::round(*self) as i32
  215 + }
  216 +
201 217 fn sqrt(self) -> Result<Self, Error> {
202 218 let x = self.sqrt();
203 219 match x.is_nan() {
... ...
Please register or login to post a comment