pub(crate) mod ball_end;
pub(crate) mod ball_end_to_edge;
pub(crate) mod square_end;
pub(crate) mod square_end_to_edge;
pub(crate) mod tapered_end;
pub(crate) mod tapered_end_to_edge;
#[cfg(test)]
pub mod tests;
use super::meshanalyzer::MeshAnalyzer;
use crate::geo::RigidTransform2D;
use crate::{
HronnError,
geo::{Area2D, ConvertTo, PlaneFromTriangle},
m_factor_from_plane_unit_normal, m_from_plane_unit_normal,
meshanalyzer::SearchResult,
triangle_normal,
util::MaximumTracker,
};
use std::{fmt::Debug, marker::PhantomData};
use vector_traits::{
num_traits::{AsPrimitive, Float},
prelude::{GenericScalar, GenericVector2, GenericVector3, HasXY, HasXYZ},
};
pub(crate) struct TriangleMetadata<T: GenericVector3, MESH: HasXYZ> {
pub(crate) scalar: T::Scalar,
pub(crate) plane: Option<PlaneMetadata<T::Vector2>>,
#[doc(hidden)]
_pdm: PhantomData<fn(MESH) -> MESH>,
}
impl<T: GenericVector3, MESH: HasXYZ> TriangleMetadata<T, MESH>
where
MESH: ConvertTo<T>,
{
pub fn new_for_ball_nose(
probe_radius: T::Scalar,
p0: MESH,
p1: MESH,
p2: MESH,
) -> Result<Self, HronnError> {
let p0: T = p0.to();
let p1: T = p1.to();
let p2: T = p2.to();
let u_normal = triangle_normal(p0, p1, p2)
.try_normalize(T::Scalar::EPSILON)
.ok_or_else(|| {
HronnError::InvalidData(
"Could not normalize normal, is the mesh triangulated?".to_string(),
)
})?;
let m_factor = m_factor_from_plane_unit_normal::<T>(u_normal);
let (p0_prim_xy, p1_prim_xy, p2_prim_xy) = {
let xy_offset = (u_normal * probe_radius).to_2d();
(
p0.to_2d() + xy_offset,
p1.to_2d() + xy_offset,
p2.to_2d() + xy_offset,
)
};
let area = Area2D::new(p0_prim_xy, p1_prim_xy, p2_prim_xy);
let pft = PlaneFromTriangle::new_from_normal(u_normal, p0);
if area.value().abs() > T::Scalar::EPSILON {
Ok(Self {
scalar: m_factor * probe_radius - probe_radius,
plane: Some(PlaneMetadata::<T::Vector2> {
pft,
translated_triangle: [p0_prim_xy, p1_prim_xy, p2_prim_xy],
}),
_pdm: PhantomData,
})
} else {
Ok(Self {
scalar: m_factor * probe_radius - probe_radius,
plane: None,
_pdm: PhantomData,
})
}
}
pub fn new_for_square_end(
probe_radius: T::Scalar,
p0: T,
p1: T,
p2: T,
) -> Result<Self, HronnError> {
let u_normal_3d = triangle_normal(p0, p1, p2)
.try_normalize(T::Scalar::EPSILON)
.ok_or_else(|| {
HronnError::InvalidData(
"Could not normalize normal, is the mesh triangulated?".to_string(),
)
})?;
let m = m_from_plane_unit_normal::<T>(u_normal_3d);
let p0_2d = p0.to_2d();
let p1_2d = p1.to_2d();
let p2_2d = p2.to_2d();
if let Some(u_normal_2d) = u_normal_3d.to_2d().try_normalize(T::Scalar::EPSILON) {
let area = Area2D::new(p0_2d, p1_2d, p2_2d);
if area.value().abs() > T::Scalar::EPSILON {
let xy_offset = u_normal_2d * probe_radius;
let pft = {
let zoffset_p0 = T::new_3d(p0.x(), p0.y(), p0.z() + m * probe_radius);
PlaneFromTriangle::new_from_normal(u_normal_3d, zoffset_p0)
};
return Ok(Self {
scalar: m * probe_radius,
plane: Some(PlaneMetadata {
pft,
translated_triangle: [
p0_2d + xy_offset,
p1_2d + xy_offset,
p2_2d + xy_offset,
],
}),
_pdm: PhantomData,
});
}
} else {
let area = Area2D::new(p0_2d, p1_2d, p2_2d);
if area.value().abs() > T::Scalar::EPSILON {
return Ok(Self {
scalar: m * probe_radius,
plane: Some(PlaneMetadata {
pft: PlaneFromTriangle::new_from_z_coord(p0.z()),
translated_triangle: [p0_2d, p1_2d, p2_2d],
}),
_pdm: PhantomData,
});
}
}
Ok(Self {
scalar: m * probe_radius,
plane: None,
_pdm: PhantomData,
})
}
pub fn new_for_tapered_nose(
cone_radius: T::Scalar,
cone_slope: T::Scalar,
p0: T,
p1: T,
p2: T,
) -> Result<Self, HronnError> {
let u_normal_3d = triangle_normal(p0, p1, p2)
.try_normalize(T::Scalar::EPSILON)
.ok_or_else(|| {
HronnError::InvalidData(
"Could not normalize normal, is the mesh triangulated?".to_string(),
)
})?;
let area = Area2D::new(p0.to_2d(), p1.to_2d(), p2.to_2d());
let m = m_from_plane_unit_normal::<T>(u_normal_3d);
let p0_2d = p0.to_2d();
let p1_2d = p1.to_2d();
let p2_2d = p2.to_2d();
if area.value().abs() > T::Scalar::EPSILON && m > T::Scalar::EPSILON {
if m > cone_slope {
if let Some(u_normal_2d) = u_normal_3d.to_2d().try_normalize(T::Scalar::EPSILON) {
let xy_offset = u_normal_2d * cone_radius;
let z_offset_p0 = T::new_3d(
p0.x(),
p0.y(),
p0.z() + m * cone_radius - cone_radius * cone_slope,
);
Ok(Self {
scalar: m,
plane: Some(PlaneMetadata {
pft: PlaneFromTriangle::new_from_normal(u_normal_3d, z_offset_p0),
translated_triangle: [
p0_2d + xy_offset,
p1_2d + xy_offset,
p2_2d + xy_offset,
],
}),
_pdm: PhantomData,
})
} else {
Ok(Self {
scalar: m,
plane: Some(PlaneMetadata::<T::Vector2> {
pft: PlaneFromTriangle::new_from_z_coord(p0.z()),
translated_triangle: [p0.to_2d(), p1.to_2d(), p2.to_2d()],
}),
_pdm: PhantomData,
})
}
} else {
Ok(Self {
scalar: m,
plane: Some(PlaneMetadata::<T::Vector2> {
pft: PlaneFromTriangle::new_from_normal(u_normal_3d, p0),
translated_triangle: [p0.to_2d(), p1.to_2d(), p2.to_2d()],
}),
_pdm: PhantomData,
})
}
} else {
Ok(Self {
scalar: m,
plane: None,
_pdm: PhantomData,
})
}
}
}
pub struct PlaneMetadata<T: GenericVector2> {
pub pft: PlaneFromTriangle<T>,
pub translated_triangle: [T; 3],
}
pub struct QueryParameters<'a, T: GenericVector3, MESH: HasXYZ> {
pub(crate) vertices: &'a [MESH],
pub(crate) indices: &'a [u32],
pub(crate) meta_data: &'a [TriangleMetadata<T, MESH>],
pub(crate) probe_radius: T::Scalar,
pub(crate) probe_height: T::Scalar,
pub(crate) search_radius: T::Scalar,
}
pub trait Probe<T: GenericVector3, MESH: HasXYZ + ConvertTo<T>> {
fn probe_radius(&self) -> T::Scalar;
fn probe_slope(&self) -> T::Scalar;
fn probe_height(&self) -> T::Scalar;
#[doc(hidden)]
fn _mesh_analyzer(&self) -> &MeshAnalyzer<'_, T, MESH>;
#[doc(hidden)]
#[allow(private_interfaces)]
fn _meta_data(&self) -> &[TriangleMetadata<T, MESH>];
#[doc(hidden)]
#[allow(clippy::type_complexity)]
fn _collision_fn(
&self,
) -> fn(
query_parameters: &QueryParameters<'_, T, MESH>,
site_index: u32,
center: T::Vector2,
mt: &mut MaximumTracker<SearchResult<T>>,
);
}
pub struct SquareEndProbe<'b, T: GenericVector3, MESH: HasXYZ>
where
MESH: ConvertTo<T>,
{
probe_radius: T::Scalar,
meta_data: Vec<TriangleMetadata<T, MESH>>,
bound_mesh_analyzer: &'b MeshAnalyzer<'b, T, MESH>,
}
impl<'b, T: GenericVector3, MESH: HasXYZ> SquareEndProbe<'b, T, MESH>
where
MESH: ConvertTo<T>,
{
pub fn new(
mesh_analyzer: &'b MeshAnalyzer<'_, T, MESH>,
probe_radius: T::Scalar,
) -> Result<Self, HronnError> {
Ok(SquareEndProbe {
probe_radius,
meta_data: square_end::shared_square_end_precompute_logic::<T, MESH>(
mesh_analyzer.vertices.as_ref(),
mesh_analyzer.indices.as_ref(),
probe_radius,
)?,
bound_mesh_analyzer: mesh_analyzer,
})
}
}
impl<T: GenericVector3, MESH: HasXYZ> Probe<T, MESH> for SquareEndProbe<'_, T, MESH>
where
MESH: ConvertTo<T>,
{
#[inline(always)]
fn probe_radius(&self) -> T::Scalar {
self.probe_radius
}
#[inline(always)]
fn probe_slope(&self) -> T::Scalar {
T::Scalar::NEG_INFINITY
}
#[inline(always)]
fn probe_height(&self) -> T::Scalar {
T::Scalar::NEG_INFINITY
}
#[inline(always)]
fn _mesh_analyzer(&self) -> &MeshAnalyzer<'_, T, MESH> {
self.bound_mesh_analyzer
}
#[inline(always)]
#[allow(private_interfaces)]
fn _meta_data(&self) -> &[TriangleMetadata<T, MESH>] {
&self.meta_data
}
#[inline(always)]
fn _collision_fn(
&self,
) -> fn(
query_parameters: &QueryParameters<'_, T, MESH>,
site_index: u32,
center: T::Vector2,
mt: &mut MaximumTracker<SearchResult<T>>,
) {
square_end::square_end_compute_collision
}
}
pub struct BallNoseProbe<'b, T: GenericVector3, MESH: HasXYZ>
where
MESH: ConvertTo<T>,
{
probe_radius: T::Scalar,
meta_data: Vec<TriangleMetadata<T, MESH>>,
bound_mesh_analyzer: &'b MeshAnalyzer<'b, T, MESH>,
}
impl<'b, T: GenericVector3, MESH: HasXYZ> BallNoseProbe<'b, T, MESH>
where
MESH: ConvertTo<T>,
{
pub fn new(
mesh_analyzer: &'b MeshAnalyzer<'_, T, MESH>,
probe_radius: T::Scalar,
) -> Result<Self, HronnError> {
Ok(BallNoseProbe {
probe_radius,
meta_data: ball_end::shared_ball_nose_precompute_logic::<T, MESH>(
mesh_analyzer.vertices.as_ref(),
mesh_analyzer.indices.as_ref(),
probe_radius,
)?,
bound_mesh_analyzer: mesh_analyzer,
})
}
}
impl<T: GenericVector3, MESH: HasXYZ> Probe<T, MESH> for BallNoseProbe<'_, T, MESH>
where
MESH: ConvertTo<T>,
T: ConvertTo<MESH>,
{
#[inline(always)]
fn probe_radius(&self) -> T::Scalar {
self.probe_radius
}
#[inline(always)]
fn probe_slope(&self) -> T::Scalar {
T::Scalar::NEG_INFINITY
}
#[inline(always)]
fn probe_height(&self) -> T::Scalar {
T::Scalar::NEG_INFINITY
}
#[inline(always)]
fn _mesh_analyzer(&self) -> &MeshAnalyzer<'_, T, MESH> {
self.bound_mesh_analyzer
}
#[inline(always)]
#[allow(private_interfaces)]
fn _meta_data(&self) -> &[TriangleMetadata<T, MESH>] {
&self.meta_data
}
#[inline(always)]
fn _collision_fn(
&self,
) -> fn(
query_parameters: &QueryParameters<'_, T, MESH>,
site_index: u32,
center: T::Vector2,
mt: &mut MaximumTracker<SearchResult<T>>,
) {
ball_end::ball_nose_compute_collision
}
}
pub struct TaperedProbe<'b, T: GenericVector3, MESH: HasXYZ>
where
MESH: ConvertTo<T>,
{
probe_radius: T::Scalar,
probe_height: T::Scalar,
slope: T::Scalar,
meta_data: Vec<TriangleMetadata<T, MESH>>,
bound_mesh_analyzer: &'b MeshAnalyzer<'b, T, MESH>,
}
impl<'b, T: GenericVector3, MESH: HasXYZ> TaperedProbe<'b, T, MESH>
where
MESH: ConvertTo<T>,
T: ConvertTo<MESH>,
f64: AsPrimitive<T::Scalar>,
{
pub fn new(
mesh_analyzer: &'b MeshAnalyzer<'_, T, MESH>,
probe_radius: T::Scalar,
probe_angle: T::Scalar,
) -> Result<Self, HronnError> {
let slope: T::Scalar = T::Scalar::ONE / (probe_angle / T::Scalar::TWO).tan();
let probe_height = probe_radius * slope;
let rv = TaperedProbe {
probe_radius,
probe_height,
slope,
meta_data: tapered_end::shared_tapered_precompute_logic::<T, MESH>(
mesh_analyzer.vertices.as_ref(),
mesh_analyzer.indices.as_ref(),
probe_radius,
slope,
)?,
bound_mesh_analyzer: mesh_analyzer,
};
println!(
"Tapered probe got radius:{} angle:{} tan(angle/2):{} height:{} ",
rv.probe_radius(),
probe_angle * (180.0 / std::f64::consts::PI).as_(),
rv.probe_slope(),
rv.probe_height()
);
Ok(rv)
}
}
impl<T: GenericVector3, MESH: HasXYZ> Probe<T, MESH> for TaperedProbe<'_, T, MESH>
where
MESH: ConvertTo<T>,
T: ConvertTo<MESH>,
{
#[inline(always)]
fn probe_radius(&self) -> T::Scalar {
self.probe_radius
}
#[inline(always)]
fn probe_slope(&self) -> T::Scalar {
self.slope
}
#[inline(always)]
fn probe_height(&self) -> T::Scalar {
self.probe_height
}
#[inline(always)]
fn _mesh_analyzer(&self) -> &MeshAnalyzer<'_, T, MESH> {
self.bound_mesh_analyzer
}
#[inline(always)]
#[allow(private_interfaces)]
fn _meta_data(&self) -> &[TriangleMetadata<T, MESH>] {
&self.meta_data
}
#[inline(always)]
fn _collision_fn(
&self,
) -> fn(
query_parameters: &QueryParameters<'_, T, MESH>,
site_index: u32,
center: T::Vector2,
mt: &mut MaximumTracker<SearchResult<T>>,
) {
tapered_end::tapered_compute_collision
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(i8)]
#[allow(dead_code)]
pub enum SkipEndpoint {
SkipP0 = -1,
NoSkip = 0,
SkipP1 = 1,
}
impl SkipEndpoint {
#[allow(dead_code)]
pub(crate) fn flip(self) -> Self {
unsafe { std::mem::transmute::<i8, Self>(-(self as i8)) }
}
}
pub struct EdgeAndCenterType<T: GenericVector3> {
center: T::Vector2,
distance_sq: T::Scalar,
t: T::Scalar,
p0: T,
p1: T,
}
impl<T: GenericVector3> PartialEq for EdgeAndCenterType<T> {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
self.get_distance_sq() == other.get_distance_sq()
}
}
impl<T: GenericVector3> PartialOrd for EdgeAndCenterType<T> {
#[inline(always)]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
match self.distance_sq.partial_cmp(&other.distance_sq) {
Some(std::cmp::Ordering::Equal) => other
.p0
.z()
.max(other.p1.z())
.partial_cmp(&self.p0.z().max(self.p1.z())),
other_ordering => other_ordering,
}
}
}
impl<T: GenericVector3> EdgeAndCenterType<T> {
pub fn rotate_translate_xz(&mut self) -> bool {
let transform = match RigidTransform2D::translate_rotate_align_x(
self.center,
self.p0.to_2d(),
self.p1.to_2d(),
) {
Some(t) => t,
None => return false,
};
self.center = transform.transform_2d_point(self.center);
self.p0 = transform.transform_point(self.p0);
self.p1 = transform.transform_point(self.p1);
let offset = T::new_3d(self.center.x(), self.p0.y(), T::Scalar::ZERO);
self.center -= offset.to_2d();
*self.center.y_mut() = self.center.y().abs();
self.p0 -= offset;
self.p1 -= offset;
true
}
#[inline(always)]
fn get_distance_sq(&self) -> T::Scalar {
self.distance_sq
}
#[inline(always)]
pub fn min(self, other: Self) -> Self {
match self.partial_cmp(&other) {
Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal) => self,
Some(std::cmp::Ordering::Greater) | None => other,
}
}
pub fn min_two(self, other1: Self, other2: Self) -> (Self, Self) {
if self <= other1 {
if self <= other2 {
(self, other1.min(other2))
} else {
(other2, self)
}
} else if other1 <= other2 {
(other1, self.min(other2))
} else {
(other2, other1)
}
}
}
#[inline(always)]
pub(crate) fn xy_distance_to_line_squared<T: GenericVector3>(
p: T::Vector2,
mut p0: T,
mut p1: T,
) -> EdgeAndCenterType<T> {
if p0.z() > p1.z() {
std::mem::swap(&mut p0, &mut p1);
}
let l0 = p0.to_2d();
let l1 = p1.to_2d();
let l0_sub_l1 = l0 - l1;
let l0_sub_l1_sq = l0_sub_l1.magnitude_sq();
let l0_sub_p = l0 - p;
let dot = l0_sub_p.dot(l0_sub_l1) / l0_sub_l1_sq;
if dot < T::Scalar::ZERO {
EdgeAndCenterType {
center: p,
distance_sq: l0_sub_p.magnitude_sq(),
t: dot,
p0,
p1,
}
} else if dot > T::Scalar::ONE {
EdgeAndCenterType {
center: p,
distance_sq: (l1 - p).magnitude_sq(),
t: dot,
p0,
p1,
}
} else {
let a_sub_p_cross_a_sub_b = l0_sub_p.perp_dot(l0_sub_l1);
EdgeAndCenterType {
center: p,
distance_sq: a_sub_p_cross_a_sub_b * a_sub_p_cross_a_sub_b / l0_sub_l1_sq,
t: dot,
p0,
p1,
}
}
}