use std::collections::BTreeMap;
use oxijolt_sys::*;
use super::{SoftBodyBendType, SoftBodyVertex, SoftBodyVertexAttributes};
use crate::limits::{
self, is_compliance, is_linear_velocity, is_local_offset, is_vertex_inverse_mass,
SoftBodyMassDistribution, SoftBodyPressureGeometry, LINEAR_VELOCITY_RULE,
};
use crate::math::jolt_length;
use crate::owned::{JoltObject, Owned};
use crate::world::ensure_initialized;
use crate::{SoftBodyError, Vec3};
pub(super) const COMPLIANCE_RULE: &str =
"a compliance must be finite and within 0..=limits::MAX_COMPLIANCE";
#[derive(Clone, Debug)]
enum GeneratedConstraints {
Uniform(SoftBodyBendType, SoftBodyVertexAttributes),
PerVertex(SoftBodyBendType, Vec<SoftBodyVertexAttributes>),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SoftBodyEdge {
pub vertices: [u32; 2],
pub compliance: f32,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SoftBodyDihedralBend {
pub vertices: [u32; 4],
pub compliance: f32,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SoftBodyVolume {
pub vertices: [u32; 4],
pub compliance: f32,
}
pub struct SoftBodySharedSettings {
settings: Owned<JPH_SoftBodySharedSettings>,
pub(super) pressure_geometry: SoftBodyPressureGeometry,
pub(super) mass_distribution: SoftBodyMassDistribution,
}
impl std::fmt::Debug for SoftBodySharedSettings {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SoftBodySharedSettings")
.field("vertex_count", &self.vertex_count())
.field("face_count", &self.face_count())
.finish_non_exhaustive()
}
}
unsafe impl Send for SoftBodySharedSettings {}
unsafe impl Sync for SoftBodySharedSettings {}
impl JoltObject for JPH_SoftBodySharedSettings {
unsafe fn destroy(ptr: *mut Self) {
unsafe { JPH_SoftBodySharedSettings_Destroy(ptr) };
}
}
impl SoftBodySharedSettings {
pub fn builder(
vertices: Vec<SoftBodyVertex>,
faces: Vec<[u32; 3]>,
) -> SoftBodySharedSettingsBuilder {
SoftBodySharedSettingsBuilder {
vertices,
faces,
generated: None,
edges: Vec::new(),
dihedral_bends: Vec::new(),
volumes: Vec::new(),
}
}
pub(crate) fn as_ptr(&self) -> *const JPH_SoftBodySharedSettings {
self.settings.as_ptr()
}
pub fn vertex_count(&self) -> usize {
unsafe { JPH_SoftBodySharedSettings_GetVertexCount(self.as_ptr()) as usize }
}
pub fn face_count(&self) -> usize {
unsafe { JPH_SoftBodySharedSettings_GetFaceCount(self.as_ptr()) as usize }
}
pub fn edge_constraint_count(&self) -> usize {
unsafe { JPH_SoftBodySharedSettings_GetEdgeConstraintCount(self.as_ptr()) as usize }
}
pub fn dihedral_bend_constraint_count(&self) -> usize {
unsafe { JPH_SoftBodySharedSettings_GetDihedralBendConstraintCount(self.as_ptr()) as usize }
}
pub fn volume_constraint_count(&self) -> usize {
unsafe { JPH_SoftBodySharedSettings_GetVolumeConstraintCount(self.as_ptr()) as usize }
}
pub fn lra_constraint_count(&self) -> usize {
unsafe { JPH_SoftBodySharedSettings_GetLRAConstraintCount(self.as_ptr()) as usize }
}
}
#[derive(Clone, Debug)]
#[must_use]
pub struct SoftBodySharedSettingsBuilder {
vertices: Vec<SoftBodyVertex>,
faces: Vec<[u32; 3]>,
generated: Option<GeneratedConstraints>,
edges: Vec<SoftBodyEdge>,
dihedral_bends: Vec<SoftBodyDihedralBend>,
volumes: Vec<SoftBodyVolume>,
}
impl SoftBodySharedSettingsBuilder {
pub fn create_constraints(
mut self,
bend: SoftBodyBendType,
attributes: SoftBodyVertexAttributes,
) -> Self {
self.generated = Some(GeneratedConstraints::Uniform(bend, attributes));
self
}
pub fn create_constraints_per_vertex(
mut self,
bend: SoftBodyBendType,
attributes: Vec<SoftBodyVertexAttributes>,
) -> Self {
self.generated = Some(GeneratedConstraints::PerVertex(bend, attributes));
self
}
pub fn edge(mut self, edge: SoftBodyEdge) -> Self {
self.edges.push(edge);
self
}
pub fn dihedral_bend(mut self, bend: SoftBodyDihedralBend) -> Self {
self.dihedral_bends.push(bend);
self
}
pub fn volume(mut self, volume: SoftBodyVolume) -> Self {
self.volumes.push(volume);
self
}
pub fn build(self) -> Result<SoftBodySharedSettings, SoftBodyError> {
if !ensure_initialized() {
return Err(SoftBodyError::InitFailed);
}
self.validate()?;
let positions: Vec<Vec3> = self.vertices.iter().map(|v| v.position).collect();
let pressure_geometry = SoftBodyPressureGeometry::new(&positions, &self.faces);
let mass_distribution = SoftBodyMassDistribution::new(
self.vertices.iter().map(|v| (v.position, v.inverse_mass)),
);
let owned = unsafe { Owned::from_raw(JPH_SoftBodySharedSettings_Create()) }
.unwrap_or_else(|| unreachable!("`new` does not return null"));
let settings = SoftBodySharedSettings {
settings: owned,
pressure_geometry,
mass_distribution,
};
let ptr = settings.settings.as_ptr();
let vertices: Vec<JPH_SoftVertex> = self.vertices.iter().map(|v| v.to_jph()).collect();
let faces: Vec<JPH_SoftFace> = self
.faces
.iter()
.map(|&[vertex1, vertex2, vertex3]| JPH_SoftFace {
vertex1,
vertex2,
vertex3,
materialIndex: 0,
})
.collect();
unsafe {
JPH_SoftBodySharedSettings_AddVertices(ptr, vertices.as_ptr(), vertices.len() as u32);
JPH_SoftBodySharedSettings_AddFaces(ptr, faces.as_ptr(), faces.len() as u32);
}
if let Some(generated) = &self.generated {
let (bend, attributes) = match generated {
GeneratedConstraints::Uniform(bend, attributes) => {
(bend, vec![attributes.to_jph()])
}
GeneratedConstraints::PerVertex(bend, attributes) => {
(bend, attributes.iter().map(|a| a.to_jph()).collect())
}
};
unsafe {
JPH_SoftBodySharedSettings_CreateConstraints2(
ptr,
attributes.as_ptr(),
attributes.len() as u32,
bend.to_jph(),
);
}
}
self.add_explicit_constraints(ptr);
unsafe { JPH_SoftBodySharedSettings_Optimize(ptr) };
Ok(settings)
}
fn add_explicit_constraints(&self, ptr: *mut JPH_SoftBodySharedSettings) {
unsafe {
for edge in &self.edges {
let [a, b] = edge.vertices;
JPH_SoftBodySharedSettings_AddEdgeConstraint(ptr, a, b, edge.compliance);
}
for bend in &self.dihedral_bends {
let [a, b, c, d] = bend.vertices;
JPH_SoftBodySharedSettings_AddDihedralBendConstraint(
ptr,
a,
b,
c,
d,
bend.compliance,
);
}
for volume in &self.volumes {
let [a, b, c, d] = volume.vertices;
JPH_SoftBodySharedSettings_AddVolumeConstraint(ptr, a, b, c, d, volume.compliance);
}
if !self.edges.is_empty() {
JPH_SoftBodySharedSettings_CalculateEdgeLengths(ptr);
}
if !self.dihedral_bends.is_empty() {
JPH_SoftBodySharedSettings_CalculateBendConstraintConstants(ptr);
}
if !self.volumes.is_empty() {
JPH_SoftBodySharedSettings_CalculateVolumeConstraintVolumes(ptr);
}
}
}
fn validate(&self) -> Result<(), SoftBodyError> {
require(
!self.vertices.is_empty(),
"a soft body needs at least one vertex",
)?;
require(
u32::try_from(self.vertices.len()).is_ok() && u32::try_from(self.faces.len()).is_ok(),
"vertex and face counts must fit u32",
)?;
let mut total_mass = 0.0_f64;
for vertex in &self.vertices {
require(
is_local_offset(vertex.position),
"a vertex position must be finite and within limits::MAX_SHAPE_EXTENT",
)?;
require(is_linear_velocity(vertex.velocity), LINEAR_VELOCITY_RULE)?;
if vertex.inverse_mass != 0.0 {
require(
is_vertex_inverse_mass(vertex.inverse_mass),
VERTEX_INVERSE_MASS_RULE,
)?;
total_mass += vertex_mass(vertex.inverse_mass);
}
}
require(total_mass <= f64::from(limits::MAX_MASS), TOTAL_MASS_RULE)?;
for &face in &self.faces {
self.validate_face(face)?;
}
if let Some(generated) = &self.generated {
let bend = match generated {
GeneratedConstraints::Uniform(bend, attributes) => {
attributes.validate()?;
*bend
}
GeneratedConstraints::PerVertex(bend, attributes) => {
require(
attributes.len() == self.vertices.len(),
"per-vertex attributes need one set per vertex",
)?;
for attributes in attributes {
attributes.validate()?;
}
*bend
}
};
if bend == SoftBodyBendType::Distance {
self.validate_distance_bends()?;
}
}
self.validate_explicit_constraints()
}
fn names_different_vertices(&self, indices: &[u32]) -> Result<(), SoftBodyError> {
let count = self.vertices.len();
require(
indices.iter().all(|&index| (index as usize) < count),
"a constraint index must name a vertex",
)?;
let distinct = indices
.iter()
.enumerate()
.all(|(i, a)| indices[i + 1..].iter().all(|b| a != b));
require(distinct, "a constraint must name different vertices")
}
fn validate_explicit_constraints(&self) -> Result<(), SoftBodyError> {
for edge in &self.edges {
self.names_different_vertices(&edge.vertices)?;
require(is_compliance(edge.compliance), COMPLIANCE_RULE)?;
let [a, b] = edge.vertices.map(|index| self.position(index));
require(is_edge_length(a, b), EDGE_LENGTH_RULE)?;
}
for bend in &self.dihedral_bends {
self.names_different_vertices(&bend.vertices)?;
require(is_compliance(bend.compliance), COMPLIANCE_RULE)?;
let [a, b, _, _] = bend.vertices.map(|index| self.position(index));
require(
is_edge_length(a, b),
"the shared edge of a bend must be at least limits::MIN_SOFT_BODY_EDGE_LENGTH long",
)?;
}
for volume in &self.volumes {
self.names_different_vertices(&volume.vertices)?;
require(is_compliance(volume.compliance), COMPLIANCE_RULE)?;
let [x1, x2, x3, x4] = volume.vertices.map(|index| self.position(index));
let six_volume = dot(
cross(difference(x1, x2), difference(x1, x3)),
difference(x1, x4),
);
require(
six_volume.is_finite() && six_volume != 0.0,
"a volume constraint must span a tetrahedron with a volume",
)?;
}
Ok(())
}
fn position(&self, index: u32) -> Vec3 {
self.vertices[index as usize].position
}
fn validate_face(&self, face: [u32; 3]) -> Result<(), SoftBodyError> {
let count = self.vertices.len();
require(
face.iter().all(|&index| (index as usize) < count),
"a face index must name a vertex",
)?;
let [a, b, c] = face;
require(
a != b && b != c && a != c,
"a face must name three different vertices",
)?;
let [pa, pb, pc] = face.map(|index| self.position(index));
for (from, to) in [(pa, pb), (pb, pc), (pc, pa)] {
require(is_edge_length(from, to), EDGE_LENGTH_RULE)?;
}
let area = jolt_length(cross(difference(pa, pb), difference(pa, pc)));
require(area.is_finite() && area > 0.0, "a face must have an area")
}
fn validate_distance_bends(&self) -> Result<(), SoftBodyError> {
let mut opposite: BTreeMap<(u32, u32), Vec<u32>> = BTreeMap::new();
for face in &self.faces {
for i in 0..3 {
let (v0, v1) = (face[i], face[(i + 1) % 3]);
opposite
.entry((v0.min(v1), v0.max(v1)))
.or_default()
.push(face[(i + 2) % 3]);
}
}
for vertices in opposite.values() {
for (i, &first) in vertices.iter().enumerate() {
for &second in &vertices[i + 1..] {
if first != second {
require(
is_edge_length(self.position(first), self.position(second)),
"vertices opposite a shared edge must be at least \
limits::MIN_SOFT_BODY_EDGE_LENGTH apart",
)?;
}
}
}
}
Ok(())
}
}
pub(super) const VERTEX_INVERSE_MASS_RULE: &str =
"vertex inverse mass must be 0 or within 1 / limits::MAX_MASS..=limits::MAX_VERTEX_INVERSE_MASS";
pub(super) const TOTAL_MASS_RULE: &str =
"the masses of the movable vertices must add up to at most limits::MAX_MASS";
pub(super) const INERTIA_RULE: &str =
"without a kinematic vertex, the vertices must spread around the body origin";
pub(super) const EDGE_LENGTH_RULE: &str =
"an edge must be at least limits::MIN_SOFT_BODY_EDGE_LENGTH long";
pub(super) fn vertex_mass(inverse_mass: f32) -> f64 {
f64::from(1.0 / inverse_mass)
}
pub(super) fn require(valid: bool, what: &'static str) -> Result<(), SoftBodyError> {
if valid {
Ok(())
} else {
Err(SoftBodyError::InvalidValue(what))
}
}
fn difference(from: Vec3, to: Vec3) -> Vec3 {
Vec3::new(to.x - from.x, to.y - from.y, to.z - from.z)
}
fn dot(a: Vec3, b: Vec3) -> f32 {
a.x * b.x + a.y * b.y + a.z * b.z
}
fn cross(a: Vec3, b: Vec3) -> Vec3 {
Vec3::new(
a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x,
)
}
pub(super) fn is_edge_length(from: Vec3, to: Vec3) -> bool {
jolt_length(difference(from, to)) >= limits::MIN_SOFT_BODY_EDGE_LENGTH
}