A 3D math playground visualizing on a canvas trait which the user needs to implement e.g. using XCB or a HTML5 Canvas for drawing as WebAssembly application. (Both exists in separate projects.)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

497 lines
18 KiB

//
// Test our fractional crate / module...
//
// Georg Hopp <georg@steffers.org>
//
// Copyright © 2019 Georg Hopp
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
use std::convert::{TryFrom, TryInto, Into};
use std::f64::consts::PI as FPI;
use std::fmt::Display;
use std::num::TryFromIntError;
use std::ops::{Add,Sub,Neg,Mul,Div};
use std::sync::mpsc;
use std::thread;
use std::time::{Duration, Instant};
use fractional::continuous::Continuous;
use fractional::easel::{ Coordinate, Coordinates, Drawable, Line, Polyline
, Polygon, Rectangle};
use fractional::fractional::{Fractional, from_vector};
use fractional::trigonometry::Trig;
use fractional::vector::{Vector};
use fractional::transform::{TMatrix, translate, rotate_x, rotate_y, rotate_z, rotate_v};
use fractional::xcb::XcbEasel;
use fractional::easel::Canvas;
fn mean(v: &Vec<i64>) -> Result<Fractional, TryFromIntError> {
let r = v.iter().fold(0, |acc, x| acc + x);
let l = i64::try_from(v.len())?;
Ok(Fractional(r, l))
}
fn common_fractional() {
let a = vec![3, 6, 1, 9];
let b = from_vector(&a);
let c = mean(&a).unwrap(); // This might fail if the len of the
// vector (usize) does not fit into i32.
let cr :f64 = c.try_into().unwrap();
println!(" [i32] : {:?}", a);
println!(" [Fractional] : {:?}", b);
println!(" mean of [i32] : {}" , c);
println!(" as f64 : {}" , cr);
println!(" again as f64 : {}" , TryInto::<f64>::try_into(c).unwrap());
}
fn continuous() {
let d = Fractional(45, 16);
let e = Fractional(16, 45);
let dc :Continuous = (&d).into();
let ec :Continuous = (&e).into();
println!("cont frac of d : {} => {:?}", d, dc);
println!("cont frac of e : {} => {:?}", e, ec);
println!(" reverted dc : {:?} {}", dc, Into::<Fractional>::into(&dc));
println!(" reverted ec : {:?} {}", ec, Into::<Fractional>::into(&ec));
}
fn sqrt() {
let f = Fractional(-9, 4);
let fr :f64 = f.try_into().unwrap();
let sq = f.sqrt();
let _sq = fr.sqrt();
println!("{:>14} : {:?} / {}", format!("sqrt {}", f), sq, _sq);
for f in [ Fractional(9, 4)
, Fractional(45, 16)
, Fractional(16, 45)
, Fractional(9, 3) ].iter() {
let fr :f64 = (*f).try_into().unwrap();
let sq = f.sqrt().unwrap();
let sqr :f64 = sq.try_into().unwrap();
let _sqr = fr.sqrt();
println!("{:>14} : {} {} / {}", format!("sqrt {}", f), sq, sqr, _sqr);
}
}
fn pi() {
let pi = Fractional::pi();
let pir :f64 = pi.try_into().unwrap();
let pit :(i32, i32) = pi.try_into().unwrap();
let pi2r :f64 = (pi * pi).try_into().unwrap();
println!(" Rust π : {}" , FPI);
println!(" π : {} {}" , pi, pir);
println!(" π as tuple : {:?}" , pit);
println!(" Rust π² : {}" , FPI * FPI);
println!(" π² : {} {}" , pi * pi, pi2r);
}
fn _sin() {
for d in [ 0, 30, 45, 90, 135, 180, 225, 270, 315
, 9, 17, 31, 73, 89, 123, 213, 312, 876 ].iter() {
let s = Fractional::sin(*d as i32);
let sr :f64 = s.try_into().unwrap();
let _s = f64::sin(*d as f64 * FPI / 180.0);
println!("{:>14} : {} {} / {}", format!("sin {}", d), s, sr, _s);
}
}
fn _tan() {
for d in [ 0, 30, 45, 90, 135, 180, 225, 270, 315
, 9, 17, 31, 73, 89, 123, 213, 312, 876 ].iter() {
let t = Fractional::tan(*d as i32);
let tr :f64 = t.try_into().unwrap();
let _t = f64::tan(*d as f64 * FPI / 180.0);
println!("{:>14} : {} {} / {}", format!("tan {}", d), t, tr, _t);
}
}
fn _cos() {
for d in [ 0, 30, 45, 90, 135, 180, 225, 270, 315
, 9, 17, 31, 73, 89, 123, 213, 312, 876 ].iter() {
let c = Fractional::cos(*d as i32);
let cr :f64 = c.try_into().unwrap();
let _c = f64::cos(*d as f64 * FPI / 180.0);
println!("{:>14} : {} {} / {}", format!("cos {}", d), c, cr, _c);
}
}
fn _vector1() {
let v1 = Vector(1.into(), 2.into(), 3.into());
let v2 = Vector(2.into(), 2.into(), 3.into());
let s :Fractional = 3.into();
_vector(v1, v2, s);
}
fn _vector2() {
let v1 = Vector(1.0, 2.0, 3.0);
let v2 = Vector(2.0, 2.0, 3.0);
let s = 3.0;
_vector(v1, v2, s);
}
fn _vector<T>(v1 :Vector<T>, v2 :Vector<T>, s :T)
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
+ Mul<Output = T> + Div<Output = T> + Trig + Copy + Display {
println!("{:>14} : {}", "Vector v1", v1);
println!("{:>14} : {}", "Vector v2", v2);
println!("{:>14} : {}", "abs v1", v1.abs());
println!("{:>14} : {}", "-v1", -v1);
println!("{:>14} : {}", "v1 + v1", v1 + v1);
println!("{:>14} : {}", "v1 - v1", v1 - v1);
println!("{:>14} : {}", "v2 - v1", v2 - v1);
println!("{:>14} : {}", format!("v1 * {}", s), v1.mul(&s));
println!("{:>14} : {}", "norm v1", v1.norm());
println!("{:>14} : {}", "abs norm v1", v1.norm().abs());
println!("{:>14} : {}", "abs v1", v1.abs());
println!("{:>14} : {}", "norm * abs", v1.norm().mul(&v1.abs()));
println!("{:>14} : {}", "distance v1 v2", v1.distance(v2));
println!("{:>14} : {}", "distance v2 v1", v2.distance(v1));
println!("{:>14} : {}", "v1 dot v2", v1.dot(v2));
println!("{:>14} : {}", "v2 dot v1", v2.dot(v1));
println!("{:>14} : {}", "v1 * v2", v1 * v2);
println!("{:>14} : {}", "v2 * v1", v2 * v1);
}
fn _transform1() {
let v = Vector(Fractional(1,1), Fractional(1,1), Fractional(1,1));
let v1 = Vector(Fractional(1,1), Fractional(2,1), Fractional(3,1));
let v2 = Vector(Fractional(1,1), Fractional(1,1), Fractional(0,1));
let v3 = Vector(Fractional(1,1), Fractional(0,1), Fractional(1,1));
_transform(v, v1, v2, v3);
}
fn _transform2() {
let v = Vector(1.0, 1.0, 1.0);
let v1 = Vector(1.0, 2.0, 3.0);
let v2 = Vector(1.0, 1.0, 0.0);
let v3 = Vector(1.0, 0.0, 1.0);
_transform(v, v1, v2, v3);
}
fn _transform<T>(v :Vector<T>, v1 :Vector<T>, v2 :Vector<T>, v3 :Vector<T>)
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
+ Mul<Output = T> + Div<Output = T> + Trig
+ From<i32> + Copy + Display {
println!("{:>14} : {}", "Vector v1", v1);
println!("{:>14} : {}", "translate v1", translate(v).apply(&v1));
println!();
fn _rot<T>( o :&str , n :&str , v :&Vector<T>
, fs :&[&dyn Fn(i32) -> TMatrix<T>] )
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
+ Mul<Output = T> + Div<Output = T> + Trig
+ From<i32> + Copy + Display {
for d in [ 30, 45, 60, 90, 120, 135, 150, 180
, 210, 225, 240, 270, 300, 315, 330 ].iter() {
let mi = fs.iter().map(|f| f(*d as i32));
println!( "{:>14} : {}"
, format!("{} {} {}", o, d, n)
, TMatrix::combine(mi).apply(v) );
}
}
println!("{:>14} : {}", "Vector v2", v2);
_rot("rot_x", "v2", &v2, &[&rotate_x]);
println!();
_rot("rot_y", "v2", &v2, &[&rotate_y]);
println!();
_rot("rot_xy", "v2", &v2, &[&rotate_x, &rotate_y]);
println!();
println!("{:>14} : {}", "Vector v3", v3);
_rot("rot_z", "v3", &v3, &[&rotate_z]);
println!();
for d in [ 30, 45, 60, 90, 120, 135, 150, 180
, 210, 225, 240, 270, 300, 315, 330 ].iter() {
println!( "{:>14} : {}"
, format!("rot_v {} v2", d)
, rotate_v(&v, *d as i32).apply(&v2));
}
}
fn _line() {
let a = (Coordinate(0, 1), Coordinate(6, 4));
let b = (Coordinate(0, 4), Coordinate(6, 1));
let c = (Coordinate(1, 0), Coordinate(6, 8));
let d = (Coordinate(1, 8), Coordinate(6, 0));
for i in [a, b, c, d].iter() {
println!("{:>14} : {}", Line(i.0, i.1), Line(i.0, i.1).plot());
println!("{:>14} : {}", Line(i.1, i.0), Line(i.1, i.0).plot());
}
println!();
let r = Rectangle(Coordinate(1, 1), Coordinate(10, 5));
println!("{:>14} : {}", r, r.plot());
println!();
let pl = Polyline(
Coordinates(vec!(a.0, a.1, b.0, b.1, c.0, c.1, d.0, d.1)));
println!("{:>14} : {}", pl, pl.plot());
println!();
let pg = Polygon(
Coordinates(vec!( Coordinate( 0, -10)
, Coordinate( 10, 10)
, Coordinate(-10, 10) )));
println!("{:>14} : {}", pg, pg.plot());
let i = Vector(Fractional( 0,1), Fractional(-30,1), Fractional(0,1));
let j = Vector(Fractional( 30,1), Fractional( 30,1), Fractional(0,1));
let k = Vector(Fractional(-30,1), Fractional( 30,1), Fractional(0,1));
let rot :TMatrix<Fractional> = rotate_z(20);
let Vector(ix, iy, _) = rot.apply(&i);
let Vector(jx, jy, _) = rot.apply(&j);
let Vector(kx, ky, _) = rot.apply(&k);
fn to_i32(x :Fractional) -> i32 {
let Fractional(n, d) = x;
(n / d + if (n % d).abs() < (n / 2).abs() { 0 } else { 1 }) as i32
}
println!();
let pg = Polygon(
Coordinates(vec!( Coordinate(to_i32(ix) + 100, to_i32(iy) + 100)
, Coordinate(to_i32(jx) + 100, to_i32(jy) + 100)
, Coordinate(to_i32(kx) + 100, to_i32(ky) + 100) )));
println!("{:>14} : {}", pg, pg.plot());
let i = Vector( 0.0, -30.0, 0.0);
let j = Vector( 30.0, 30.0, 0.0);
let k = Vector(-30.0, 30.0, 0.0);
let rot :TMatrix<f64> = rotate_z(20);
let Vector(ix, iy, _) = rot.apply(&i);
let Vector(jx, jy, _) = rot.apply(&j);
let Vector(kx, ky, _) = rot.apply(&k);
fn to_i32_2(x :f64) -> i32 {
x.round() as i32
}
println!();
let pg = Polygon(
Coordinates(vec!( Coordinate(to_i32_2(ix) + 100, to_i32_2(iy) + 100)
, Coordinate(to_i32_2(jx) + 100, to_i32_2(jy) + 100)
, Coordinate(to_i32_2(kx) + 100, to_i32_2(ky) + 100) )));
println!("{:>14} : {}", pg, pg.plot());
}
fn main() {
common_fractional();
println!();
continuous();
println!();
sqrt();
println!();
pi();
println!();
_sin();
println!();
_cos();
println!();
_tan();
println!();
_vector1();
println!();
_vector2();
println!();
_transform1();
println!();
_transform2();
println!();
_line();
let xcb = XcbEasel::new().unwrap();
let mut canvas = xcb.canvas(151, 151).unwrap();
canvas.set_title("Something...");
canvas.init_events();
let (tx, rx) = mpsc::channel();
// TODO I ran into overflow issues using fractionals so for now
// use floating point values.
// https://rechneronline.de/pi/tetrahedron.php
// yi = a / 12 * √6
let yi = 60.0 / 12.0 * 6.0.sqrt().unwrap();
// yc = a / 4 * √6
let yc = 60.0 / 4.0 * 6.0.sqrt().unwrap();
// zi = √3 / 6 * a
let zi = 3.0.sqrt().unwrap() / 6.0 * 60.0;
// zc = √3 / 3 * a
let zc = 3.0.sqrt().unwrap() / 3.0 * 60.0;
let i = Vector( 0.0, yc, 0.0);
let j = Vector(-30.0, -yi, -zi);
let k = Vector( 30.0, -yi, -zi);
let l = Vector( 0.0, -yi, zc);
let cf1 = Vector(-30.0, 30.0, -30.0);
let cf2 = Vector(-30.0, -30.0, -30.0);
let cf3 = Vector( 30.0, -30.0, -30.0);
let cf4 = Vector( 30.0, 30.0, -30.0);
let cb1 = Vector(-30.0, 30.0, 30.0);
let cb2 = Vector(-30.0, -30.0, 30.0);
let cb3 = Vector( 30.0, -30.0, 30.0);
let cb4 = Vector( 30.0, 30.0, 30.0);
fn to_screen(c: &dyn Canvas, v :Vector<f64>) -> Coordinate {
// TODO .. these are in fact constants that should be stored once
// somewhere… Rust doesn't let me make this static here.
// In a way they are part of the canvas and they should change as the
// canvas is changing…
let fovx :f64 = 1.0 / <f64 as Trig>::tan(50);
let fovy :f64 = c.width() as f64 / c.height() as f64 * fovx;
let xs = ( v.x() / v.z() * fovx * c.width() as f64 ).round() as i32
+ c.width() as i32 / 2;
let ys = ( -v.y() / v.z() * fovy * c.height() as f64 ).round() as i32
+ c.height() as i32 / 2;
Coordinate(xs, ys)
}
canvas.start_events(tx);
let start = Instant::now();
let step = Duration::from_millis(25);
let mut last = Instant::now();
thread::spawn(move || {
//const DWC :f64 = 10.0;
loop {
let deg = ((start.elapsed() / 20).as_millis() % 360) as i32;
let rot1 :TMatrix<f64> = rotate_z(deg)
* rotate_x(-deg*2)
* translate(Vector(0.0, 0.0, 150.0));
let rot2 :TMatrix<f64> = rotate_z(deg)
* rotate_y(-deg*2)
* translate(Vector(0.0, 0.0, 150.0));
let ia = rot1.apply(&i);
let ja = rot1.apply(&j);
let ka = rot1.apply(&k);
let la = rot1.apply(&l);
let cf1a = rot2.apply(&cf1);
let cf2a = rot2.apply(&cf2);
let cf3a = rot2.apply(&cf3);
let cf4a = rot2.apply(&cf4);
let cb1a = rot2.apply(&cb1);
let cb2a = rot2.apply(&cb2);
let cb3a = rot2.apply(&cb3);
let cb4a = rot2.apply(&cb4);
let pg1 = Polygon(Coordinates(vec!( to_screen(&canvas, ja)
, to_screen(&canvas, ka)
, to_screen(&canvas, la) )));
let pg2 = Polygon(Coordinates(vec!( to_screen(&canvas, ja)
, to_screen(&canvas, ia)
, to_screen(&canvas, ka) )));
let pg3 = Polygon(Coordinates(vec!( to_screen(&canvas, la)
, to_screen(&canvas, ia)
, to_screen(&canvas, ja) )));
let pg4 = Polygon(Coordinates(vec!( to_screen(&canvas, ka)
, to_screen(&canvas, ia)
, to_screen(&canvas, la) )));
// front: cf1 cf2 cf3 cf4
let cf = Polygon(Coordinates(vec!( to_screen(&canvas, cf1a)
, to_screen(&canvas, cf2a)
, to_screen(&canvas, cf3a)
, to_screen(&canvas, cf4a) )));
// back: cb4 cb3 cb2 cb1
let cb = Polygon(Coordinates(vec!( to_screen(&canvas, cb4a)
, to_screen(&canvas, cb3a)
, to_screen(&canvas, cb2a)
, to_screen(&canvas, cb1a) )));
// top: cf2 cb2 cb3 cf3
let ct = Polygon(Coordinates(vec!( to_screen(&canvas, cf2a)
, to_screen(&canvas, cb2a)
, to_screen(&canvas, cb3a)
, to_screen(&canvas, cf3a) )));
// bottom: cf1 cf4 cb4 cb1
let co = Polygon(Coordinates(vec!( to_screen(&canvas, cf1a)
, to_screen(&canvas, cf4a)
, to_screen(&canvas, cb4a)
, to_screen(&canvas, cb1a) )));
// left: cf1 cb1 cb2 cf2
let cl = Polygon(Coordinates(vec!( to_screen(&canvas, cf1a)
, to_screen(&canvas, cb1a)
, to_screen(&canvas, cb2a)
, to_screen(&canvas, cf2a) )));
// right: cf3 cb3 cb4 cf4
let cr = Polygon(Coordinates(vec!( to_screen(&canvas, cf3a)
, to_screen(&canvas, cb3a)
, to_screen(&canvas, cb4a)
, to_screen(&canvas, cf4a) )));
canvas.clear();
canvas.draw(&pg1, Coordinate(0,0), 0xFFFF00);
canvas.draw(&pg2, Coordinate(0,0), 0xFFFF00);
canvas.draw(&pg3, Coordinate(0,0), 0xFFFF00);
canvas.draw(&pg4, Coordinate(0,0), 0xFFFF00);
canvas.draw( &cf, Coordinate(0,0), 0x0000FF);
canvas.draw( &cb, Coordinate(0,0), 0x0000FF);
canvas.draw( &ct, Coordinate(0,0), 0x0000FF);
canvas.draw( &co, Coordinate(0,0), 0x0000FF);
canvas.draw( &cl, Coordinate(0,0), 0x0000FF);
canvas.draw( &cr, Coordinate(0,0), 0x0000FF);
let passed = Instant::now() - last;
let f = (passed.as_nanos() / step.as_nanos()) as u32;
if f > 1 {
println!("!!! Detected frame drop");
}
last = last + step*(f + 1);
canvas.put_text( Coordinate(10, 15)
, &format!( "sleep: {:?}"
, last - Instant::now() ));
canvas.show();
thread::sleep(last - Instant::now());
}
});
for x in rx {
match x {
1 => break,
_ => {},
}
}
}