Showing
4 changed files
with
188 additions
and
121 deletions
fractional/src/geometry.rs
0 → 100644
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