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,
}
impl Stroke {
pub fn new(width: f32, color: Color) -> Self {
Self {
width,
color,
curve: false,
}
}
pub fn curve(mut self) -> Self {
self.curve = true;
self
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct Run {
pub first: u32,
pub len: u32,
pub width: f32,
}
#[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,
});
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,
},
&[],
),
}
}
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);
}
}