use super::nurbs_pcurve_parameter_domain;
use crate::features::FiniteVector3;
use crate::geometry::pcurve::{PcurveNurbs, PcurveNurbsPoles};
use crate::math::{Point2, Vector3};
use crate::scalar::FiniteReal;
const EPS_EVAL_CLAMPED_NURBS_PCURVE_ENDPOINT_FRAMES_E12: f64 = 1.0e-12;
const EPS_EVAL_FITTED_NURBS_OFFSET_CANDIDATE_E12: f64 = 1.0e-12;
const EPS_EVAL_FITTED_NURBS_OFFSET_CANDIDATE_E9: f64 = 1.0e-9;
pub(super) fn clamped_nurbs_pcurve_endpoint_frames(
curve: &PcurveNurbs,
) -> Option<[(Point2, Point2); 2]> {
let knots = curve.knots();
let points = curve.pole_rows();
let domain = nurbs_pcurve_parameter_domain(curve.degree(), knots, points.count())?;
let [lower, upper] = domain.endpoints();
let degree = cadmpeg_core::decode::index_from_u32(curve.degree());
for knot in knots.iter().take(degree + 1) {
if *knot != lower {
return None;
}
}
for knot in knots.iter().skip(points.count()).take(degree + 1) {
if *knot != upper {
return None;
}
}
if let PcurveNurbsPoles::Rational { points } = points {
for pole in points {
if pole.weight.get() <= 0.0 {
return None;
}
}
}
let start = points.point_at(0).map(crate::units::FinitePoint2::get)?;
let last = points.count().checked_sub(1)?;
let end = points.point_at(last).map(crate::units::FinitePoint2::get)?;
let mut start_tangent = None;
for index in 1..points.count() {
let point = points.point_at(index)?;
let tangent = Point2::new(point.u - start.u, point.v - start.v);
if tangent.u.hypot(tangent.v) > EPS_EVAL_CLAMPED_NURBS_PCURVE_ENDPOINT_FRAMES_E12 {
start_tangent = Some(tangent);
break;
}
}
let start_tangent = start_tangent?;
let mut end_tangent = None;
for index in (0..last).rev() {
let point = points.point_at(index)?;
let tangent = Point2::new(end.u - point.u, end.v - point.v);
if tangent.u.hypot(tangent.v) > EPS_EVAL_CLAMPED_NURBS_PCURVE_ENDPOINT_FRAMES_E12 {
end_tangent = Some(tangent);
break;
}
}
end_tangent.map(|end_tangent| [(start, start_tangent), (end, end_tangent)])
}
pub(super) fn fitted_nurbs_offset_candidate(
source: [(Point2, Point2); 2],
result: [(Point2, Point2); 2],
linear_tolerance: f64,
) -> Option<FiniteReal> {
let mut distances = [0.0; 2];
for ordinal in 0..2 {
let (source_point, source_tangent) = source[ordinal];
let (result_point, result_tangent) = result[ordinal];
let source_length = source_tangent.u.hypot(source_tangent.v);
let result_length = result_tangent.u.hypot(result_tangent.v);
if source_length <= EPS_EVAL_FITTED_NURBS_OFFSET_CANDIDATE_E12
|| result_length <= EPS_EVAL_FITTED_NURBS_OFFSET_CANDIDATE_E12
{
return None;
}
let source_unit =
FiniteVector3::new(Vector3::new(source_tangent.u, source_tangent.v, 0.0))?
.unit_nonzero()?;
let result_unit =
FiniteVector3::new(Vector3::new(result_tangent.u, result_tangent.v, 0.0))?
.unit_nonzero()?;
if source_unit.cross(result_unit).norm() > EPS_EVAL_FITTED_NURBS_OFFSET_CANDIDATE_E9 {
return None;
}
let offset_projection = |x, y| {
crate::math::sum::finite_dot(
[
result_point.u,
-source_point.u,
result_point.v,
-source_point.v,
],
[x, x, y, y],
)
.ok()
};
let tangential = offset_projection(source_unit.x, source_unit.y)?.get();
let coordinate_scale = 1.0
+ source_point
.u
.abs()
.max(source_point.v.abs())
.max(result_point.u.abs())
.max(result_point.v.abs());
if tangential.abs()
> linear_tolerance.max(EPS_EVAL_FITTED_NURBS_OFFSET_CANDIDATE_E9 * coordinate_scale)
{
return None;
}
distances[ordinal] = offset_projection(-source_unit.y, source_unit.x)?.get();
}
let scale = 1.0 + distances[0].abs().max(distances[1].abs());
((distances[0] - distances[1]).abs()
<= linear_tolerance.max(EPS_EVAL_FITTED_NURBS_OFFSET_CANDIDATE_E9 * scale)
&& distances[0].abs() > linear_tolerance.max(EPS_EVAL_FITTED_NURBS_OFFSET_CANDIDATE_E9))
.then(|| crate::math::interpolate(distances[0], distances[1], 0.5))
.flatten()
}
#[cfg(test)]
mod tests {
use crate::geometry::pcurve::PcurveNurbs;
use crate::math::Point2;
fn curve(v: f64, weights: Option<Vec<f64>>) -> PcurveNurbs {
PcurveNurbs::from_lanes(
1,
vec![0.0, 0.0, 0.5, 1.0, 1.0],
vec![
Point2::new(0.0, v),
Point2::new(1.0, v),
Point2::new(2.0, v),
],
weights,
false,
)
.unwrap()
}
#[test]
fn fitted_sketch_nurbs_offset_measures_the_frame_distance() {
let source = crate::sketches::SketchGeometry::nurbs(curve(0.0, Some(vec![1.0, 2.0, 1.0])));
let result = crate::sketches::SketchGeometry::nurbs(curve(1.0, None));
let ctx = cadmpeg_test_support::service_decode_context();
let distance =
super::super::fitted_nurbs_offset_frame_distance(&source, &result, 0.0).unwrap();
assert_eq!(distance.get(), 1.0);
ctx.finish_session().unwrap();
}
#[test]
fn sketch_nurbs_endpoint_frames_keep_weight_and_degenerate_absence() {
let negative_weight = curve(0.0, Some(vec![1.0, -1.0, 1.0]));
assert_eq!(
super::clamped_nurbs_pcurve_endpoint_frames(&negative_weight),
None
);
let degenerate = PcurveNurbs::from_lanes(
1,
vec![0.0, 0.0, 1.0, 1.0],
vec![Point2::new(1.0, 2.0); 2],
None,
false,
)
.unwrap();
assert_eq!(
super::clamped_nurbs_pcurve_endpoint_frames(°enerate),
None
);
let offset = super::clamped_nurbs_pcurve_endpoint_frames(&curve(0.0, None)).unwrap();
assert_eq!(
offset,
[
(Point2::new(0.0, 0.0), Point2::new(1.0, 0.0)),
(Point2::new(2.0, 0.0), Point2::new(1.0, 0.0))
]
);
}
}