use crate::color::Color;
use crate::geom::{Rect, Vec2};
use crate::retain::Kept;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct LineId(pub u32);
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Stroke {
pub width: f32,
pub color: Color,
pub curve: bool,
pub dash: Dash,
}
impl Stroke {
pub fn new(width: f32, color: Color) -> Self {
Self {
width,
color,
curve: false,
dash: Dash::SOLID,
}
}
pub fn curve(mut self) -> Self {
self.curve = true;
self
}
pub fn dash(mut self, on: f32, off: f32) -> Self {
self.dash = Dash {
offset: self.dash.offset,
..Dash::new(on, off)
};
self
}
pub fn dashed(mut self, dash: Dash) -> Self {
self.dash = dash;
self
}
pub fn dash_offset(mut self, offset: f32) -> Self {
self.dash.offset = offset;
self
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Dash {
pub pattern: [f32; 4],
pub offset: f32,
}
impl Default for Dash {
fn default() -> Self {
Dash::SOLID
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Cut {
pub lens: [f32; 4],
pub offset: f32,
}
impl Cut {
pub fn period(&self) -> f32 {
self.lens.iter().sum()
}
pub fn finer_than(&self, min: f32) -> bool {
let pair = self.period() * 0.5;
pair.is_nan() || pair < min
}
pub(crate) fn hash(&self) -> u64 {
let [a, b, c, d] = self.lens.map(|l| u64::from(l.to_bits()));
let mut h = a ^ b.rotate_left(16) ^ c.rotate_left(32) ^ d.rotate_left(48);
h = h.wrapping_mul(0x0000_0100_0000_01b3) ^ u64::from(self.offset.to_bits());
h.wrapping_mul(0x0000_0100_0000_01b3)
}
}
pub const MAX_MARKS: usize = 16384;
impl Dash {
pub const SOLID: Dash = Dash {
pattern: [0.0; 4],
offset: 0.0,
};
pub fn new(on: f32, off: f32) -> Self {
Dash {
pattern: [on, off, on, off],
offset: 0.0,
}
}
pub fn of(lengths: &[f32]) -> Option<Self> {
let pattern = match *lengths {
[a] => [a, a, a, a],
[a, b] => [a, b, a, b],
[a, b, c, d] => [a, b, c, d],
_ => return None,
};
Some(Dash {
pattern,
offset: 0.0,
})
}
pub fn offset(mut self, offset: f32) -> Self {
self.offset = offset;
self
}
pub fn is_solid(&self) -> bool {
self.cut(0.0).is_none()
}
#[inline]
pub fn cut(&self, width: f32) -> Option<Cut> {
if self.pattern == Dash::SOLID.pattern {
return None;
}
self.cut_pattern(width)
}
#[cold]
#[inline(never)]
fn cut_pattern(&self, width: f32) -> Option<Cut> {
if !self.pattern.iter().all(|l| l.is_finite()) || !self.offset.is_finite() {
return None;
}
let [a, b, c, d] = self.pattern.map(|l| l.max(0.0));
if b <= 0.0 && d <= 0.0 {
return None;
}
let (first, second) = match (a + b > 0.0, c + d > 0.0) {
(true, true) => ([a, b], [c, d]),
(true, false) => ([a, b], [a, b]),
(false, _) => ([c, d], [c, d]),
};
let w = width.max(0.0);
let centre = |[on, off]: [f32; 2]| {
let mark = (on - w).max(0.0);
[mark, on + off - mark]
};
let ([m0, g0], [m1, g1]) = (centre(first), centre(second));
let period = m0 + g0 + m1 + g1;
let (lens, from) = match (g0 > w, g1 > w) {
(true, true) => ([m0, g0, m1, g1], 0.0),
(false, true) => {
let m = m0 + g0 + m1;
([m, g1, m, g1], 0.0)
}
(true, false) => {
let m = m1 + g1 + m0;
([m, g0, m, g0], m0 + g0)
}
(false, false) => return None,
};
Some(Cut {
lens,
offset: (self.offset - from).rem_euclid(period),
})
}
}
impl Cut {
pub fn marks(&self, points: &[Vec2], min: f32, mut mark: impl FnMut(Vec2, Vec2)) -> bool {
let period = self.period();
let piece = |pair: &[Vec2]| (pair[1].x - pair[0].x).hypot(pair[1].y - pair[0].y);
let total: f32 = points.windows(2).map(piece).sum();
let marks = total / period * 2.0;
if self.finer_than(min) || marks.is_nan() || marks > MAX_MARKS as f32 || total <= 0.0 {
return false;
}
let (mut i, mut left) = (0, self.lens[0]);
let mut skip = self.offset;
while skip >= left && skip > 0.0 {
skip -= left;
i = (i + 1) & 3;
left = self.lens[i];
}
left -= skip;
for pair in points.windows(2) {
let len = piece(pair);
if len <= 0.0 {
continue;
}
let (a, b) = (pair[0], pair[1]);
let at = |s: f32| {
let t = s / len;
Vec2::new(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t)
};
let mut pos = 0.0;
loop {
let take = left.min(len - pos);
if i & 1 == 0 {
mark(at(pos), at(pos + take));
}
pos += take;
left -= take;
if left > 0.0 {
break;
}
i = (i + 1) & 3;
left = self.lens[i];
if pos >= len && left > 0.0 {
break;
}
}
}
true
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct Run {
pub first: u32,
pub len: u32,
pub width: f32,
pub dash: Option<Cut>,
}
#[derive(Default)]
pub struct LineStore {
runs: Kept<Run>,
points: Kept<Vec2>,
}
pub(crate) fn pad(width: f32) -> f32 {
width.max(0.0) * 0.5 + 2.0
}
pub const CURVE_STEP: f32 = 6.0;
pub const CURVE_MAX_PIECES: usize = 32;
impl LineStore {
pub(crate) fn begin_frame(&mut self, keep_prev: bool) {
self.runs.begin(keep_prev);
self.points.begin(keep_prev);
}
pub(crate) fn push(&mut self, points: &[Vec2], stroke: &Stroke) -> Option<(LineId, Rect)> {
if points.len() < 2 {
return None;
}
let first = self.points.len();
if stroke.curve && points.len() > 2 {
flatten_curve(points, &mut self.points);
} else {
self.points.extend_from_slice(points);
}
let run = &mut self.points[first..];
let (mut min, mut max) = (run[0], run[0]);
for p in run.iter() {
min.x = min.x.min(p.x);
min.y = min.y.min(p.y);
max.x = max.x.max(p.x);
max.y = max.y.max(p.y);
}
let pad = pad(stroke.width);
let origin = Vec2::new(min.x - pad, min.y - pad);
for p in run.iter_mut() {
p.x -= origin.x;
p.y -= origin.y;
}
let rect = Rect::new(
origin.x,
origin.y,
max.x - min.x + 2.0 * pad,
max.y - min.y + 2.0 * pad,
);
let id = LineId(self.runs.len() as u32);
self.runs.push(Run {
first: first as u32,
len: (self.points.len() - first) as u32,
width: stroke.width,
dash: stroke.dash.cut(stroke.width),
});
Some((id, rect))
}
pub(crate) fn run(&self, id: LineId) -> (Run, &[Vec2]) {
let run = self.runs[id.0 as usize];
(
run,
&self.points[run.first as usize..(run.first + run.len) as usize],
)
}
pub(crate) fn prev_run(&self, id: LineId) -> (Run, &[Vec2]) {
match self.runs.prev().get(id.0 as usize) {
Some(run) => (
*run,
&self.points.prev()[run.first as usize..(run.first + run.len) as usize],
),
None => (
Run {
first: 0,
len: 0,
width: 0.0,
dash: None,
},
&[],
),
}
}
pub fn len(&self) -> usize {
self.runs.len()
}
pub fn is_empty(&self) -> bool {
self.runs.is_empty()
}
}
pub fn flatten_curve(knots: &[Vec2], out: &mut Vec<Vec2>) {
let n = knots.len();
if n < 2 {
return;
}
out.push(knots[0]);
let (mut prev, mut prev_step) = (0.0, 0.0);
let (mut chord, mut step) = chord_and_step(knots[0], knots[1]);
for i in 0..n - 1 {
let (p1, p2) = (knots[i], knots[i + 1]);
let (next, next_step) = match knots.get(i + 2) {
Some(&p) => chord_and_step(p2, p),
None => (0.0, 0.0),
};
let pieces = ((chord / CURVE_STEP).ceil() as usize).clamp(1, CURVE_MAX_PIECES);
if chord == 0.0 {
out.push(p2);
} else {
let mirror = |p: Vec2, q: Vec2| Vec2::new(2.0 * p.x - q.x, 2.0 * p.y - q.y);
let (p0, s0) = match prev > 0.0 {
true => (knots[i - 1], prev_step),
false => (mirror(p1, p2), step),
};
let (p3, s2) = match next > 0.0 {
true => (knots[i + 2], next_step),
false => (mirror(p2, p1), step),
};
let span = Span::new([p0, p1, p2, p3], [0.0, s0, s0 + step, s0 + step + s2]);
for k in 1..pieces {
out.push(span.at(s0 + step * (k as f32 / pieces as f32)));
}
out.push(p2);
}
(prev, prev_step) = (chord, step);
(chord, step) = (next, next_step);
}
}
fn chord_and_step(a: Vec2, b: Vec2) -> (f32, f32) {
let chord = ((b.x - a.x).powi(2) + (b.y - a.y).powi(2)).sqrt();
(chord, chord.sqrt())
}
struct Span {
p: [Vec2; 4],
t: [f32; 4],
inv: [f32; 5],
}
impl Span {
fn new(p: [Vec2; 4], t: [f32; 4]) -> Self {
let inv = [
1.0 / (t[1] - t[0]),
1.0 / (t[2] - t[1]),
1.0 / (t[3] - t[2]),
1.0 / (t[2] - t[0]),
1.0 / (t[3] - t[1]),
];
Span { p, t, inv }
}
fn at(&self, u: f32) -> Vec2 {
let (p, t) = (&self.p, &self.t);
let blend = |a: Vec2, b: Vec2, ta: f32, inv: f32| {
let w = (u - ta) * inv;
Vec2::new(a.x + (b.x - a.x) * w, a.y + (b.y - a.y) * w)
};
let a1 = blend(p[0], p[1], t[0], self.inv[0]);
let a2 = blend(p[1], p[2], t[1], self.inv[1]);
let a3 = blend(p[2], p[3], t[2], self.inv[2]);
let b1 = blend(a1, a2, t[0], self.inv[3]);
let b2 = blend(a2, a3, t[1], self.inv[4]);
blend(b1, b2, t[1], self.inv[1])
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_curve_passes_through_its_knots_and_ends_where_they_end() {
let knots = [
Vec2::new(0.0, 0.0),
Vec2::new(30.0, 40.0),
Vec2::new(60.0, 0.0),
];
let mut out = Vec::new();
flatten_curve(&knots, &mut out);
assert_eq!(out[0], knots[0]);
assert_eq!(*out.last().unwrap(), knots[2]);
assert_eq!(out.len(), 19);
assert_eq!(out[9], knots[1]);
}
fn worst_turn(pts: &[Vec2]) -> f32 {
pts.windows(3).fold(0.0f32, |worst, w| {
let (a, b) = (
Vec2::new(w[1].x - w[0].x, w[1].y - w[0].y),
Vec2::new(w[2].x - w[1].x, w[2].y - w[1].y),
);
let len = |v: Vec2| (v.x * v.x + v.y * v.y).sqrt();
let (la, lb) = (len(a), len(b));
if la < 1e-6 || lb < 1e-6 {
return worst;
}
let cos = ((a.x * b.x + a.y * b.y) / (la * lb)).clamp(-1.0, 1.0);
worst.max(cos.acos().to_degrees())
})
}
fn ties_a_loop(pts: &[Vec2]) -> bool {
let side =
|a: Vec2, b: Vec2, c: Vec2| (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
(0..pts.len() - 1).any(|i| {
(i + 2..pts.len() - 1).any(|j| {
let (a, b, c, d) = (pts[i], pts[i + 1], pts[j], pts[j + 1]);
side(a, b, c) * side(a, b, d) < 0.0 && side(c, d, a) * side(c, d, b) < 0.0
})
})
}
#[test]
fn a_tight_knot_between_two_long_ones_neither_loops_nor_cusps() {
let knots = [
Vec2::new(0.0, 400.0),
Vec2::new(600.0, 100.0),
Vec2::new(630.0, 80.0),
Vec2::new(560.0, 400.0),
];
let mut out = Vec::new();
flatten_curve(&knots, &mut out);
assert!(!ties_a_loop(&out), "the run crosses itself");
assert!(worst_turn(&out) < 45.0, "cusp: {:.1}°", worst_turn(&out));
}
#[test]
fn a_repeated_knot_is_a_kink_and_not_a_division_by_zero() {
let knots = [
Vec2::new(0.0, 0.0),
Vec2::new(40.0, 0.0),
Vec2::new(40.0, 0.0),
Vec2::new(40.0, 40.0),
Vec2::new(80.0, 40.0),
];
let mut out = Vec::new();
flatten_curve(&knots, &mut out);
assert!(out.iter().all(|p| p.x.is_finite() && p.y.is_finite()));
assert_eq!(out[0], knots[0]);
assert_eq!(*out.last().unwrap(), knots[4]);
}
#[test]
fn two_points_are_one_segment_even_as_a_curve() {
let mut store = LineStore::default();
let (id, rect) = store
.push(
&[Vec2::new(10.0, 10.0), Vec2::new(50.0, 40.0)],
&Stroke::new(2.0, Color::WHITE).curve(),
)
.unwrap();
let (run, pts) = store.run(id);
assert_eq!(run.len, 2);
assert_eq!(run.width, 2.0);
assert_eq!((rect.x, rect.y, rect.w, rect.h), (7.0, 7.0, 46.0, 36.0));
assert_eq!(pts[0], Vec2::new(3.0, 3.0));
assert_eq!(pts[1], Vec2::new(43.0, 33.0));
}
#[test]
fn one_point_draws_nothing() {
let mut store = LineStore::default();
assert!(
store
.push(&[Vec2::new(1.0, 1.0)], &Stroke::new(1.0, Color::WHITE))
.is_none()
);
}
#[test]
fn the_previous_frame_is_kept_only_when_asked() {
let mut store = LineStore::default();
let (id, _) = store
.push(
&[Vec2::new(0.0, 0.0), Vec2::new(4.0, 0.0)],
&Stroke::new(1.0, Color::WHITE),
)
.unwrap();
store.begin_frame(true);
assert_eq!(store.prev_run(id).0.len, 2);
assert!(store.is_empty());
store.begin_frame(false);
assert_eq!(store.prev_run(id).0.len, 0);
}
}