use axiolid_contracts::{BackendId, GeomError, GeomResult, Operation};
use axiolid_core::{Frame2, Point2, Point3, Scalar, Vec2, Vec3};
use axiolid_curve::{Curve2, Elevated3, Intrinsic2};
const GAUSS_NODES: [Scalar; 8] = [
-0.960_289_856_497_536_2,
-0.796_666_477_413_626_7,
-0.525_532_409_916_328_9,
-0.183_434_642_495_649_8,
0.183_434_642_495_649_8,
0.525_532_409_916_328_9,
0.796_666_477_413_626_7,
0.960_289_856_497_536_2,
];
const GAUSS_WEIGHTS: [Scalar; 8] = [
0.101_228_536_290_376_3,
0.222_381_034_453_374_5,
0.313_706_645_877_887_3,
0.362_683_783_378_361_9,
0.362_683_783_378_361_9,
0.313_706_645_877_887_3,
0.222_381_034_453_374_5,
0.101_228_536_290_376_3,
];
const PANELS_PER_RADIAN: Scalar = 4.0;
const MAX_PANELS: usize = 4096;
fn unsupported() -> GeomError {
GeomError::Unsupported {
backend: BackendId::new("axiolid-evaluate"),
operation: Operation::CurveEvaluation,
}
}
fn invalid(detail: &str) -> GeomError {
GeomError::InvalidInput(detail.to_owned())
}
fn panel_count(curve: &Intrinsic2, s: Scalar) -> GeomResult<usize> {
let variation = curve
.turning_variation_bound(s)
.ok_or_else(|| invalid("curvature law does not integrate over the requested span"))?;
let wanted = (variation * PANELS_PER_RADIAN).ceil().max(1.0);
if !wanted.is_finite() || wanted > MAX_PANELS as Scalar {
return Err(invalid("curvature law needs an unbounded number of panels"));
}
Ok(wanted as usize)
}
pub fn intrinsic_point(curve: &Intrinsic2, s: Scalar) -> GeomResult<Point2> {
if !s.is_finite() {
return Err(invalid("arc length must be finite"));
}
let mut bounds = vec![0.0];
bounds.extend(curve.curvature.seams_within(s));
bounds.push(s);
let mut x = 0.0;
let mut y = 0.0;
for window in bounds.windows(2) {
let (lo, hi) = (window[0], window[1]);
if hi <= lo {
continue;
}
let span = hi - lo;
let panels = panel_count(curve, hi)?.max(1);
let step = span / panels as Scalar;
for panel in 0..panels {
let a = lo + step * panel as Scalar;
let half = step / 2.0;
let mid = a + half;
for (node, weight) in GAUSS_NODES.iter().zip(GAUSS_WEIGHTS.iter()) {
let u = mid + half * node;
let heading = curve
.heading_at(u)
.ok_or_else(|| invalid("curvature law does not integrate to the sample"))?;
x += weight * half * heading.cos();
y += weight * half * heading.sin();
}
}
}
Ok(place2(&curve.start, Vec2::new(x, y)))
}
pub fn intrinsic_tangent(curve: &Intrinsic2, s: Scalar) -> GeomResult<Vec2> {
if !s.is_finite() {
return Err(invalid("arc length must be finite"));
}
let heading = curve
.heading_at(s)
.ok_or_else(|| invalid("curvature law does not integrate to the requested arc length"))?;
let local = Vec2::new(heading.cos(), heading.sin());
Ok(rotate2(&curve.start, local))
}
fn place2(frame: &Frame2, local: Vec2) -> Point2 {
Point2::new(
frame.origin.x + frame.x.x * local.x + frame.y.x * local.y,
frame.origin.y + frame.x.y * local.x + frame.y.y * local.y,
)
}
fn rotate2(frame: &Frame2, local: Vec2) -> Vec2 {
Vec2::new(
frame.x.x * local.x + frame.y.x * local.y,
frame.x.y * local.x + frame.y.y * local.y,
)
}
fn plan_point(plan: &Curve2, d: Scalar) -> GeomResult<Point2> {
match plan {
Curve2::Line(line) => {
let direction = unit2(line.direction)?;
Ok(Point2::new(
line.origin.x + direction.x * d,
line.origin.y + direction.y * d,
))
}
Curve2::Circle(circle) => {
if circle.radius <= 0.0 || !circle.radius.is_finite() {
return Err(invalid("circle radius must be positive and finite"));
}
let angle = d / circle.radius;
Ok(place2(
&circle.frame,
Vec2::new(circle.radius * angle.cos(), circle.radius * angle.sin()),
))
}
Curve2::Intrinsic(intrinsic) => intrinsic_point(intrinsic, d),
Curve2::Chain(chain) => crate::chain::chain_point(chain, d),
_ => Err(unsupported()),
}
}
fn plan_tangent(plan: &Curve2, d: Scalar) -> GeomResult<Vec2> {
match plan {
Curve2::Line(line) => unit2(line.direction),
Curve2::Circle(circle) => {
if circle.radius <= 0.0 || !circle.radius.is_finite() {
return Err(invalid("circle radius must be positive and finite"));
}
let angle = d / circle.radius;
Ok(rotate2(&circle.frame, Vec2::new(-angle.sin(), angle.cos())))
}
Curve2::Intrinsic(intrinsic) => intrinsic_tangent(intrinsic, d),
Curve2::Chain(chain) => unit2(crate::chain::chain_tangent(chain, d)?),
_ => Err(unsupported()),
}
}
pub(crate) fn plan_derivative(plan: &Curve2, d: Scalar) -> GeomResult<Vec2> {
match plan {
Curve2::Chain(chain) => crate::chain::chain_tangent(chain, d),
other => plan_tangent(other, d),
}
}
pub(crate) fn plan_second_derivative(plan: &Curve2, d: Scalar) -> GeomResult<Vec2> {
if !d.is_finite() {
return Err(invalid("plan distance must be finite"));
}
match plan {
Curve2::Line(line) => {
unit2(line.direction)?;
Ok(Vec2::ZERO)
}
Curve2::Circle(circle) => {
if circle.radius <= 0.0 || !circle.radius.is_finite() {
return Err(invalid("circle radius must be positive and finite"));
}
let angle = d / circle.radius;
Ok(rotate2(
&circle.frame,
Vec2::new(-angle.cos(), -angle.sin()) / circle.radius,
))
}
Curve2::Intrinsic(intrinsic) => {
let heading = intrinsic
.heading_at(d)
.ok_or_else(|| invalid("curvature law does not integrate to that distance"))?;
let kappa = crate::frenet::law_value(&intrinsic.curvature, d)
.ok_or_else(|| invalid("curvature law is not defined at that distance"))?;
Ok(rotate2(
&intrinsic.start,
Vec2::new(-heading.sin(), heading.cos()) * kappa,
))
}
Curve2::Chain(chain) => crate::chain::chain_second_derivative(chain, d),
_ => Err(unsupported()),
}
}
fn unit2(v: Vec2) -> GeomResult<Vec2> {
let length = (v.x * v.x + v.y * v.y).sqrt();
if !length.is_finite() || length == 0.0 {
return Err(invalid("direction must be finite and non-zero"));
}
Ok(Vec2::new(v.x / length, v.y / length))
}
pub fn elevated_point(curve: &Elevated3, d: Scalar) -> GeomResult<Point3> {
if !d.is_finite() {
return Err(invalid("plan distance must be finite"));
}
let planar = plan_point(&curve.plan, d)?;
let height = crate::elevation::elevation_height(&curve.elevation, d)?;
Ok(Point3::new(planar.x, planar.y, height))
}
pub fn elevated_tangent(curve: &Elevated3, d: Scalar) -> GeomResult<Vec3> {
if !d.is_finite() {
return Err(invalid("plan distance must be finite"));
}
let planar = plan_tangent(&curve.plan, d)?;
let grade = crate::elevation::elevation_grade(&curve.elevation, d)?;
let scale = (1.0 + grade * grade).sqrt();
if !scale.is_finite() || scale == 0.0 {
return Err(invalid("grade does not give a finite tangent"));
}
Ok(Vec3::new(planar.x / scale, planar.y / scale, grade / scale))
}