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 | +} | ... | ... |
... | ... | @@ -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