#![allow(unused)]
#![allow(clippy::unreadable_literal)]
#![allow(clippy::many_single_char_names)]
use std::env;
use std::time::{Duration, Instant};
use kurbo::{ParamCurve, ParamCurveArclen, ParamCurveDeriv, QuadBez};
use kurbo::common::{GAUSS_LEGENDRE_COEFFS_24, GAUSS_LEGENDRE_COEFFS_5, GAUSS_LEGENDRE_COEFFS_7};
fn gauss_arclen_3(q: QuadBez) -> f64 {
let v0 = (-0.492943519233745 * q.p0.to_vec2()
+ 0.430331482911935 * q.p1.to_vec2()
+ 0.0626120363218102 * q.p2.to_vec2())
.hypot();
let v1 = ((q.p2 - q.p0) * 0.4444444444444444).hypot();
let v2 = (-0.0626120363218102 * q.p0.to_vec2() - 0.430331482911935 * q.p1.to_vec2()
+ 0.492943519233745 * q.p2.to_vec2())
.hypot();
v0 + v1 + v2
}
fn awesome_quad_arclen(q: QuadBez, accuracy: f64, depth: usize, count: &mut usize) -> f64 {
let pm = q.p0.midpoint(q.p2);
let d1 = q.p1 - pm;
let d = q.p2 - q.p0;
let dhypot2 = d.hypot2();
let x = 2.0 * d.dot(d1) / dhypot2;
let y = 2.0 * d.cross(d1) / dhypot2;
let lc = (q.p2 - q.p0).hypot();
let lp = (q.p1 - q.p0).hypot() + (q.p2 - q.p1).hypot();
let est_err = 0.06 * (lp - lc) * (x * x + y * y).powf(2.0);
if est_err < accuracy || depth == 16 {
*count += 1;
gauss_arclen_3(q)
} else {
let (q0, q1) = q.subdivide();
awesome_quad_arclen(q0, accuracy * 0.5, depth + 1, count)
+ awesome_quad_arclen(q1, accuracy * 0.5, depth + 1, count)
}
}
const MAX_DEPTH: usize = 16;
fn gravesen_rec(q: &QuadBez, l0: f64, accuracy: f64, depth: usize, count: &mut usize) -> f64 {
let (q0, q1) = q.subdivide();
let l0_q0 = calc_l0(q0);
let l0_q1 = calc_l0(q1);
let l1 = l0_q0 + l0_q1;
let error = (l0 - l1) * (1.0 / 15.0);
if error.abs() < accuracy || depth == MAX_DEPTH {
*count += 1;
l1 - error
} else {
gravesen_rec(&q0, l0_q0, accuracy * 0.5, depth + 1, count)
+ gravesen_rec(&q1, l0_q1, accuracy * 0.5, depth + 1, count)
}
}
fn gauss_arclen_5(q: QuadBez) -> f64 {
q.gauss_arclen(GAUSS_LEGENDRE_COEFFS_5)
}
fn gauss_arclen_7(q: QuadBez) -> f64 {
q.gauss_arclen(GAUSS_LEGENDRE_COEFFS_7)
}
fn gauss_arclen_24(q: QuadBez) -> f64 {
q.gauss_arclen(GAUSS_LEGENDRE_COEFFS_24)
}
fn awesome_quad_arclen7(q: QuadBez, accuracy: f64, depth: usize, count: &mut usize) -> f64 {
let pm = q.p0.midpoint(q.p2);
let d1 = q.p1 - pm;
let d = q.p2 - q.p0;
let dhypot2 = d.hypot2();
let x = 2.0 * d.dot(d1) / dhypot2;
let y = 2.0 * d.cross(d1) / dhypot2;
let lc = (q.p2 - q.p0).hypot();
let lp = (q.p1 - q.p0).hypot() + (q.p2 - q.p1).hypot();
let est_err = 2.5e-2 * (lp - lc) * (x * x + y * y).powf(8.0).tanh();
if est_err < accuracy || depth == 22 {
*count += 1;
gauss_arclen_7(q)
} else {
let (q0, q1) = q.subdivide();
awesome_quad_arclen7(q0, accuracy * 0.5, depth + 1, count)
+ awesome_quad_arclen7(q1, accuracy * 0.5, depth + 1, count)
}
}
fn awesome_quad_arclen24(q: QuadBez, accuracy: f64, depth: usize, count: &mut usize) -> f64 {
let pm = q.p0.midpoint(q.p2);
let d1 = q.p1 - pm;
let d = q.p2 - q.p0;
let dhypot2 = d.hypot2();
let x = 2.0 * d.dot(d1) / dhypot2;
let y = 2.0 * d.cross(d1) / dhypot2;
let lc = (q.p2 - q.p0).hypot();
let lp = (q.p1 - q.p0).hypot() + (q.p2 - q.p1).hypot();
let est_err = 1.0 * (lp - lc) * (0.5 * x * x + 0.1 * y * y).powf(16.0).tanh();
if est_err < accuracy || depth == 16 {
*count += 1;
gauss_arclen_24(q)
} else {
let (q0, q1) = q.subdivide();
awesome_quad_arclen24(q0, accuracy * 0.5, depth + 1, count)
+ awesome_quad_arclen24(q1, accuracy * 0.5, depth + 1, count)
}
}
fn quad_arclen_analytical(q: QuadBez) -> f64 {
let d2 = q.p0.to_vec2() - 2.0 * q.p1.to_vec2() + q.p2.to_vec2();
let a = d2.hypot2();
let d1 = q.p1 - q.p0;
let c = d1.hypot2();
if a < 5e-4 * c {
return gauss_arclen_3(q);
}
let b = 2.0 * d2.dot(d1);
let sabc = (a + b + c).sqrt();
let a2 = a.powf(-0.5);
let a32 = a2.powi(3);
let c2 = 2.0 * c.sqrt();
let ba_c2 = b * a2 + c2;
let v0 = 0.25 * a2 * a2 * b * (2.0 * sabc - c2) + sabc;
if ba_c2 < 1e-13 {
v0
} else {
v0 + 0.25 * a32 * (4.0 * c * a - b * b) * (((2.0 * a + b) * a2 + 2.0 * sabc) / ba_c2).ln()
}
}
fn calc_l0(q: QuadBez) -> f64 {
let lc = (q.p2 - q.p0).hypot();
let lp = (q.p1 - q.p0).hypot() + (q.p2 - q.p1).hypot();
(2.0 / 3.0) * lc + (1.0 / 3.0) * lp
}
fn with_subdiv(q: QuadBez, f: &dyn Fn(QuadBez) -> f64, depth: usize) -> f64 {
if depth == 0 {
f(q)
} else {
let (q0, q1) = q.subdivide();
with_subdiv(q0, f, depth - 1) + with_subdiv(q1, f, depth - 1)
}
}
fn duration_to_time(d: Duration) -> f64 {
1e-9 * (d.subsec_nanos() as f64) + (d.as_secs() as f64)
}
fn run_simple() {
let q = QuadBez::new((0.0, 0.0), (0.0, 0.5), (1.0, 1.0));
let true_len = q.arclen(1e-13);
for i in 0..20 {
let n = 1 << i;
let start_time = Instant::now();
let mut est = 0.0;
let mut last = q.start();
let dt = (n as f64).recip();
for j in 0..n {
let t = ((j + 1) as f64) * dt;
let p = q.eval(t);
est += (p - last).hypot();
last = p;
}
let elapsed = duration_to_time(start_time.elapsed());
let err = true_len - est;
if i > 0 {
println!("{} {}", elapsed, err);
}
}
}
fn main() {
let mut n_subdiv = 0;
let mut func: &dyn Fn(QuadBez) -> f64 = &gauss_arclen_3;
for arg in env::args().skip(1) {
if arg == "gauss3" {
func = &gauss_arclen_3;
} else if arg == "gauss5" {
func = &gauss_arclen_5;
} else if arg == "gauss7" {
func = &gauss_arclen_7;
} else if arg == "gauss24" {
func = &gauss_arclen_24;
} else if arg == "l0" {
func = &calc_l0;
} else if arg == "simple" {
run_simple();
return;
} else if let Ok(n) = arg.parse() {
n_subdiv = n;
} else {
println!("usage: arclen_accuracy [func] n_subdiv");
std::process::exit(1);
}
}
let n = 400;
let accuracy = 1e-6;
for i in 0..=n {
let x = 2.0 * (i as f64) * (n as f64).recip();
for j in 0..=n {
let y = 2.0 * (j as f64) * (n as f64).recip();
let q = QuadBez::new((-1.0, 0.0), (x, y), (1.0, 0.0));
let mut count = 0;
let accurate_arclen = awesome_quad_arclen7(q, 1e-15, 0, &mut count);
let start_time = Instant::now();
let est = quad_arclen_analytical(q);
let elapsed = start_time.elapsed();
let error = est - accurate_arclen;
println!("{} {} {}", x, y, (error.abs() + 1e-18).log10());
}
println!();
}
}