use crate::get_stroke_radius::get_stroke_radius;
use crate::types::{StrokeOptions, StrokePoint};
use crate::vec::{add, dist2, dpr, mul, neg, per, rot_around};
use std::f64::consts::PI;
const RATE_OF_PRESSURE_CHANGE: f64 = 0.275;
const FIXED_PI: f64 = PI + 0.0001;
pub fn get_stroke_outline_points(
points: &[StrokePoint],
options: &StrokeOptions,
) -> Vec<[f64; 2]> {
let size = options.size.unwrap_or(16.0);
let smoothing = options.smoothing.unwrap_or(0.5);
let thinning = options.thinning.unwrap_or(0.5);
let simulate_pressure = options.simulate_pressure.unwrap_or(true);
let _is_complete = options.last.unwrap_or(false);
let easing_fn = options.easing.unwrap_or(|t| t);
let start_options = options.start.clone().unwrap_or_default();
let end_options = options.end.clone().unwrap_or_default();
let cap_start = start_options.cap.unwrap_or(true);
let cap_end = end_options.cap.unwrap_or(true);
let taper_start_ease = start_options.easing.unwrap_or(|t| t * (2.0 - t));
let taper_end_ease = end_options.easing.unwrap_or(|t| 1.0 - (1.0 - t).powi(3));
if points.is_empty() || size <= 0.0 {
return vec![];
}
let total_length = points.last().map(|p| p.running_length).unwrap_or(0.0);
let min_distance = (size * smoothing).powi(2);
let mut left_pts: Vec<[f64; 2]> = Vec::new();
let mut right_pts: Vec<[f64; 2]> = Vec::new();
let mut prev_pressure = points.iter().take(10).fold(points[0].pressure, |acc, curr| {
let mut pressure = curr.pressure;
if simulate_pressure {
let sp = f64::min(1.0, curr.distance / size);
let rp = f64::min(1.0, 1.0 - sp);
pressure = f64::min(1.0, acc + (rp - acc) * (sp * RATE_OF_PRESSURE_CHANGE));
}
(acc + pressure) / 2.0
});
let first_point_radius = if thinning > 0.0 {
get_stroke_radius(size, thinning, points[0].pressure, Some(easing_fn))
} else {
size / 2.0
};
let mut prev_vector = points[0].vector;
let mut pl = points[0].point;
let mut pr = pl;
let mut is_prev_sharp_corner = false;
let taper_start = match &start_options.taper {
Some(taper) => match taper {
crate::types::TaperType::Bool(false) => 0.0,
crate::types::TaperType::Bool(true) => f64::max(size, total_length),
crate::types::TaperType::Number(value) => *value,
},
None => 0.0,
};
let taper_end = match &end_options.taper {
Some(taper) => match taper {
crate::types::TaperType::Bool(false) => 0.0,
crate::types::TaperType::Bool(true) => f64::max(size, total_length),
crate::types::TaperType::Number(value) => *value,
},
None => 0.0,
};
for (i, curr) in points.iter().enumerate() {
if i == 0 {
continue;
}
let point = curr.point;
let vector = curr.vector;
let distance = curr.distance;
let running_length = curr.running_length;
let mut pressure = curr.pressure;
if thinning > 0.0 && simulate_pressure {
let sp = f64::min(1.0, distance / size);
let rp = f64::min(1.0, 1.0 - sp);
pressure = f64::min(
1.0,
prev_pressure + (rp - prev_pressure) * (sp * RATE_OF_PRESSURE_CHANGE),
);
}
prev_pressure = pressure;
let radius = if thinning > 0.0 {
get_stroke_radius(size, thinning, pressure, Some(easing_fn))
} else {
size / 2.0
};
let ts = if running_length < taper_start {
taper_start_ease(running_length / taper_start)
} else {
1.0
};
let te = if total_length - running_length < taper_end {
taper_end_ease((total_length - running_length) / taper_end)
} else {
1.0
};
let radius = f64::max(0.01, radius * f64::min(ts, te));
let normal_vector = per(vector);
let offset_vector = mul(normal_vector, radius);
let left_point = add(point, offset_vector);
let right_point = add(point, neg(offset_vector));
let is_sharp_corner = dpr(prev_vector, vector) < 0.0;
if is_sharp_corner && !is_prev_sharp_corner {
if dist2(left_point, pl) > min_distance {
left_pts.push(left_point);
pl = left_point;
}
if dist2(right_point, pr) > min_distance {
right_pts.push(right_point);
pr = right_point;
}
} else {
if !is_prev_sharp_corner {
let prev_normal = per(prev_vector);
let offset_a = mul(prev_normal, radius);
let tl = add(point, offset_a);
let tr = add(point, neg(offset_a));
if dist2(pl, tl) > min_distance {
left_pts.push(tl);
pl = tl;
}
if dist2(pr, tr) > min_distance {
right_pts.push(tr);
pr = tr;
}
}
if dist2(pl, left_point) > min_distance {
left_pts.push(left_point);
pl = left_point;
}
if dist2(pr, right_point) > min_distance {
right_pts.push(right_point);
pr = right_point;
}
}
prev_vector = vector;
is_prev_sharp_corner = is_sharp_corner;
}
let mut result = Vec::new();
let close_path = options.closed.unwrap_or(false);
if cap_start {
let first_point = points[0].point;
let first_normal = per(points[0].vector);
let offset_vector = mul(first_normal, first_point_radius);
let start_left = add(first_point, offset_vector);
let start_right = add(first_point, neg(offset_vector));
result.push(start_left);
if points.len() > 1 {
let steps = 4;
for i in 0..=steps {
let t = i as f64 / steps as f64;
let angle = FIXED_PI - t * FIXED_PI;
result.push(rot_around(start_right, first_point, angle));
}
} else {
result.push(start_right);
}
}
for p in right_pts.iter() {
result.push(*p);
}
if cap_end && !right_pts.is_empty() {
let last_point = points.last().map(|p| p.point).unwrap_or_default();
let last_vector = points.last().map(|p| p.vector).unwrap_or_default();
let last_normal = per(last_vector);
let last_radius = if points.len() > 1 {
let last_pressure = points.last().map(|p| p.pressure).unwrap_or(0.5);
if thinning > 0.0 {
get_stroke_radius(size, thinning, last_pressure, Some(easing_fn))
} else {
size / 2.0
}
} else {
first_point_radius
};
let tapered_radius = if taper_end > 0.0 {
0.01
} else {
last_radius
};
let offset_vector = mul(last_normal, tapered_radius);
let end_right = add(last_point, neg(offset_vector));
let end_left = add(last_point, offset_vector);
if points.len() > 1 {
let steps = 4;
for i in 0..=steps {
let t = i as f64 / steps as f64;
result.push(rot_around(end_right, last_point, t * FIXED_PI));
}
} else {
result.push(end_left);
}
}
for p in left_pts.iter().rev() {
result.push(*p);
}
if close_path && !result.is_empty() && result.len() > 1 {
result.push(result[0]);
}
else if !result.is_empty() && result.len() > 1 && result[0] != result[result.len() - 1] {
result.push(result[0]);
}
result
}