use super::shapes::{Box3D, Sphere, Triangle3D};
#[derive(Debug, Clone)]
pub struct CollisionInfo {
pub contact_point: [f64; 3],
pub normal: [f64; 3],
pub penetration: f64,
}
#[allow(dead_code)]
#[allow(clippy::too_many_arguments)]
pub fn sphere_sphere_impulse(
sphere1_pos: &[f64; 3],
sphere1_vel: &[f64; 3],
sphere1_mass: f64,
sphere2_pos: &[f64; 3],
sphere2_vel: &[f64; 3],
sphere2_mass: f64,
restitution: f64,
) -> ([f64; 3], [f64; 3]) {
let normal = [
sphere2_pos[0] - sphere1_pos[0],
sphere2_pos[1] - sphere1_pos[1],
sphere2_pos[2] - sphere1_pos[2],
];
let normal_length =
(normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2]).sqrt();
if normal_length < 1e-10 {
return ([0.0, 0.0, 0.0], [0.0, 0.0, 0.0]);
}
let unit_normal = [
normal[0] / normal_length,
normal[1] / normal_length,
normal[2] / normal_length,
];
let relative_velocity = [
sphere2_vel[0] - sphere1_vel[0],
sphere2_vel[1] - sphere1_vel[1],
sphere2_vel[2] - sphere1_vel[2],
];
let velocity_along_normal = relative_velocity[0] * unit_normal[0]
+ relative_velocity[1] * unit_normal[1]
+ relative_velocity[2] * unit_normal[2];
if velocity_along_normal > 0.0 {
return ([0.0, 0.0, 0.0], [0.0, 0.0, 0.0]);
}
let inverse_mass1 = if sphere1_mass == 0.0 {
0.0
} else {
1.0 / sphere1_mass
};
let inverse_mass2 = if sphere2_mass == 0.0 {
0.0
} else {
1.0 / sphere2_mass
};
let impulse_scalar =
-(1.0 + restitution) * velocity_along_normal / (inverse_mass1 + inverse_mass2);
let impulse1 = [
-impulse_scalar * unit_normal[0] * inverse_mass1,
-impulse_scalar * unit_normal[1] * inverse_mass1,
-impulse_scalar * unit_normal[2] * inverse_mass1,
];
let impulse2 = [
impulse_scalar * unit_normal[0] * inverse_mass2,
impulse_scalar * unit_normal[1] * inverse_mass2,
impulse_scalar * unit_normal[2] * inverse_mass2,
];
(impulse1, impulse2)
}
#[allow(dead_code)]
pub fn resolve_sphere_sphere_penetration(
sphere1: &Sphere,
sphere1_mass: f64,
sphere2: &Sphere,
sphere2_mass: f64,
) -> ([f64; 3], [f64; 3]) {
let normal = [
sphere2.center[0] - sphere1.center[0],
sphere2.center[1] - sphere1.center[1],
sphere2.center[2] - sphere1.center[2],
];
let distance = (normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2]).sqrt();
if distance >= sphere1.radius + sphere2.radius {
return ([0.0, 0.0, 0.0], [0.0, 0.0, 0.0]);
}
let penetration = sphere1.radius + sphere2.radius - distance;
let unit_normal = if distance < 1e-10 {
[1.0, 0.0, 0.0]
} else {
[
normal[0] / distance,
normal[1] / distance,
normal[2] / distance,
]
};
let total_mass = sphere1_mass + sphere2_mass;
let mass_ratio1 = if total_mass == 0.0 {
0.5
} else {
sphere2_mass / total_mass
};
let mass_ratio2 = if total_mass == 0.0 {
0.5
} else {
sphere1_mass / total_mass
};
let adjustment1 = [
-unit_normal[0] * penetration * mass_ratio1,
-unit_normal[1] * penetration * mass_ratio1,
-unit_normal[2] * penetration * mass_ratio1,
];
let adjustment2 = [
unit_normal[0] * penetration * mass_ratio2,
unit_normal[1] * penetration * mass_ratio2,
unit_normal[2] * penetration * mass_ratio2,
];
(adjustment1, adjustment2)
}
#[allow(clippy::too_many_arguments)]
#[allow(dead_code)]
pub fn sphere_box_impulse(
_sphere_pos: &[f64; 3],
sphere_vel: &[f64; 3],
sphere_mass: f64,
_box_pos: &[f64; 3],
box_vel: &[f64; 3],
box_mass: f64,
_box_dims: &[f64; 3],
collision_info: &CollisionInfo,
restitution: f64,
) -> ([f64; 3], [f64; 3]) {
let relative_velocity = [
sphere_vel[0] - box_vel[0],
sphere_vel[1] - box_vel[1],
sphere_vel[2] - box_vel[2],
];
let velocity_along_normal = relative_velocity[0] * collision_info.normal[0]
+ relative_velocity[1] * collision_info.normal[1]
+ relative_velocity[2] * collision_info.normal[2];
if velocity_along_normal > 0.0 {
return ([0.0, 0.0, 0.0], [0.0, 0.0, 0.0]);
}
let inverse_mass1 = if sphere_mass == 0.0 {
0.0
} else {
1.0 / sphere_mass
};
let inverse_mass2 = if box_mass == 0.0 { 0.0 } else { 1.0 / box_mass };
let impulse_scalar =
-(1.0 + restitution) * velocity_along_normal / (inverse_mass1 + inverse_mass2);
let impulse1 = [
impulse_scalar * collision_info.normal[0] * inverse_mass1,
impulse_scalar * collision_info.normal[1] * inverse_mass1,
impulse_scalar * collision_info.normal[2] * inverse_mass1,
];
let impulse2 = [
-impulse_scalar * collision_info.normal[0] * inverse_mass2,
-impulse_scalar * collision_info.normal[1] * inverse_mass2,
-impulse_scalar * collision_info.normal[2] * inverse_mass2,
];
(impulse1, impulse2)
}
#[allow(dead_code)]
pub fn find_sphere_box_collision(sphere: &Sphere, box3d: &Box3D) -> Option<CollisionInfo> {
let closest_x = sphere.center[0].max(box3d.min[0]).min(box3d.max[0]);
let closest_y = sphere.center[1].max(box3d.min[1]).min(box3d.max[1]);
let closest_z = sphere.center[2].max(box3d.min[2]).min(box3d.max[2]);
let closest_point = [closest_x, closest_y, closest_z];
let dx = sphere.center[0] - closest_point[0];
let dy = sphere.center[1] - closest_point[1];
let dz = sphere.center[2] - closest_point[2];
let distance_squared = dx * dx + dy * dy + dz * dz;
if distance_squared > sphere.radius * sphere.radius {
return None;
}
let distance = distance_squared.sqrt();
let normal = if distance < 1e-10 {
let box_center = [
(box3d.min[0] + box3d.max[0]) * 0.5,
(box3d.min[1] + box3d.max[1]) * 0.5,
(box3d.min[2] + box3d.max[2]) * 0.5,
];
let half_width = (box3d.max[0] - box3d.min[0]) * 0.5;
let half_height = (box3d.max[1] - box3d.min[1]) * 0.5;
let half_depth = (box3d.max[2] - box3d.min[2]) * 0.5;
let dx = (sphere.center[0] - box_center[0]).abs() / half_width;
let dy = (sphere.center[1] - box_center[1]).abs() / half_height;
let dz = (sphere.center[2] - box_center[2]).abs() / half_depth;
if dx > dy && dx > dz {
[(sphere.center[0] - box_center[0]).signum(), 0.0, 0.0]
} else if dy > dz {
[0.0, (sphere.center[1] - box_center[1]).signum(), 0.0]
} else {
[0.0, 0.0, (sphere.center[2] - box_center[2]).signum()]
}
} else {
[dx / distance, dy / distance, dz / distance]
};
let penetration = sphere.radius - distance;
Some(CollisionInfo {
contact_point: closest_point,
normal,
penetration,
})
}
#[allow(dead_code)]
pub fn find_sphere_triangle_collision(
sphere: &Sphere,
triangle: &Triangle3D,
) -> Option<CollisionInfo> {
let edge1 = [
triangle.v2[0] - triangle.v1[0],
triangle.v2[1] - triangle.v1[1],
triangle.v2[2] - triangle.v1[2],
];
let edge2 = [
triangle.v3[0] - triangle.v1[0],
triangle.v3[1] - triangle.v1[1],
triangle.v3[2] - triangle.v1[2],
];
let normal = [
edge1[1] * edge2[2] - edge1[2] * edge2[1],
edge1[2] * edge2[0] - edge1[0] * edge2[2],
edge1[0] * edge2[1] - edge1[1] * edge2[0],
];
let normal_length =
(normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2]).sqrt();
if normal_length < 1e-10 {
return None; }
let normalized_normal = [
normal[0] / normal_length,
normal[1] / normal_length,
normal[2] / normal_length,
];
let dist_to_plane = (sphere.center[0] - triangle.v1[0]) * normalized_normal[0]
+ (sphere.center[1] - triangle.v1[1]) * normalized_normal[1]
+ (sphere.center[2] - triangle.v1[2]) * normalized_normal[2];
if dist_to_plane.abs() > sphere.radius {
return None;
}
let projected_center = [
sphere.center[0] - dist_to_plane * normalized_normal[0],
sphere.center[1] - dist_to_plane * normalized_normal[1],
sphere.center[2] - dist_to_plane * normalized_normal[2],
];
let inside_triangle =
super::narrowphase::point_triangle3d_collision(&projected_center, triangle);
if inside_triangle {
let contact_normal = if dist_to_plane > 0.0 {
normalized_normal
} else {
[
-normalized_normal[0],
-normalized_normal[1],
-normalized_normal[2],
]
};
return Some(CollisionInfo {
contact_point: [
sphere.center[0] - contact_normal[0] * sphere.radius,
sphere.center[1] - contact_normal[1] * sphere.radius,
sphere.center[2] - contact_normal[2] * sphere.radius,
],
normal: contact_normal,
penetration: sphere.radius - dist_to_plane.abs(),
});
}
let edges = [
(triangle.v1, triangle.v2),
(triangle.v2, triangle.v3),
(triangle.v3, triangle.v1),
];
let mut closest_point = [0.0, 0.0, 0.0];
let mut min_distance = f64::INFINITY;
for &(start, end) in &edges {
let edge_vec = [end[0] - start[0], end[1] - start[1], end[2] - start[2]];
let sphere_vec = [
sphere.center[0] - start[0],
sphere.center[1] - start[1],
sphere.center[2] - start[2],
];
let edge_length_squared =
edge_vec[0] * edge_vec[0] + edge_vec[1] * edge_vec[1] + edge_vec[2] * edge_vec[2];
let t = (sphere_vec[0] * edge_vec[0]
+ sphere_vec[1] * edge_vec[1]
+ sphere_vec[2] * edge_vec[2])
/ edge_length_squared;
let t_clamped = t.clamp(0.0, 1.0);
let point = [
start[0] + t_clamped * edge_vec[0],
start[1] + t_clamped * edge_vec[1],
start[2] + t_clamped * edge_vec[2],
];
let dx = sphere.center[0] - point[0];
let dy = sphere.center[1] - point[1];
let dz = sphere.center[2] - point[2];
let distance_squared = dx * dx + dy * dy + dz * dz;
if distance_squared < min_distance {
min_distance = distance_squared;
closest_point = point;
}
}
let vertices = [triangle.v1, triangle.v2, triangle.v3];
for &vertex in &vertices {
let dx = sphere.center[0] - vertex[0];
let dy = sphere.center[1] - vertex[1];
let dz = sphere.center[2] - vertex[2];
let distance_squared = dx * dx + dy * dy + dz * dz;
if distance_squared < min_distance {
min_distance = distance_squared;
closest_point = vertex;
}
}
if min_distance > sphere.radius * sphere.radius {
return None;
}
let distance = min_distance.sqrt();
let normal = [
(sphere.center[0] - closest_point[0]) / distance,
(sphere.center[1] - closest_point[1]) / distance,
(sphere.center[2] - closest_point[2]) / distance,
];
Some(CollisionInfo {
contact_point: closest_point,
normal,
penetration: sphere.radius - distance,
})
}