use std::f64::consts::PI;
#[derive(Debug, Clone)]
pub struct ContactPair {
pub node_a: usize,
pub node_b: usize,
pub gap: f64,
pub normal: [f64; 3],
pub lambda: f64,
}
#[derive(Debug, Clone, Copy)]
pub struct Aabb2d {
pub min: [f64; 2],
pub max: [f64; 2],
}
impl Aabb2d {
pub fn new(min: [f64; 2], max: [f64; 2]) -> Self {
Self { min, max }
}
pub fn expanded(&self, margin: f64) -> Self {
Self {
min: [self.min[0] - margin, self.min[1] - margin],
max: [self.max[0] + margin, self.max[1] + margin],
}
}
pub fn overlaps(&self, other: &Aabb2d) -> bool {
self.max[0] >= other.min[0]
&& self.min[0] <= other.max[0]
&& self.max[1] >= other.min[1]
&& self.min[1] <= other.max[1]
}
pub fn from_points(pts: &[[f64; 2]]) -> Option<Self> {
if pts.is_empty() {
return None;
}
let mut mn = pts[0];
let mut mx = pts[0];
for p in pts.iter().skip(1) {
if p[0] < mn[0] {
mn[0] = p[0];
}
if p[1] < mn[1] {
mn[1] = p[1];
}
if p[0] > mx[0] {
mx[0] = p[0];
}
if p[1] > mx[1] {
mx[1] = p[1];
}
}
Some(Self { min: mn, max: mx })
}
pub fn center(&self) -> [f64; 2] {
[
0.5 * (self.min[0] + self.max[0]),
0.5 * (self.min[1] + self.max[1]),
]
}
}
pub struct NodeToSegmentContact;
impl NodeToSegmentContact {
pub fn project_node_to_segment_3d(
slave: [f64; 3],
seg_start: [f64; 3],
seg_end: [f64; 3],
) -> (f64, [f64; 3]) {
let ab = [
seg_end[0] - seg_start[0],
seg_end[1] - seg_start[1],
seg_end[2] - seg_start[2],
];
let ap = [
slave[0] - seg_start[0],
slave[1] - seg_start[1],
slave[2] - seg_start[2],
];
let ab_sq = ab[0] * ab[0] + ab[1] * ab[1] + ab[2] * ab[2];
let xi = if ab_sq > 1e-30 {
(ap[0] * ab[0] + ap[1] * ab[1] + ap[2] * ab[2]) / ab_sq
} else {
0.0
}
.clamp(0.0, 1.0);
let closest = [
seg_start[0] + xi * ab[0],
seg_start[1] + xi * ab[1],
seg_start[2] + xi * ab[2],
];
let gap = [
slave[0] - closest[0],
slave[1] - closest[1],
slave[2] - closest[2],
];
(xi, gap)
}
pub fn signed_gap(
slave: [f64; 3],
seg_start: [f64; 3],
seg_end: [f64; 3],
outward_normal: [f64; 3],
) -> f64 {
let (_, gap_vec) = Self::project_node_to_segment_3d(slave, seg_start, seg_end);
gap_vec[0] * outward_normal[0]
+ gap_vec[1] * outward_normal[1]
+ gap_vec[2] * outward_normal[2]
}
pub fn penalty_force(
slave: [f64; 3],
seg_start: [f64; 3],
seg_end: [f64; 3],
outward_normal: [f64; 3],
penalty: f64,
) -> [f64; 3] {
let gap = Self::signed_gap(slave, seg_start, seg_end, outward_normal);
if gap >= 0.0 {
return [0.0; 3];
}
let pen = -gap;
[
penalty * pen * outward_normal[0],
penalty * pen * outward_normal[1],
penalty * pen * outward_normal[2],
]
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct HertzResult {
pub contact_radius: f64,
pub force: f64,
pub peak_pressure: f64,
pub approach: f64,
}
pub struct ContactPenetrationDepth;
impl ContactPenetrationDepth {
pub fn from_force_sphere_sphere(
force: f64,
r1: f64,
r2: f64,
e1: f64,
nu1: f64,
e2: f64,
nu2: f64,
) -> f64 {
let e_star = {
let inv = (1.0 - nu1 * nu1) / e1 + (1.0 - nu2 * nu2) / e2;
1.0 / inv
};
let r_star = 1.0 / (1.0 / r1 + 1.0 / r2);
let numerator = 3.0 * force;
let denominator = 4.0 * e_star * r_star.sqrt();
(numerator / denominator).powf(2.0 / 3.0)
}
}
#[derive(Debug, Clone)]
pub struct PenaltyContact {
pub penalty: f64,
pub friction_coeff: f64,
}
impl PenaltyContact {
pub fn normal_force(&self, gap: f64) -> f64 {
self.penalty * (-gap).max(0.0)
}
pub fn tangential_force(&self, slip: [f64; 3], normal_f: f64) -> [f64; 3] {
let slip_norm = (slip[0] * slip[0] + slip[1] * slip[1] + slip[2] * slip[2]).sqrt();
if slip_norm < 1e-30 || normal_f <= 0.0 {
return [0.0; 3];
}
let limit = self.friction_coeff * normal_f;
let scale = limit / slip_norm;
[-slip[0] * scale, -slip[1] * scale, -slip[2] * scale]
}
}
pub struct MortarContact;
impl MortarContact {
pub fn mortar_d_integral(slave_xi: f64, segment_length: f64) -> [f64; 2] {
let n1 = 1.0 - slave_xi;
let n2 = slave_xi;
[n1 * segment_length, n2 * segment_length]
}
pub fn mortar_m_integral(segment_length: f64) -> [[f64; 2]; 2] {
let l = segment_length;
[[2.0 * l / 6.0, l / 6.0], [l / 6.0, 2.0 * l / 6.0]]
}
pub fn project_slave_to_master(
slave_pos: [f64; 2],
master_start: [f64; 2],
master_end: [f64; 2],
) -> (f64, f64) {
let dx = master_end[0] - master_start[0];
let dy = master_end[1] - master_start[1];
let seg_len_sq = dx * dx + dy * dy;
if seg_len_sq < 1e-30 {
let gap_x = slave_pos[0] - master_start[0];
let gap_y = slave_pos[1] - master_start[1];
return (0.0, (gap_x * gap_x + gap_y * gap_y).sqrt());
}
let t_x = slave_pos[0] - master_start[0];
let t_y = slave_pos[1] - master_start[1];
let xi = ((t_x * dx + t_y * dy) / seg_len_sq).clamp(0.0, 1.0);
let cp = [master_start[0] + xi * dx, master_start[1] + xi * dy];
let gap_x = slave_pos[0] - cp[0];
let gap_y = slave_pos[1] - cp[1];
let gap = (gap_x * gap_x + gap_y * gap_y).sqrt();
(xi, gap)
}
}
#[derive(Debug, Clone)]
pub struct Aabb {
pub min: [f64; 3],
pub max: [f64; 3],
}
impl Aabb {
pub fn new(min: [f64; 3], max: [f64; 3]) -> Self {
Self { min, max }
}
pub fn expanded(&self, margin: f64) -> Self {
Self {
min: [
self.min[0] - margin,
self.min[1] - margin,
self.min[2] - margin,
],
max: [
self.max[0] + margin,
self.max[1] + margin,
self.max[2] + margin,
],
}
}
pub fn overlaps(&self, other: &Aabb) -> bool {
self.min[0] <= other.max[0]
&& self.max[0] >= other.min[0]
&& self.min[1] <= other.max[1]
&& self.max[1] >= other.min[1]
&& self.min[2] <= other.max[2]
&& self.max[2] >= other.min[2]
}
pub fn contains(&self, point: [f64; 3]) -> bool {
point[0] >= self.min[0]
&& point[0] <= self.max[0]
&& point[1] >= self.min[1]
&& point[1] <= self.max[1]
&& point[2] >= self.min[2]
&& point[2] <= self.max[2]
}
pub fn from_points(points: &[[f64; 3]]) -> Option<Self> {
if points.is_empty() {
return None;
}
let mut min = points[0];
let mut max = points[0];
for p in points.iter().skip(1) {
for k in 0..3 {
if p[k] < min[k] {
min[k] = p[k];
}
if p[k] > max[k] {
max[k] = p[k];
}
}
}
Some(Self { min, max })
}
}
pub struct ContactStiffness;
impl ContactStiffness {
pub fn assemble(
pairs: &[ContactPair],
penalty: f64,
_n_dof: usize,
) -> Vec<(usize, usize, f64)> {
let mut triplets = Vec::new();
for pair in pairs {
if pair.gap >= 0.0 {
continue;
}
for k in 0..3 {
let dof_a = 3 * pair.node_a + k;
let dof_b = 3 * pair.node_b + k;
let kc = penalty * pair.normal[k] * pair.normal[k];
triplets.push((dof_a, dof_a, kc));
triplets.push((dof_b, dof_b, kc));
triplets.push((dof_a, dof_b, -kc));
triplets.push((dof_b, dof_a, -kc));
}
}
triplets
}
}
pub struct HertzValidator;
impl HertzValidator {
pub fn validate_force(result: &HertzResult, e_star: f64, r_star: f64) -> f64 {
let expected = (4.0 / 3.0) * e_star * r_star.sqrt() * result.approach.powf(1.5);
if expected.abs() < 1e-30 {
return 0.0;
}
(result.force - expected).abs() / expected
}
pub fn validate_contact_radius(result: &HertzResult, r_star: f64) -> f64 {
let expected_a = (r_star * result.approach).sqrt();
if expected_a.abs() < 1e-30 {
return 0.0;
}
(result.contact_radius - expected_a).abs() / expected_a
}
pub fn validate_peak_pressure(result: &HertzResult) -> f64 {
if result.contact_radius < 1e-30 {
return 0.0;
}
let expected = 3.0 * result.force / (2.0 * PI * result.contact_radius.powi(2));
if expected.abs() < 1e-30 {
return 0.0;
}
(result.peak_pressure - expected).abs() / expected
}
pub fn mean_pressure(result: &HertzResult) -> f64 {
if result.contact_radius < 1e-30 {
return 0.0;
}
result.force / (PI * result.contact_radius.powi(2))
}
pub fn validate_pressure_ratio(result: &HertzResult) -> f64 {
let p_mean = Self::mean_pressure(result);
if p_mean.abs() < 1e-30 {
return 0.0;
}
let expected_ratio = 1.5;
let actual_ratio = result.peak_pressure / p_mean;
(actual_ratio - expected_ratio).abs() / expected_ratio
}
}
pub struct FemContactDetector {
pub skin_distance: f64,
}
impl FemContactDetector {
pub fn new(skin_distance: f64) -> Self {
Self { skin_distance }
}
pub fn find_candidates(
&self,
slave_nodes: &[[f64; 3]],
master_nodes: &[[f64; 3]],
) -> Vec<(usize, usize)> {
let mut pairs = Vec::new();
for (i, s) in slave_nodes.iter().enumerate() {
for (j, m) in master_nodes.iter().enumerate() {
let dx = s[0] - m[0];
let dy = s[1] - m[1];
let dz = s[2] - m[2];
let dist = (dx * dx + dy * dy + dz * dz).sqrt();
if dist <= self.skin_distance {
pairs.push((i, j));
}
}
}
pairs
}
pub fn nodes_inside_aabb(&self, slave_nodes: &[[f64; 3]], master_aabb: &Aabb) -> Vec<usize> {
let expanded = master_aabb.expanded(self.skin_distance);
slave_nodes
.iter()
.enumerate()
.filter(|(_, p)| expanded.contains(**p))
.map(|(i, _)| i)
.collect()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FrictionStatus {
Open,
Stick,
Slip,
}
pub struct EllipticalHertz;
impl EllipticalHertz {
pub fn contact(r_x: f64, r_y: f64, e_star: f64, force: f64) -> EllipticalHertzResult {
let r_eff = (r_x * r_y).sqrt();
let delta = (force * force / (e_star * e_star * r_eff)).cbrt() * 0.5;
let a_circ = (r_eff * delta).sqrt();
let ratio = (r_x / r_y).powf(1.0 / 3.0);
let a = a_circ * ratio.sqrt();
let b = a_circ / ratio.sqrt();
let p0 = 3.0 * force / (2.0 * PI * a * b);
EllipticalHertzResult {
semi_axis_a: a,
semi_axis_b: b,
peak_pressure: p0,
approach: delta,
}
}
pub fn pressure_distribution(x: f64, y: f64, p0: f64, semi_a: f64, semi_b: f64) -> f64 {
let arg = (x / semi_a) * (x / semi_a) + (y / semi_b) * (y / semi_b);
if arg > 1.0 {
0.0
} else {
p0 * (1.0 - arg).sqrt()
}
}
}
pub struct HertzContact;
impl HertzContact {
#[inline]
fn reduced_modulus(e1: f64, nu1: f64, e2: f64, nu2: f64) -> f64 {
let inv = (1.0 - nu1 * nu1) / e1 + (1.0 - nu2 * nu2) / e2;
1.0 / inv
}
pub fn sphere_sphere(
r1: f64,
r2: f64,
e1: f64,
nu1: f64,
e2: f64,
nu2: f64,
approach: f64,
) -> HertzResult {
let e_star = Self::reduced_modulus(e1, nu1, e2, nu2);
let r_star = 1.0 / (1.0 / r1 + 1.0 / r2);
let a = (r_star * approach).sqrt();
let force = (4.0 / 3.0) * e_star * r_star.sqrt() * approach.powf(1.5);
let peak_pressure = if a > 0.0 {
3.0 * force / (2.0 * PI * a * a)
} else {
0.0
};
HertzResult {
contact_radius: a,
force,
peak_pressure,
approach,
}
}
pub fn sphere_flat(r: f64, e1: f64, nu1: f64, e2: f64, nu2: f64, approach: f64) -> HertzResult {
let e_star = Self::reduced_modulus(e1, nu1, e2, nu2);
let r_star = r;
let a = (r_star * approach).sqrt();
let force = (4.0 / 3.0) * e_star * r_star.sqrt() * approach.powf(1.5);
let peak_pressure = if a > 0.0 {
3.0 * force / (2.0 * PI * a * a)
} else {
0.0
};
HertzResult {
contact_radius: a,
force,
peak_pressure,
approach,
}
}
pub fn cylinder_flat_2d(
r: f64,
e1: f64,
nu1: f64,
e2: f64,
nu2: f64,
half_length: f64,
approach: f64,
) -> HertzResult {
let e_star = Self::reduced_modulus(e1, nu1, e2, nu2);
let r_star = r;
let b = (4.0 * r_star * approach / PI).sqrt();
let length = 2.0 * half_length;
let force = (PI / 2.0) * e_star * approach * length;
let peak_pressure = if b > 0.0 && length > 0.0 {
2.0 * force / (PI * b * length)
} else {
0.0
};
HertzResult {
contact_radius: b,
force,
peak_pressure,
approach,
}
}
}
#[derive(Debug, Clone)]
pub struct DualContactState {
pub lambda: Vec<f64>,
pub r: f64,
}
impl DualContactState {
pub fn new(n_pairs: usize, r: f64) -> Self {
Self {
lambda: vec![0.0; n_pairs],
r,
}
}
pub fn uzawa_step(&mut self, gaps: &[f64]) {
for (lam, &g) in self.lambda.iter_mut().zip(gaps.iter()) {
*lam = (*lam + self.r * g).min(0.0);
}
}
pub fn active_set(&self) -> Vec<usize> {
self.lambda
.iter()
.enumerate()
.filter(|&(_, l)| *l < 0.0)
.map(|(i, _)| i)
.collect()
}
pub fn contact_forces(&self, normals: &[[f64; 3]]) -> Vec<[f64; 3]> {
self.lambda
.iter()
.zip(normals.iter())
.map(|(&lam, n)| [-lam * n[0], -lam * n[1], -lam * n[2]])
.collect()
}
}
#[derive(Debug, Clone)]
pub struct EllipticalHertzResult {
pub semi_axis_a: f64,
pub semi_axis_b: f64,
pub peak_pressure: f64,
pub approach: f64,
}
#[derive(Debug, Clone)]
pub struct AugmentedLagrangianSolver {
pub penalty: f64,
pub lambda_n: Vec<f64>,
pub lambda_t: Vec<f64>,
pub tol: f64,
pub max_iter: usize,
}
impl AugmentedLagrangianSolver {
pub fn new(penalty: f64, n_pairs: usize, tol: f64, max_iter: usize) -> Self {
Self {
penalty,
lambda_n: vec![0.0; n_pairs],
lambda_t: vec![0.0; n_pairs],
tol,
max_iter,
}
}
pub fn uzawa_update(&mut self, gaps: &[f64], slidings: &[f64], friction_coeff: f64) -> bool {
assert_eq!(gaps.len(), self.lambda_n.len());
assert_eq!(slidings.len(), self.lambda_t.len());
let mut max_change = 0.0_f64;
for i in 0..self.lambda_n.len() {
let old_n = self.lambda_n[i];
self.lambda_n[i] = (self.lambda_n[i] + self.penalty * gaps[i]).min(0.0);
let old_t = self.lambda_t[i];
let trial = self.lambda_t[i] + self.penalty * slidings[i];
let limit = friction_coeff * self.lambda_n[i].abs();
self.lambda_t[i] = trial.clamp(-limit, limit);
max_change = max_change.max((self.lambda_n[i] - old_n).abs());
max_change = max_change.max((self.lambda_t[i] - old_t).abs());
}
max_change < self.tol
}
pub fn normal_force(&self, pair: usize, gap: f64) -> f64 {
(self.lambda_n[pair] + self.penalty * gap).min(0.0)
}
}
#[derive(Debug, Clone)]
pub struct PenaltyParameters {
pub normal_stiffness: f64,
pub tangential_stiffness: f64,
pub regularization_velocity: f64,
pub max_penetration: f64,
pub scaling_factor: f64,
}
impl PenaltyParameters {
pub fn from_material(youngs_modulus: f64, element_size: f64, alpha: f64) -> Self {
let k_n = alpha * youngs_modulus / element_size;
Self {
normal_stiffness: k_n,
tangential_stiffness: k_n * 0.5,
regularization_velocity: 1e-6,
max_penetration: element_size * 0.1,
scaling_factor: 10.0,
}
}
pub fn adaptive_stiffness(&self, penetration: f64) -> f64 {
if penetration > self.max_penetration {
self.normal_stiffness * self.scaling_factor
} else {
self.normal_stiffness
}
}
pub fn normal_force(&self, gap: f64) -> f64 {
let pen = (-gap).max(0.0);
let k = self.adaptive_stiffness(pen);
k * pen
}
pub fn friction_force(&self, normal_force: f64, sliding_vel: f64, friction_coeff: f64) -> f64 {
friction_coeff * normal_force * (sliding_vel / self.regularization_velocity).tanh()
}
}
#[derive(Debug, Clone)]
pub struct AugmentedLagrangianContact {
pub penalty: f64,
pub lambda: Vec<f64>,
}
impl AugmentedLagrangianContact {
pub fn new(penalty: f64, n: usize) -> Self {
Self {
penalty,
lambda: vec![0.0; n],
}
}
pub fn update_multipliers(&mut self, gaps: &[f64]) {
assert_eq!(gaps.len(), self.lambda.len());
for (lam, &g) in self.lambda.iter_mut().zip(gaps.iter()) {
*lam = (*lam - self.penalty * g).max(0.0);
}
}
}
pub struct SegmentToSegmentContact;
impl SegmentToSegmentContact {
pub fn segment_distance(
p1_start: [f64; 2],
p1_end: [f64; 2],
p2_start: [f64; 2],
p2_end: [f64; 2],
) -> (f64, f64, f64) {
let d1 = [p1_end[0] - p1_start[0], p1_end[1] - p1_start[1]];
let d2 = [p2_end[0] - p2_start[0], p2_end[1] - p2_start[1]];
let r = [p1_start[0] - p2_start[0], p1_start[1] - p2_start[1]];
let a = d1[0] * d1[0] + d1[1] * d1[1];
let e = d2[0] * d2[0] + d2[1] * d2[1];
let f = d2[0] * r[0] + d2[1] * r[1];
let b = d1[0] * d2[0] + d1[1] * d2[1];
let c = d1[0] * r[0] + d1[1] * r[1];
let denom = a * e - b * b;
let (t1, t2);
if denom.abs() < 1e-30 {
t1 = 0.0;
t2 = if e.abs() > 1e-30 { f / e } else { 0.0 };
} else {
t1 = ((b * f - c * e) / denom).clamp(0.0, 1.0);
t2 = ((a * f - b * c) / denom).clamp(0.0, 1.0);
}
let cp1 = [p1_start[0] + t1 * d1[0], p1_start[1] + t1 * d1[1]];
let cp2 = [p2_start[0] + t2 * d2[0], p2_start[1] + t2 * d2[1]];
let diff = [cp1[0] - cp2[0], cp1[1] - cp2[1]];
let dist = (diff[0] * diff[0] + diff[1] * diff[1]).sqrt();
(dist, t1, t2)
}
pub fn gap_function(
p1_start: [f64; 2],
p1_end: [f64; 2],
p2_start: [f64; 2],
p2_end: [f64; 2],
outward_normal: [f64; 2],
) -> f64 {
let (dist, t1, _t2) = Self::segment_distance(p1_start, p1_end, p2_start, p2_end);
let d1 = [p1_end[0] - p1_start[0], p1_end[1] - p1_start[1]];
let d2 = [p2_end[0] - p2_start[0], p2_end[1] - p2_start[1]];
let cp1 = [p1_start[0] + t1 * d1[0], p1_start[1] + t1 * d1[1]];
let _t2_val = _t2;
let cp2 = [p2_start[0] + _t2_val * d2[0], p2_start[1] + _t2_val * d2[1]];
let diff = [cp1[0] - cp2[0], cp1[1] - cp2[1]];
let signed_gap = diff[0] * outward_normal[0] + diff[1] * outward_normal[1];
if signed_gap.abs() < 1e-30 {
dist
} else {
signed_gap
}
}
}