Browse Source

use easel3d crate now

master
Georg Hopp 6 years ago
parent
commit
8f79b4d2a8
Signed by: ghopp GPG Key ID: 4C5D226768784538
  1. 20
      Cargo.toml
  2. 121
      README.md
  3. 520
      src/easel.rs
  4. 68
      src/easel3d_wasm.rs
  5. 377
      src/geometry.rs
  6. 245
      src/lib.rs
  7. 186
      src/transform.rs
  8. 143
      src/trigonometry.rs
  9. 10
      src/utils.rs
  10. 139
      src/vector.rs
  11. 24
      www/.bin/create-wasm-app.js
  12. 67
      www/README.md
  13. 2
      www/index.html
  14. 7
      www/index.js
  15. 14
      www/package-lock.json
  16. 22
      www/package.json

20
Cargo.toml

@ -1,5 +1,5 @@
[package]
name = "wasm-game-of-life"
name = "easel3d-wasm"
version = "0.1.0"
authors = ["hopp@silpion.de"]
edition = "2018"
@ -7,29 +7,13 @@ edition = "2018"
[lib]
crate-type = ["cdylib", "rlib"]
[features]
default = ["console_error_panic_hook"]
[dependencies]
wasm-bindgen = "0.2"
lazy_static = "1.4.0"
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
# code size when deploying.
console_error_panic_hook = { version = "0.1.1", optional = true }
# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
# compared to the default allocator's ~10K. It is slower than the default
# allocator, however.
#
# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now.
easel3d = { path = "../easel3d" }
wee_alloc = { version = "0.4.2", optional = true }
[dev-dependencies]
wasm-bindgen-test = "0.2"
[profile.release]
# Tell `rustc` to optimize for small code size.
opt-level = "s"

121
README.md

@ -1,116 +1,45 @@
<div align="center">
# Easel3D-WASM
<h1><code>wasm-pack-template</code></h1>
<strong>A template for kick starting a Rust and WebAssembly project using <a href="https://github.com/rustwasm/wasm-pack">wasm-pack</a>.</strong>
<p>
<a href="https://travis-ci.org/rustwasm/wasm-pack-template"><img src="https://img.shields.io/travis/rustwasm/wasm-pack-template.svg?style=flat-square" alt="Build Status" /></a>
</p>
<h3>
<a href="https://rustwasm.github.io/docs/wasm-pack/tutorials/npm-browser-packages/index.html">Tutorial</a>
<span> | </span>
<a href="https://discordapp.com/channels/442252698964721669/443151097398296587">Chat</a>
</h3>
<sub>Built with 🦀🕸 by <a href="https://rustwasm.github.io/">The Rust and WebAssembly Working Group</a></sub>
</div>
## About
[**📚 Read this template tutorial! 📚**][template-docs]
This template is designed for compiling Rust libraries into WebAssembly and
publishing the resulting package to NPM.
Be sure to check out [other `wasm-pack` tutorials online][tutorials] for other
templates and usages of `wasm-pack`.
[tutorials]: https://rustwasm.github.io/docs/wasm-pack/tutorials/index.html
[template-docs]: https://rustwasm.github.io/docs/wasm-pack/tutorials/npm-browser-packages/index.html
## 🚴 Usage
### 🐑 Use `cargo generate` to Clone this Template
[Learn more about `cargo generate` here.](https://github.com/ashleygwilliams/cargo-generate)
```
cargo generate --git https://github.com/rustwasm/wasm-pack-template.git --name my-project
cd my-project
```
### 🛠️ Build with `wasm-pack build`
```
wasm-pack build
```
### 🔬 Test in Headless Browsers with `wasm-pack test`
```
wasm-pack test --headless --firefox
```
### 🎁 Publish to NPM with `wasm-pack publish`
```
wasm-pack publish
```
## 🔋 Batteries Included
* [`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) for communicating
between WebAssembly and JavaScript.
* [`console_error_panic_hook`](https://github.com/rustwasm/console_error_panic_hook)
for logging panic messages to the developer console.
* [`wee_alloc`](https://github.com/rustwasm/wee_alloc), an allocator optimized
for small code size.
# Rust playground
Things I have recently done while learning the Rust programming language.
WebAssembly demo application using Easel3D...
## Synopsis
Change in one of the toplevel subdirectories and try `cargo build` or
`cargo run`. Maybe not everything is working oob. Feel free to fix whatever
you want.
Checkout this repository as well as the _easel3d_ repository to the same
destination directory.
## Description
In the root of this repository call `wasm-pack build`.
Then change to www and call `npm install`.
Various small examples I have tried while learning rust. The biggest and
currently most active project is **fractional** which started as an
implamentation of a rational number data type and then switched to a 3D
math playground visualizing using **XCB** (in future it might also use
a **HTML5 Canvas** for drawing as WebAssembly application.
Using fractions with 3D math has several drawbacks:
To test the stuff call `npm run start` from www directory and in your browser
open http:://localhost:8080/.
1. A huge part of 3D math is non rational, like sin, cos, tan and sqrt.
2. The numerator and denominator tend to become very huge while nearing to non
rational numbers and reduction is difficult and time consuming.
3. Because of 2 it is way slower than the floating point calculation (at least
with a decent coprocessor).
## Description
Anyway, implementing the vector math stuff for both fractions and floating
point was a nice playground for generics and traits. In future I might add
another data type which implements the math as done by David Braben for the
elite computer game.
A demo application using easel3d to draw in an HTML5 canvas element in a web
page.
## Requirements
### Always
- A recent version of the Rust programming language as well as tooling.
Currently I use Rust 1.39.0.
- wasm-pack to build to wasm target
- npm for Javascript code.
- A browser capable of executing WebAssembly.
### For fractional
## Dependencies
- A running X Server with **XCB** and **X11-SHM** extentions
### Rust crates.
## Dependencies
- easel3d (from parent directory)
- wasm-bindgen =0.2
- wee-alloc =0.4.2 (optional)
Along with the dependencies of the external crates. `wasm-pack build` should
take care of having them available.
### Javascript
...
Various things npm installs.
## Contributing

520
src/easel.rs

@ -1,520 +0,0 @@
//
// This is an abstraction over a drawing environment.
// Future note: z-Buffer is described here:
// https://www.scratchapixel.com/lessons/3d-basic-rendering/rasterization-practical-implementation/perspective-correct-interpolation-vertex-attributes
//
// 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::cmp;
use std::fmt::{Formatter, Debug, Display, Result};
use std::ops::{Add, Sub, Div};
use std::sync::mpsc;
pub trait Easel {
//fn canvas(&mut self, width :u16, height :u16) -> Option<&dyn Canvas>;
}
pub trait Canvas<T> {
fn init_events(&self);
fn start_events(&self, tx :mpsc::Sender<i32>);
fn width(&self) -> u16;
fn height(&self) -> u16;
fn clear(&mut self);
fn draw(&mut self, c :&dyn Drawable<T>, ofs :Coordinate<T>, color :u32);
fn put_text(&self, ofs :Coordinate<T>, s :&str);
fn set_pixel(&mut self, c :Coordinate<T>, color :u32);
fn show(&self);
}
pub trait Drawable<T> {
fn plot(&self) -> Coordinates<T>;
}
pub trait Fillable<T>
where T: Add<Output = T> + Sub<Output = T> + Div<Output = T>
+ Debug + Copy + From<i32> {
fn fill(&self, canvas :&mut dyn Canvas<T>, color :u32);
}
#[derive(Debug, Clone, Copy)]
pub struct Coordinate<T>(pub i32, pub i32, pub T);
#[derive(Debug, Clone)]
pub struct Coordinates<T>(pub Vec<Coordinate<T>>);
#[derive(Debug, Clone, Copy)]
pub struct LineIterator<T> where T: Debug {
a :Option<Coordinate<T>>
, b :Coordinate<T>
, dx :i32
, dy :i32
, dz :T
, sx :i32
, sy :i32
, err :i32
, only_edges :bool
}
impl<T> Iterator for LineIterator<T>
where T: Add<Output = T> + Debug + Copy + From<i32> {
type Item = Coordinate<T>;
fn next(&mut self) -> Option<Self::Item> {
match self.a {
None => None,
Some(a) => {
let Coordinate(ax, ay, az) = a;
let Coordinate(bx, by, _) = self.b;
if ax != bx || ay != by {
match (2 * self.err >= self.dy, 2 * self.err <= self.dx ) {
(true, false) => {
let r = self.a;
self.a = Some(Coordinate( ax + self.sx
, ay
, az + self.dz ));
self.err = self.err + self.dy;
if self.only_edges { self.next() } else { r }
},
(false, true) => {
let r = self.a;
self.a = Some(Coordinate( ax
, ay + self.sy
, az + self.dz ));
self.err = self.err + self.dx;
r
},
_ => {
let r = self.a;
self.a = Some(Coordinate( ax + self.sx
, ay + self.sy
, az + self.dz ));
self.err = self.err + self.dx + self.dy;
r
},
}
} else {
self.a = None;
Some(self.b)
}
}
}
}
}
impl<T> Coordinate<T>
where T: Add<Output = T> + Sub<Output = T> + Div<Output = T>
+ Debug + Clone + Copy + From<i32> {
fn iter(self, b :&Self, only_edges :bool) -> LineIterator<T> {
let Coordinate(ax, ay, az) = self;
let Coordinate(bx, by, bz) = *b;
let dx = (bx - ax).abs();
let dy = -(by - ay).abs();
LineIterator { a: Some(self)
, b: *b
, dx: dx
, dy: dy
, dz: (bz - az) / cmp::max(dx, -dy).into()
, sx: if ax < bx { 1 } else { -1 }
, sy: if ay < by { 1 } else { -1 }
, err: dx + dy
, only_edges: only_edges
}
}
fn line_iter(self, b :&Self) -> LineIterator<T> {
self.iter(b, false)
}
fn line(self, b :&Self) -> Vec<Self> {
self.line_iter(b).collect()
}
fn edge_iter(self, b :&Self) -> LineIterator<T> {
self.iter(b, true)
}
fn edge(self, b :&Self) -> Vec<Self> {
self.edge_iter(b).collect()
}
fn face(edges :&[Self]) -> Vec<Self> {
edges.to_vec()
}
}
impl<T> Display for Coordinate<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "<{},{}>", self.0, self.1)
}
}
impl<T> Display for Coordinates<T> where T: Copy {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
let Coordinates(is) = self;
let c = match is[..] {
[] => String::from(""),
[a] => format!("{}", a),
_ => {
let mut a = format!("{}", is[0]);
for i in is[1..].iter() {
a = a + &format!(",{}", i);
}
a
}
};
write!(f, "Coordinates[{}]", c)
}
}
#[derive(Debug, Clone, Copy)]
pub struct Point<T>(pub Coordinate<T>);
impl<T> Drawable<T> for Point<T> where T: Copy {
fn plot(&self) -> Coordinates<T> {
let Point(c) = *self;
Coordinates(vec!(c))
}
}
impl<T> Display for Point<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
let Point(p) = self;
write!(f, "Point[{}]", p)
}
}
#[derive(Debug, Clone, Copy)]
pub struct Line<T>(pub Coordinate<T>, pub Coordinate<T>);
impl<T> Drawable<T> for Line<T>
where T: Add<Output = T> + Sub<Output = T> + Div<Output = T>
+ Debug + Clone + Copy + From<i32> {
fn plot(&self) -> Coordinates<T> {
let Line(a, b) = *self;
Coordinates(a.line(&b))
}
}
impl<T> Display for Line<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
let Line(a, b) = self;
write!(f, "Line[{},{}]", a, b)
}
}
#[derive(Debug, Clone)]
pub struct Polyline<T>(pub Coordinates<T>);
impl<T> Drawable<T> for Polyline<T>
where T: Add<Output = T> + Sub<Output = T> + Div<Output = T>
+ Debug + Clone + Copy + From<i32> {
fn plot(&self) -> Coordinates<T> {
let Polyline(Coordinates(cs)) = self;
match cs[..] {
[] => Coordinates(Vec::<Coordinate<T>>::new()),
[a] => Coordinates(vec!(a)),
[a, b] => Coordinates(a.line(&b)),
_ => {
let (a, b) = (cs[0], cs[1]);
let mut r = a.line(&b);
let mut i = b;
for j in cs[2..].iter() {
r.append(&mut i.line(j)[1..].to_vec());
i = *j;
}
Coordinates(r)
},
}
}
}
impl<T> Display for Polyline<T> where T: Copy {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
let Polyline(a) = self;
write!(f, "PLine[{}]", a)
}
}
#[derive(Debug, Clone, Copy)]
enum Direction { Left, Right }
#[derive(Debug, Clone)]
pub struct Polygon<T>(pub Coordinates<T>);
#[derive(Debug, Clone)]
enum VertexIteratorMode { Vertex, Edge }
#[derive(Debug, Clone)]
pub struct VertexIterator<'a,T> where T: Debug {
p :&'a Polygon<T>,
top :usize,
current :Option<usize>,
edge :Option<LineIterator<T>>,
mode :VertexIteratorMode,
direction :Direction,
}
impl<'a,T> VertexIterator<'a,T>
where T: Add<Output = T> + Sub<Output = T> + Div<Output = T>
+ Debug + Copy + From<i32> {
fn edge(p :&'a Polygon<T>, direction :Direction) -> Self {
let top = p.vert_min(direction);
let next = p.next_y(top, direction);
let edge = match next {
None => None,
Some(next) => Some(p.vertex(top).edge_iter(&p.vertex(next))),
};
VertexIterator { p: p
, top: top
, current: next
, edge: edge
, mode: VertexIteratorMode::Edge
, direction: direction }
}
fn vertex(p :&'a Polygon<T>, direction :Direction) -> Self {
let top = p.vert_min(direction);
let next = p.next_y(top, direction);
VertexIterator { p: p
, top: top
, current: next
, edge: None
, mode: VertexIteratorMode::Vertex
, direction: direction }
}
// if this yields "None" we are finished.
fn next_edge(&mut self) -> Option<LineIterator<T>> {
let current = self.current?;
let next = self.p.next_y(current, self.direction)?;
let mut edge = self.p.vertex(current).edge_iter(&self.p.vertex(next));
match edge.next() {
// It should be impossible that a new edge iterator has no values
// at all… anyway, just in case I handle it here.
None => self.next_edge(),
Some(_) => {
self.current = Some(next);
self.edge = Some(edge);
self.edge
},
}
}
}
impl<'a,T> Iterator for VertexIterator<'a,T>
where T: Add<Output = T> + Sub<Output = T> + Div<Output = T>
+ Debug + Copy + From<i32> {
type Item = Coordinate<T>;
fn next(&mut self) -> Option<Self::Item> {
match self.mode {
VertexIteratorMode::Edge => {
// if for whatever reason edge is "None" finish this iterator.
let next = self.edge.as_mut()?.next();
match next {
Some(_) => next,
None => {
self.next_edge()?;
self.next()
},
}
},
VertexIteratorMode::Vertex => {
let current = self.current?;
self.current = self.p.next_y(current, self.direction);
Some(self.p.vertex(current))
},
}
}
}
impl<T> Polygon<T>
where T: Add<Output = T> + Sub<Output = T> + Div<Output = T>
+ Copy + Debug + From<i32> {
#[inline]
fn vertex(&self, v :usize) -> Coordinate<T> {
let Polygon(Coordinates(cs)) = self;
cs[v]
}
fn vert_min<'a>(&'a self, d :Direction) -> usize {
let Polygon(Coordinates(cs)) = self;
type ICoord<'a,T> = (usize, &'a Coordinate<T>);
// TODO I guess the problem here is that it does not account for the
// same y vertex on the beggining and the end. So i guess correct
// would be finding the first one and then dependings on the
// given direction either search left or right for same y's.
let fold = |acc :Option<ICoord<'a,T>>, x :ICoord<'a,T>|
match acc {
None => Some(x),
Some(a) => {
let Coordinate(_, ay, _) = a.1;
let Coordinate(_, xy, _) = x.1;
if xy < ay {Some(x)} else {Some(a)}
},
};
let mut min = cs.iter().enumerate().fold(None, fold).unwrap().0;
let mut next = self.step(min, d);
while self.vertex(min).1 == self.vertex(next).1 {
min = next;
next = self.step(min, d);
}
min
}
fn left_edge(&self) -> VertexIterator<T> {
VertexIterator::edge(self, Direction::Left)
}
fn right_edge(&self) -> VertexIterator<T> {
VertexIterator::edge(self, Direction::Right)
}
fn left_vertices(&self) -> VertexIterator<T> {
VertexIterator::vertex(self, Direction::Left)
}
fn right_vertices(&self) -> VertexIterator<T> {
VertexIterator::vertex(self, Direction::Right)
}
fn left(&self, v :usize) -> usize {
let Polygon(Coordinates(cs)) = self;
match v {
0 => cs.len() - 1,
_ => v - 1,
}
}
fn right(&self, v :usize) -> usize {
let Polygon(Coordinates(cs)) = self;
(v + 1) % cs.len()
}
fn step(&self, v :usize, d :Direction) -> usize {
match d {
Direction::Left => self.left(v),
Direction::Right => self.right(v),
}
}
fn next_y(&self, c :usize, d :Direction) -> Option<usize> {
fn inner<T>( p :&Polygon<T>
, c :usize
, n :usize
, d :Direction) -> Option<usize>
where T: Add<Output = T> + Sub<Output = T> + Div<Output = T>
+ Copy + Debug + From<i32> {
if c == n {
None
} else {
let Coordinate(_, cy, _) = p.vertex(c);
let Coordinate(_, ny, _) = p.vertex(n);
if ny < cy { None } else { Some(n) }
}
}
inner(self, c, self.step(c, d), d)
}
pub fn debug(&self) {
let mut left = self.left_vertices();
let mut right = self.right_vertices();
if left.find(|l| right.find(|r| l.0 == r.0).is_some()).is_some() {
let left :Vec<Coordinate<T>> = self.left_vertices().collect();
let right :Vec<Coordinate<T>> = self.right_vertices().collect();
println!("===");
println!("== poly : {:?}", self);
println!("== ltop : {:?}", self.vert_min(Direction::Left));
println!("== rtop : {:?}", self.vert_min(Direction::Right));
println!("== left : {:?}", left);
println!("== right : {:?}", right);
println!("===");
}
}
}
impl<T> Drawable<T> for Polygon<T>
where T: Add<Output = T> + Sub<Output = T> + Div<Output = T>
+ Debug + Clone + Copy + From<i32> {
fn plot(&self) -> Coordinates<T> {
let Polygon(Coordinates(cs)) = self;
match cs[..] {
[] => Coordinates(Vec::<Coordinate<T>>::new()),
[a] => Coordinates(vec!(a)),
[a, b] => Coordinates(a.line(&b)),
_ => {
let (a, b) = (cs[0], cs[1]);
let mut r = a.line(&b);
let mut i = b;
for j in cs[2..].iter() {
r.append(&mut i.line(j)[1..].to_vec());
i = *j;
}
let mut j = a.line(&i);
let l = j.len();
if l > 1 {
r.append(&mut j[1..l-1].to_vec());
}
Coordinates(r)
},
}
}
}
impl<T> Fillable<T> for Polygon<T>
where T: Add<Output = T> + Sub<Output = T> + Div<Output = T>
+ Debug + Clone + Copy + From<i32> {
fn fill(&self, canvas :&mut dyn Canvas<T>, color :u32) {
let scanlines = self.left_edge().zip(self.right_edge());
for l in scanlines.flat_map(|(l, r)| l.line_iter(&r)) {
canvas.set_pixel(l, color);
}
}
}
impl<T> Display for Polygon<T> where T: Copy {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
let Polygon(a) = self;
write!(f, "Poly[{}]", a)
}
}

68
src/easel3d_wasm.rs

@ -0,0 +1,68 @@
use std::sync::mpsc;
use easel3d::easel::canvas::{Canvas, Vertex};
use easel3d::easel::drawable::Drawable;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Color(u8, u8, u8, u8);
pub struct WasmCanvas { width :u16
, height :u16
, size :usize
, zbuf :Vec<f64>
, image :Vec<Color> }
impl WasmCanvas {
pub fn new(width :u16, height :u16) -> Self {
let size = width as usize * height as usize;
Self { width: width
, height: height
, size: size
, zbuf: vec!(0.0; size)
, image: vec!(Color(0, 0, 0, 0xFF); size) }
}
pub fn image(&self) -> *const Color {
self.image.as_ptr()
}
}
impl Canvas<f64> for WasmCanvas {
#[inline]
fn width(&self) -> u16 {
self.width
}
#[inline]
fn height(&self) -> u16 {
self.height
}
fn clear(&mut self) {
self.zbuf = vec!(0.0; self.size);
self.image = vec!(Color(0, 0, 0, 0xFF); self.size);
}
fn set_pixel(&mut self, c :Vertex<f64>, color :u32) {
let (x, y, zr) = c.as_tuple();
let idx :usize = (y * (self.width as i32) + x) as usize;
let r = ((color >> 16) & 0xFF) as u8;
let g = ((color >> 8) & 0xFF) as u8;
let b = ( color & 0xFF) as u8;
if self.zbuf[idx] < zr {
self.zbuf[idx] = zr;
self.image[idx] = Color(r, g, b, 0xFF);
}
}
// Empty implementations for now… mostly not needed because it is
// done from JavaScript…
fn init_events(&self) {}
fn start_events(&self, _ :mpsc::Sender<i32>) {}
fn draw(&mut self, _ :&dyn Drawable<f64>, _ :u32) {}
fn put_text(&self, _ :Vertex<f64>, _ :&str) {}
fn show(&self) {}
}

377
src/geometry.rs

@ -1,377 +0,0 @@
//
// Basic geometric things...
//
// 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::{From, Into};
use std::ops::{Add,Sub,Neg,Mul,Div};
use std::fmt::Debug;
use crate::easel::{Canvas, Coordinate, Coordinates, Polygon};
use crate::transform::{TMatrix, Transformable};
use crate::trigonometry::Trig;
use crate::vector::Vector;
#[derive(Debug, Clone)]
pub struct Face<T>
where T: Add + Sub + Neg + Mul + Div + Copy + Trig {
corners :Vec<usize>,
normal :Option<Vector<T>>,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct Point<T>(pub Vector<T>, T)
where T: Add + Sub + Neg + Mul + Div + PartialEq + Copy + Trig;
impl<T> Point<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
+ Mul<Output = T> + Div<Output = T>
+ PartialEq + Trig + Copy + From<i32> {
pub fn new(x :T, y :T, z :T) -> Self {
Self(Vector(x, y, z), 1.into())
}
}
impl<T> Add for Point<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
+ Mul<Output = T> + Div<Output = T>
+ PartialEq + Trig + Copy {
type Output = Self;
fn add(self, other :Self) -> Self {
let Point(v1, w1) = self;
let Point(v2, w2) = other;
Self(v1 + v2, w1 + w2)
}
}
impl<T> Neg for Point<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
+ Mul<Output = T> + Div<Output = T>
+ PartialEq + Trig + Copy {
type Output = Self;
fn neg(self) -> Self {
let Point(v, w) = self;
Self(-v, -w)
}
}
impl<T> Sub for Point<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
+ Mul<Output = T> + Div<Output = T>
+ PartialEq + Trig + Copy {
type Output = Self;
fn sub(self, other :Self) -> Self {
self + -other
}
}
impl<T> Mul for Point<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
+ Mul<Output = T> + Div<Output = T>
+ PartialEq + Trig + Copy + From<i32> {
type Output = Self;
fn mul(self, other :Self) -> Self {
let a :Vector<T> = self.into();
let b :Vector<T> = other.into();
Point(a * b, 1.into())
}
}
impl<T> From<Vector<T>> for Point<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
+ Mul<Output = T> + Div<Output = T>
+ PartialEq + Trig + Copy + From<i32> {
fn from(v :Vector<T>) -> Self {
Point(v, 1.into())
}
}
impl<T> Into<Vector<T>> for Point<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
+ Mul<Output = T> + Div<Output = T>
+ PartialEq + Trig + Copy + From<i32> {
fn into(self) -> Vector<T> {
let Point(v, w) = self;
if w == 0.into() {
v
} else {
v.mul(&w.recip())
}
}
}
impl<T> Transformable<T> for Point<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
+ Mul<Output = T> + Div<Output = T>
+ PartialEq + Debug + Trig + Copy + From<i32> {
fn transform(&self, m :&TMatrix<T>) -> Self {
let Point(v, w) = *self;
let (v, w) = m.apply(&v, w);
if w == 0.into() {
v.into()
} else {
v.mul(&w.recip()).into()
}
}
}
#[derive(Debug)]
pub struct Polyeder<T>
where T: Add + Sub + Neg + Mul + Div + PartialEq + Copy + Trig {
points :Vec<Point<T>>,
faces :Vec<Face<T>>,
}
pub trait Primitives<T>
where T: Add + Sub + Neg + Mul + Div + Debug + Copy + Trig + From<i32> {
fn transform(&self, m :&TMatrix<T>) -> Self;
fn project( &self
, camera :&Camera<T>
, light :&DirectLight<T>
, col :u32 ) -> Vec<(Polygon<T>, u32)>;
}
#[derive(Debug, Clone, Copy)]
pub struct Camera<T>
where T: Add + Sub + Neg + Mul + Div + Debug + Copy + Trig + From<i32> {
width :T,
height :T,
distance :T,
project :TMatrix<T>,
}
pub struct DirectLight<T>
where T: Add + Sub + Neg + Mul + Div + Debug + Copy + Trig + From<i32> {
direction: Vector<T>,
}
impl<T> Camera<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
+ Mul<Output = T> + Div<Output = T>
+ PartialEq + Debug + Copy + Trig + From<i32> {
// This code assumes that the size of the viewport is always
// equal to the size of the physical screen… e.g. window/canvas thus some
// effects can't be done. See book for examples with different viewport
// and screen sizes.
pub fn new(c :&dyn Canvas<T>, angle :i32) -> Self {
let width :T = (c.width() as i32).into();
let height :T = (c.height() as i32).into();
let d :T = 1.into();
let fov = T::cot(angle) * width;
let wh = width / 2.into();
let hh = height / 2.into();
Camera { width: width
, height: height
, distance: d
, project: TMatrix::new(
( fov, 0.into(), wh, 0.into())
, (0.into(), fov, hh, 0.into())
, (0.into(), 0.into(), d, 1.into())
, (0.into(), 0.into(), 1.into(), 0.into()) ) }
}
pub fn get_distance(&self) -> T {
self.distance
}
pub fn get_projection(&self) -> TMatrix<T> {
self.project
}
pub fn project(&self, p :Point<T>) -> Point<T> {
p.transform(&self.project)
}
}
impl<T> DirectLight<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
+ Mul<Output = T> + Div<Output = T>
+ Debug + Copy + Trig + From<i32> {
pub fn new(v :Vector<T>) -> Self {
DirectLight{ direction: v }
}
pub fn dir(&self) -> Vector<T> {
self.direction
}
}
impl<T> Face<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
+ Mul<Output = T> + Div<Output = T>
+ PartialEq + Debug + Copy + Trig + From<i32> {
fn new(corners :Vec<usize>, ps :&[Point<T>]) -> Self {
let mut f = Face{ corners: corners, normal: None };
f.update_normal(ps);
f
}
fn update_normal(&mut self, ps :&[Point<T>]) {
let edge10 :Vector<T> = (ps[self.corners[1]] - ps[self.corners[0]]).into();
let edge12 :Vector<T> = (ps[self.corners[1]] - ps[self.corners[2]]).into();
self.normal = Some(edge10 * edge12);
}
}
impl<T> Polyeder<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
+ Mul<Output = T> + Div<Output = T>
+ PartialEq + Debug + Copy + Trig + From<i32> {
fn update_normals(&mut self) {
for f in self.faces.iter_mut() {
f.update_normal(&self.points);
}
}
// construct via cube, see polyhedra.pdf
pub fn tetrahedron(a :T) -> Polyeder<T> {
let f2 :T = 2.into();
let ch = a / (f2 * T::sqrt(f2).unwrap());
let ps = vec!( Point::new(-ch, -ch, ch) // A
, Point::new(-ch, ch, -ch) // C
, Point::new( ch, -ch, -ch) // E
, Point::new( ch, ch, ch) ); // G
// bottom: 1, 2, 3
let fs = vec!( Face::new(vec!(2, 1, 0), &ps) // bottom
, Face::new(vec!(3, 2, 0), &ps)
, Face::new(vec!(0, 1, 3), &ps)
, Face::new(vec!(1, 2, 3), &ps) );
//let fs = vec!( Face::new(vec!(0, 1, 2), &ps) // bottom
// , Face::new(vec!(0, 2, 3), &ps)
// , Face::new(vec!(3, 1, 0), &ps)
// , Face::new(vec!(3, 2, 1), &ps) );
Polyeder{ points: ps, faces: fs }
}
pub fn triangle(a :T) -> Polyeder<T> {
let f0 :T = 0.into();
let f3 :T = 3.into();
let f6 :T = 6.into();
let zi :T = T::sqrt(f3).unwrap() / f6 * a;
let zc :T = T::sqrt(f3).unwrap() / f3 * a;
let ah :T = a / 2.into();
let ps = vec!( Point::new(-ah, f0, -zi)
, Point::new( f0, f0, zc)
, Point::new( ah, f0, -zi) );
let fs = vec!(Face::new(vec!(0, 1, 2), &ps));
Polyeder{ points: ps, faces: fs }
}
pub fn cube(a :T) -> Polyeder<T> {
let ah :T = a / From::<i32>::from(2);
let ps = vec!( Point::new(-ah, ah, -ah) // 0 => front 1
, Point::new(-ah, -ah, -ah) // 1 => front 2
, Point::new( ah, -ah, -ah) // 2 => front 3
, Point::new( ah, ah, -ah) // 3 => front 4
, Point::new(-ah, ah, ah) // 4 => back 1
, Point::new(-ah, -ah, ah) // 5 => back 2
, Point::new( ah, -ah, ah) // 6 => back 3
, Point::new( ah, ah, ah) ); // 7 => back 4
let fs = vec!( Face::new(vec!(0, 1, 2, 3), &ps) // front
, Face::new(vec!(7, 6, 5, 4), &ps) // back
, Face::new(vec!(1, 5, 6, 2), &ps) // top
, Face::new(vec!(0, 3, 7, 4), &ps) // bottom
, Face::new(vec!(0, 4, 5, 1), &ps) // left
, Face::new(vec!(2, 6, 7, 3), &ps) ); // right
Polyeder{ points: ps, faces: fs }
}
}
impl<T> Primitives<T> for Polyeder<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
+ Mul<Output = T> + Div<Output = T>
+ Debug + Copy + Trig + From<i32> + PartialOrd {
// TODO Maybe this should also be an instance of Transformable…
fn transform(&self, m :&TMatrix<T>) -> Self {
let Polyeder{ points: ps, faces: fs } = self;
let mut p = Polyeder{
points: ps.iter().map(|p| p.transform(m)).collect()
, faces: fs.to_vec()
};
// TODO alternatively we could rotate the normals too, but this cannot
// done with the original matrix… the question is, what is faster.
p.update_normals();
p
}
fn project( &self
, camera :&Camera<T>
, light :&DirectLight<T>
, color :u32 ) -> Vec<(Polygon<T>, u32)> {
// Helper to create a Polygon from Coordinates…
// TODO probably there needs to be a Polygon constructor for this.
fn polygon<I, T>(c :I) -> Polygon<T>
where I: Iterator<Item = Coordinate<T>> {
Polygon(Coordinates(c.collect()))
}
// this one does the projection... as the projection was the last
// matrix we do not need to do it here.
let to_coord = |p :&usize| {
let Point(v, _) = camera.project(self.points[*p]);
Coordinate(T::round(&v.x()), T::round(&v.y()), v.z() - 1.into())
};
let to_poly = |f :&Face<T>| {
let pg = polygon(f.corners.iter().map(to_coord));
let mut r :T = (((color >> 16) & 0xFF) as i32).into();
let mut g :T = (((color >> 8) & 0xFF) as i32).into();
let mut b :T = (((color ) & 0xFF) as i32).into();
let lf :T = match f.normal {
None => 1.into(),
Some(n) => n.dot(light.dir())
/ (n.mag() * light.dir().mag()),
};
// this "if" represents a first simple backface culling
// approach. We only return face that face towards us.
if lf < 0.into() {
r = r * -lf;
g = g * -lf;
b = b * -lf;
let c :u32 = (r.round() as u32) << 16
| (g.round() as u32) << 8
| (b.round() as u32);
Some((pg, c))
} else {
None
}};
self.faces.iter().filter_map(to_poly).collect()
}
}

245
src/lib.rs

@ -1,23 +1,19 @@
extern crate lazy_static;
extern crate easel3d;
pub type Error = &'static str;
mod easel3d_wasm;
use easel3d_wasm::{WasmCanvas, Color};
pub mod easel;
pub mod transform;
pub mod trigonometry;
pub mod vector;
pub mod geometry;
use easel3d::easel::canvas::Canvas;
use easel3d::easel::fillable::Fillable;
mod utils;
use easel3d::math::transform::TMatrix;
use easel3d::math::vector::Vector;
use vector::Vector;
use easel::{Canvas, Coordinate, Drawable, Fillable};
use geometry::{Camera, DirectLight, Polyeder, Primitives};
use transform::{TMatrix};
use easel3d::space::camera::Camera;
use easel3d::space::light::DirectLight;
use easel3d::space::polyeder::Polyeder;
use easel3d::space::primitives::Primitives;
use std::fmt::{Display, Formatter, Result};
use std::sync::mpsc;
use std::time::Instant;
use wasm_bindgen::prelude::*;
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
@ -27,56 +23,37 @@ use wasm_bindgen::prelude::*;
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[wasm_bindgen]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Color(u8, u8, u8, u8);
#[wasm_bindgen]
pub struct View3d { width :u16
, height :u16
, size :usize
pub struct View3d { canvas :WasmCanvas
, degree :i32
//, start :Instant
, tetrahedron :Polyeder<f64>
, cube :Polyeder<f64>
, camera :Option<Camera<f64>>
, light :DirectLight<f64>
, zbuf :Vec<f64>
, image :Vec<Color>
}
, camera :Camera<f64>
, light :DirectLight<f64> }
#[wasm_bindgen]
impl View3d {
pub fn new(width :u16, height :u16) -> Self {
let size = width as usize * height as usize;
let light_vector = Vector(0.0, 0.0, 1.0);
let canvas = WasmCanvas::new(width, height);
let camera = Camera::<f64>::new(&canvas, 45);
let mut view3d = Self { width: width
, height: height
, size: size
, degree: 0
//, start: Instant::now()
, tetrahedron: Polyeder::tetrahedron(100.0)
, cube: Polyeder::cube(56.25)
, camera: None
, light: DirectLight::new(light_vector)
, zbuf: vec!(0.0; size)
, image: vec!(Color(0, 0, 0, 0xFF); size)
};
view3d.camera = Some(Camera::<f64>::new(&view3d, 45));
view3d
Self { canvas: canvas
, degree: 0
, tetrahedron: Polyeder::tetrahedron(100.0)
, cube: Polyeder::cube(56.25)
, camera: camera
, light: DirectLight::new(light_vector) }
}
pub fn width(&self) -> u16 {
self.width
self.canvas.width()
}
pub fn height(&self) -> u16 {
self.height
self.canvas.height()
}
pub fn update(&mut self) {
//let deg = ((self.start.elapsed() / 25).as_millis() % 360) as i32;
let t = TMatrix::translate(Vector(0.0, 0.0, 150.0));
let rz = TMatrix::rotate_z(self.degree);
let rx = TMatrix::rotate_x(-self.degree*2);
@ -88,176 +65,22 @@ impl View3d {
let objects = vec!( (self.tetrahedron.transform(&rot1), 0xFFFF00)
, ( self.cube.transform(&rot2), 0x0000FF) );
self.degree = (self.degree + 1) % 360;
self.clear();
match self.camera {
None => {},
Some(camera) => {
for (o, color) in objects {
for (pg, c) in o.project(&camera, &self.light, color) {
(&pg).fill(self, c);
}
}
},
}
}
pub fn image(&self) -> *const Color {
self.image.as_ptr()
}
}
impl Canvas<f64> for View3d {
fn width(&self) -> u16 {
self.width
}
fn height(&self) -> u16 {
self.height
}
fn clear(&mut self) {
self.zbuf = vec!(0.0; self.size);
self.image = vec!(Color(0, 0, 0, 0xFF); self.size);
}
fn set_pixel(&mut self, c :Coordinate<f64>, color :u32) {
let Coordinate(x, y, zr) = c;
let idx :usize = (y * (self.width as i32) + x) as usize;
let r = ((color >> 16) & 0xFF) as u8;
let g = ((color >> 8) & 0xFF) as u8;
let b = ( color & 0xFF) as u8;
if self.zbuf[idx] < zr {
self.zbuf[idx] = zr;
self.image[idx] = Color(r, g, b, 0xFF);
}
}
// Empty implementations for now… mostly not needed because it is
// done from JavaScript…
fn init_events(&self) {}
fn start_events(&self, _ :mpsc::Sender<i32>) {}
fn draw( &mut self, _ :&dyn Drawable<f64>, _ :Coordinate<f64>, _ :u32 ) {}
fn put_text(&self, _ :Coordinate<f64>, _ :&str) {}
fn show(&self) {}
}
#[wasm_bindgen]
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Cell {
Dead = 0,
Alive = 1,
}
#[wasm_bindgen]
pub struct Universe {
width :u32,
height :u32,
cells :Vec<Cell>,
}
#[wasm_bindgen]
impl Universe {
pub fn new() -> Universe {
let width = 64;
let height = 64;
let init_cells = |i :u32| {
if i % 2 == 0 || i % 7 == 0 { Cell::Alive } else { Cell::Dead }
};
let cells = (0..width * height).map(init_cells).collect();
Universe {
width: width,
height: height,
cells: cells,
}
}
pub fn width(&self) -> u32 {
self.width
}
pub fn height(&self) -> u32 {
self.height
}
pub fn cells(&self) -> *const Cell {
self.cells.as_ptr()
}
pub fn render(&self) -> String {
self.to_string()
}
pub fn tick(&mut self) {
let mut next = self.cells.clone();
let rlx = TMatrix::rotate_x(-self.degree/4);
let rly = TMatrix::rotate_y(-self.degree/1);
let light = self.light.transform(&TMatrix::combine(vec!(rlx, rly)));
for row in 0..self.height {
for col in 0..self.width {
let idx = self.get_index(row, col);
let cell = self.cells[idx];
let live_neighbors = self.live_neighbor_count(row, col);
self.degree = (self.degree + 1) % (4*360);
// Game of life rules....
let next_cell = match (cell, live_neighbors) {
(Cell::Alive, 2) |
(Cell::Alive, 3) => Cell::Alive,
(Cell::Alive, _) => Cell::Dead,
( Cell::Dead, 3) => Cell::Alive,
( otherwise, _) => otherwise,
};
self.canvas.clear();
next[idx] = next_cell;
for (o, color) in objects {
for (pg, c) in o.project(&self.camera, &light, color) {
(&pg).fill(&mut self.canvas, c);
}
}
self.cells = next;
}
fn get_index(&self, row :u32, col :u32) -> usize {
(row * self.width + col) as usize
}
fn live_neighbor_count(&self, row :u32, col :u32) -> u8 {
let mut count = 0;
for delta_row in [self.height - 1, 0, 1].iter().cloned() {
for delta_col in [self.width - 1, 0, 1].iter().cloned() {
if delta_row == 0 && delta_col == 0 {
continue;
}
let neighbor_row = (row + delta_row) % self.height;
let neighbor_col = (col + delta_col) % self.width;
let idx = self.get_index(neighbor_row, neighbor_col);
count += self.cells[idx] as u8;
}
}
count
}
}
impl Display for Universe {
fn fmt(&self, f :&mut Formatter) -> Result {
for line in self.cells.as_slice().chunks(self.width as usize) {
for &cell in line {
let symbol = match cell {
Cell::Dead => ' ',
Cell::Alive => '*',
};
write!(f, "{}", symbol)?;
}
write!(f, "\n")?;
}
Ok(())
pub fn image(&self) -> *const Color {
self.canvas.image()
}
}

186
src/transform.rs

@ -1,186 +0,0 @@
//
// Transformation of vectors in a given coordinate system...
//
// 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::ops::{Add, Sub, Neg, Mul, Div};
use std::fmt::Debug;
use crate::Vector;
use crate::trigonometry::Trig;
#[derive(Debug, Clone, Copy)]
pub struct TMatrix<T>( (T, T, T, T)
, (T, T, T, T)
, (T, T, T, T)
, (T, T, T, T) )
where T: Add + Sub + Neg + Mul + Div + Debug + Trig + From<i32> + Copy;
pub trait Transformable<T>
where T: Add + Sub + Neg + Mul + Div + Debug + Trig + From<i32> + Copy {
fn transform(&self, m :&TMatrix<T>) -> Self;
}
impl<T> TMatrix<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
+ Mul<Output = T> + Div<Output = T>
+ Debug + Trig + From<i32> + Copy {
pub fn new( r1 :(T, T, T, T)
, r2 :(T, T, T, T)
, r3 :(T, T, T, T)
, r4 :(T, T, T, T) ) -> Self {
TMatrix(r1, r2, r3, r4)
}
pub fn unit() -> Self {
Self::new( (1.into(), 0.into(), 0.into(), 0.into())
, (0.into(), 1.into(), 0.into(), 0.into())
, (0.into(), 0.into(), 1.into(), 0.into())
, (0.into(), 0.into(), 0.into(), 1.into()) )
}
pub fn translate(v :Vector<T>) -> Self {
let Vector(x, y, z) = v;
Self::new( (1.into(), 0.into(), 0.into(), x)
, (0.into(), 1.into(), 0.into(), y)
, (0.into(), 0.into(), 1.into(), z)
, (0.into(), 0.into(), 0.into(), 1.into()) )
}
pub fn rotate_x(a :i32) -> Self {
let sin :T = Trig::sin(a);
let cos :T = Trig::cos(a);
Self::new( (1.into(), 0.into(), 0.into(), 0.into())
, (0.into(), cos , -sin , 0.into())
, (0.into(), sin , cos , 0.into())
, (0.into(), 0.into(), 0.into(), 1.into()) )
}
pub fn rotate_y(a :i32) -> Self {
let sin :T = Trig::sin(a);
let cos :T = Trig::cos(a);
Self::new( (cos , 0.into(), sin , 0.into())
, (0.into(), 1.into(), 0.into(), 0.into())
, (-sin , 0.into(), cos , 0.into())
, (0.into(), 0.into(), 0.into(), 1.into()) )
}
pub fn rotate_z(a :i32) -> Self {
let sin :T = Trig::sin(a);
let cos :T = Trig::cos(a);
Self::new( (cos , -sin , 0.into(), 0.into())
, (sin , cos , 0.into(), 0.into())
, (0.into(), 0.into(), 1.into(), 0.into())
, (0.into(), 0.into(), 0.into(), 1.into()) )
}
pub fn rotate_v(v :&Vector<T>, a :i32) -> Self {
let Vector(x, y, z) = *v;
let sin :T = Trig::sin(a);
let cos :T = Trig::cos(a);
let zero :T = 0.into();
let one :T = 1.into();
Self::new( ( (one - cos) * x * x + cos
, (one - cos) * x * y - sin * z
, (one - cos) * x * z + sin * y
, zero )
, ( (one - cos) * x * y + sin * z
, (one - cos) * y * y + cos
, (one - cos) * y * z - sin * x
, zero )
, ( (one - cos) * x * z - sin * y
, (one - cos) * y * z + sin * x
, (one - cos) * z * z + cos
, zero )
, (0.into(), 0.into(), 0.into(), 1.into()) )
}
pub fn scale(v :Vector<T>) -> Self {
let Vector(x, y, z) = v;
Self::new( ( x, 0.into(), 0.into(), 0.into())
, (0.into(), y, 0.into(), 0.into())
, (0.into(), 0.into(), z, 0.into())
, (0.into(), 0.into(), 0.into(), 1.into()) )
}
pub fn combine<I>(mi :I) -> TMatrix<T>
where I: IntoIterator<Item = TMatrix<T>> {
mi.into_iter().fold(Self::unit(), |acc, x| x * acc)
}
pub fn apply(&self, v :&Vector<T>, w :T) -> (Vector<T>, T) {
let TMatrix( (a11, a12, a13, a14)
, (a21, a22, a23, a24)
, (a31, a32, a33, a34)
, (a41, a42, a43, a44) ) = *self;
let Vector(x, y, z) = *v;
let v = Vector( a11 * x + a12 * y + a13 * z + a14 * w
, a21 * x + a22 * y + a23 * z + a24 * w
, a31 * x + a32 * y + a33 * z + a34 * w );
let w = a41 * x + a42 * y + a43 * z + a44 * w;
//v.mul(&w.recip())
(v, w)
}
}
impl<T> Mul for TMatrix<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
+ Mul<Output = T> + Div<Output = T>
+ Debug + Trig + From<i32> + Copy {
type Output = Self;
// ATTENTION: This is not commutative, nor assoziative.
fn mul(self, other :Self) -> Self {
let TMatrix( (a11, a12, a13, a14)
, (a21, a22, a23, a24)
, (a31, a32, a33, a34)
, (a41, a42, a43, a44) ) = self;
let TMatrix( (b11, b12, b13, b14)
, (b21, b22, b23, b24)
, (b31, b32, b33, b34)
, (b41, b42, b43, b44) ) = other;
TMatrix( ( a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41
, a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42
, a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43
, a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44 )
, ( a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41
, a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42
, a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43
, a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44 )
, ( a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41
, a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42
, a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43
, a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44 )
, ( a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41
, a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42
, a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43
, a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44 ) )
}
}

143
src/trigonometry.rs

@ -1,143 +0,0 @@
//
// Some trigonometic functions with Fractions results.
// Currently only sin, cos and tan are implemented.
// As I was unable to find a really good integral approximation for them I
// implement them as a table which is predefined using the floating point
// function f64::sin and then transformed into a fraction of a given
// PRECISION.
// These approximations are quite good and for a few edge cases
// even better than the floating point implementations.
//
// 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::ops::Div;
use std::ops::Neg;
use std::marker::Sized;
use crate::Error;
pub trait Trig {
fn pi() -> Self;
fn recip(self) -> Self;
fn round(&self) -> i32;
fn sqrt(self) -> Result<Self, Error> where Self: Sized;
fn sintab() -> Vec<Self> where Self: Sized;
fn tantab() -> Vec<Self> where Self: Sized;
fn sin(d :i32) -> Self
where Self: Sized + Neg<Output = Self> + Copy {
match d {
0 ..=90 => Self::sintab()[d as usize],
91 ..=180 => Self::sintab()[180 - d as usize],
181..=270 => -Self::sintab()[d as usize - 180],
271..=359 => -Self::sintab()[360 - d as usize],
_ => {
Self::sin(if d < 0 { d % 360 + 360 } else { d % 360 })
},
}
}
fn cos(d :i32) -> Self
where Self: Sized + Neg<Output = Self> + Copy {
match d {
0 ..=90 => Self::sintab()[90 - d as usize],
91 ..=180 => -Self::sintab()[90 - (180 - d as usize)],
181..=270 => -Self::sintab()[90 - (d as usize - 180)],
271..=359 => Self::sintab()[90 - (360 - d as usize)],
_ => {
Self::cos(if d < 0 { d % 360 + 360 } else { d % 360 })
},
}
}
fn tan(d :i32) -> Self where Self: Sized + Copy {
match d {
0 ..=179 => Self::tantab()[d as usize],
180..=359 => Self::tantab()[d as usize - 180],
_ => {
Self::tan(if d < 0 { d % 360 + 360 } else { d % 360 })
},
}
}
fn cot(d :i32) -> Self
where Self: Sized + Copy + From<i32> + Div<Output = Self> {
Into::<Self>::into(1) / Self::tan(d)
}
}
impl Trig for f64 {
fn pi() -> Self {
std::f64::consts::PI
}
fn recip(self) -> Self {
self.recip()
}
fn round(&self) -> i32 {
f64::round(*self) as i32
}
fn sqrt(self) -> Result<Self, Error> {
let x = self.sqrt();
match x.is_nan() {
true => Err("sqrt on negative undefined"),
false => Ok(x),
}
}
fn sintab() -> Vec<Self> {
lazy_static::lazy_static! {
static ref SINTAB :Vec<f64> =
(0..=90).map(|x| _sin(x)).collect();
}
// f64 sin. (From 0° to 90°)
fn _sin(d: u32) -> f64 {
match d {
0 => 0.0,
90 => 1.0,
_ => (d as f64).to_radians().sin(),
}
}
SINTAB.to_vec()
}
fn tantab() -> Vec<Self> {
// This table exists only because the sin(α) / cos(α) method
// yields very large unreducable denominators in a lot of cases.
lazy_static::lazy_static! {
static ref TANTAB :Vec<f64> =
(0..180).map(|x| _tan(x)).collect();
}
// fractional tan from f64 tan. (From 0° to 179°)
fn _tan(d: u32) -> f64 {
match d {
0 => 0.0,
45 => 1.0,
90 => std::f64::INFINITY,
135 => -1.0,
_ => (d as f64).to_radians().tan(),
}
}
TANTAB.to_vec()
}
}

10
src/utils.rs

@ -1,10 +0,0 @@
pub fn set_panic_hook() {
// When the `console_error_panic_hook` feature is enabled, we can call the
// `set_panic_hook` function at least once during initialization, and then
// we will get better error messages if our code ever panics.
//
// For more details see
// https://github.com/rustwasm/console_error_panic_hook#readme
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}

139
src/vector.rs

@ -1,139 +0,0 @@
//
// Stuff for manipulating 3 dimensional vectors.
//
// 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::fmt::{Debug, Display, Formatter, Result};
use std::ops::{Add, Sub, Neg, Mul, Div};
use crate::trigonometry::Trig;
use crate::transform::{TMatrix, Transformable};
#[derive(Debug, Eq, Clone, Copy)]
pub struct Vector<T>(pub T, pub T, pub T)
where T: Add + Sub + Neg + Mul + Div + Trig + Copy;
impl<T> Vector<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
+ Mul<Output = T> + Div<Output = T> + Trig + Copy {
pub fn x(self) -> T { self.0 }
pub fn y(self) -> T { self.1 }
pub fn z(self) -> T { self.2 }
pub fn mag(self) -> T {
let Vector(x, y, z) = self;
(x * x + y * y + z * z).sqrt().unwrap()
}
pub fn mul(self, s :&T) -> Self {
let Vector(x, y, z) = self;
Vector(x * *s, y * *s, z * *s)
}
pub fn dot(self, other :Self) -> T {
let Vector(x1, y1, z1) = self;
let Vector(x2, y2, z2) = other;
x1 * x2 + y1 * y2 + z1 * z2
}
pub fn norm(self) -> Self {
// TODO This can result in 0 or inf Vectors…
// Maybe we need to handle zero and inf magnitude here…
self.mul(&self.mag().recip())
}
pub fn distance(self, other :Self) -> T {
(self - other).mag()
}
}
impl<T> Display for Vector<T>
where T: Add + Sub + Neg + Mul + Div + Trig + Display + Copy {
fn fmt(&self, f :&mut Formatter<'_>) -> Result {
let Vector(x, y, z) = self;
write!(f, "({}, {}, {})", x, y, z)
}
}
impl<T> PartialEq for Vector<T>
where T: Add + Sub + Neg + Mul + Div + Trig + PartialEq + Copy {
fn eq(&self, other :&Self) -> bool {
let Vector(x1, y1, z1) = self;
let Vector(x2, y2, z2) = other;
x1 == x2 && y1 == y2 && z1 == z2
}
}
impl<T> Add for Vector<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
+ Mul<Output = T> + Div<Output = T> + Trig + Copy {
type Output = Self;
fn add(self, other :Self) -> Self {
let Vector(x1, y1, z1) = self;
let Vector(x2, y2, z2) = other;
Vector(x1 + x2, y1 + y2, z1 + z2)
}
}
impl<T> Sub for Vector<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
+ Mul<Output = T> + Div<Output = T> + Trig + Copy {
type Output = Self;
fn sub(self, other :Self) -> Self {
self + -other
}
}
impl<T> Neg for Vector<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
+ Mul<Output = T> + Div<Output = T> + Trig + Copy {
type Output = Self;
fn neg(self) -> Self {
let Vector(x, y, z) = self;
Self(-x, -y, -z)
}
}
impl<T> Mul for Vector<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
+ Mul<Output = T> + Div<Output = T> + Trig + Copy {
type Output = Self;
fn mul(self, other :Self) -> Self {
let Vector(ax, ay, az) = self;
let Vector(bx, by, bz) = other;
Vector( ay * bz - az * by
, az * bx - ax * bz
, ax * by - ay * bx )
}
}
impl<T> Transformable<T> for Vector<T>
where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
+ Mul<Output = T> + Div<Output = T>
+ Trig + Copy + Debug + From<i32> {
fn transform(&self, m :&TMatrix<T>) -> Self {
let (v, _) = m.apply(self, 0.into());
v
}
}

24
www/.bin/create-wasm-app.js

@ -1,24 +0,0 @@
#!/usr/bin/env node
const { spawn } = require("child_process");
const fs = require("fs");
let folderName = '.';
if (process.argv.length >= 3) {
folderName = process.argv[2];
if (!fs.existsSync(folderName)) {
fs.mkdirSync(folderName);
}
}
const clone = spawn("git", ["clone", "https://github.com/rustwasm/create-wasm-app.git", folderName]);
clone.on("close", code => {
if (code !== 0) {
console.error("cloning the template failed!")
process.exit(code);
} else {
console.log("🦀 Rust + 🕸 Wasm = ❤");
}
});

67
www/README.md

@ -1,67 +0,0 @@
<div align="center">
<h1><code>create-wasm-app</code></h1>
<strong>An <code>npm init</code> template for kick starting a project that uses NPM packages containing Rust-generated WebAssembly and bundles them with Webpack.</strong>
<p>
<a href="https://travis-ci.org/rustwasm/create-wasm-app"><img src="https://img.shields.io/travis/rustwasm/create-wasm-app.svg?style=flat-square" alt="Build Status" /></a>
</p>
<h3>
<a href="#usage">Usage</a>
<span> | </span>
<a href="https://discordapp.com/channels/442252698964721669/443151097398296587">Chat</a>
</h3>
<sub>Built with 🦀🕸 by <a href="https://rustwasm.github.io/">The Rust and WebAssembly Working Group</a></sub>
</div>
## About
This template is designed for depending on NPM packages that contain
Rust-generated WebAssembly and using them to create a Website.
* Want to create an NPM package with Rust and WebAssembly? [Check out
`wasm-pack-template`.](https://github.com/rustwasm/wasm-pack-template)
* Want to make a monorepo-style Website without publishing to NPM? Check out
[`rust-webpack-template`](https://github.com/rustwasm/rust-webpack-template)
and/or
[`rust-parcel-template`](https://github.com/rustwasm/rust-parcel-template).
## 🚴 Usage
```
npm init wasm-app
```
## 🔋 Batteries Included
- `.gitignore`: ignores `node_modules`
- `LICENSE-APACHE` and `LICENSE-MIT`: most Rust projects are licensed this way, so these are included for you
- `README.md`: the file you are reading now!
- `index.html`: a bare bones html document that includes the webpack bundle
- `index.js`: example js file with a comment showing how to import and use a wasm pkg
- `package.json` and `package-lock.json`:
- pulls in devDependencies for using webpack:
- [`webpack`](https://www.npmjs.com/package/webpack)
- [`webpack-cli`](https://www.npmjs.com/package/webpack-cli)
- [`webpack-dev-server`](https://www.npmjs.com/package/webpack-dev-server)
- defines a `start` script to run `webpack-dev-server`
- `webpack.config.js`: configuration file for bundling your js with webpack
## License
Licensed under either of
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally
submitted for inclusion in the work by you, as defined in the Apache-2.0
license, shall be dual licensed as above, without any additional terms or
conditions.

2
www/index.html

@ -2,7 +2,7 @@
<html>
<head>
<meta charset="utf-8">
<title>wasm-game-of-life!</title>
<title>easel3d</title>
<style>
body {
position: absolute;

7
www/index.js

@ -1,8 +1,8 @@
import { View3d } from "wasm-game-of-life";
import { memory } from "wasm-game-of-life/wasm_game_of_life_bg";
import { View3d } from "easel3d-wasm";
import { memory } from "easel3d-wasm/easel3d_wasm_bg";
// 3D canvas stuff
const view3d = View3d.new(300, 300);
const view3d = View3d.new(301, 301);
const view3d_canvas = document.getElementById("view3d");
view3d_canvas.width = view3d.width();
@ -17,7 +17,6 @@ const view3d_renderLoop = () => {
}
const drawView3d = () => {
const view3d_imagePtr = view3d.image();
const view3d_image = new ImageData(
new Uint8ClampedArray( memory.buffer
, view3d.image()

14
www/package-lock.json

@ -1,5 +1,5 @@
{
"name": "create-wasm-app",
"name": "easel3d-wasm",
"version": "0.1.0",
"lockfileVersion": 1,
"requires": true,
@ -1486,6 +1486,9 @@
"stream-shift": "^1.0.0"
}
},
"easel3d-wasm": {
"version": "file:../pkg"
},
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@ -2836,12 +2839,6 @@
"minimalistic-assert": "^1.0.1"
}
},
"hello-wasm-pack": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/hello-wasm-pack/-/hello-wasm-pack-0.1.0.tgz",
"integrity": "sha512-3hx0GDkDLf/a9ThCMV2qG4mwza8N/MCtm8aeFFc/cdBCL2zMJ1kW1wjNl7xPqD1lz8Yl5+uhnc/cpui4dLwz/w==",
"dev": true
},
"hmac-drbg": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
@ -5511,9 +5508,6 @@
"integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==",
"dev": true
},
"wasm-game-of-life": {
"version": "file:../pkg"
},
"watchpack": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz",

22
www/package.json

@ -1,40 +1,32 @@
{
"name": "create-wasm-app",
"name": "easel3d-wasm",
"version": "0.1.0",
"description": "create an app to consume rust-generated wasm packages",
"main": "index.js",
"bin": {
"create-wasm-app": ".bin/create-wasm-app.js"
},
"scripts": {
"build": "webpack --config webpack.config.js",
"start": "webpack-dev-server"
},
"repository": {
"type": "git",
"url": "git+https://github.com/rustwasm/create-wasm-app.git"
"url": "https://git.silpion.de/scm/~hopp/easel3d-wasm.git"
},
"keywords": [
"webassembly",
"wasm",
"rust",
"webpack"
"webpack",
"easel3d"
],
"author": "Ashley Williams <ashley666ashley@gmail.com>",
"license": "(MIT OR Apache-2.0)",
"bugs": {
"url": "https://github.com/rustwasm/create-wasm-app/issues"
},
"homepage": "https://github.com/rustwasm/create-wasm-app#readme",
"author": "Georg Hopp <hopp@silpion.de>",
"license": "GPL-3.0-or-later",
"devDependencies": {
"wasm-game-of-life": "file:../pkg",
"hello-wasm-pack": "^0.1.0",
"webpack": "^4.29.3",
"webpack-cli": "^3.1.0",
"webpack-dev-server": "^3.1.5",
"copy-webpack-plugin": "^5.0.0"
},
"dependencies": {
"wasm-game-of-life": "file:../pkg"
"easel3d-wasm": "file:../pkg"
}
}
Loading…
Cancel
Save