use super::{
fountain::Dots,
path::{P, Quad},
};
use crate::{Point, Stroke};
pub(super) fn prepare(s: &Stroke, tolerance: f32) -> Dots {
let rendering = s.rendering.as_ref().unwrap();
let stylus = rendering.tool_type_raw == 2;
let pressure_at = |i| {
if stylus {
(s.pressures[i] as f32).min(1.)
} else {
0.5
}
};
let short_move_threshold = if rendering.tool_type_raw == 1 {
50.
} else {
5.
};
let size = s.pen_width;
let mut previous = P::from(s.points[0]);
let mut midpoint = previous;
let mut width = (size * 0.5) * pressure_at(0);
let mut residual = 0.;
let mut alternate = false;
let mut ratios = [0.; 3];
let mut ratio_count = 0;
let mut dots = Dots {
directions: Some(vec![]),
points: vec![],
radii: vec![],
sample_ends: vec![0; s.points.len()],
};
const REPEAT: f32 = 0.82;
for i in 1..s.points.len() {
let p = P::from(s.points[i]);
let delta = p.sub(previous);
let distance = delta.len();
if distance >= tolerance {
alternate = if distance < short_move_threshold {
!alternate
} else {
true
};
if alternate {
let ratio = delta.y / distance;
ratios[ratio_count % 3] = ratio;
if ratio_count == 0 {
ratios.fill(ratio);
}
ratio_count += 1;
let ratio = ((ratios[0] + ratios[1]) + ratios[2]) / 3.;
let pressure = pressure_at(i);
let tilt = if stylus {
s.tilts.get(i).copied().unwrap_or(0.) as f32
} else {
0.8
};
let degrees = ((tilt * 180.) as f64 / std::f64::consts::PI) as f32;
let tilt = (degrees.min(75.) - 15.).max(0.) / 60. * 3.;
let raw = size / 3. + (tilt.mul_add(0.5, (pressure + pressure) * 0.5) * size) * 0.5;
let target = (((ratio * raw) as f64).mul_add(0.8, raw as f64) * 0.5) as f32;
let step = size / if ratio > 0. { 4. } else { 2. };
let limited = if (width - target).abs() > step {
width + if width > target { -step } else { step }
} else {
target
};
let target = ((pressure * 0.7) * size).max(limited).max(size / 3.);
let next_mid = previous.mid(p);
let q = Quad::new(midpoint, previous, next_mid);
if q.length > 0. && q.length >= residual {
let count = ((q.length - residual) / REPEAT) as usize + 1;
let increment = (target - width) / count as f32;
let mut d = residual;
let mut w = width;
while d <= q.length {
w += increment;
emit(&mut dots, q.at(d), w * 0.5, Some(q.normalized_tangent(d)));
d += REPEAT;
}
residual = d - q.length;
previous = p;
midpoint = next_mid;
width = target;
} else if q.length == 0. {
midpoint = next_mid;
}
}
}
dots.sample_ends[i] = dots.points.len();
}
let last = s.points.len() - 1;
if dots.points.is_empty() {
emit(&mut dots, previous, size * 0.25, None);
} else {
let p = P::from(s.points[last]);
let q = Quad::new(midpoint, previous, p);
let mut d = residual;
while q.length > 0. && d <= q.length {
emit(
&mut dots,
q.at(d),
width * 0.5,
Some(q.normalized_tangent(d)),
);
d += REPEAT;
}
emit(&mut dots, p, width * 0.5, None);
}
dots.sample_ends[last] = dots.points.len();
if rendering.properties.fixed_width {
let fixed_radius = (rendering.style.fixed_width.unwrap() * 0.5).max(0.1);
dots.radii.fill(fixed_radius as f64);
}
dots
}
fn emit(dots: &mut Dots, p: P, radius: f32, tangent: Option<P>) {
let directions = dots.directions.as_mut().unwrap();
let fallback = Point { x: 0., y: 1. };
let direction = tangent.map_or_else(
|| directions.last().copied().unwrap_or(fallback),
|v| {
if v.x == 0. && v.y == 0. {
fallback
} else {
Point {
x: v.x as f64,
y: v.y as f64,
}
}
},
);
directions.push(direction);
dots.points.push(Point {
x: p.x as f64,
y: p.y as f64,
});
dots.radii.push(radius.max(0.1) as f64);
}