use avian3d::prelude::Collider;
use bevy::prelude::*;
use symbios_turtle_3d::{Skeleton, SkeletonPoint};
#[derive(Debug, Clone)]
pub struct PositionedCollider {
pub transform: Transform,
pub collider: Collider,
pub radius: f32,
pub length: f32,
}
pub struct ColliderGenerator {
min_radius: f32,
}
impl Default for ColliderGenerator {
fn default() -> Self {
Self { min_radius: 0.0 }
}
}
impl ColliderGenerator {
pub fn new() -> Self {
Self::default()
}
pub fn with_min_radius(mut self, min_radius: f32) -> Self {
self.min_radius = min_radius.max(0.0);
self
}
pub fn build(&self, skeleton: &Skeleton) -> Option<Collider> {
let parts = self.build_parts(skeleton);
if parts.is_empty() {
return None;
}
Some(Collider::compound(
parts
.into_iter()
.map(|p| (p.transform.translation, p.transform.rotation, p.collider))
.collect::<Vec<_>>(),
))
}
pub fn build_parts(&self, skeleton: &Skeleton) -> Vec<PositionedCollider> {
let mut colliders = Vec::new();
for strand in &skeleton.strands {
if strand.len() < 2 {
continue;
}
self.process_strand(strand, &mut colliders);
}
colliders
}
fn process_strand(&self, points: &[SkeletonPoint], colliders: &mut Vec<PositionedCollider>) {
if points.len() < 2 {
return;
}
let filtered_points: Vec<&SkeletonPoint> = {
let mut result = vec![&points[0]];
for point in &points[1..] {
let last = result.last().unwrap();
if last.position.distance_squared(point.position) > 0.000001 {
result.push(point);
}
}
result
};
if filtered_points.len() < 2 {
return;
}
for i in 0..filtered_points.len() - 1 {
let start = filtered_points[i];
let end = filtered_points[i + 1];
let avg_radius = (start.radius + end.radius) * 0.5;
if avg_radius < self.min_radius {
continue;
}
let segment_vec = end.position - start.position;
let length = segment_vec.length();
if length < 0.0001 {
continue;
}
let center = (start.position + end.position) * 0.5;
let direction = segment_vec / length;
let rotation = Quat::from_rotation_arc(Vec3::Y, direction);
let collider = if length < 2.0 * avg_radius {
Collider::sphere(avg_radius)
} else {
let cylinder_length = length - 2.0 * avg_radius;
Collider::capsule(avg_radius, cylinder_length)
};
colliders.push(PositionedCollider {
transform: Transform::from_translation(center).with_rotation(rotation),
collider,
radius: avg_radius,
length,
});
}
}
}