use crate::color::{lerp_color, Color, ColorSpace};
use crate::geometry::{Point, Rect};
use crate::plot::projection::InteriorSample;
use super::GeomContext;
#[derive(Clone, Copy)]
pub(crate) struct VertexAttrs {
pub half_width_px: f64,
pub color: Color,
}
pub(crate) struct PathVertex {
pub frac: [f64; 2],
pub offset_px: (f64, f64),
pub attrs: Option<VertexAttrs>,
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum GapPolicy {
Skip,
Split,
}
#[derive(Default)]
pub(crate) struct VertexRun {
pub points: Vec<Point>,
pub widths: Vec<f64>,
pub colors: Vec<Color>,
}
pub(crate) struct PathOptions {
pub gap: GapPolicy,
pub closing: bool,
pub min_run_len: usize,
pub color_space: ColorSpace,
}
impl PathOptions {
pub(crate) fn path(min_run_len: usize) -> Self {
PathOptions {
gap: GapPolicy::Skip,
closing: false,
min_run_len,
color_space: ColorSpace::default(),
}
}
pub(crate) fn ring(min_run_len: usize) -> Self {
PathOptions {
closing: true,
..PathOptions::path(min_run_len)
}
}
pub(crate) fn split(min_run_len: usize) -> Self {
PathOptions {
gap: GapPolicy::Split,
..PathOptions::path(min_run_len)
}
}
pub(crate) fn with_color_space(mut self, space: ColorSpace) -> Self {
self.color_space = space;
self
}
}
pub(crate) fn project_and_densify<F>(
ctx: &GeomContext<'_>,
count: usize,
opts: &PathOptions,
mut vertex: F,
) -> Vec<VertexRun>
where
F: FnMut(usize) -> PathVertex,
{
let panel = ctx.panel_rect;
let is_linear = ctx.projection.is_linear();
let mut runs: Vec<VertexRun> = Vec::new();
let mut run = VertexRun::default();
let mut samples: Vec<InteriorSample> = Vec::new();
let mut prev: Option<([f64; 2], Option<VertexAttrs>)> = None;
let mut first: Option<([f64; 2], Option<VertexAttrs>)> = None;
for k in 0..count {
let v = vertex(k);
let Some(pt) = project_vertex(ctx, panel, &v) else {
if opts.gap == GapPolicy::Split {
flush_run(&mut runs, &mut run, opts.min_run_len);
prev = None;
}
continue;
};
if !is_linear {
if let Some((prev_frac, prev_attrs)) = prev {
samples.clear();
ctx.projection
.interpolate_segment_with_t(panel, &prev_frac, &v.frac, &mut samples);
push_interior(&mut run, &samples, prev_attrs, v.attrs, opts.color_space);
}
}
run.points.push(pt);
if let Some(a) = v.attrs {
run.widths.push(a.half_width_px);
run.colors.push(a.color);
}
if first.is_none() {
first = Some((v.frac, v.attrs));
}
prev = Some((v.frac, v.attrs));
}
if opts.closing && !is_linear {
if let (Some((prev_frac, prev_attrs)), Some((first_frac, first_attrs))) = (prev, first) {
if prev_frac != first_frac {
samples.clear();
ctx.projection.interpolate_closing_segment_with_t(
panel,
&prev_frac,
&first_frac,
&mut samples,
);
push_interior(
&mut run,
&samples,
prev_attrs,
first_attrs,
opts.color_space,
);
}
}
}
flush_run(&mut runs, &mut run, opts.min_run_len);
runs
}
pub(crate) fn project_and_densify_one<F>(
ctx: &GeomContext<'_>,
count: usize,
opts: &PathOptions,
vertex: F,
) -> VertexRun
where
F: FnMut(usize) -> PathVertex,
{
project_and_densify(ctx, count, opts, vertex)
.into_iter()
.next()
.unwrap_or_default()
}
fn project_vertex(ctx: &GeomContext<'_>, panel: Rect, v: &PathVertex) -> Option<Point> {
if !v.frac[0].is_finite() || !v.frac[1].is_finite() {
return None;
}
let (px, py) = ctx.projection.project_to_panel_px(panel, &v.frac);
let pt = Point::new(px + v.offset_px.0, py - v.offset_px.1);
(pt.x.is_finite() && pt.y.is_finite()).then_some(pt)
}
fn push_interior(
run: &mut VertexRun,
samples: &[InteriorSample],
from: Option<VertexAttrs>,
to: Option<VertexAttrs>,
space: ColorSpace,
) {
match (from, to) {
(Some(a), Some(b)) => {
for s in samples {
run.points.push(Point::new(s.px, s.py));
run.widths
.push(a.half_width_px + s.t * (b.half_width_px - a.half_width_px));
run.colors.push(lerp_color(a.color, b.color, s.t, space));
}
}
_ => {
for s in samples {
run.points.push(Point::new(s.px, s.py));
}
}
}
}
fn flush_run(runs: &mut Vec<VertexRun>, run: &mut VertexRun, min_run_len: usize) {
if run.points.len() >= min_run_len {
runs.push(std::mem::take(run));
} else {
run.points.clear();
run.widths.clear();
run.colors.clear();
}
}