Browse Source

split easel into multiple files

master
Georg Hopp 6 years ago
parent
commit
c856f7b8c5
Signed by: ghopp GPG Key ID: 4C5D226768784538
  1. 520
      src/easel.rs
  2. 119
      src/easel/canvas.rs
  3. 26
      src/easel/drawable.rs
  4. 31
      src/easel/fillable.rs
  5. 45
      src/easel/line.rs
  6. 90
      src/easel/line_iterator.rs
  7. 41
      src/easel/mod.rs
  8. 42
      src/easel/point.rs
  9. 176
      src/easel/polygon.rs
  10. 61
      src/easel/polyline.rs
  11. 122
      src/easel/vertex_iterator.rs
  12. 22
      src/geometry.rs
  13. 282
      src/lib.rs
  14. 10
      src/utils.rs

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)
}
}

119
src/easel/canvas.rs

@ -0,0 +1,119 @@
//
// …
//
// 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, Div, Sub};
use std::sync::mpsc;
use super::drawable::Drawable;
use super::line_iterator::LineIterator;
// A 2D drawing surface.
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>, color :u32);
fn set_pixel(&mut self, c :Vertex<T>, color :u32);
fn put_text(&self, ofs :Vertex<T>, s :&str);
fn show(&self);
}
// A Vertex is a position on a 2D drawing surface along other stuff
// that needs to iterate between those coordinates.
#[derive(Debug, Clone, Copy)]
pub struct Vertex<T>{ x :i32 // canvas x coordinate
, y :i32 // canvas y coordinate
, zr :T } // z reciprocal from 3D projection.
pub type Vertices<T> = Vec<Vertex<T>>;
impl<T> Vertex<T>
where T: Add<Output = T> + Sub<Output = T> + Div<Output = T>
+ Debug + Clone + Copy + From<i32> {
pub fn new(x :i32, y :i32, zr :T) -> Self {
Vertex{x, y, zr}
}
#[inline]
pub fn as_tuple(&self) -> (i32, i32, T) {
(self.x, self.y, self.zr)
}
#[inline]
pub fn same_x(&self, b :&Self) -> bool {
self.x == b.x
}
#[inline]
pub fn same_y(&self, b :&Self) -> bool {
self.y == b.y
}
#[inline]
pub fn same_position(&self, b :&Self) -> bool {
self.same_x(b) && self.same_y(b)
}
fn iter(self, b :Self, only_edges :bool) -> LineIterator<T> {
LineIterator::new(self, b, only_edges)
}
pub fn line_iter(self, b :Self) -> LineIterator<T> {
self.iter(b, false)
}
pub fn line(self, b :Self) -> Vec<Self> {
self.line_iter(b).collect()
}
pub 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 Vertex<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
write!(f, "<{},{}>", self.x, self.y)
}
}
impl<T> Add for Vertex<T> where T: Add<Output=T> {
type Output = Self;
fn add(self, other :Self) -> Vertex<T> {
Vertex{ x: self.x + other.x
, y: self.y + other.y
, zr: self.zr + other.zr }
}
}

26
src/easel/drawable.rs

@ -0,0 +1,26 @@
//
// …
//
// 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 super::canvas::Vertices;
pub trait Drawable<T> {
fn plot(&self) -> Vertices<T>;
}

31
src/easel/fillable.rs

@ -0,0 +1,31 @@
//
// …
//
// 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;
use std::ops::{Add, Div, Sub};
use super::canvas::Canvas;
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);
}

45
src/easel/line.rs

@ -0,0 +1,45 @@
//
// …
//
// 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, Div, Sub};
use super::canvas::{Vertex, Vertices};
use super::drawable::Drawable;
#[derive(Debug, Clone, Copy)]
pub struct Line<T>(pub Vertex<T>, pub Vertex<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) -> Vertices<T> {
let Line(a, b) = *self;
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)
}
}

90
src/easel/line_iterator.rs

@ -0,0 +1,90 @@
//
// …
//
// 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::max;
use std::fmt::Debug;
use std::ops::{Add, Div, Sub};
use super::canvas::Vertex;
#[derive(Debug, Clone, Copy)]
pub struct LineIterator<T> where T: Debug {
a :Option<Vertex<T>>
, b :Vertex<T>
, dx :i32
, dy :i32
, incx :Vertex<T>
, incy :Vertex<T>
, incxy :Vertex<T>
, err :i32
, edges :bool
}
impl<T> LineIterator<T>
where T: Add<Output=T> + Div<Output=T> + Sub<Output=T>
+ Debug + Clone + Copy + From<i32> {
pub fn new(a :Vertex<T>, b :Vertex<T>, edges :bool) -> LineIterator<T> {
let (ax, ay, azr) = a.as_tuple();
let (bx, by, bzr) = b.as_tuple();
let dx = (bx - ax).abs();
let dy = -(by - ay).abs();
let dz = (bzr - azr) / max(dx, -dy).into();
let sx = if ax < bx { 1 } else { -1 };
let sy = if ay < by { 1 } else { -1 };
LineIterator { a: Some(a)
, b: b
, dx: dx
, dy: dy
, incx: Vertex::new( sx, 0.into(), dz)
, incy: Vertex::new(0.into(), sy, dz)
, incxy: Vertex::new( sx, sy, dz)
, err: dx + dy
, edges: edges }
}
}
impl<T> Iterator for LineIterator<T>
where T: Add<Output=T> + Div<Output=T> + Sub<Output=T>
+ Debug + Copy + From<i32> {
type Item = Vertex<T>;
// Bresenham based line iteration.
fn next(&mut self) -> Option<Self::Item> {
if ! self.a?.same_position(&self.b) {
let ret = self.a;
let inc = match (2*self.err >= self.dy, 2*self.err <= self.dx ) {
(true, false) => ( self.incx, self.dy, self.edges),
(false, true) => ( self.incy, self.dx, false),
_ => (self.incxy, self.dx+self.dy, false),
};
self.a = Some(self.a? + inc.0);
self.err = self.err + inc.1;
if inc.2 { self.next() } else { ret }
} else {
self.a = None;
Some(self.b)
}
}
}

41
src/easel/mod.rs

@ -0,0 +1,41 @@
//
// …
//
// 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/>.
//
pub trait Easel {
}
// Trait to implement for a concrete canvas as well as struct for canvas
// coordinates (which also includes z reciprocal)
pub mod canvas;
// Traits that new drawing primitives must implement to be drawn on a canvas.
pub mod drawable;
pub mod fillable;
// Drawing primitives
pub mod line;
pub mod point;
pub mod polygon;
pub mod polyline;
// Helper iterators to find all positions a primitive needs to draw.
mod line_iterator;
mod vertex_iterator;

42
src/easel/point.rs

@ -0,0 +1,42 @@
//
// …
//
// 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::{Display, Formatter, Result};
use super::canvas::{Vertex, Vertices};
use super::drawable::Drawable;
#[derive(Debug, Clone, Copy)]
pub struct Point<T>(pub Vertex<T>);
impl<T> Drawable<T> for Point<T> where T: Copy {
fn plot(&self) -> Vertices<T> {
let Point(c) = *self;
vec!(c)
}
}
impl<T> Display for Point<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
let Point(p) = self;
write!(f, "Point[{}]", p)
}
}

176
src/easel/polygon.rs

@ -0,0 +1,176 @@
//
// …
//
// 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;
use std::ops::{Add, Div, Sub};
use super::canvas::{Canvas, Vertex, Vertices};
use super::drawable::Drawable;
use super::fillable::Fillable;
use super::vertex_iterator::{Direction, VertexIterator};
#[derive(Debug, Clone)]
pub struct Polygon<T>(pub Vertices<T>);
impl<T> Polygon<T>
where T: Add<Output = T> + Sub<Output = T> + Div<Output = T>
+ Copy + Debug + From<i32> {
#[inline]
pub fn vertex(&self, v :usize) -> Vertex<T> {
let Polygon(cs) = self;
cs[v]
}
pub fn vert_min<'a>(&'a self, d :Direction) -> usize {
let Polygon(cs) = self;
type ICoord<'a, T> = (usize, &'a Vertex<T>);
let fold = |acc :Option<ICoord<'a,T>>, x :ICoord<'a, T>|
match acc {
None => Some(x),
Some(a) => {
let (_, ay, _) = a.1.as_tuple();
let (_, xy, _) = x.1.as_tuple();
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).same_y(&self.vertex(next)) {
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::line(self, Direction::Left)
}
fn right_vertices(&self) -> VertexIterator<T> {
VertexIterator::line(self, Direction::Right)
}
fn left(&self, v :usize) -> usize {
let Polygon(cs) = self;
match v {
0 => cs.len() - 1,
_ => v - 1,
}
}
fn right(&self, v :usize) -> usize {
let Polygon(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),
}
}
pub 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 (_, cy, _) = p.vertex(c).as_tuple();
let (_, ny, _) = p.vertex(n).as_tuple();
if ny < cy { None } else { Some(n) }
}
}
inner(self, c, self.step(c, d), d)
}
}
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) -> Vertices<T> {
let Polygon(cs) = self;
match cs[..] {
[] => Vec::<Vertex<T>>::new(),
[a] => vec!(a),
[a, b] => 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());
}
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());
let vertices = |(l, r) :(Vertex<T>, Vertex<T>)| l.line_iter(r);
for p in scanlines.flat_map(vertices) {
canvas.set_pixel(p, 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)
}
}
*/

61
src/easel/polyline.rs

@ -0,0 +1,61 @@
//
// …
//
// 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;
use std::ops::{Add, Div, Sub};
use super::canvas::{Vertex, Vertices};
use super::drawable::Drawable;
#[derive(Debug, Clone)]
pub struct Polyline<T>(pub Vertices<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) -> Vertices<T> {
let Polyline(cs) = self;
match cs[..] {
[] => Vec::<Vertex<T>>::new(),
[a] => vec!(a),
[a, b] => 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;
}
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)
}
}
*/

122
src/easel/vertex_iterator.rs

@ -0,0 +1,122 @@
//
// …
//
// 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;
use std::ops::{Add, Sub, Div};
use super::line_iterator::LineIterator;
use super::canvas::Vertex;
use super::polygon::Polygon;
#[derive(Debug, Clone, Copy)]
pub enum Direction {
Left,
Right,
}
#[derive(Debug, Clone)]
enum VertexIteratorMode {
Line,
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> {
pub fn line(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::Line
, direction: direction }
}
pub fn edge(p :&'a Polygon<T>, direction :Direction) -> Self {
let mut vi = Self::line(p, direction);
vi.mode = VertexIteratorMode::Edge;
vi.edge = match vi.current {
None => None,
Some(next) => Some(p.vertex(vi.top).edge_iter(p.vertex(next))),
};
vi
}
// 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 = Vertex<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::Line => {
let current = self.current?;
self.current = self.p.next_y(current, self.direction);
Some(self.p.vertex(current))
},
}
}
}

22
src/geometry.rs

@ -22,7 +22,8 @@ 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::easel::canvas::{Canvas, Vertex};
use crate::easel::polygon::Polygon;
use crate::transform::{TMatrix, Transformable};
use crate::trigonometry::Trig;
use crate::vector::Vector;
@ -251,20 +252,15 @@ where T: Add<Output = T> + Sub<Output = T> + Neg<Output = 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
let ps = vec!( Point::new(-ch, -ch, ch)
, Point::new(-ch, ch, -ch)
, Point::new( ch, -ch, -ch)
, Point::new( ch, ch, ch) );
// 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 }
}
@ -335,15 +331,15 @@ where T: Add<Output = T> + Sub<Output = T> + Neg<Output = T>
// 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()))
where I: Iterator<Item = Vertex<T>> {
Polygon(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())
Vertex::new(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));

282
src/lib.rs

@ -1,3 +1,35 @@
//
// easel3d is a library that provides basic math for 3D transformation and
// projection as well as a simple software resterizer.
//
// All of them implemented as generics so that they work with f64 and other
// suitable types.
//
// This is mainly the result of my learning rust experiments. So it is
// very likely not optimal and improvements and suggestions are welcome.
//
// The rasterization part, called easel consists of two traits (easel and
// canvas) where the one is cuttently empty because I found no methods that
// I currently need. And the other needs to be implemented to get a
// concrete rasterizer. After that one can use all of the other types there.
//
// 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/>.
//
extern crate lazy_static;
pub type Error = &'static str;
@ -11,253 +43,3 @@ pub mod geometry;
mod utils;
use vector::Vector;
use easel::{Canvas, Coordinate, Drawable, Fillable};
use geometry::{Camera, DirectLight, Polyeder, Primitives};
use transform::{TMatrix};
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
// allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
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
, degree :i32
//, start :Instant
, tetrahedron :Polyeder<f64>
, cube :Polyeder<f64>
, camera :Option<Camera<f64>>
, light :DirectLight<f64>
, zbuf :Vec<f64>
, image :Vec<Color>
}
#[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 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
}
pub fn width(&self) -> u16 {
self.width
}
pub fn height(&self) -> u16 {
self.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);
let ry = TMatrix::rotate_y(-self.degree*2);
let rot1 = TMatrix::combine(vec!(rz, rx, t));
let rot2 = TMatrix::combine(vec!(rz, ry, t));
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();
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);
// 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,
};
next[idx] = next_cell;
}
}
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(())
}
}

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();
}
Loading…
Cancel
Save