use crate::geom::{intersection, Vec2, Vec2w};
use crate::path::PathOp;
use std::fmt;
type Vid = u16;
#[derive(Clone, Copy, Debug)]
pub enum JoinStyle {
Miter(f32),
Bevel,
Round,
}
#[derive(Clone, Copy, PartialEq, Debug)]
enum Dir {
Forward,
Reverse,
}
struct SubStroke {
start: Vid,
n_points: Vid,
joined: bool,
done: bool,
}
pub struct Stroke {
join_style: JoinStyle,
tol_sq: f32,
points: Vec<Vec2w>,
subs: Vec<SubStroke>,
}
impl SubStroke {
fn new(start: Vid) -> SubStroke {
SubStroke {
start,
n_points: 0 as Vid,
joined: false,
done: false,
}
}
fn next(&self, vid: Vid, dir: Dir) -> Vid {
match dir {
Dir::Forward => {
let v = vid + 1 as Vid;
if v < self.start + self.n_points {
v
} else {
self.start
}
}
Dir::Reverse => {
if vid > self.start {
vid - 1 as Vid
} else {
self.start + self.n_points - 1 as Vid
}
}
}
}
fn count(&self) -> Vid {
if self.joined {
self.n_points + 1 as Vid
} else if self.n_points > 0 {
self.n_points - 1 as Vid
} else {
0 as Vid
}
}
}
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)?;
for v in sub.start..(sub.start + sub.n_points) {
write!(f, "{:?} ", self.get_point(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(0 as Vid));
Stroke {
join_style,
tol_sq,
points,
subs,
}
}
fn is_within_tolerance2(&self, a: Vec2, b: Vec2) -> bool {
assert!(self.tol_sq > 0f32);
a.dist_sq(b) <= self.tol_sq
}
fn sub_count(&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].count()
}
fn sub_current(&mut self) -> &mut SubStroke {
self.subs.last_mut().unwrap()
}
fn sub_add(&mut self) {
let vid = self.points.len() as Vid;
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 get_point(&self, vid: Vid) -> Vec2w {
self.points[vid as usize]
}
pub fn add_point(&mut self, pt: Vec2w) {
let n_pts = self.points.len();
if n_pts < Vid::max_value() as usize {
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: Vec2w) -> bool {
if let Some(p) = self.points.last() {
pt.v == p.v
} 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.sub_count();
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) > 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<(Vec2, Vec2)> = None;
let mut v0 = start;
let mut v1 = self.next(v0, dir);
let joined = self.sub_joined(i);
for _ in 0..self.sub_points(i) {
let p0 = self.get_point(v0);
let p1 = self.get_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: Vec2w, p1: Vec2w) -> (Vec2, Vec2) {
let pp0 = p0.v;
let pp1 = p1.v;
let vr = (pp1 - pp0).right().normalize();
let pr0 = pp0 + vr * (p0.w / 2f32);
let pr1 = pp1 + vr * (p1.w / 2f32);
(pr0, pr1)
}
fn stroke_point(&self, ops: &mut Vec<PathOp>, pt: Vec2) {
ops.push(PathOp::Line(pt.x, pt.y));
}
fn stroke_join(
&self,
ops: &mut Vec<PathOp>,
p: Vec2w,
a0: Vec2,
a1: Vec2,
b0: Vec2,
b1: Vec2,
) {
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: Vec2,
a1: Vec2,
b0: Vec2,
b1: Vec2,
ml: f32,
) {
if ml > 0f32 {
let sm_min = 1f32 / ml;
let th = (a1 - a0).angle_rel(b0 - b1);
let sm = (th / 2f32).sin().abs();
if sm >= sm_min && sm < 1f32 {
if let Some(xp) = intersection(a0, a1, b0, b1) {
self.stroke_point(ops, xp);
return;
}
}
}
self.stroke_bevel(ops, a1, b0);
}
fn stroke_bevel(&self, ops: &mut Vec<PathOp>, a1: Vec2, b0: Vec2) {
self.stroke_point(ops, a1);
self.stroke_point(ops, b0);
}
fn stroke_round(
&self,
ops: &mut Vec<PathOp>,
p: Vec2w,
a0: Vec2,
a1: Vec2,
b0: Vec2,
b1: Vec2,
) {
let th = (a1 - a0).angle_rel(b0 - b1);
if th <= 0f32 {
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: Vec2w, a: Vec2, b: Vec2) {
let p2 = p.v;
let vr = (b - a).right().normalize();
let c = p2 + vr * (p.w / 2f32);
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);
}
}
}