mod bodies;
mod chunk;
mod contact;
mod partition;
use alloc::vec::Vec;
use crate::physics::fanout::Fanout;
use super::config::{SimConfig, Softness};
use super::contact::Manifold;
use super::island::Islands;
use super::joint::{Joint, JointSolver, Prepared, Push};
use chunk::{Chunk, Tuning};
use contact::ContactConstraint;
use partition::{Ends, Partition};
pub(crate) use bodies::{Bodies, SolverBody};
pub(crate) const MAX_WORKERS: usize = 64;
#[derive(Debug, Clone, Copy)]
pub(crate) struct ContactLoad {
pub(crate) manifold: u32,
pub(crate) impulse: f32,
}
pub(crate) struct Solver {
bodies: Vec<SolverBody>,
taken: Vec<bool>,
active: Vec<u32>,
constraints: Vec<ContactConstraint>,
loads: Vec<ContactLoad>,
joints: JointSolver,
partition: Partition,
}
impl Solver {
pub(crate) fn with_capacity(capacity: usize) -> Self {
Solver {
bodies: alloc::vec![SolverBody::IMMOVABLE; capacity],
taken: alloc::vec![false; capacity],
active: Vec::with_capacity(capacity),
constraints: Vec::with_capacity(capacity * 2),
loads: Vec::with_capacity(capacity * 2),
joints: JointSolver::with_capacity(capacity),
partition: Partition::with_capacity(capacity),
}
}
pub(crate) fn reserved_bytes(&self) -> u64 {
(self.bodies.capacity() * size_of::<SolverBody>()
+ self.taken.capacity()
+ self.active.capacity() * size_of::<u32>()
+ self.constraints.capacity() * size_of::<ContactConstraint>()
+ self.loads.capacity() * size_of::<ContactLoad>()) as u64
+ self.joints.reserved_bytes()
+ self.partition.reserved_bytes()
}
pub(crate) fn begin(&mut self) {
self.active.clear();
self.loads.clear();
self.taken.fill(false);
}
pub(crate) fn loads(&self) -> &[ContactLoad] {
&self.loads
}
pub(crate) fn set_body(&mut self, slot: u32, body: SolverBody) {
if body.simulated && !self.taken[slot as usize] {
self.active.push(slot);
}
self.taken[slot as usize] = true;
self.bodies[slot as usize] = body;
}
pub(crate) fn body(&self, slot: u32) -> &SolverBody {
&self.bodies[slot as usize]
}
pub(crate) fn is_idle(&self) -> bool {
self.active.is_empty()
}
#[cfg(test)]
pub(crate) fn constraint_count(&self) -> usize {
self.loads.len()
}
#[cfg(test)]
pub(crate) fn island_count(&self) -> usize {
self.partition.islands()
}
#[cfg(test)]
pub(crate) fn chunk_count(&self) -> usize {
self.partition.ends().len()
}
pub(crate) fn run(&mut self, work: Work<'_>, fanout: &impl Fanout, workers: usize) {
let Work {
manifolds,
joints,
islands,
config,
dt,
} = work;
let substeps = config.substep_count();
let h = dt / substeps as f32;
let inv_h = if h > 0.0 { 1.0 / h } else { 0.0 };
let hertz = config.contact_hertz.min(0.25 * inv_h);
let soft = Softness::new(hertz, config.contact_damping_ratio, h);
let joint_soft = Softness::new(
config.joint_hertz.min(0.25 * inv_h),
config.joint_damping_ratio,
h,
);
let push = |soft: Softness, use_bias: bool| Push {
soft,
inv_h,
max_push: config.max_push_velocity,
use_bias,
};
self.partition.build(
partition::Work {
bodies: &self.bodies,
taken: &self.taken,
active: &self.active,
manifolds,
joints,
},
islands,
workers.clamp(1, MAX_WORKERS),
);
let held = self.partition.ends().last().copied().unwrap_or_default();
if self.constraints.len() < held.contacts {
self.constraints
.resize_with(held.contacts, ContactConstraint::default);
}
self.joints
.prepare(self.partition.joint_source(), joints, &self.bodies);
let tuning = Tuning {
manifolds,
config,
substeps,
h,
inv_h,
soft,
joint_biased: push(joint_soft, true),
joint_rigid: push(Softness::RIGID, false),
};
let mut chunks: [Chunk<'_>; MAX_WORKERS] = core::array::from_fn(|_| Chunk::default());
let cut = share_out(
&mut chunks,
Split {
bodies: &mut self.bodies,
active: self.partition.active(),
constraints: &mut self.constraints[..held.contacts],
contact_source: self.partition.contact_source(),
joints: self.joints.rows_mut(),
ends: self.partition.ends(),
},
);
fanout.for_each(&mut chunks[..cut], |chunk| chunk::run(chunk, &tuning));
self.record_loads();
for at in self.partition.contact_order() {
self.constraints[*at as usize].store(manifolds);
}
self.joints.store(joints);
}
fn record_loads(&mut self) {
self.loads.clear();
for at in self.partition.contact_order() {
let constraint = &self.constraints[*at as usize];
if constraint.is_empty() {
continue;
}
self.loads.push(ContactLoad {
manifold: constraint.manifold(),
impulse: constraint.delivered(),
});
}
}
}
pub(crate) struct Work<'a> {
pub(crate) manifolds: &'a mut [Manifold],
pub(crate) joints: &'a mut [Joint],
pub(crate) islands: &'a mut Islands,
pub(crate) config: &'a SimConfig,
pub(crate) dt: f32,
}
struct Split<'a> {
bodies: &'a mut [SolverBody],
active: &'a [u32],
constraints: &'a mut [ContactConstraint],
contact_source: &'a [u32],
joints: &'a mut [Prepared],
ends: &'a [Ends],
}
fn share_out<'a>(out: &mut [Chunk<'a>], split: Split<'a>) -> usize {
let Split {
bodies,
mut active,
mut constraints,
mut contact_source,
mut joints,
ends,
} = split;
let whole = Bodies::new(bodies);
let mut from = Ends::default();
let cut = ends.len().min(out.len());
for (chunk, to) in out.iter_mut().zip(&ends[..cut]) {
let (own_active, rest) = active.split_at(to.bodies - from.bodies);
active = rest;
let (own_constraints, rest) =
core::mem::take(&mut constraints).split_at_mut(to.contacts - from.contacts);
constraints = rest;
let (own_source, rest) = contact_source.split_at(to.contacts - from.contacts);
contact_source = rest;
let (own_joints, rest) = core::mem::take(&mut joints).split_at_mut(to.joints - from.joints);
joints = rest;
*chunk = Chunk {
bodies: unsafe { whole.share() },
active: own_active,
constraints: own_constraints,
contact_source: own_source,
joints: own_joints,
};
from = *to;
}
cut
}
#[cfg(test)]
mod tests;