1use crate::kinematic_traits::{
4 Joints, Kinematics, Solutions, ENV_START_IDX, J1, J5, J6, J_BASE, J_TOOL,
5};
6
7use nalgebra::Isometry3;
8use parry3d::shape::TriMesh;
9use rayon::prelude::{IntoParallelRefIterator, ParallelIterator};
10use std::collections::{HashMap, HashSet};
11use parry3d::bounding_volume::{Aabb, BoundingVolume};
12use parry3d::math::Point;
13
14pub struct BaseBody {
20 pub mesh: TriMesh,
21 pub base_pose: Isometry3<f32>,
22}
23
24pub struct CollisionBody {
28 pub mesh: TriMesh,
30 pub pose: Isometry3<f32>,
32}
33
34pub fn transform_mesh(shape: &TriMesh, local_transform: &Isometry3<f32>) -> TriMesh {
37 TriMesh::new(
39 shape
40 .vertices()
41 .iter()
42 .map(|v| local_transform.transform_point(v))
43 .collect(),
44 shape.indices().to_vec(),
45 ).expect("Failed to build trimesh")
46}
47
48struct CollisionTask<'a> {
51 i: u16, j: u16, transform_i: &'a Isometry3<f32>,
54 transform_j: &'a Isometry3<f32>,
55 shape_i: &'a TriMesh,
56 shape_j: &'a TriMesh,
57}
58
59impl CollisionTask<'_> {
60 #[must_use = "Ignoring collision check may cause collision."]
61 fn collides(&self, safety: &SafetyDistances) -> Option<(u16, u16)> {
62 let r_min = *safety.min_distance(self.i, self.j);
63 let collides = if r_min <= NEVER_COLLIDES {
64 false
65 } else if r_min == TOUCH_ONLY {
66 parry3d::query::intersection_test(
67 self.transform_i,
68 self.shape_i,
69 self.transform_j,
70 self.shape_j,
71 )
72 .expect(SUPPORTED)
73 } else {
74 let (sm_shape, sm_transform, bg_shape, bg_transform) =
77 if self.shape_i.vertices().len() < self.shape_j.vertices().len() {
78 (self.shape_i, self.transform_i, self.shape_j, self.transform_j)
79 } else {
80 (self.shape_j, self.transform_j, self.shape_i, self.transform_i)
81 };
82 let am_aaabb = sm_shape.local_aabb().loosened(r_min);
86 let sm_abb_mesh = build_trimesh_from_aabb(am_aaabb);
87 if !parry3d::query::intersection_test(
88 sm_transform,
89 &sm_abb_mesh,
90 bg_transform,
91 bg_shape,
92 ).expect(SUPPORTED) {
93 false
94 } else {
95 parry3d::query::distance(
96 self.transform_i,
97 self.shape_i,
98 self.transform_j,
99 self.shape_j,
100 )
101 .expect(SUPPORTED)
102 <= r_min
103 }
104 };
105
106 if collides {
107 Some((self.i.min(self.j), self.i.max(self.j)))
108 } else {
109 None
110 }
111 }
112}
113
114fn build_trimesh_from_aabb(aabb: Aabb) -> TriMesh {
116 let min: Point<f32> = aabb.mins;
117 let max: Point<f32> = aabb.maxs;
118 let vertices = vec![
120 min, Point::new(max.x, min.y, min.z), Point::new(min.x, max.y, min.z), Point::new(max.x, max.y, min.z), Point::new(min.x, min.y, max.z), Point::new(max.x, min.y, max.z), Point::new(min.x, max.y, max.z), max, ];
129
130 const INDICES: [[u32; 3]; 12] = [
132 [0, 1, 2],
134 [2, 1, 3],
135
136 [4, 5, 6],
138 [6, 5, 7],
139
140 [2, 3, 6],
142 [6, 3, 7],
143
144 [0, 1, 4],
146 [4, 1, 5],
147
148 [0, 2, 4],
150 [4, 2, 6],
151
152 [1, 3, 5],
154 [5, 3, 7],
155 ];
156
157 TriMesh::new(vertices, INDICES.to_vec()).expect("Failed to build TrimMesh from AABB")
159}
160
161pub struct RobotBody {
163 pub joint_meshes: [TriMesh; 6],
165
166 pub tool: Option<TriMesh>,
168
169 pub base: Option<BaseBody>,
171
172 pub collision_environment: Vec<CollisionBody>,
174
175 pub safety: SafetyDistances,
179}
180
181pub const NEVER_COLLIDES: f32 = -1.0;
184
185pub const TOUCH_ONLY: f32 = 0.0;
188
189#[derive(Clone, Copy, Debug, PartialEq)]
190pub enum CheckMode {
191 FirstCollisionOnly,
192 AllCollsions,
193 NoCheck,
194}
195
196#[derive(Clone, Debug)]
200pub struct SafetyDistances {
201 pub to_environment: f32,
203
204 pub to_robot_default: f32,
206
207 pub special_distances: HashMap<(u16, u16), f32>,
231
232 pub mode: CheckMode,
235}
236
237impl SafetyDistances {
238 pub fn distances(pairs: &[((usize, usize), f32)]) -> HashMap<(u16, u16), f32> {
241 let mut result = HashMap::with_capacity(pairs.len());
242
243 for &((a, b), value) in pairs {
244 result.insert((a as u16, b as u16), value);
246 }
247 result
248 }
249
250 pub fn min_distance(&self, from: u16, to: u16) -> &f32 {
253 if let Some(r) = self.special_distances.get(&(from, to)) {
254 return r;
255 } else if let Some(r) = self.special_distances.get(&(to, from)) {
256 return r;
257 } else if from as usize >= ENV_START_IDX || to as usize >= ENV_START_IDX {
258 return &self.to_environment;
259 } else {
260 return &self.to_robot_default;
261 }
262 }
263
264 pub fn standard(mode: CheckMode) -> SafetyDistances {
268 SafetyDistances {
269 to_environment: TOUCH_ONLY,
270 to_robot_default: TOUCH_ONLY,
271 special_distances: HashMap::new(),
272 mode,
273 }
274 }
275}
276
277impl RobotBody {
279 #[must_use = "Ignoring collision check may cause collision."]
283 pub fn collision_details(
284 &self,
285 qs: &Joints,
286 kinematics: &dyn Kinematics,
287 ) -> Vec<(usize, usize)> {
288 let joint_poses = kinematics.forward_with_joint_poses(qs);
289 let joint_poses_f32: [Isometry3<f32>; 6] = joint_poses.map(|pose| pose.cast::<f32>());
290 self.detect_collisions(&joint_poses_f32, &self.safety, None)
291 }
292
293 #[must_use = "Ignoring collision check may cause collision."]
297 pub fn collides(&self, qs: &Joints, kinematics: &dyn Kinematics) -> bool {
298 if self.safety.mode == CheckMode::NoCheck {
299 return false;
300 }
301 let joint_poses = kinematics.forward_with_joint_poses(qs);
302 let joint_poses_f32: [Isometry3<f32>; 6] = joint_poses.map(|pose| pose.cast::<f32>());
303 let safety = &self.safety;
304 let override_mode = Some(CheckMode::FirstCollisionOnly);
305 let empty_set: HashSet<usize> = HashSet::with_capacity(0);
306 !self
307 .detect_collisions_with_skips(&joint_poses_f32, &safety, &override_mode, &empty_set)
308 .is_empty()
309 }
310
311 #[must_use = "Ignoring collision check may cause collision."]
315 pub fn near(
316 &self,
317 qs: &Joints,
318 kinematics: &dyn Kinematics,
319 safety_distances: &SafetyDistances,
320 ) -> Vec<(usize, usize)> {
321 let joint_poses = kinematics.forward_with_joint_poses(qs);
322 let joint_poses_f32: [Isometry3<f32>; 6] = joint_poses.map(|pose| pose.cast::<f32>());
323 self.detect_collisions(&joint_poses_f32, &safety_distances, None)
324 }
325
326 pub fn non_colliding_offsets(
331 &self,
332 initial: &Joints,
333 from: &Joints,
334 to: &Joints,
335 kinematics: &dyn Kinematics,
336 ) -> Solutions {
337 let mut tasks = Vec::with_capacity(12);
339 for joint_index in 0..6 {
340 for &target in &[from, to] {
341 tasks.push((joint_index, target));
342 }
343 }
344
345 tasks
347 .par_iter()
348 .filter_map(|&(joint_index, target)| {
349 let mut new_joints = *initial;
350 new_joints[joint_index] = target[joint_index];
351
352 if let Some(constraints) = kinematics.constraints() {
354 if !constraints.compliant(&new_joints) {
355 return None;
356 }
357 }
358
359 let joint_poses = kinematics.forward_with_joint_poses(&new_joints);
361 let joint_poses_f32: [Isometry3<f32>; 6] =
362 joint_poses.map(|pose| pose.cast::<f32>());
363
364 let skip_indices: HashSet<usize> = (0..joint_index).collect();
366
367 if self
369 .detect_collisions_with_skips(
370 &joint_poses_f32,
371 &self.safety,
372 &Some(CheckMode::FirstCollisionOnly),
373 &skip_indices,
374 )
375 .is_empty()
376 {
377 return Some(new_joints); } else {
379 return None;
380 }
381 })
382 .collect() }
384}
385
386const SUPPORTED: &'static str = "Mesh intersection should be supported by Parry3d";
387
388impl RobotBody {
389 fn process_collision_tasks(
391 tasks: Vec<CollisionTask>,
392 safety: &SafetyDistances,
393 override_mode: &Option<CheckMode>,
394 ) -> Vec<(u16, u16)> {
395 let mode = override_mode.unwrap_or(safety.mode);
396
397 if mode == CheckMode::NoCheck {
398 Vec::new()
399 } else if mode == CheckMode::FirstCollisionOnly {
400 tasks
402 .par_iter()
403 .find_map_any(|task| task.collides(&safety))
404 .into_iter() .collect()
406 } else {
407 tasks
409 .par_iter()
410 .filter_map(|task| task.collides(&safety))
411 .collect()
412 }
413 }
414
415 fn count_tasks(&self, skip: &HashSet<usize>) -> usize {
418 if skip.len() >= 6 {
419 panic!(
420 "At most 5 joints can be skipped, but {} were passed: {:?}",
421 skip.len(),
422 skip
423 );
424 }
425 let tool_env_tasks = if self.tool.is_some() {
426 self.collision_environment.len()
427 } else {
428 0
429 };
430
431 let joint_joint_tasks = 10; let joint_env_tasks = (6 - skip.len()) * self.collision_environment.len();
433 let joint_tool_tasks = if self.tool.is_some() {
434 4 } else {
436 0
437 };
438
439 let joint_base_tasks = if self.base.is_some() {
440 5 } else {
442 0
443 };
444 let tool_base_task = if self.tool.is_some() && self.base.is_some() {
445 1 } else {
447 0
448 };
449
450 tool_env_tasks
452 + joint_joint_tasks
453 + joint_env_tasks
454 + joint_tool_tasks
455 + joint_base_tasks
456 + tool_base_task
457 }
458
459 fn detect_collisions(
460 &self,
461 joint_poses: &[Isometry3<f32>; 6],
462 safety: &SafetyDistances,
463 override_mode: Option<CheckMode>,
464 ) -> Vec<(usize, usize)> {
465 let empty_set: HashSet<usize> = HashSet::with_capacity(0);
466 self.detect_collisions_with_skips(joint_poses, &safety, &override_mode, &empty_set)
468 .iter()
469 .map(|&col_pair| (col_pair.0 as usize, col_pair.1 as usize))
470 .collect()
471 }
472
473 fn detect_collisions_with_skips(
474 &self,
475 joint_poses: &[Isometry3<f32>; 6],
476 safety_distances: &SafetyDistances,
477 override_mode: &Option<CheckMode>,
478 skip: &HashSet<usize>,
479 ) -> Vec<(u16, u16)> {
480 let mut tasks = Vec::with_capacity(self.count_tasks(&skip));
481
482 let check_tool = !skip.contains(&J_TOOL);
484 if check_tool {
485 if let Some(tool) = &self.tool {
486 for (env_idx, env_obj) in self.collision_environment.iter().enumerate() {
487 if self.check_required(J_TOOL, (ENV_START_IDX + env_idx) as usize, &skip) {
488 tasks.push(CollisionTask {
489 i: J_TOOL as u16,
490 j: (ENV_START_IDX + env_idx) as u16,
491 transform_i: &joint_poses[J6],
492 transform_j: &env_obj.pose,
493 shape_i: &tool,
494 shape_j: &env_obj.mesh,
495 });
496 }
497 }
498 }
499 }
500
501 for i in 0..6 {
502 for j in ((i + 1)..6).rev() {
503 if j - i > 1 && self.check_required(i, j, &skip) {
505 tasks.push(CollisionTask {
506 i: i as u16,
507 j: j as u16,
508 transform_i: &joint_poses[i],
509 transform_j: &joint_poses[j],
510 shape_i: &self.joint_meshes[i],
511 shape_j: &self.joint_meshes[j],
512 });
513 }
514 }
515
516 for (env_idx, env_obj) in self.collision_environment.iter().enumerate() {
518 if self.check_required(i, ENV_START_IDX + env_idx, &skip) {
521 tasks.push(CollisionTask {
522 i: i as u16,
523 j: (ENV_START_IDX + env_idx) as u16,
524 transform_i: &joint_poses[i],
525 transform_j: &env_obj.pose,
526 shape_i: &self.joint_meshes[i],
527 shape_j: &env_obj.mesh,
528 });
529 }
530 }
531
532 if check_tool && i != J6 && i != J5 && self.check_required(i, J_TOOL, &skip) {
534 if let Some(tool) = &self.tool {
535 let accessory_pose = &joint_poses[J6];
536 tasks.push(CollisionTask {
537 i: i as u16,
538 j: J_TOOL as u16,
539 transform_i: &joint_poses[i],
540 transform_j: accessory_pose,
541 shape_i: &self.joint_meshes[i],
542 shape_j: &tool,
543 });
544 }
545 }
546
547 if i != J1 && !skip.contains(&i) && self.check_required(i, J1, &skip) {
550 if let Some(base) = &self.base {
551 let accessory = &base.mesh;
552 let accessory_pose = &base.base_pose;
553 tasks.push(CollisionTask {
554 i: i as u16,
555 j: J_BASE as u16,
556 transform_i: &joint_poses[i],
557 transform_j: accessory_pose,
558 shape_i: &self.joint_meshes[i],
559 shape_j: &accessory,
560 });
561 }
562 }
563 }
564
565 if check_tool || self.check_required(J_TOOL, J_BASE, &skip) {
567 if let (Some(tool), Some(base)) = (&self.tool, &self.base) {
568 tasks.push(CollisionTask {
569 i: J_TOOL as u16,
570 j: J_BASE as u16,
571 transform_i: &joint_poses[J6],
572 transform_j: &base.base_pose,
573 shape_i: &tool,
574 shape_j: &base.mesh,
575 });
576 }
577 }
578 Self::process_collision_tasks(tasks, safety_distances, override_mode)
579 }
580
581 fn check_required(&self, i: usize, j: usize, skip: &HashSet<usize>) -> bool {
582 !skip.contains(&i) && !skip.contains(&j) &&
583 self.safety.min_distance(i as u16, j as u16) > &NEVER_COLLIDES
584 }
585}
586
587#[cfg(test)]
588mod tests {
589 use super::*;
590 use nalgebra::Point3;
591 use parry3d::shape::TriMesh;
592
593 fn create_trimesh(x: f32, y: f32, z: f32) -> TriMesh {
594 TriMesh::new(
596 vec![
597 Point3::new(z, y, z),
598 Point3::new(x + 1.0, y, z),
599 Point3::new(x, y + 1.0, z),
600 Point3::new(x, y, z + 1.0),
601 ],
602 vec![[0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3]],
603 ).expect("Failed to build test trimesh")
604 }
605
606 #[test]
607 fn test_collision_detection() {
608 let identity = Isometry3::identity();
611 let joints: [TriMesh; 6] = [
612 create_trimesh(0.0, 0.0, 0.0),
613 create_trimesh(0.01, 0.01, 0.01),
614 create_trimesh(0.1, 0.1, 0.1),
615 transform_mesh(
617 &create_trimesh(0.0, 0.0, 0.0),
618 &Isometry3::translation(20.0, 20.0, 20.0),
619 ),
620 create_trimesh(30.0, 30.0, 30.0),
621 transform_mesh(
623 &create_trimesh(0.0, 0.0, 0.0),
624 &Isometry3::translation(0.02, 0.02, 0.02),
625 ),
626 ];
627
628 let robot = RobotBody {
629 joint_meshes: joints,
630 tool: None,
631 base: None,
632 collision_environment: vec![],
633 safety: SafetyDistances::standard(CheckMode::AllCollsions),
634 };
635
636 let collisions = robot.detect_collisions(&[identity; 6], &robot.safety, None);
637 assert!(
638 !collisions.is_empty(),
639 "Expected at least one collision, but none were detected."
640 );
641
642 assert_eq!(collisions.len(), 4, "Expected exactly 4 collisions.");
644
645 let mut expected_collisions = vec![
647 (0usize, 2usize),
648 (0usize, 5usize),
649 (1usize, 5usize),
650 (2usize, 5usize),
651 ];
652
653 for collision in &mut expected_collisions {
655 let (a, b) = *collision;
656 if a > b {
657 *collision = (b, a); }
659 }
660
661 for (shape_a, shape_b) in &collisions {
662 let mut collision_names = (*shape_a, *shape_b);
663 if collision_names.0 > collision_names.1 {
664 collision_names = (collision_names.1, collision_names.0); }
666
667 assert!(
668 expected_collisions.contains(&collision_names),
669 "Unexpected collision between [{}] and [{}]",
670 shape_a,
671 shape_b
672 );
673 }
674
675 for (shape_a, shape_b) in &collisions {
677 assert_ne!(
678 *shape_a, 3,
679 "Shape [3] should not be involved in any collision."
680 );
681 assert_ne!(
682 *shape_b, 3,
683 "Shape [3] should not be involved in any collision."
684 );
685 }
686 }
687}