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