Files
cadcraft/crates/engine/tests/it/snap_intersections_aperture.rs
aiongg 9cf115e808 CI: one test binary per crate, nextest, line tables only (#1989)
* Build: line tables only in dev, no debug info for dependencies

* Tests: one integration-test binary per crate

Each crate's tests/*.rs becomes a module of tests/it/main.rs. Files that
install process-wide state (the file io hooks, user aliases, SAVETIME and
SAVEFILEPATH, user linetype and pattern files, registered fonts) stay their
own test binaries.

* xtask ci: nextest, tests without examples, one wasm check call
2026-10-23 13:34:26 -04:00

154 lines
6.0 KiB
Rust

//! Finite and apparent intersection behavior around the cursor aperture.
use cadcraft_doc::{Common, Drawing, Ellipse, EntityKind, Line, Space};
use cadcraft_engine::snap::{self, SnapHit, mode};
use cadcraft_geom::{Vec2, Vec3};
fn add_line(d: &mut Drawing, a: Vec2, b: Vec2) {
d.add(&Space::Model, Common::default(), EntityKind::Line(Line { a: a.to3(0.0), b: b.to3(0.0) })).unwrap();
}
fn add_ellipse(d: &mut Drawing, center: Vec2, major: Vec2, ratio: f64) {
d.add(
&Space::Model,
Common::default(),
EntityKind::Ellipse(Ellipse { center: center.to3(0.0), major: major.to3(0.0), ratio, start: 0.0, end: std::f64::consts::TAU }),
)
.unwrap();
}
fn hit(d: &Drawing, cursor: Vec2, aperture: f64, osmode: u32) -> Option<SnapHit> {
snap::osnap(d, &Space::Model, cursor, aperture, osmode, None, false)
}
#[test]
fn finite_intersection_on_the_aperture_boundary_is_included() {
let mut d = Drawing::new_imperial();
add_line(&mut d, Vec2::new(-1.0, 0.4), Vec2::new(1.0, 0.4));
add_line(&mut d, Vec2::new(0.3, -1.0), Vec2::new(0.3, 1.0));
let h = hit(&d, Vec2::ZERO, 0.5, mode::INT).expect("intersection on aperture boundary");
assert_eq!(h.mode, mode::INT);
assert!(h.point.near(Vec2::new(0.3, 0.4), 1e-12), "{h:?}");
}
#[test]
fn finite_intersection_just_outside_the_aperture_is_rejected() {
let mut d = Drawing::new_imperial();
let x = 0.300001;
add_line(&mut d, Vec2::new(-1.0, 0.4), Vec2::new(1.0, 0.4));
add_line(&mut d, Vec2::new(x, -1.0), Vec2::new(x, 1.0));
assert!(hit(&d, Vec2::ZERO, 0.5, mode::INT).is_none());
}
#[test]
fn finite_line_parameter_tolerance_is_preserved() {
let mut d = Drawing::new_imperial();
let x = 1.0 + 5e-10;
add_line(&mut d, Vec2::ZERO, Vec2::new(1.0, 0.0));
add_line(&mut d, Vec2::new(x, -1.0), Vec2::new(x, 1.0));
let h = hit(&d, Vec2::new(x, 0.0), 1e-9, mode::INT).expect("intersection within the existing line parameter tolerance");
assert_eq!(h.mode, mode::INT);
assert!(h.point.near(Vec2::new(x, 0.0), 1e-15), "{h:?}");
}
#[test]
fn tessellated_line_parameter_tolerance_is_preserved() {
let mut d = Drawing::new_imperial();
add_ellipse(&mut d, Vec2::ZERO, Vec2::new(10.0, 0.0), 0.5);
let x = 10.0 + 5e-12;
add_line(&mut d, Vec2::new(x, -1.0), Vec2::new(x, 1.0));
let h = hit(&d, Vec2::new(x, 0.0), 1e-10, mode::INT).expect("finite intersection within a tessellated chord's parameter tolerance");
assert_eq!(h.mode, mode::INT);
assert!(h.point.near(Vec2::new(x, 0.0), 1e-10), "{h:?}");
}
#[test]
fn large_tessellated_chord_parameter_slack_is_not_pruned() {
let mut d = Drawing::new_imperial();
let radius = 1e12;
let step = std::f64::consts::TAU / cadcraft_geom::arc_segments(radius, std::f64::consts::TAU, radius * 1e-3) as f64;
let next = Vec2::new(-radius + radius * step.cos(), radius * 0.5 * step.sin());
let t = 1.0 / next.x;
assert!(t.abs() < 1e-9);
let cursor = Vec2::new(1.0, t * next.y);
add_ellipse(&mut d, Vec2::new(-radius, 0.0), Vec2::new(radius, 0.0), 0.5);
add_line(&mut d, Vec2::new(1.0, cursor.y - 100.0), Vec2::new(1.0, cursor.y + 100.0));
let h = hit(&d, cursor, 0.5, mode::INT).expect("finite intersection within a long tessellated chord's parameter tolerance");
assert_eq!(h.mode, mode::INT);
assert!(h.point.near(cursor, 1e-6), "{h:?}");
}
#[test]
fn tessellated_ellipse_intersection_near_cursor_is_kept() {
let mut d = Drawing::new_imperial();
add_ellipse(&mut d, Vec2::ZERO, Vec2::new(10.0, 0.0), 0.5);
add_line(&mut d, Vec2::new(9.0, 0.0), Vec2::new(11.0, 0.0));
let h = hit(&d, Vec2::new(10.0, 0.0), 0.05, mode::INT).expect("ellipse and line intersection");
assert_eq!(h.mode, mode::INT);
assert!(h.point.near(Vec2::new(10.0, 0.0), 1e-9), "{h:?}");
}
#[test]
fn ellipse_bounds_covering_cursor_do_not_create_a_curve_hit() {
let mut d = Drawing::new_imperial();
for i in 0..12usize {
let radius = 50.0 + i as f64 * 10.0;
let major = Vec2::from_angle(i as f64 * 0.17) * radius;
add_ellipse(&mut d, Vec2::ZERO, major, 0.75 + (i % 5) as f64 * 0.04);
}
assert!(hit(&d, Vec2::ZERO, 0.1, mode::INT).is_none());
}
#[test]
fn apparent_intersection_keeps_segments_outside_the_finite_aperture() {
let mut d = Drawing::new_imperial();
add_line(&mut d, Vec2::new(0.15, 0.0), Vec2::new(0.18, 0.0));
add_line(&mut d, Vec2::new(0.0, 0.15), Vec2::new(0.0, 0.18));
let h = hit(&d, Vec2::ZERO, 0.1, mode::APP).expect("apparent intersection of extended lines");
assert_eq!(h.mode, mode::APP);
assert!(h.point.near(Vec2::ZERO, 1e-12), "{h:?}");
}
#[test]
fn real_intersection_keeps_precedence_when_app_is_also_enabled() {
let mut d = Drawing::new_imperial();
add_line(&mut d, Vec2::new(-1.0, 0.0), Vec2::new(1.0, 0.0));
add_line(&mut d, Vec2::new(0.0, -1.0), Vec2::new(0.0, 1.0));
add_line(&mut d, Vec2::new(0.15, 0.04), Vec2::new(0.18, 0.04));
add_line(&mut d, Vec2::new(0.04, 0.15), Vec2::new(0.04, 0.18));
let h = hit(&d, Vec2::ZERO, 0.1, mode::INT | mode::APP).expect("real and apparent intersections");
assert_eq!(h.mode, mode::INT);
assert!(h.point.near(Vec2::ZERO, 1e-12), "{h:?}");
}
#[test]
fn line_and_circle_intersections_remain_available() {
let mut d = Drawing::new_imperial();
d.add(&Space::Model, Common::default(), EntityKind::Circle(cadcraft_doc::Circle { center: Vec3::ZERO, radius: 2.0 })).unwrap();
add_line(&mut d, Vec2::new(-3.0, 0.0), Vec2::new(3.0, 0.0));
add_line(&mut d, Vec2::new(0.0, -3.0), Vec2::new(0.0, 3.0));
let h = hit(&d, Vec2::ZERO, 0.1, mode::INT).expect("crossing lines inside a circle drawing");
assert_eq!(h.mode, mode::INT);
assert!(h.point.near(Vec2::ZERO, 1e-12), "{h:?}");
}
#[test]
fn non_finite_query_inputs_return_no_hit() {
let mut d = Drawing::new_imperial();
add_line(&mut d, Vec2::new(-1.0, 0.0), Vec2::new(1.0, 0.0));
add_line(&mut d, Vec2::new(0.0, -1.0), Vec2::new(0.0, 1.0));
assert!(hit(&d, Vec2::ZERO, f64::NAN, mode::INT).is_none());
assert!(hit(&d, Vec2::new(f64::INFINITY, 0.0), 0.1, mode::INT).is_none());
}