use crate::geom::WidePt;
use crate::path::PathOp;
use crate::vid::Vid;
use pointy::{Line, Pt};
use std::fmt;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum JoinStyle {
Miter(f32),
Bevel,
Round,
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum Dir {
Forward,
Reverse,
}
#[derive(Clone)]
struct SubStroke {
start: Vid,
n_points: Vid,
joined: bool,
done: bool,
}
#[derive(Clone)]
pub struct Stroke {
join_style: JoinStyle,
tol_sq: f32,
points: Vec<WidePt>,
subs: Vec<SubStroke>,
}
impl SubStroke {
fn new(start: Vid) -> SubStroke {
SubStroke {
start,
n_points: Vid(0),
joined: false,
done: false,
}
}
fn next(&self, vid: Vid, dir: Dir) -> Vid {
match dir {
Dir::Forward => {
let v = vid + 1;
if v < self.start + self.n_points {
v
} else {
self.start
}
}
Dir::Reverse => {
if vid > self.start {
vid - 1
} else {
self.start + self.n_points - 1
}
}
}
}
fn len(&self) -> Vid {
if self.joined {
self.n_points + 1
} else if self.n_points > Vid(0) {
self.n_points - 1
} else {
Vid(0)
}
}
}
impl fmt::Debug for Stroke {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for sub in &self.subs {
write!(f, "sub {:?}+{:?} ", sub.start, sub.n_points)?;
let end = sub.start + sub.n_points;
for v in usize::from(sub.start)..usize::from(end) {
write!(f, "{:?} ", self.point(Vid::from(v)))?;
}
}
Ok(())
}
}
impl Stroke {
pub fn new(join_style: JoinStyle, tol_sq: f32) -> Stroke {
let points = Vec::with_capacity(1024);
let mut subs = Vec::with_capacity(16);
subs.push(SubStroke::new(Vid(0)));
Stroke {
join_style,
tol_sq,
points,
subs,
}
}
fn is_within_tolerance2(&self, a: Pt<f32>, b: Pt<f32>) -> bool {
assert!(self.tol_sq > 0.0);
a.dist_sq(b) <= self.tol_sq
}
fn len(&self) -> usize {
self.subs.len()
}
fn sub_start(&self, i: usize) -> Vid {
self.subs[i].start
}
fn sub_end(&self, i: usize) -> Vid {
let sub = &self.subs[i];
sub.next(sub.start, Dir::Reverse)
}
fn sub_joined(&self, i: usize) -> bool {
self.subs[i].joined
}
fn sub_points(&self, i: usize) -> Vid {
self.subs[i].len()
}
fn sub_current(&mut self) -> &mut SubStroke {
self.subs.last_mut().unwrap()
}
fn sub_add(&mut self) {
let vid = Vid::from(self.points.len());
self.subs.push(SubStroke::new(vid));
}
fn sub_add_point(&mut self) {
let sub = self.sub_current();
sub.n_points += 1;
}
fn sub_at(&self, vid: Vid) -> &SubStroke {
let n_subs = self.subs.len();
for i in 0..n_subs {
let sub = &self.subs[i];
if vid < sub.start + sub.n_points {
return sub;
}
}
unreachable!();
}
fn next(&self, vid: Vid, dir: Dir) -> Vid {
let sub = self.sub_at(vid);
sub.next(vid, dir)
}
fn point(&self, vid: Vid) -> WidePt {
self.points[usize::from(vid)]
}
pub fn add_point(&mut self, pt: WidePt) {
let n_pts = self.points.len();
if n_pts < usize::from(Vid::MAX) {
let done = self.sub_current().done;
if done {
self.sub_add();
}
if done || !self.coincident(pt) {
self.points.push(pt);
self.sub_add_point();
}
}
}
fn coincident(&self, pt: WidePt) -> bool {
if let Some(p) = self.points.last() {
pt.0 == p.0
} else {
false
}
}
pub fn close(&mut self, joined: bool) {
if !self.points.is_empty() {
let sub = self.sub_current();
sub.joined = joined;
sub.done = true;
}
}
pub fn path_ops(&self) -> Vec<PathOp> {
let mut ops = vec![];
let n_subs = self.len();
for i in 0..n_subs {
self.stroke_sub(&mut ops, i);
}
ops
}
fn stroke_sub(&self, ops: &mut Vec<PathOp>, i: usize) {
if self.sub_points(i) > Vid(0) {
let start = self.sub_start(i);
let end = self.sub_end(i);
let joined = self.sub_joined(i);
self.stroke_side(ops, i, start, Dir::Forward);
if joined {
ops.push(PathOp::Close());
}
self.stroke_side(ops, i, end, Dir::Reverse);
ops.push(PathOp::Close());
}
}
fn stroke_side(
&self,
ops: &mut Vec<PathOp>,
i: usize,
start: Vid,
dir: Dir,
) {
let mut xr: Option<(Pt<f32>, Pt<f32>)> = None;
let mut v0 = start;
let mut v1 = self.next(v0, dir);
let joined = self.sub_joined(i);
for _ in 0..usize::from(self.sub_points(i)) {
let p0 = self.point(v0);
let p1 = self.point(v1);
let bounds = self.stroke_offset(p0, p1);
let (pr0, pr1) = bounds;
if let Some((xr0, xr1)) = xr {
self.stroke_join(ops, p0, xr0, xr1, pr0, pr1);
} else if !joined {
self.stroke_point(ops, pr0);
}
xr = Some(bounds);
v0 = v1;
v1 = self.next(v1, dir);
}
if !joined {
if let Some((_, xr1)) = xr {
self.stroke_point(ops, xr1);
}
}
}
fn stroke_offset(&self, p0: WidePt, p1: WidePt) -> (Pt<f32>, Pt<f32>) {
let pp0 = p0.0;
let pp1 = p1.0;
let vr = (pp1 - pp0).right().normalize();
let pr0 = pp0 + vr * (p0.w() / 2.0);
let pr1 = pp1 + vr * (p1.w() / 2.0);
(pr0, pr1)
}
fn stroke_point(&self, ops: &mut Vec<PathOp>, pt: Pt<f32>) {
ops.push(PathOp::Line(pt));
}
fn stroke_join(
&self,
ops: &mut Vec<PathOp>,
p: WidePt,
a0: Pt<f32>,
a1: Pt<f32>,
b0: Pt<f32>,
b1: Pt<f32>,
) {
match self.join_style {
JoinStyle::Miter(ml) => self.stroke_miter(ops, a0, a1, b0, b1, ml),
JoinStyle::Bevel => self.stroke_bevel(ops, a1, b0),
JoinStyle::Round => self.stroke_round(ops, p, a0, a1, b0, b1),
}
}
fn stroke_miter(
&self,
ops: &mut Vec<PathOp>,
a0: Pt<f32>,
a1: Pt<f32>,
b0: Pt<f32>,
b1: Pt<f32>,
ml: f32,
) {
if ml > 0.0 {
let sm_min = 1.0 / ml;
let th = (a1 - a0).angle_rel(b0 - b1);
let sm = (th / 2.0).sin().abs();
if sm >= sm_min && sm < 1.0 {
let lna = Line::new(a0, a1);
let lnb = Line::new(b0, b1);
if let Some(xp) = lna.intersection(lnb) {
self.stroke_point(ops, xp);
return;
}
}
}
self.stroke_bevel(ops, a1, b0);
}
fn stroke_bevel(&self, ops: &mut Vec<PathOp>, a1: Pt<f32>, b0: Pt<f32>) {
self.stroke_point(ops, a1);
self.stroke_point(ops, b0);
}
fn stroke_round(
&self,
ops: &mut Vec<PathOp>,
p: WidePt,
a0: Pt<f32>,
a1: Pt<f32>,
b0: Pt<f32>,
b1: Pt<f32>,
) {
let th = (a1 - a0).angle_rel(b0 - b1);
if th <= 0.0 {
self.stroke_bevel(ops, a1, b0);
} else {
self.stroke_point(ops, a1);
self.stroke_arc(ops, p, a1, b0);
}
}
fn stroke_arc(
&self,
ops: &mut Vec<PathOp>,
p: WidePt,
a: Pt<f32>,
b: Pt<f32>,
) {
let p2 = p.0;
let vr = (b - a).right().normalize();
let c = p2 + vr * (p.w() / 2.0);
let ab = a.midpoint(b);
if self.is_within_tolerance2(c, ab) {
self.stroke_point(ops, b);
} else {
self.stroke_arc(ops, p, a, c);
self.stroke_arc(ops, p, c, b);
}
}
}