use super::EdgeAndCenterType;
use super::tapered_end::ConeProperties;
use crate::meshanalyzer::SearchResult;
use crate::prelude::MaximumTracker;
use vector_traits::approx::ulps_eq;
use vector_traits::num_traits::Float;
use vector_traits::prelude::{GenericScalar, GenericVector2, GenericVector3, HasXY};
pub(crate) fn tapered_end_to_edge_collision<T: GenericVector3>(
mut edge: EdgeAndCenterType<T>,
cone: ConeProperties<T>,
site_index: u32,
mt: &mut MaximumTracker<SearchResult<T>>,
) {
if edge.distance_sq > cone.r_sq {
return;
}
debug_assert!(edge.p0.z() <= edge.p1.z());
if !edge.rotate_translate_xz() {
cone_z_value_from_single_point_line_in_xz_plane(
edge.p1.z(),
cone.r_sq,
cone.height,
(edge.center - edge.p0.to_2d()).magnitude_sq(),
site_index,
mt,
);
return;
}
solve_collision_line_in_xz_plane(edge, cone, site_index, mt)
}
#[inline(always)]
fn solve_collision_line_in_xz_plane<T: GenericVector3>(
edge: EdgeAndCenterType<T>,
cone: ConeProperties<T>,
site_index: u32,
mt: &mut MaximumTracker<SearchResult<T>>,
) {
debug_assert!(edge.center.y() >= T::Scalar::ZERO);
if edge.center.y() > cone.radius {
return;
}
let line_dir = edge.p1 - edge.p0;
let line_length_sq = line_dir.x().powi(2);
if line_length_sq > T::Scalar::ZERO {
let t = -edge.p0.x() / line_dir.x();
debug_assert!(ulps_eq!(t, edge.t), "{} != {}", t, edge.t);
let closest_point = edge.p0.to_2d() + line_dir.to_2d() * t;
let dist_sq = (edge.center - closest_point).magnitude_sq();
if dist_sq > cone.r_sq {
return;
}
}
let dz = edge.p1.z() - edge.p0.z(); debug_assert!(ulps_eq!(dz, line_dir.z()));
let dx = edge.p1.x() - edge.p0.x();
debug_assert!(ulps_eq!(dx, line_dir.x()));
let line_slope = dz / dx;
if line_slope > cone.slope {
return solve_steep_line_case_line_in_xz_plane(edge, cone, site_index, mt);
}
if edge.center.y() < T::Scalar::EPSILON {
solve_steep_cone_y_zero_case_line_in_xz_plane(edge, cone, line_slope, site_index, mt);
} else {
solve_steep_cone_case_line_in_xz_plane(edge, cone, line_slope, site_index, mt);
}
}
fn solve_steep_line_case_line_in_xz_plane<T: GenericVector3>(
edge: EdgeAndCenterType<T>,
cone: ConeProperties<T>,
site_index: u32,
mt: &mut MaximumTracker<SearchResult<T>>,
) {
let cone_y = edge.center.y();
let cr = cone.radius;
let ch = cone.height;
let line_vec = edge.p1 - edge.p0;
let high_xy_dist_sq = edge.p1.x().powi(2) + cone_y.powi(2);
if high_xy_dist_sq <= cone.r_sq {
let z_offset = high_xy_dist_sq.sqrt() * cone.slope;
let cz = edge.p1.z() - z_offset;
mt.insert(SearchResult::new(site_index, cz));
return;
}
let a = line_vec.x().powi(2);
let b = T::Scalar::TWO * edge.p0.x() * line_vec.x();
let c = edge.p0.x().powi(2) + cone_y.powi(2) - cr.powi(2);
let discriminant = b.powi(2) - T::Scalar::FOUR * a * c;
if discriminant < T::Scalar::ZERO {
return; }
let sqrt_discriminant = discriminant.sqrt();
if a.abs() < T::Scalar::EPSILON {
if b.abs() < T::Scalar::EPSILON {
return;
}
let t = -c / b;
if t < T::Scalar::ZERO || t > T::Scalar::ONE {
return;
}
let contact_point = edge.p0 + line_vec * t;
let cz = contact_point.z() - ch;
mt.insert(SearchResult::new(site_index, cz));
return;
}
let t1 = (-b - sqrt_discriminant) / (T::Scalar::TWO * a);
let t2 = (-b + sqrt_discriminant) / (T::Scalar::TWO * a);
let t_min = T::Scalar::ZERO.max(t1.min(t2));
let t_max = T::Scalar::ONE.min(t1.max(t2));
if t_max < T::Scalar::ZERO || t_min > T::Scalar::ONE {
return;
}
let mut candidates = Vec::new();
if t_min >= T::Scalar::ZERO && t_min <= T::Scalar::ONE {
candidates.push(t_min);
}
if t_max >= T::Scalar::ZERO
&& t_max <= T::Scalar::ONE
&& (t_max - t_min).abs() > T::Scalar::EPSILON
{
candidates.push(t_max);
}
let mut highest_z = T::Scalar::NEG_INFINITY;
let mut highest_t = None;
for &t in &candidates {
let point = edge.p0 + line_vec * t;
let point_z = point.z();
if point_z > highest_z {
highest_z = point_z;
highest_t = Some(t);
}
}
if let Some(t) = highest_t {
let contact_point = edge.p0 + line_vec * t;
let cz = contact_point.z() - ch;
mt.insert(SearchResult::new(site_index, cz));
}
}
pub(super) fn solve_steep_cone_y_zero_case_line_in_xz_plane<T: GenericVector3>(
edge: EdgeAndCenterType<T>,
cone: ConeProperties<T>,
line_slope: T::Scalar,
site_index: u32,
mt: &mut MaximumTracker<SearchResult<T>>,
) {
let b = edge.p0.z() - line_slope * edge.p0.x();
let x0 = edge.p0.x();
let x1 = edge.p1.x();
let min_x = x0.min(x1);
let max_x = x0.max(x1);
if max_x < -cone.radius || min_x > cone.radius {
return;
}
if min_x <= T::Scalar::ZERO && T::Scalar::ZERO <= max_x {
let _touch_x = T::Scalar::ZERO;
let touch_line_z = b; let cone_z = touch_line_z;
mt.insert(SearchResult::new(site_index, cone_z));
return;
}
let touch_x = if min_x.abs() < max_x.abs() {
min_x
} else {
max_x
};
let touch_line_z = line_slope * touch_x + b;
let cone_z = touch_line_z - cone.slope * touch_x.abs();
mt.insert(SearchResult::new(site_index, cone_z));
}
fn solve_steep_cone_case_line_in_xz_plane<T: GenericVector3>(
edge: EdgeAndCenterType<T>,
cone: ConeProperties<T>,
line_slope: T::Scalar,
site_index: u32,
mt: &mut MaximumTracker<SearchResult<T>>,
) {
let cone_y = edge.center.y().abs();
let cr = cone.radius;
let cone_slope = cone.slope;
let cy = cone_y;
let dx = edge.p1.x() - edge.p0.x();
for endpoint in [edge.p0, edge.p1].iter() {
let dist_to_endpoint_sq = point_xy_distance_to_cone_vertex_sq(endpoint.x(), cy);
if dist_to_endpoint_sq <= cone.r_sq {
let cone_z = end_point_cone_collision_with_distance(
endpoint.z(),
cone_slope,
dist_to_endpoint_sq.sqrt(),
);
mt.insert(SearchResult::new(site_index, cone_z));
}
}
debug_assert!(cone_y >= T::Scalar::ZERO);
debug_assert!(cy.abs() <= cr);
debug_assert!(
dx.abs() > T::Scalar::EPSILON,
"dx.abs():{} <= T::Scalar::EPSILON",
dx.abs()
);
let b = edge.p0.z() - line_slope * edge.p0.x();
debug_assert!(
line_slope >= T::Scalar::ZERO,
"Line slope ({cone_slope}) should be >= 0: ({line_slope})",
);
debug_assert!(
cone_slope > line_slope,
"Cone slope ({cone_slope}) is <= m ({line_slope})",
);
if line_slope < T::Scalar::EPSILON {
let (x0, x1, z0, z1) = if edge.p0.x() < edge.p1.x() {
(edge.p0.x(), edge.p1.x(), edge.p0.z(), edge.p1.z())
} else {
(edge.p1.x(), edge.p0.x(), edge.p1.z(), edge.p0.z())
};
let t = -x0 / (x1 - x0);
let z_at_zero = z0 + t * (z1 - z0);
if x0 <= T::Scalar::ZERO && T::Scalar::ZERO <= x1 {
let dist_to_axis = cone_y;
if dist_to_axis <= cr {
let cone_z = z_at_zero - cone_slope * dist_to_axis;
mt.insert(SearchResult::new(site_index, cone_z));
}
return;
} else if T::Scalar::ZERO < x0 {
let dist_to_low_point_sq = point_xy_distance_to_cone_vertex_sq(x0, cy);
if dist_to_low_point_sq <= cone.r_sq {
let cone_z = end_point_cone_collision_with_distance(
z0,
cone_slope,
dist_to_low_point_sq.sqrt(),
);
mt.insert(SearchResult::new(site_index, cone_z));
return;
}
} else if T::Scalar::ZERO > x1 {
let dist_to_high_point_sq = point_xy_distance_to_cone_vertex_sq(x1, cy);
if dist_to_high_point_sq <= cone.r_sq {
let cone_z = end_point_cone_collision_with_distance(
z1,
cone_slope,
dist_to_high_point_sq.sqrt(),
);
mt.insert(SearchResult::new(site_index, cone_z));
return;
}
}
} else {
let denominator = (cone_slope.powi(2) - line_slope.powi(2)).sqrt();
if denominator > T::Scalar::EPSILON {
let touch_x = line_slope * cy / denominator;
let min_x = edge.p0.x().min(edge.p1.x());
let max_x = edge.p0.x().max(edge.p1.x());
if (min_x..=max_x).contains(&touch_x) {
let touch_line_z = line_slope * touch_x + b;
let dist = point_xy_distance_to_cone_vertex_sq(touch_x, cy).sqrt();
let touch_cone_z = cone_slope * dist;
let cone_z = touch_line_z - touch_cone_z;
if dist <= cr {
mt.insert(SearchResult::new(site_index, cone_z));
return;
}
}
}
}
let direction = edge.p1 - edge.p0;
let a = direction.x().powi(2) + direction.y().powi(2);
let b = T::Scalar::TWO * (edge.p0.x() * direction.x() + (edge.p0.y() - cy) * direction.y());
let c = edge.p0.x().powi(2) + (edge.p0.y() - cy).powi(2) - cone.r_sq;
let discriminant = b.powi(2) - T::Scalar::FOUR * a * c;
if discriminant >= T::Scalar::ZERO && a.abs() > T::Scalar::EPSILON {
let sqrt_discriminant = discriminant.sqrt();
let t1 = (-b + sqrt_discriminant) / (T::Scalar::TWO * a);
let t2 = (-b - sqrt_discriminant) / (T::Scalar::TWO * a);
for t in [t1, t2].iter() {
if *t >= T::Scalar::ZERO && *t <= T::Scalar::ONE {
let intersection = edge.p0 + direction * (*t);
let point_xy_dist =
point_xy_distance_to_cone_vertex_sq(intersection.x(), cy).sqrt();
let cone_height_at_point = cone_slope * point_xy_dist;
let cone_z = intersection.z() - cone_height_at_point;
mt.insert(SearchResult::new(site_index, cone_z));
}
}
}
}
#[inline(always)]
fn point_xy_distance_to_cone_vertex_sq<T: GenericScalar>(x: T, cy: T) -> T {
Float::powi(x, 2) + Float::powi(cy, 2)
}
#[inline(always)]
fn end_point_cone_collision_with_distance<T: GenericScalar>(
endpoint_z: T,
slope: T,
dist_to_endpoint: T,
) -> T {
endpoint_z - slope * dist_to_endpoint
}
#[inline(always)]
fn cone_z_value_from_single_point_line_in_xz_plane<T: GenericVector3>(
endpoint_z: T::Scalar,
r_sq: T::Scalar,
height: T::Scalar,
dist_sq: T::Scalar,
site_index: u32,
mt: &mut MaximumTracker<SearchResult<T>>,
) {
if dist_sq < r_sq {
mt.insert(SearchResult::new(
site_index,
endpoint_z - height * (dist_sq / r_sq).sqrt(),
));
}
}