use super::functions::*;
#[derive(Debug, Clone)]
pub struct TissueLayer {
pub name: String,
pub young_modulus: f64,
pub poisson_ratio: f64,
pub thickness: f64,
pub density: f64,
pub fracture_toughness: f64,
}
impl TissueLayer {
pub fn new(
name: impl Into<String>,
young_modulus: f64,
poisson_ratio: f64,
thickness: f64,
density: f64,
fracture_toughness: f64,
) -> Self {
Self {
name: name.into(),
young_modulus,
poisson_ratio,
thickness,
density,
fracture_toughness,
}
}
pub fn shear_modulus(&self) -> f64 {
self.young_modulus / (2.0 * (1.0 + self.poisson_ratio))
}
pub fn bulk_modulus(&self) -> f64 {
self.young_modulus / (3.0 * (1.0 - 2.0 * self.poisson_ratio))
}
pub fn lame_lambda(&self) -> f64 {
let nu = self.poisson_ratio;
self.young_modulus * nu / ((1.0 + nu) * (1.0 - 2.0 * nu))
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ToolGeometry {
Sphere,
Needle,
Blade,
}
#[derive(Debug, Clone)]
pub struct HapticFeedback {
pub force: [f64; 3],
pub torque: [f64; 3],
pub contact_energy: f64,
pub cutting_energy: f64,
pub mean_det_f: f64,
}
impl HapticFeedback {
pub fn zero() -> Self {
Self {
force: [0.0; 3],
torque: [0.0; 3],
contact_energy: 0.0,
cutting_energy: 0.0,
mean_det_f: 1.0,
}
}
pub fn force_magnitude(&self) -> f64 {
norm3(self.force)
}
pub fn torque_magnitude(&self) -> f64 {
norm3(self.torque)
}
pub fn add_contact(
&mut self,
normal: [f64; 3],
depth: f64,
stiffness: f64,
moment_arm: [f64; 3],
) {
let f = scale3(normal, stiffness * depth);
self.force = add3(self.force, f);
self.torque = add3(self.torque, cross3(moment_arm, f));
self.contact_energy += 0.5 * stiffness * depth * depth;
}
pub fn reset(&mut self) {
*self = Self::zero();
}
}
#[derive(Debug, Clone)]
pub struct TissueModel {
pub nodes: Vec<TissueNode>,
pub elements: Vec<[usize; 4]>,
pub layers: Vec<TissueLayer>,
pub pre_stress: f64,
pub damping_alpha: f64,
pub damping_beta: f64,
}
impl TissueModel {
pub fn new(layers: Vec<TissueLayer>, pre_stress: f64) -> Self {
Self {
nodes: Vec::new(),
elements: Vec::new(),
layers,
pre_stress,
damping_alpha: 0.02,
damping_beta: 0.002,
}
}
pub fn add_node(&mut self, node: TissueNode) -> usize {
let idx = self.nodes.len();
self.nodes.push(node);
idx
}
pub fn add_element(&mut self, indices: [usize; 4]) {
self.elements.push(indices);
}
pub fn deformation_gradient(&self, elem_idx: usize, rest_positions: &[[f64; 3]]) -> [f64; 9] {
let idx = self.elements[elem_idx];
let x = [
self.nodes[idx[0]].position,
self.nodes[idx[1]].position,
self.nodes[idx[2]].position,
self.nodes[idx[3]].position,
];
let x_ref = [
rest_positions[idx[0]],
rest_positions[idx[1]],
rest_positions[idx[2]],
rest_positions[idx[3]],
];
let d0 = sub3(x_ref[1], x_ref[0]);
let d1 = sub3(x_ref[2], x_ref[0]);
let d2 = sub3(x_ref[3], x_ref[0]);
let e0 = sub3(x[1], x[0]);
let e1 = sub3(x[2], x[0]);
let e2 = sub3(x[3], x[0]);
let ds_inv = mat3_inv([
d0[0], d1[0], d2[0], d0[1], d1[1], d2[1], d0[2], d1[2], d2[2],
]);
let dm = [
e0[0], e1[0], e2[0], e0[1], e1[1], e2[1], e0[2], e1[2], e2[2],
];
mat3_mul(dm, ds_inv)
}
pub fn elastic_energy_density(&self, elem_idx: usize, rest_positions: &[[f64; 3]]) -> f64 {
let layer_idx = self.nodes[self.elements[elem_idx][0]].layer_index;
let layer = &self.layers[layer_idx.min(self.layers.len() - 1)];
let lam = layer.lame_lambda();
let mu = layer.shear_modulus();
let f = self.deformation_gradient(elem_idx, rest_positions);
let eps = [
f[0] - 1.0,
0.5 * (f[1] + f[3]),
0.5 * (f[2] + f[6]),
0.5 * (f[3] + f[1]),
f[4] - 1.0,
0.5 * (f[5] + f[7]),
0.5 * (f[6] + f[2]),
0.5 * (f[7] + f[5]),
f[8] - 1.0,
];
let trace_eps = eps[0] + eps[4] + eps[8];
let trace_eps2 = mat3_frobenius_sq(eps);
0.5 * lam * trace_eps * trace_eps + mu * trace_eps2
}
pub fn step(&mut self, dt: f64, external_force: [f64; 3]) {
for node in &mut self.nodes {
if node.is_static {
continue;
}
let damp = scale3(node.velocity, -self.damping_alpha);
let accel = scale3(
add3(add3(node.force, damp), external_force),
node.inv_mass(),
);
node.velocity = add3(node.velocity, scale3(accel, dt));
node.position = add3(node.position, scale3(node.velocity, dt));
node.force = [0.0; 3];
}
}
}
#[derive(Debug, Clone)]
pub struct SurgicalTool {
pub name: String,
pub geometry: ToolGeometry,
pub tip_radius: f64,
pub position: [f64; 3],
pub orientation: [f64; 3],
pub velocity: [f64; 3],
pub angular_velocity: [f64; 3],
}
impl SurgicalTool {
pub fn new(
name: impl Into<String>,
geometry: ToolGeometry,
tip_radius: f64,
position: [f64; 3],
) -> Self {
Self {
name: name.into(),
geometry,
tip_radius,
position,
orientation: [0.0, 1.0, 0.0],
velocity: [0.0; 3],
angular_velocity: [0.0; 3],
}
}
pub fn translate(&mut self, delta: [f64; 3]) {
self.position = add3(self.position, delta);
self.velocity = delta;
}
pub fn contact_depth(&self, node_pos: [f64; 3], node_radius: f64) -> f64 {
let dist = norm3(sub3(node_pos, self.position));
let threshold = self.tip_radius + node_radius;
threshold - dist
}
pub fn is_in_contact(&self, node_pos: [f64; 3], node_radius: f64) -> bool {
self.contact_depth(node_pos, node_radius) > 0.0
}
pub fn contact_normal(&self, node_pos: [f64; 3]) -> [f64; 3] {
normalize3(sub3(node_pos, self.position))
}
pub fn integrate(&mut self, dt: f64) {
self.position = add3(self.position, scale3(self.velocity, dt));
}
}
#[derive(Debug, Clone)]
pub struct CuttingModel {
pub edges: Vec<TissueEdge>,
pub gc: f64,
pub sharpness: f64,
}
impl CuttingModel {
pub fn new(gc: f64, sharpness: f64) -> Self {
Self {
edges: Vec::new(),
gc,
sharpness: sharpness.clamp(1e-6, 1.0),
}
}
pub fn add_edge(&mut self, edge: TissueEdge) {
self.edges.push(edge);
}
pub fn apply_cut(
&mut self,
contact_pos: [f64; 3],
cutting_force: f64,
influence_radius: f64,
nodes: &[[f64; 3]],
) {
for edge in &mut self.edges {
if edge.is_severed() {
continue;
}
let mid = scale3(add3(nodes[edge.node_a], nodes[edge.node_b]), 0.5);
let dist = norm3(sub3(mid, contact_pos));
if dist < influence_radius {
let attenuation = 1.0 - dist / influence_radius;
let delta_d = self.sharpness * cutting_force * attenuation / self.gc;
edge.apply_damage(delta_d);
}
}
}
pub fn num_severed(&self) -> usize {
self.edges.iter().filter(|e| e.is_severed()).count()
}
pub fn crack_path(&self, nodes: &[[f64; 3]]) -> Vec<[f64; 3]> {
self.edges
.iter()
.filter(|e| e.is_severed())
.map(|e| scale3(add3(nodes[e.node_a], nodes[e.node_b]), 0.5))
.collect()
}
}
#[derive(Debug, Clone)]
pub struct TissueNode {
pub position: [f64; 3],
pub velocity: [f64; 3],
pub force: [f64; 3],
pub mass: f64,
pub is_static: bool,
pub layer_index: usize,
}
impl TissueNode {
pub fn new(position: [f64; 3], mass: f64, layer_index: usize) -> Self {
Self {
position,
velocity: [0.0; 3],
force: [0.0; 3],
mass,
is_static: false,
layer_index,
}
}
pub fn new_static(position: [f64; 3], layer_index: usize) -> Self {
Self {
is_static: true,
mass: 0.0,
..Self::new(position, 0.0, layer_index)
}
}
pub fn inv_mass(&self) -> f64 {
if self.is_static || self.mass <= 0.0 {
0.0
} else {
1.0 / self.mass
}
}
}
#[derive(Debug, Clone)]
pub struct SimulationStep {
pub tissue: TissueModel,
pub tool: SurgicalTool,
pub cutting: CuttingModel,
pub sutures: Vec<SuturModel>,
pub haptic: HapticFeedback,
pub contact_stiffness: f64,
pub node_radius: f64,
pub rest_positions: Vec<[f64; 3]>,
pub time: f64,
}
impl SimulationStep {
pub fn new(
tissue: TissueModel,
tool: SurgicalTool,
cutting: CuttingModel,
contact_stiffness: f64,
node_radius: f64,
) -> Self {
let rest_positions = tissue.nodes.iter().map(|n| n.position).collect();
Self {
tissue,
tool,
cutting,
sutures: Vec::new(),
haptic: HapticFeedback::zero(),
contact_stiffness,
node_radius,
rest_positions,
time: 0.0,
}
}
pub fn add_suture(&mut self, suture: SuturModel) {
self.sutures.push(suture);
}
pub fn advance(&mut self, dt: f64, gravity: [f64; 3]) {
self.haptic.reset();
let tool_pos = self.tool.position;
let tool_r = self.tool.tip_radius;
let node_r = self.node_radius;
let k_c = self.contact_stiffness;
for node in &mut self.tissue.nodes {
if node.is_static {
continue;
}
let dist = norm3(sub3(node.position, tool_pos));
let threshold = tool_r + node_r;
if dist < threshold {
let depth = threshold - dist;
let normal = normalize3(sub3(node.position, tool_pos));
let contact_force = scale3(normal, k_c * depth);
node.force = add3(node.force, contact_force);
let moment_arm = sub3(node.position, tool_pos);
self.haptic
.add_contact(scale3(normal, -1.0), depth, k_c, moment_arm);
}
}
let cutting_force = self.haptic.force_magnitude();
let node_positions: Vec<[f64; 3]> = self.tissue.nodes.iter().map(|n| n.position).collect();
self.cutting
.apply_cut(tool_pos, cutting_force, tool_r * 2.0, &node_positions);
self.haptic.cutting_energy += cutting_force * norm3(self.tool.velocity) * dt;
for suture in &mut self.sutures {
if !suture.active {
continue;
}
let (force_a, _tension) = suture.tension_force(&self.tissue.nodes);
let force_b = scale3(force_a, -1.0);
let ia = suture.node_a;
let ib = suture.node_b;
self.tissue.nodes[ia].force = add3(self.tissue.nodes[ia].force, force_a);
self.tissue.nodes[ib].force = add3(self.tissue.nodes[ib].force, force_b);
}
self.tissue.step(dt, gravity);
for suture in &mut self.sutures {
suture.advance_healing(dt);
}
self.tool.integrate(dt);
self.time += dt;
}
pub fn num_cuts(&self) -> usize {
self.cutting.num_severed()
}
}
#[derive(Debug, Clone)]
pub struct NeoHookeanParams {
pub mu: f64,
pub kappa: f64,
pub eta: f64,
}
impl NeoHookeanParams {
pub fn from_young(young_modulus: f64, poisson_ratio: f64, eta: f64) -> Self {
let nu = poisson_ratio;
let mu = young_modulus / (2.0 * (1.0 + nu));
let kappa = young_modulus / (3.0 * (1.0 - 2.0 * nu));
Self { mu, kappa, eta }
}
pub fn strain_energy_density(&self, f: &[f64; 9]) -> f64 {
let j = mat3_det(*f);
if j <= 0.0 {
return 0.0;
}
let ft_f = mat3_sym_product_fft(*f);
let i1 = ft_f[0] + ft_f[4] + ft_f[8];
let ln_j = j.ln();
self.mu / 2.0 * (i1 - 3.0) - self.mu * ln_j + self.kappa / 2.0 * ln_j * ln_j
}
pub fn viscous_stress(&self, f: &[f64; 9]) -> f64 {
let sym_trace = (f[0] + f[4] + f[8]) / 3.0;
self.eta * sym_trace
}
}
#[derive(Debug, Clone)]
pub struct SoftTissueModel {
pub nodes: Vec<[f64; 3]>,
pub velocities: Vec<[f64; 3]>,
pub masses: Vec<f64>,
pub fixed: Vec<bool>,
pub elements: Vec<[usize; 4]>,
pub materials: Vec<NeoHookeanParams>,
pub rest_positions: Vec<[f64; 3]>,
pub alpha: f64,
}
impl SoftTissueModel {
pub fn new(alpha: f64) -> Self {
Self {
nodes: Vec::new(),
velocities: Vec::new(),
masses: Vec::new(),
fixed: Vec::new(),
elements: Vec::new(),
materials: Vec::new(),
rest_positions: Vec::new(),
alpha,
}
}
pub fn add_node(&mut self, position: [f64; 3], mass: f64, is_fixed: bool) -> usize {
let idx = self.nodes.len();
self.nodes.push(position);
self.velocities.push([0.0; 3]);
self.masses.push(mass);
self.fixed.push(is_fixed);
self.rest_positions.push(position);
idx
}
pub fn add_element(&mut self, indices: [usize; 4], material: NeoHookeanParams) {
self.elements.push(indices);
self.materials.push(material);
}
pub fn deformation_gradient(&self, e: usize) -> [f64; 9] {
let idx = self.elements[e];
let x = [
self.nodes[idx[0]],
self.nodes[idx[1]],
self.nodes[idx[2]],
self.nodes[idx[3]],
];
let x_ref = [
self.rest_positions[idx[0]],
self.rest_positions[idx[1]],
self.rest_positions[idx[2]],
self.rest_positions[idx[3]],
];
let d0 = sub3(x_ref[1], x_ref[0]);
let d1 = sub3(x_ref[2], x_ref[0]);
let d2 = sub3(x_ref[3], x_ref[0]);
let e0 = sub3(x[1], x[0]);
let e1 = sub3(x[2], x[0]);
let e2 = sub3(x[3], x[0]);
let ds = [
d0[0], d1[0], d2[0], d0[1], d1[1], d2[1], d0[2], d1[2], d2[2],
];
let dm = [
e0[0], e1[0], e2[0], e0[1], e1[1], e2[1], e0[2], e1[2], e2[2],
];
mat3_mul(dm, mat3_inv(ds))
}
pub fn element_energy(&self, e: usize) -> f64 {
let f = self.deformation_gradient(e);
self.materials[e].strain_energy_density(&f)
}
pub fn total_energy(&self) -> f64 {
(0..self.elements.len())
.map(|e| self.element_energy(e))
.sum()
}
pub fn element_volume(&self, e: usize) -> f64 {
let idx = self.elements[e];
let p0 = self.nodes[idx[0]];
let p1 = self.nodes[idx[1]];
let p2 = self.nodes[idx[2]];
let p3 = self.nodes[idx[3]];
let a = sub3(p1, p0);
let b = sub3(p2, p0);
let c = sub3(p3, p0);
let cr = cross3(b, c);
(dot3(a, cr) / 6.0).abs()
}
pub fn step(&mut self, dt: f64, gravity: [f64; 3]) {
for i in 0..self.nodes.len() {
if self.fixed[i] || self.masses[i] <= 0.0 {
continue;
}
let damp = scale3(self.velocities[i], -self.alpha);
let accel = scale3(add3(gravity, damp), 1.0);
self.velocities[i] = add3(self.velocities[i], scale3(accel, dt));
self.nodes[i] = add3(self.nodes[i], scale3(self.velocities[i], dt));
}
}
pub fn n_elements(&self) -> usize {
self.elements.len()
}
pub fn n_nodes(&self) -> usize {
self.nodes.len()
}
}
#[derive(Debug, Clone)]
pub struct CuttingSimulation {
pub edges: Vec<MeshEdge>,
pub gc: f64,
pub sharpness: f64,
pub severed_indices: Vec<usize>,
}
impl CuttingSimulation {
pub fn new(gc: f64, sharpness: f64) -> Self {
Self {
edges: Vec::new(),
gc,
sharpness: sharpness.clamp(1e-6, 1.0),
severed_indices: Vec::new(),
}
}
pub fn add_edge(&mut self, edge: MeshEdge) {
self.edges.push(edge);
}
pub fn apply_cut(
&mut self,
contact_pos: [f64; 3],
force: f64,
radius: f64,
nodes: &[[f64; 3]],
) {
for (i, edge) in self.edges.iter_mut().enumerate() {
if edge.severed {
continue;
}
let mid = edge.midpoint(nodes);
let dist = norm3(sub3(mid, contact_pos));
if dist < radius {
let atten = 1.0 - dist / radius;
let delta = self.sharpness * force * atten / self.gc;
edge.apply_damage(delta);
if edge.severed
&& !CuttingSimulation::already_recorded(&Self::severed_snapshot(&[i]), &[i])
{
}
}
}
for (i, edge) in self.edges.iter().enumerate() {
if edge.severed && !self.severed_indices.contains(&i) {
self.severed_indices.push(i);
}
}
}
fn already_recorded(indices: &[usize], new: &[usize]) -> bool {
new.iter().all(|n| indices.contains(n))
}
fn severed_snapshot(indices: &[usize]) -> Vec<usize> {
indices.to_vec()
}
pub fn n_severed(&self) -> usize {
self.severed_indices.len()
}
pub fn crack_path(&self, nodes: &[[f64; 3]]) -> Vec<[f64; 3]> {
self.severed_indices
.iter()
.map(|&i| self.edges[i].midpoint(nodes))
.collect()
}
pub fn n_intact(&self) -> usize {
self.edges.iter().filter(|e| !e.severed).count()
}
pub fn remesh(&mut self) -> Vec<MeshEdge> {
let severed: Vec<MeshEdge> = self
.severed_indices
.iter()
.map(|&i| self.edges[i].clone())
.collect();
self.edges.retain(|e| !e.severed);
self.severed_indices.clear();
severed
}
}
#[derive(Debug, Clone)]
pub struct ContactParams {
pub stiffness: f64,
pub damping: f64,
pub friction: f64,
pub static_friction_multiplier: f64,
}
impl ContactParams {
pub fn new(stiffness: f64, damping: f64, friction: f64) -> Self {
Self {
stiffness,
damping,
friction,
static_friction_multiplier: 1.2_f64,
}
}
}
#[derive(Debug, Clone)]
pub struct CollisionResponseSurgical {
pub params: ContactParams,
pub normal_force: f64,
pub friction_force: [f64; 3],
}
impl CollisionResponseSurgical {
pub fn new(params: ContactParams) -> Self {
Self {
params,
normal_force: 0.0,
friction_force: [0.0; 3],
}
}
pub fn compute_contact(
&mut self,
penetration_depth: f64,
contact_normal: [f64; 3],
relative_velocity: [f64; 3],
) -> ([f64; 3], [f64; 3]) {
if penetration_depth <= 0.0 {
self.normal_force = 0.0;
self.friction_force = [0.0; 3];
return ([0.0; 3], [0.0; 3]);
}
let vn = dot3(relative_velocity, contact_normal);
let fn_val =
(self.params.stiffness * penetration_depth - self.params.damping * vn).max(0.0);
self.normal_force = fn_val;
let vt = sub3(relative_velocity, scale3(contact_normal, vn));
let vt_norm = norm3(vt);
let ft = if vt_norm > 1e-12 {
let ft_max = self.params.friction * fn_val;
let dir = normalize3(vt);
scale3(dir, -ft_max.min(vt_norm * self.params.stiffness))
} else {
[0.0; 3]
};
self.friction_force = ft;
let force_on_node = add3(scale3(contact_normal, fn_val), ft);
let force_on_tool = scale3(force_on_node, -1.0);
(force_on_node, force_on_tool)
}
pub fn total_force_magnitude(&self) -> f64 {
let total = add3(
scale3([0.0, 0.0, 1.0], self.normal_force),
self.friction_force,
);
norm3(total)
}
}
#[derive(Debug, Clone)]
pub struct SutureThread {
pub node_a: usize,
pub node_b: usize,
pub rest_length: f64,
pub material: SutureMaterial,
pub intact: bool,
pub healing: f64,
}
impl SutureThread {
pub fn new(node_a: usize, node_b: usize, rest_length: f64, material: SutureMaterial) -> Self {
Self {
node_a,
node_b,
rest_length,
material,
intact: true,
healing: 0.0,
}
}
pub fn force(&self, nodes: &[[f64; 3]]) -> ([f64; 3], f64) {
if !self.intact {
return ([0.0; 3], 0.0);
}
let pa = nodes[self.node_a];
let pb = nodes[self.node_b];
let delta = sub3(pb, pa);
let dist = norm3(delta);
if dist < 1e-12 {
return ([0.0; 3], 0.0);
}
let elongation = (dist - self.rest_length).max(0.0);
let tension = self.material.elastic_force(elongation);
let dir = normalize3(delta);
(scale3(dir, tension), tension)
}
pub fn heal(&mut self, dt: f64, tau: f64) {
if self.intact {
self.healing = (self.healing + dt / tau).min(1.0);
}
}
pub fn cut(&mut self) {
self.intact = false;
}
pub fn is_healed(&self) -> bool {
self.healing >= 1.0
}
}
#[derive(Debug, Clone)]
pub struct BloodVessel {
pub position: [f64; 3],
pub radius: f64,
pub wall_strength: f64,
pub pressure: f64,
pub ruptured: bool,
}
impl BloodVessel {
pub fn new(position: [f64; 3], radius: f64, wall_strength: f64, pressure: f64) -> Self {
Self {
position,
radius,
wall_strength,
pressure,
ruptured: false,
}
}
pub fn check_rupture(&mut self, applied_stress: f64) -> bool {
if applied_stress > self.wall_strength {
self.ruptured = true;
}
self.ruptured
}
pub fn bleeding_rate(&self, blood_viscosity: f64) -> f64 {
if !self.ruptured || blood_viscosity < 1e-15 {
return 0.0;
}
let l = self.radius.max(1e-6);
std::f64::consts::PI * self.radius.powi(4) * self.pressure / (8.0 * blood_viscosity * l)
}
pub fn blood_loss(&self, dt: f64, blood_viscosity: f64) -> f64 {
self.bleeding_rate(blood_viscosity) * dt
}
}
#[derive(Debug, Clone)]
pub struct TissueEdge {
pub node_a: usize,
pub node_b: usize,
pub rest_length: f64,
pub status: EdgeStatus,
pub damage: f64,
}
impl TissueEdge {
pub fn new(node_a: usize, node_b: usize, rest_length: f64) -> Self {
Self {
node_a,
node_b,
rest_length,
status: EdgeStatus::Intact,
damage: 0.0,
}
}
pub fn apply_damage(&mut self, delta_damage: f64) {
self.damage = (self.damage + delta_damage).min(1.0);
self.status = if self.damage >= 1.0 {
EdgeStatus::Severed
} else if self.damage > 0.0 {
EdgeStatus::Partial(self.damage)
} else {
EdgeStatus::Intact
};
}
pub fn is_severed(&self) -> bool {
matches!(self.status, EdgeStatus::Severed)
}
}
#[derive(Debug, Clone)]
pub struct MeshEdge {
pub node_a: usize,
pub node_b: usize,
pub severed: bool,
pub damage: f64,
pub damage_threshold: f64,
}
impl MeshEdge {
pub fn new(node_a: usize, node_b: usize, damage_threshold: f64) -> Self {
Self {
node_a,
node_b,
severed: false,
damage: 0.0,
damage_threshold,
}
}
pub fn apply_damage(&mut self, delta: f64) {
self.damage = (self.damage + delta).min(1.0);
if self.damage >= self.damage_threshold {
self.severed = true;
}
}
pub fn midpoint(&self, nodes: &[[f64; 3]]) -> [f64; 3] {
scale3(add3(nodes[self.node_a], nodes[self.node_b]), 0.5)
}
}
#[derive(Debug, Clone)]
pub struct BleedingModel {
pub vessels: Vec<BloodVessel>,
pub blood_viscosity: f64,
pub total_blood_loss: f64,
}
impl BleedingModel {
pub fn new(vessels: Vec<BloodVessel>, blood_viscosity: f64) -> Self {
Self {
vessels,
blood_viscosity,
total_blood_loss: 0.0,
}
}
pub fn apply_trauma(&mut self, contact_pos: [f64; 3], force: f64, radius: f64) -> usize {
let mut new_ruptures = 0;
for vessel in &mut self.vessels {
if vessel.ruptured {
continue;
}
let dist = norm3(sub3(vessel.position, contact_pos));
if dist < radius {
let stress = force / (std::f64::consts::PI * (radius * radius).max(1e-20))
* (1.0 - dist / radius);
if vessel.check_rupture(stress) {
new_ruptures += 1;
}
}
}
new_ruptures
}
pub fn step(&mut self, dt: f64) {
for vessel in &self.vessels {
self.total_blood_loss += vessel.blood_loss(dt, self.blood_viscosity);
}
}
pub fn n_ruptured(&self) -> usize {
self.vessels.iter().filter(|v| v.ruptured).count()
}
pub fn total_bleeding_rate(&self) -> f64 {
self.vessels
.iter()
.map(|v| v.bleeding_rate(self.blood_viscosity))
.sum()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum HealingState {
Active,
Healing(f64),
Healed,
}
#[derive(Debug, Clone)]
pub struct SutureMaterial {
pub young_modulus: f64,
pub cross_section: f64,
pub ultimate_stress: f64,
pub knot_efficiency: f64,
pub pretension: f64,
}
impl SutureMaterial {
pub fn new(
young_modulus: f64,
cross_section: f64,
ultimate_stress: f64,
knot_efficiency: f64,
pretension: f64,
) -> Self {
Self {
young_modulus,
cross_section,
ultimate_stress,
knot_efficiency: knot_efficiency.clamp(0.0, 1.0),
pretension,
}
}
pub fn axial_stiffness(&self) -> f64 {
self.young_modulus * self.cross_section
}
pub fn max_force(&self) -> f64 {
self.ultimate_stress * self.cross_section * self.knot_efficiency
}
pub fn elastic_force(&self, elongation: f64) -> f64 {
(self.pretension + self.axial_stiffness() * elongation.max(0.0)).min(self.max_force())
}
pub fn will_fail(&self, applied_force: f64) -> bool {
applied_force > self.max_force()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum EdgeStatus {
Intact,
Partial(f64),
Severed,
}
#[derive(Debug, Clone)]
pub struct SuturModel {
pub node_a: usize,
pub node_b: usize,
pub target_gap: f64,
pub stiffness: f64,
pub max_tension: f64,
pub healing: HealingState,
pub heal_time: f64,
pub heal_tau: f64,
pub active: bool,
}
impl SuturModel {
pub fn new(
node_a: usize,
node_b: usize,
target_gap: f64,
stiffness: f64,
max_tension: f64,
heal_tau: f64,
) -> Self {
Self {
node_a,
node_b,
target_gap,
stiffness,
max_tension,
healing: HealingState::Active,
heal_time: 0.0,
heal_tau,
active: true,
}
}
pub fn tension_force(&self, nodes: &[TissueNode]) -> ([f64; 3], f64) {
let pa = nodes[self.node_a].position;
let pb = nodes[self.node_b].position;
let delta = sub3(pb, pa);
let dist = norm3(delta);
if dist < 1e-12 {
return ([0.0; 3], 0.0);
}
let n = normalize3(delta);
let tension = self.stiffness * (dist - self.target_gap);
let force = scale3(n, tension);
(force, tension.abs())
}
pub fn advance_healing(&mut self, dt: f64) {
if !self.active {
return;
}
self.heal_time += dt;
let fraction = 1.0 - (-self.heal_time / self.heal_tau).exp();
self.healing = if fraction >= 0.99 {
HealingState::Healed
} else {
HealingState::Healing(fraction)
};
}
pub fn check_failure(&mut self, nodes: &[TissueNode]) -> bool {
let (_force, tension) = self.tension_force(nodes);
if tension > self.max_tension {
self.active = false;
true
} else {
false
}
}
pub fn healing_fraction(&self) -> f64 {
match self.healing {
HealingState::Active => 0.0,
HealingState::Healing(f) => f,
HealingState::Healed => 1.0,
}
}
}