use crate::types::{BezierSegment, EncodedScaledBezierChain, ScaledBezierChain};
use crate::utils::decode;
pub fn query_curve(curve: &ScaledBezierChain, scaled_x: f64) -> Option<f64> {
if curve.len() < 8 {
return None;
}
let scale_x = curve[0];
let scale_y = curve[1];
let offset_x = curve[2];
let offset_y = curve[3];
if scale_x == 0.0 || scale_y == 0.0 {
panic!("Scale factors cannot be 0");
}
let x = (scaled_x / scale_x) - offset_x;
let first = 4;
let last = curve.len() - 1;
if x < curve[first] || x > curve[last - 1] {
return None;
}
for i in (first..curve.len()).step_by(6) {
if (curve[i] - x).abs() < 1e-15 {
return Some(to_external_coordinate(curve[i + 1], scale_y, offset_y));
}
}
let mut segment_start_index = None;
for i in (first..curve.len() - 7).step_by(6) {
let start_x = curve[i];
let end_x = curve[i + 6];
if x >= start_x && x <= end_x {
segment_start_index = Some(i);
break;
}
}
let segment_start_index = segment_start_index?;
let segment_slice = &curve[segment_start_index..segment_start_index + 8];
let segment: BezierSegment = segment_slice.try_into().unwrap();
for attempts in 0..10 {
let tweak = 0.0001 * (attempts as f64);
let adjusted_x = if x >= 1.0 { x - tweak } else { x + tweak };
let mut t = get_t_at_x(&segment, adjusted_x);
if t.is_none() {
t = get_t_at_x_alternative(&segment, adjusted_x, 1e-6, 100);
}
if let Some(t_value) = t {
let point = get_point_on_curve_at_t(&segment, t_value);
let y = if point[1].abs() < 1e-15 {
0.0
} else {
point[1]
}; return Some(to_external_coordinate(y, scale_y, offset_y));
}
}
None
}
fn to_external_coordinate(value: f64, scale_y: f64, offset_y: f64) -> f64 {
let scaled = (value + offset_y) * scale_y;
if scaled == -0.0 {
0.0
} else {
scaled
}
}
fn get_point_on_curve_at_t(segment: &BezierSegment, t: f64) -> [f64; 2] {
let mt = 1.0 - t;
let mt2 = mt * mt;
let t2 = t * t;
let a = mt2 * mt;
let b = mt2 * t * 3.0;
let c = mt * t2 * 3.0;
let d = t * t2;
let x = a * segment[0] + b * segment[2] + c * segment[4] + d * segment[6];
let y = a * segment[1] + b * segment[3] + c * segment[5] + d * segment[7];
[x, y]
}
fn get_derivative_at_t(segment: &BezierSegment, t: f64) -> [f64; 2] {
let mt = 1.0 - t;
let a = -3.0 * mt * mt;
let b = 3.0 * mt * (mt - 2.0 * t);
let c = 3.0 * t * (2.0 * mt - t);
let d = 3.0 * t * t;
let x = a * segment[0] + b * segment[2] + c * segment[4] + d * segment[6];
let y = a * segment[1] + b * segment[3] + c * segment[5] + d * segment[7];
[x, y]
}
fn get_t_at_x(segment: &BezierSegment, x: f64) -> Option<f64> {
let mut t = 0.5;
let mut iteration_count = 0;
loop {
let point = get_point_on_curve_at_t(segment, t);
let derivative = get_derivative_at_t(segment, t);
let x_at_t = point[0];
let x_derivative_at_t = derivative[0];
let x_difference = x - x_at_t;
if x_derivative_at_t.abs() > 1e-6 {
t += x_difference / x_derivative_at_t;
}
t = t.clamp(0.0, 1.0);
iteration_count += 1;
if x_difference.abs() <= 1e-6 {
return Some(t);
}
if iteration_count > 15 {
return None;
}
}
}
fn get_t_at_x_alternative(
segment: &BezierSegment,
x: f64,
tolerance: f64,
max_iterations: usize,
) -> Option<f64> {
let mut a = 0.0;
let mut b = 1.0;
let mut t;
for _ in 0..max_iterations {
t = (a + b) / 2.0;
let x_at_t = get_point_on_curve_at_t(segment, t)[0];
if (x_at_t - x).abs() <= tolerance {
return Some(t);
}
let x_at_a = get_point_on_curve_at_t(segment, a)[0];
if (x_at_t > x) != (x_at_a > x) {
b = t;
} else {
a = t;
}
}
None
}
pub fn query_encoded_curve(encoded_chain: &EncodedScaledBezierChain, scaled_x: f64) -> Option<f64> {
match decode(encoded_chain) {
Ok(chain) => query_curve(&chain, scaled_x),
Err(_) => None,
}
}
pub fn get_encoded_curve_query_function(
encoded_chain: &EncodedScaledBezierChain,
) -> Option<impl Fn(f64) -> Option<f64> + '_> {
match decode(encoded_chain) {
Ok(decoded_chain) => Some(move |scaled_x: f64| query_curve(&decoded_chain, scaled_x)),
Err(_) => None,
}
}