use crate::fig::Fig;
use crate::geom::{float_lerp, Transform, Vec2, Vec2w};
use crate::path::{FillRule, PathOp};
use crate::stroker::{JoinStyle, Stroke};
use pix::matte::Matte8;
use pix::Raster;
use std::borrow::Borrow;
pub struct Plotter {
matte: Raster<Matte8>,
sgn_area: Vec<i16>,
pen: Vec2w,
transform: Transform,
tol_sq: f32,
s_width: f32,
join_style: JoinStyle,
}
trait PlotDest {
fn add_point(&mut self, pt: Vec2w);
fn close(&mut self, joined: bool);
}
impl PlotDest for Fig {
fn add_point(&mut self, pt: Vec2w) {
Fig::add_point(self, pt.v);
}
fn close(&mut self, _joined: bool) {
Fig::close(self);
}
}
impl PlotDest for Stroke {
fn add_point(&mut self, pt: Vec2w) {
Stroke::add_point(self, pt);
}
fn close(&mut self, joined: bool) {
Stroke::close(self, joined);
}
}
impl Plotter {
pub fn new(width: u32, height: u32) -> Plotter {
let tol = 0.3;
let w = if width > 0 { width } else { 100 };
let h = if height > 0 { height } else { 100 };
let len = w as usize;
let cap = ((len + 7) >> 3) << 3;
let mut sgn_area = vec![0i16; cap];
for _ in 0..cap - len {
sgn_area.pop();
}
Plotter {
matte: Raster::with_clear(w, h),
sgn_area,
pen: Vec2w::new(0.0, 0.0, 1.0),
transform: Transform::new(),
tol_sq: tol * tol,
s_width: 1.0,
join_style: JoinStyle::Miter(4.0),
}
}
pub fn width(&self) -> u32 {
self.matte.width()
}
pub fn height(&self) -> u32 {
self.matte.height()
}
fn reset(&mut self) {
self.pen = Vec2w::new(0.0, 0.0, self.s_width);
}
pub fn clear_matte(&mut self) -> &mut Self {
self.matte.clear();
self
}
pub fn set_tolerance(&mut self, t: f32) -> &mut Self {
let tol = t.max(0.01);
self.tol_sq = tol * tol;
self
}
pub fn set_transform(&mut self, t: Transform) -> &mut Self {
self.transform = t;
self
}
fn pen_width(&mut self, width: f32) {
self.s_width = width;
}
pub fn set_join(&mut self, js: JoinStyle) -> &mut Self {
self.join_style = js;
self
}
fn move_pen(&mut self, p: Vec2w) {
self.pen = p;
}
fn transform_point(&self, p: Vec2w) -> Vec2w {
let pt = self.transform * p.v;
Vec2w::new(pt.x, pt.y, p.w)
}
fn add_ops<T, D>(&mut self, ops: T, dst: &mut D)
where
T: IntoIterator,
T::Item: Borrow<PathOp>,
D: PlotDest,
{
self.reset();
for op in ops {
self.add_op(dst, op.borrow());
}
}
fn add_op<D: PlotDest>(&mut self, dst: &mut D, op: &PathOp) {
match *op {
PathOp::Close() => self.close(dst),
PathOp::Move(bx, by) => self.move_to(dst, bx, by),
PathOp::Line(bx, by) => self.line_to(dst, bx, by),
PathOp::Quad(bx, by, cx, cy) => self.quad_to(dst, bx, by, cx, cy),
PathOp::Cubic(bx, by, cx, cy, dx, dy) => {
self.cubic_to(dst, bx, by, cx, cy, dx, dy)
}
PathOp::PenWidth(w) => self.pen_width(w),
};
}
fn close<D: PlotDest>(&mut self, dst: &mut D) {
dst.close(true);
self.reset();
}
fn move_to<D: PlotDest>(&mut self, dst: &mut D, bx: f32, by: f32) {
let p = Vec2w::new(bx, by, self.s_width);
dst.close(false);
let b = self.transform_point(p);
dst.add_point(b);
self.move_pen(p);
}
fn line_to<D: PlotDest>(&mut self, dst: &mut D, bx: f32, by: f32) {
let p = Vec2w::new(bx, by, self.s_width);
let b = self.transform_point(p);
dst.add_point(b);
self.move_pen(p);
}
fn quad_to<D: PlotDest>(
&mut self,
dst: &mut D,
bx: f32,
by: f32,
cx: f32,
cy: f32,
) {
let pen = self.pen;
let bb = Vec2w::new(bx, by, (pen.w + self.s_width) / 2.0);
let cc = Vec2w::new(cx, cy, self.s_width);
let a = self.transform_point(pen);
let b = self.transform_point(bb);
let c = self.transform_point(cc);
self.quad_to_tran(dst, a, b, c);
self.move_pen(cc);
}
fn quad_to_tran<D: PlotDest>(
&self,
dst: &mut D,
a: Vec2w,
b: Vec2w,
c: Vec2w,
) {
let ab = a.midpoint(b);
let bc = b.midpoint(c);
let ab_bc = ab.midpoint(bc);
let ac = a.midpoint(c);
if self.is_within_tolerance(ab_bc, ac) {
dst.add_point(c);
} else {
self.quad_to_tran(dst, a, ab, ab_bc);
self.quad_to_tran(dst, ab_bc, bc, c);
}
}
fn is_within_tolerance(&self, a: Vec2w, b: Vec2w) -> bool {
self.is_within_tolerance2(a.v, b.v)
}
fn is_within_tolerance2(&self, a: Vec2, b: Vec2) -> bool {
assert!(self.tol_sq > 0.0);
a.dist_sq(b) <= self.tol_sq
}
fn cubic_to<D: PlotDest>(
&mut self,
dst: &mut D,
bx: f32,
by: f32,
cx: f32,
cy: f32,
dx: f32,
dy: f32,
) {
let pen = self.pen;
let bw = float_lerp(pen.w, self.s_width, 1.0 / 3.0);
let cw = float_lerp(pen.w, self.s_width, 2.0 / 3.0);
let bb = Vec2w::new(bx, by, bw);
let cc = Vec2w::new(cx, cy, cw);
let dd = Vec2w::new(dx, dy, self.s_width);
let a = self.transform_point(pen);
let b = self.transform_point(bb);
let c = self.transform_point(cc);
let d = self.transform_point(dd);
self.cubic_to_tran(dst, a, b, c, d);
self.move_pen(dd);
}
fn cubic_to_tran<D: PlotDest>(
&self,
dst: &mut D,
a: Vec2w,
b: Vec2w,
c: Vec2w,
d: Vec2w,
) {
let ab = a.midpoint(b);
let bc = b.midpoint(c);
let cd = c.midpoint(d);
let ab_bc = ab.midpoint(bc);
let bc_cd = bc.midpoint(cd);
let e = ab_bc.midpoint(bc_cd);
let ad = a.midpoint(d);
if self.is_within_tolerance(e, ad) {
dst.add_point(d);
} else {
self.cubic_to_tran(dst, a, ab, ab_bc, e);
self.cubic_to_tran(dst, e, bc_cd, cd, d);
}
}
pub fn fill<T>(&mut self, ops: T, rule: FillRule) -> &mut Raster<Matte8>
where
T: IntoIterator,
T::Item: Borrow<PathOp>,
{
let mut fig = Fig::new();
self.add_ops(ops, &mut fig);
fig.close();
fig.fill(&mut self.matte, &mut self.sgn_area[..], rule);
&mut self.matte
}
pub fn stroke<T>(&mut self, ops: T) -> &mut Raster<Matte8>
where
T: IntoIterator,
T::Item: Borrow<PathOp>,
{
let mut stroke = Stroke::new(self.join_style, self.tol_sq);
self.add_ops(ops, &mut stroke);
let ops = stroke.path_ops();
self.fill(ops.iter(), FillRule::NonZero)
}
pub fn matte(&mut self) -> &mut Raster<Matte8> {
&mut self.matte
}
}