Skip to content

Commit

Permalink
Support fill (#129)
Browse files Browse the repository at this point in the history
  • Loading branch information
pearmini authored Aug 1, 2023
1 parent bd97444 commit cf62c82
Show file tree
Hide file tree
Showing 13 changed files with 232 additions and 151 deletions.
6 changes: 0 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,24 +216,18 @@ app.stroke("@", "steelblue", "orange");

<a name="app-nostroke" href="#app-nostroke">#</a> app.**noStroke**()

> WIP
```js
app.noStroke();
```

<a name="app-fill" href="#app-fill">#</a> app.**fill**(_ch[, fg[, bg]]_)

> WIP
```js
app.fill("@", "steelblue", "orange");
```

<a name="app-nofill" href="#app-nofill">#</a> app.**noFill**()

> WIP
```js
app.noFill();
```
Expand Down
13 changes: 13 additions & 0 deletions rust/src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,25 @@ use wasm_bindgen::prelude::*;
#[wasm_bindgen]
impl Renderer {
pub fn stroke(&mut self, ch: u32, ch1: u32, fg: u32, bg: u32) {
self.has_stroke = true;
self.stroke_color = [ch, ch1, fg, bg];
}
pub fn fill(&mut self, ch: u32, ch1: u32, fg: u32, bg: u32) {
self.has_fill = true;
self.fill_color = [ch, ch1, fg, bg];
}
pub fn background(&mut self, ch: u32, ch1: u32, fg: u32, bg: u32) {
self.has_background = true;
self.background_color = [ch, ch1, fg, bg];
}
#[wasm_bindgen(js_name = "noStroke")]
pub fn no_stroke(&mut self) {
self.has_stroke = false;
}
#[wasm_bindgen(js_name = "noFill")]
pub fn no_fill(&mut self) {
self.has_fill = false;
}
}

#[cfg(test)]
Expand Down
13 changes: 11 additions & 2 deletions rust/src/globals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,23 @@ pub struct Vertex {
pub(crate) y: f64,
}

pub type Edge<'a> = [&'a Vertex; 2];
pub struct Point {
pub(crate) color: Color,
pub(crate) x: isize,
pub(crate) y: isize,
}

pub type Edge<'a> = [&'a Point; 2];

pub type Color = [u32; CELL_SIZE];

pub struct Shape {
pub(crate) vertices: Vec<Vertex>,
pub(crate) matrix: Matrix3,
pub(crate) closed: bool,
pub(crate) is_closed: bool,
pub(crate) has_stroke: bool,
pub(crate) has_fill: bool,
pub(crate) fill_color: Color,
}

pub type Matrix3 = [f64; 9];
Expand Down
1 change: 0 additions & 1 deletion rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,3 @@ mod pipeline;
mod primitives;
pub mod renderer;
mod transform;
mod utils;
218 changes: 130 additions & 88 deletions rust/src/pipeline.rs
Original file line number Diff line number Diff line change
@@ -1,135 +1,168 @@
extern crate wasm_bindgen;
use crate::{
globals::{Color, Edge, Matrix3, Vector3, Vertex, CELL_SIZE, NULL_VALUE},
globals::{Color, Edge, Matrix3, Point, Vector3, Vertex, CELL_SIZE, NULL_VALUE},
matrix3::{matrix3_identity, matrix3_transform},
renderer::Renderer,
utils::{ascending, map},
};
use std::ptr;
use std::{cmp, ptr};
use wasm_bindgen::prelude::*;

fn vertex_processing(vertices: &Vec<Vertex>, m: &Matrix3) -> Vec<Vertex> {
let mut transformed: Vec<Vertex> = vec![];
fn vertex_processing(vertices: &Vec<Vertex>, m: &Matrix3) -> Vec<Point> {
let mut transformed: Vec<Point> = vec![];
for vertex in vertices {
let v: Vector3 = [vertex.x, vertex.y, 1.0];
let out: Vector3 = matrix3_transform(&v, &m);
transformed.push(Vertex {
transformed.push(Point {
color: vertex.color,
x: out[0],
y: out[1],
x: out[0].round() as isize,
y: out[1].round() as isize,
})
}
transformed
}

fn primitive_assembly(vertices: &Vec<Vertex>, closed: bool) -> Vec<Edge> {
fn primitive_assembly(vertices: &Vec<Point>, is_closed: bool) -> Vec<Edge> {
if vertices.len() == 0 {
vec![]
} else if vertices.len() == 1 {
let edge: Edge = [&vertices[0], &vertices[0]];
vec![edge]
} else {
let mut edges: Vec<Edge> = vec![];
for i in 1..vertices.len() {
let from: &Vertex = &vertices[i - 1];
let to: &Vertex = &vertices[i];
let edge: Edge = [from, to];
edges.push(edge);
}
if closed {
let from: &Vertex = &vertices[0];
let to: &Vertex = &vertices[vertices.len() - 1];
let len: usize = vertices.len();
let end: usize = if is_closed { len + 1 } else { len };
for i in 1..end {
let from: &Point = &vertices[i - 1];
let to: &Point = &vertices[i % len];
let edge: Edge = [from, to];
edges.push(edge);
}
edges
}
}

fn rasterization(edges: &Vec<Edge>) -> Vec<Vertex> {
let mut vertices: Vec<Vertex> = vec![];
fn rasterization(
edges: &Vec<Edge>,
fill_color: Color,
has_stroke: bool,
has_fill: bool,
) -> Vec<Point> {
let mut vertices: Vec<Point> = vec![];
if has_fill {
rasterization_stroke(&mut vertices, edges, fill_color, true);
rasterization_fill(&mut vertices, fill_color);
}
if has_stroke {
rasterization_stroke(&mut vertices, edges, fill_color, false);
}
vertices
}

fn rasterization_stroke(
vertices: &mut Vec<Point>,
edges: &Vec<Edge>,
fill_color: Color,
use_fill: bool,
) {
for i in 0..edges.len() {
let edge: [&Vertex; 2] = edges[i];
let from: &Vertex = edge[0];
let to: &Vertex = edge[1];
let edge: Edge = edges[i];
let from: &Point = edge[0];
let to: &Point = edge[1];
if ptr::eq(from, to) {
vertices.push(Vertex {
vertices.push(Point {
color: from.color,
x: from.x,
y: from.y,
})
} else {
if i == edges.len() - 1 {
vertices.append(&mut rasterize_line(from, to, true));
let next: Edge = edges[(i + 1) % edges.len()];
let next_from: &Point = next[0];
let color: Color = if use_fill { fill_color } else { from.color };
let line: &mut Vec<Point> = &mut rasterization_line(from, to, color);
if next_from.x == to.x && next_from.y == to.y && line.len() >= 2 {
line.pop();
vertices.append(line);
} else {
let next: [&Vertex; 2] = edges[i + 1];
let next_from: &Vertex = next[0];
if next_from.x == to.x && next_from.y == to.y {
vertices.append(&mut rasterize_line(from, to, false));
} else {
vertices.append(&mut rasterize_line(from, to, true));
}
vertices.append(line);
}
}
}
vertices
}

fn rasterize_line(from: &Vertex, to: &Vertex, trailing: bool) -> Vec<Vertex> {
if from.x == to.x && from.y == to.y {
return vec![Vertex {
color: from.color,
x: from.x,
y: from.y,
}];
fn rasterization_fill(vertices: &mut Vec<Point>, color: Color) {
let mut y0: isize = isize::MAX;
let mut y1: isize = isize::MIN;
for vertex in &mut *vertices {
y0 = cmp::min(y0, vertex.y);
y1 = cmp::max(y1, vertex.y);
}
let mut vertices: Vec<Vertex> = vec![];
let dx: f64 = (to.x - from.x).abs();
let dy: f64 = (to.y - from.y).abs();
if dx >= dy {
let mut x0: f64 = from.x.round();
let mut x1: f64 = to.x.round();
ascending(&mut x0, &mut x1);
let end: isize = if trailing {
x1 as isize + 1
} else {
x1 as isize
};
for x in (x0 as isize)..end {
let y: f64 = map(x as f64, from.x, to.x, from.y, to.y);
vertices.push(Vertex {
x: x as f64,
y,
color: from.color,
})

let mut lookup: Vec<Vec<isize>> = vec![vec![]; (y1 - y0) as usize + 1];
for vertex in &mut *vertices {
let index: usize = (vertex.y - y0) as usize;
lookup[index].push(vertex.x as isize)
}

for i in 0..lookup.len() {
let line: &mut Vec<isize> = &mut lookup[i];
line.sort();
let y: isize = (i as isize) + y0;
let mut in_polygon: bool = false;
for i in 0..line.len() {
if in_polygon {
let x0: isize = line[i - 1];
let x1: isize = line[i];
for x in (x0 + 1)..x1 {
vertices.push(Point { color, x, y })
}
}
in_polygon = !in_polygon;
}
} else {
let mut y0: f64 = from.y.round();
let mut y1: f64 = to.y.round();
ascending(&mut y0, &mut y1);
let end: isize = if trailing {
y1 as isize + 1
} else {
y1 as isize
}
}

// @see https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
fn rasterization_line(from: &Point, to: &Point, color: Color) -> Vec<Point> {
let dx: isize = (to.x - from.x).abs();
let dy: isize = -(to.y - from.y).abs();
let sx: isize = if from.x < to.x { 1 } else { -1 };
let sy: isize = if from.y < to.y { 1 } else { -1 };
let mut vertices: Vec<Point> = vec![];
let mut x: isize = from.x;
let mut y: isize = from.y;
let mut error: isize = dx + dy;

loop {
vertices.push(Point { color, x, y });
if x == to.x && y == to.y {
break;
};
for y in (y0 as isize)..end {
let x: f64 = map(y as f64, from.y, to.y, from.x, to.x);
vertices.push(Vertex {
x,
y: y as f64,
color: from.color,
})
let error2: isize = error * 2;
if error2 >= dy {
if x == to.x {
break;
}
error = error + dy;
x = x + sx;
}
if error2 <= dx {
if y == to.y {
break;
}
error = error + dx;
y = y + sy;
}
}

vertices
}

fn clipping(vertices: &Vec<Vertex>, cols: isize, rows: isize) -> Vec<usize> {
fn clipping(vertices: &Vec<Point>, cols: isize, rows: isize) -> Vec<usize> {
let mut clipped: Vec<usize> = vec![];
for i in 0..vertices.len() {
let vertex: &Vertex = &vertices[i];
let x: isize = vertex.x.round() as isize;
let y: isize = vertex.y.round() as isize;
let vertex: &Point = &vertices[i];
let x: isize = vertex.x;
let y: isize = vertex.y;
if x >= 0 && x < cols && y >= 0 && y < rows {
clipped.push(i);
}
Expand All @@ -138,15 +171,15 @@ fn clipping(vertices: &Vec<Vertex>, cols: isize, rows: isize) -> Vec<usize> {
}

fn fragment_processing(
vertices: &Vec<Point>,
clipped: &Vec<usize>,
vertices: &Vec<Vertex>,
buffer: &mut Vec<u32>,
cols: isize,
) {
for i in clipped {
let vertex: &Vertex = &vertices[*i];
let x: isize = vertex.x.round() as isize;
let y: isize = vertex.y.round() as isize;
let vertex: &Point = &vertices[*i];
let x: isize = vertex.x;
let y: isize = vertex.y;
let index: usize = ((x + y * cols) as usize) * CELL_SIZE;
buffer[index] = vertex.color[0];
buffer[index + 1] = vertex.color[1];
Expand Down Expand Up @@ -188,13 +221,20 @@ impl Renderer {
self.cols as isize,
);
for shape in &self.shapes {
let transformed: Vec<Vertex> = vertex_processing(&shape.vertices, &shape.matrix);
let primitive: Vec<Edge> = primitive_assembly(&transformed, shape.closed);
let fragment: Vec<Vertex> = rasterization(&primitive);
let transformed: Vec<Point> = vertex_processing(&shape.vertices, &shape.matrix);
let primitive: Vec<Edge> = primitive_assembly(&transformed, shape.is_closed);
let fragment: Vec<Point> = rasterization(
&primitive,
shape.fill_color,
shape.has_stroke,
shape.has_fill,
);
let clipped: Vec<usize> = clipping(&fragment, self.cols as isize, self.rows as isize);
fragment_processing(&clipped, &fragment, &mut self.buffer, self.cols as isize);
fragment_processing(&fragment, &clipped, &mut self.buffer, self.cols as isize);
}
self.has_background = false;
self.has_stroke = true;
self.has_fill = false;
self.shapes.clear();
self.stacks.clear();
self.mode_view = matrix3_identity();
Expand All @@ -213,9 +253,11 @@ mod tests {
renderer.render();
assert_eq!(renderer.shapes.len(), 0);
assert_eq!(renderer.stacks.len(), 0);
assert_eq!(renderer.has_background, false);
assert_eq!(renderer.mode_view[0], 1.0);
assert_eq!(renderer.mode_view[4], 1.0);
assert_eq!(renderer.mode_view[8], 1.0);
assert_eq!(renderer.has_background, false);
assert_eq!(renderer.has_stroke, true);
assert_eq!(renderer.has_fill, false);
}
}
Loading

0 comments on commit cf62c82

Please sign in to comment.