diff --git a/src/easel.rs b/src/easel.rs deleted file mode 100644 index c9a3f7e..0000000 --- a/src/easel.rs +++ /dev/null @@ -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 -// -// 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 . -// -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 { - fn init_events(&self); - fn start_events(&self, tx :mpsc::Sender); - - fn width(&self) -> u16; - fn height(&self) -> u16; - - fn clear(&mut self); - fn draw(&mut self, c :&dyn Drawable, ofs :Coordinate, color :u32); - fn put_text(&self, ofs :Coordinate, s :&str); - fn set_pixel(&mut self, c :Coordinate, color :u32); - fn show(&self); -} - -pub trait Drawable { - fn plot(&self) -> Coordinates; -} - -pub trait Fillable -where T: Add + Sub + Div - + Debug + Copy + From { - fn fill(&self, canvas :&mut dyn Canvas, color :u32); -} - -#[derive(Debug, Clone, Copy)] -pub struct Coordinate(pub i32, pub i32, pub T); - -#[derive(Debug, Clone)] -pub struct Coordinates(pub Vec>); - -#[derive(Debug, Clone, Copy)] -pub struct LineIterator where T: Debug { - a :Option> - , b :Coordinate - , dx :i32 - , dy :i32 - , dz :T - , sx :i32 - , sy :i32 - , err :i32 - , only_edges :bool -} - -impl Iterator for LineIterator -where T: Add + Debug + Copy + From { - type Item = Coordinate; - - fn next(&mut self) -> Option { - 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 Coordinate -where T: Add + Sub + Div - + Debug + Clone + Copy + From { - fn iter(self, b :&Self, only_edges :bool) -> LineIterator { - 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 { - self.iter(b, false) - } - - fn line(self, b :&Self) -> Vec { - self.line_iter(b).collect() - } - - fn edge_iter(self, b :&Self) -> LineIterator { - self.iter(b, true) - } - - fn edge(self, b :&Self) -> Vec { - self.edge_iter(b).collect() - } - - fn face(edges :&[Self]) -> Vec { - edges.to_vec() - } -} - -impl Display for Coordinate { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - write!(f, "<{},{}>", self.0, self.1) - } -} - -impl Display for Coordinates 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(pub Coordinate); - -impl Drawable for Point where T: Copy { - fn plot(&self) -> Coordinates { - let Point(c) = *self; - Coordinates(vec!(c)) - } -} - -impl Display for Point { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - let Point(p) = self; - write!(f, "Point[{}]", p) - } -} - -#[derive(Debug, Clone, Copy)] -pub struct Line(pub Coordinate, pub Coordinate); - -impl Drawable for Line -where T: Add + Sub + Div - + Debug + Clone + Copy + From { - fn plot(&self) -> Coordinates { - let Line(a, b) = *self; - Coordinates(a.line(&b)) - } -} - -impl Display for Line { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - let Line(a, b) = self; - write!(f, "Line[{},{}]", a, b) - } -} - -#[derive(Debug, Clone)] -pub struct Polyline(pub Coordinates); - -impl Drawable for Polyline -where T: Add + Sub + Div - + Debug + Clone + Copy + From { - fn plot(&self) -> Coordinates { - let Polyline(Coordinates(cs)) = self; - - match cs[..] { - [] => Coordinates(Vec::>::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 Display for Polyline 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(pub Coordinates); - -#[derive(Debug, Clone)] -enum VertexIteratorMode { Vertex, Edge } -#[derive(Debug, Clone)] -pub struct VertexIterator<'a,T> where T: Debug { - p :&'a Polygon, - top :usize, - current :Option, - edge :Option>, - mode :VertexIteratorMode, - direction :Direction, -} - -impl<'a,T> VertexIterator<'a,T> -where T: Add + Sub + Div - + Debug + Copy + From { - fn edge(p :&'a Polygon, 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, 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> { - 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 + Sub + Div - + Debug + Copy + From { - type Item = Coordinate; - - fn next(&mut self) -> Option { - 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 Polygon -where T: Add + Sub + Div - + Copy + Debug + From { - #[inline] - fn vertex(&self, v :usize) -> Coordinate { - 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); - - // 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>, 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 { - VertexIterator::edge(self, Direction::Left) - } - - fn right_edge(&self) -> VertexIterator { - VertexIterator::edge(self, Direction::Right) - } - - fn left_vertices(&self) -> VertexIterator { - VertexIterator::vertex(self, Direction::Left) - } - - fn right_vertices(&self) -> VertexIterator { - 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 { - fn inner( p :&Polygon - , c :usize - , n :usize - , d :Direction) -> Option - where T: Add + Sub + Div - + Copy + Debug + From { - 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> = self.left_vertices().collect(); - let right :Vec> = 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 Drawable for Polygon -where T: Add + Sub + Div - + Debug + Clone + Copy + From { - fn plot(&self) -> Coordinates { - let Polygon(Coordinates(cs)) = self; - - match cs[..] { - [] => Coordinates(Vec::>::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 Fillable for Polygon -where T: Add + Sub + Div - + Debug + Clone + Copy + From { - fn fill(&self, canvas :&mut dyn Canvas, 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 Display for Polygon where T: Copy { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - let Polygon(a) = self; - write!(f, "Poly[{}]", a) - } -} diff --git a/src/easel/canvas.rs b/src/easel/canvas.rs new file mode 100644 index 0000000..849c13a --- /dev/null +++ b/src/easel/canvas.rs @@ -0,0 +1,119 @@ +// +// … +// +// Georg Hopp +// +// 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 . +// + +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 { + fn init_events(&self); + fn start_events(&self, tx :mpsc::Sender); + + fn width(&self) -> u16; + fn height(&self) -> u16; + + fn clear(&mut self); + fn draw(&mut self, c :&dyn Drawable, color :u32); + fn set_pixel(&mut self, c :Vertex, color :u32); + fn put_text(&self, ofs :Vertex, 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{ x :i32 // canvas x coordinate + , y :i32 // canvas y coordinate + , zr :T } // z reciprocal from 3D projection. + +pub type Vertices = Vec>; + +impl Vertex +where T: Add + Sub + Div + + Debug + Clone + Copy + From { + 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 { + LineIterator::new(self, b, only_edges) + } + + pub fn line_iter(self, b :Self) -> LineIterator { + self.iter(b, false) + } + + pub fn line(self, b :Self) -> Vec { + self.line_iter(b).collect() + } + + pub fn edge_iter(self, b :Self) -> LineIterator { + self.iter(b, true) + } + + fn edge(self, b :Self) -> Vec { + self.edge_iter(b).collect() + } + + fn face(edges :&[Self]) -> Vec { + edges.to_vec() + } +} + +impl Display for Vertex { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + write!(f, "<{},{}>", self.x, self.y) + } +} + +impl Add for Vertex where T: Add { + type Output = Self; + + fn add(self, other :Self) -> Vertex { + Vertex{ x: self.x + other.x + , y: self.y + other.y + , zr: self.zr + other.zr } + } +} diff --git a/src/easel/drawable.rs b/src/easel/drawable.rs new file mode 100644 index 0000000..c521332 --- /dev/null +++ b/src/easel/drawable.rs @@ -0,0 +1,26 @@ +// +// … +// +// Georg Hopp +// +// 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 . +// + +use super::canvas::Vertices; + +pub trait Drawable { + fn plot(&self) -> Vertices; +} diff --git a/src/easel/fillable.rs b/src/easel/fillable.rs new file mode 100644 index 0000000..f0493b8 --- /dev/null +++ b/src/easel/fillable.rs @@ -0,0 +1,31 @@ +// +// … +// +// Georg Hopp +// +// 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 . +// + +use std::fmt::Debug; +use std::ops::{Add, Div, Sub}; + +use super::canvas::Canvas; + +pub trait Fillable +where T: Add + Sub + Div + + Debug + Copy + From { + fn fill(&self, canvas :&mut dyn Canvas, color :u32); +} diff --git a/src/easel/line.rs b/src/easel/line.rs new file mode 100644 index 0000000..fef8675 --- /dev/null +++ b/src/easel/line.rs @@ -0,0 +1,45 @@ +// +// … +// +// Georg Hopp +// +// 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 . +// + +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(pub Vertex, pub Vertex); + +impl Drawable for Line +where T: Add + Sub + Div + + Debug + Clone + Copy + From { + fn plot(&self) -> Vertices { + let Line(a, b) = *self; + a.line(b) + } +} + +impl Display for Line { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + let Line(a, b) = self; + write!(f, "Line[{},{}]", a, b) + } +} diff --git a/src/easel/line_iterator.rs b/src/easel/line_iterator.rs new file mode 100644 index 0000000..a559681 --- /dev/null +++ b/src/easel/line_iterator.rs @@ -0,0 +1,90 @@ +// +// … +// +// Georg Hopp +// +// 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 . +// + +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 where T: Debug { + a :Option> + , b :Vertex + , dx :i32 + , dy :i32 + , incx :Vertex + , incy :Vertex + , incxy :Vertex + , err :i32 + , edges :bool +} + +impl LineIterator +where T: Add + Div + Sub + + Debug + Clone + Copy + From { + pub fn new(a :Vertex, b :Vertex, edges :bool) -> LineIterator { + 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 Iterator for LineIterator +where T: Add + Div + Sub + + Debug + Copy + From { + type Item = Vertex; + + // Bresenham based line iteration. + fn next(&mut self) -> Option { + 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) + } + } +} diff --git a/src/easel/mod.rs b/src/easel/mod.rs new file mode 100644 index 0000000..a3ae8ae --- /dev/null +++ b/src/easel/mod.rs @@ -0,0 +1,41 @@ +// +// … +// +// Georg Hopp +// +// 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 . +// + +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; diff --git a/src/easel/point.rs b/src/easel/point.rs new file mode 100644 index 0000000..4aaa395 --- /dev/null +++ b/src/easel/point.rs @@ -0,0 +1,42 @@ +// +// … +// +// Georg Hopp +// +// 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 . +// + +use std::fmt::{Display, Formatter, Result}; + +use super::canvas::{Vertex, Vertices}; +use super::drawable::Drawable; + +#[derive(Debug, Clone, Copy)] +pub struct Point(pub Vertex); + +impl Drawable for Point where T: Copy { + fn plot(&self) -> Vertices { + let Point(c) = *self; + vec!(c) + } +} + +impl Display for Point { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + let Point(p) = self; + write!(f, "Point[{}]", p) + } +} diff --git a/src/easel/polygon.rs b/src/easel/polygon.rs new file mode 100644 index 0000000..e26a730 --- /dev/null +++ b/src/easel/polygon.rs @@ -0,0 +1,176 @@ +// +// … +// +// Georg Hopp +// +// 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 . +// + +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(pub Vertices); + +impl Polygon +where T: Add + Sub + Div + + Copy + Debug + From { + #[inline] + pub fn vertex(&self, v :usize) -> Vertex { + 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); + + let fold = |acc :Option>, 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 { + VertexIterator::edge(self, Direction::Left) + } + + fn right_edge(&self) -> VertexIterator { + VertexIterator::edge(self, Direction::Right) + } + + fn left_vertices(&self) -> VertexIterator { + VertexIterator::line(self, Direction::Left) + } + + fn right_vertices(&self) -> VertexIterator { + 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 { + fn inner( p :&Polygon + , c :usize + , n :usize + , d :Direction) -> Option + where T: Add + Sub + Div + + Copy + Debug + From { + 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 Drawable for Polygon +where T: Add + Sub + Div + + Debug + Clone + Copy + From { + fn plot(&self) -> Vertices { + let Polygon(cs) = self; + + match cs[..] { + [] => Vec::>::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 Fillable for Polygon +where T: Add + Sub + Div + + Debug + Clone + Copy + From { + fn fill(&self, canvas :&mut dyn Canvas, color :u32) { + let scanlines = self.left_edge().zip(self.right_edge()); + let vertices = |(l, r) :(Vertex, Vertex)| l.line_iter(r); + + for p in scanlines.flat_map(vertices) { + canvas.set_pixel(p, color); + } + } +} + +/* +impl Display for Polygon where T: Copy { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + let Polygon(a) = self; + write!(f, "Poly[{}]", a) + } +} +*/ diff --git a/src/easel/polyline.rs b/src/easel/polyline.rs new file mode 100644 index 0000000..73fce25 --- /dev/null +++ b/src/easel/polyline.rs @@ -0,0 +1,61 @@ +// +// … +// +// Georg Hopp +// +// 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 . +// + +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(pub Vertices); + +impl Drawable for Polyline +where T: Add + Sub + Div + + Debug + Clone + Copy + From { + fn plot(&self) -> Vertices { + let Polyline(cs) = self; + + match cs[..] { + [] => Vec::>::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 Display for Polyline where T: Copy { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + let Polyline(a) = self; + write!(f, "PLine[{}]", a) + } +} +*/ diff --git a/src/easel/vertex_iterator.rs b/src/easel/vertex_iterator.rs new file mode 100644 index 0000000..257f9d4 --- /dev/null +++ b/src/easel/vertex_iterator.rs @@ -0,0 +1,122 @@ +// +// … +// +// Georg Hopp +// +// 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 . +// +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, + top :usize, + current :Option, + edge :Option>, + mode :VertexIteratorMode, + direction :Direction, +} + +impl<'a, T> VertexIterator<'a, T> +where T: Add + Sub + Div + + Debug + Copy + From { + pub fn line(p :&'a Polygon, 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, 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> { + 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 + Sub + Div + + Debug + Copy + From { + type Item = Vertex; + + fn next(&mut self) -> Option { + 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)) + }, + } + } +} diff --git a/src/geometry.rs b/src/geometry.rs index 0320378..80ee0df 100644 --- a/src/geometry.rs +++ b/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 + Sub + Neg 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 + Sub + Neg // Helper to create a Polygon from Coordinates… // TODO probably there needs to be a Polygon constructor for this. fn polygon(c :I) -> Polygon - where I: Iterator> { - Polygon(Coordinates(c.collect())) + where I: Iterator> { + 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| { let pg = polygon(f.corners.iter().map(to_coord)); diff --git a/src/lib.rs b/src/lib.rs index 63bdda0..336c784 100644 --- a/src/lib.rs +++ b/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 +// +// 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 . +// 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 - , cube :Polyeder - , camera :Option> - , light :DirectLight - , zbuf :Vec - , image :Vec -} - -#[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::::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 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, 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) {} - fn draw( &mut self, _ :&dyn Drawable, _ :Coordinate, _ :u32 ) {} - fn put_text(&self, _ :Coordinate, _ :&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, -} - -#[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(()) - } -} diff --git a/src/utils.rs b/src/utils.rs deleted file mode 100644 index b1d7929..0000000 --- a/src/utils.rs +++ /dev/null @@ -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(); -}