use crate::alloc_prelude::*;
use crate::math::{DIM, Real, Vector};
#[cfg(not(feature = "std"))]
#[allow(unused_imports)]
use simba::scalar::{ComplexField as _, RealField as _};
use super::{SoftBody, SoftBodyCell, SoftMeshCellBinding};
fn barycentric(point: Vector, cell: [Vector; DIM + 1]) -> Option<[Real; DIM + 1]> {
let frame = SoftBody::cell_edge_matrix(cell);
if SoftBody::cell_volume(cell).abs() <= Real::EPSILON {
return None;
}
let coords = frame.inverse() * (point - cell[0]);
let mut weights = [0.0; DIM + 1];
weights[0] = 1.0;
for k in 0..DIM {
weights[k + 1] = coords[k];
weights[0] -= coords[k];
}
Some(weights)
}
fn distance_squared_to_cell(point: Vector, cell: [Vector; DIM + 1]) -> Real {
use parry::query::PointQuery;
#[cfg(feature = "dim2")]
{
let triangle = parry::shape::Triangle::new(cell[0], cell[1], cell[2]);
(triangle.project_local_point(point, true).point - point).length_squared()
}
#[cfg(feature = "dim3")]
{
let faces = [
[cell[0], cell[2], cell[1]],
[cell[0], cell[1], cell[3]],
[cell[0], cell[3], cell[2]],
[cell[1], cell[2], cell[3]],
];
let mut inside = true;
let mut closest = Real::MAX;
for face in faces {
let triangle = parry::shape::Triangle::new(face[0], face[1], face[2]);
let normal = (face[1] - face[0]).cross(face[2] - face[0]);
inside &= (point - face[0]).dot(normal) <= 0.0;
closest = closest
.min((triangle.project_local_point(point, true).point - point).length_squared());
}
if inside { 0.0 } else { closest }
}
}
pub(crate) fn bind_to_cells(
vertices: &[Vector],
indices: &[[u32; DIM]],
cells: &[SoftBodyCell],
positions: &[Vector],
) -> Option<Vec<SoftMeshCellBinding>> {
use parry::bounding_volume::Aabb;
use parry::partitioning::{Bvh, BvhBuildStrategy};
use parry::query::PointQuery;
if vertices.is_empty() || indices.is_empty() || cells.is_empty() {
return None;
}
let cell_points = |cell: &SoftBodyCell| cell.vertices.map(|vid| positions[vid as usize]);
let leaves: Vec<Aabb> = cells
.iter()
.map(|cell| Aabb::from_points(cell_points(cell).iter().copied()))
.collect();
let bvh = Bvh::from_leaves(BvhBuildStrategy::Binned, &leaves);
let mut bindings = Vec::with_capacity(vertices.len());
for vertex in vertices {
let closest = bvh.find_best(
Real::MAX,
|node, _| node.aabb().distance_to_local_point(*vertex, true),
|cell, _| {
let points = cell_points(&cells[cell as usize]);
Some(distance_squared_to_cell(*vertex, points).sqrt())
},
);
let (cell, _) = closest?;
let binding = match barycentric(*vertex, cell_points(&cells[cell as usize])) {
Some(weights) => SoftMeshCellBinding { cell, weights },
None => {
let mut by_distance: Vec<(Real, u32)> = cells
.iter()
.enumerate()
.map(|(id, cell)| {
(
distance_squared_to_cell(*vertex, cell_points(cell)),
id as u32,
)
})
.collect();
by_distance.sort_unstable_by(|a, b| a.0.total_cmp(&b.0));
by_distance.iter().find_map(|(_, cell)| {
let weights = barycentric(*vertex, cell_points(&cells[*cell as usize]))?;
Some(SoftMeshCellBinding {
cell: *cell,
weights,
})
})?
}
};
bindings.push(binding);
}
Some(bindings)
}
#[derive(Clone, Debug)]
pub enum SoftMeshBindingMode {
Direct {
particles: Vec<u32>,
},
DirectByPosition {
eps: Real,
},
Skinned,
}
#[derive(Clone, Debug)]
pub struct SoftMeshBinding {
pub(crate) mode: SoftMeshBindingMode,
pub(crate) self_contacts: bool,
}
impl SoftMeshBinding {
pub fn direct(particles: Vec<u32>) -> Self {
Self::with_mode(SoftMeshBindingMode::Direct { particles })
}
pub fn direct_by_position(eps: Real) -> Self {
Self::with_mode(SoftMeshBindingMode::DirectByPosition { eps })
}
pub fn skinned() -> Self {
Self::with_mode(SoftMeshBindingMode::Skinned)
}
fn with_mode(mode: SoftMeshBindingMode) -> Self {
Self {
mode,
self_contacts: false,
}
}
pub fn self_contacts(mut self, enabled: bool) -> Self {
self.self_contacts = enabled;
self
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, thiserror::Error)]
pub enum SoftBindingError {
#[error("the parent body is not a soft-body cluster proxy")]
NotAClusterProxy,
#[error("a deformable collider must be a polyline or a trimesh")]
UnsupportedShape,
#[error("a deformable collider's shape must have the deformable flag")]
NotDeformable,
#[error("the mesh has no vertex or no element")]
DegenerateMesh,
#[error("vertex {vertex} is bound to particle {particle}, which is not in the cluster")]
VertexOutsideCluster {
vertex: u32,
particle: u32,
},
#[error("no particle of the cluster is close enough to vertex {vertex}")]
UnmatchedVertex {
vertex: u32,
},
#[error("no cell of the cluster can hold vertex {vertex}")]
UnboundVertex {
vertex: u32,
},
}
pub(super) fn closest_cell_binding(
body: &SoftBody,
position: Vector,
allowed: impl Fn(u32) -> bool,
) -> Option<SoftMeshCellBinding> {
let cell_points =
|cell: &SoftBodyCell| cell.vertices.map(|v| body.particles[v as usize].position);
let mut by_distance: Vec<(Real, u32)> = body
.cells
.iter()
.enumerate()
.filter(|(id, _)| allowed(*id as u32))
.map(|(id, cell)| {
(
distance_squared_to_cell(position, cell_points(cell)),
id as u32,
)
})
.collect();
by_distance.sort_unstable_by(|a, b| a.0.total_cmp(&b.0));
by_distance.iter().find_map(|(_, cell)| {
let weights = barycentric(position, cell_points(&body.cells[*cell as usize]))?;
Some(SoftMeshCellBinding {
cell: *cell,
weights,
})
})
}