1use std::{
17 collections::HashMap,
18 path::Path,
19 time::{Duration, Instant},
20};
21
22use k::nalgebra as na;
23use na::RealField;
24use ncollide3d::{
25 partitioning::{BVH, BVT},
26 query,
27 shape::{Compound, Shape, ShapeHandle},
28};
29use tracing::{debug, warn};
30
31use super::urdf::{k_link_geometry_to_shape_handle, urdf_geometry_to_shape_handle};
32use crate::errors::*;
33
34type NameShapeMap<T> = HashMap<String, Vec<(ShapeHandle<T>, na::Isometry3<T>)>>;
35
36pub struct EnvCollisionNames<'a, 'b, T>
38where
39 T: RealField + Copy,
40{
41 detector: &'a CollisionDetector<T>,
42 target_shape: &'b dyn Shape<T>,
43 target_pose: &'b na::Isometry3<T>,
44 joints: Vec<&'b k::Node<T>>,
45 index: usize,
46}
47
48impl<'a, 'b, T> EnvCollisionNames<'a, 'b, T>
49where
50 T: RealField + Copy + k::SubsetOf<f64>,
51{
52 pub fn new(
53 detector: &'a CollisionDetector<T>,
54 robot: &'b k::Chain<T>,
55 target_shape: &'b dyn Shape<T>,
56 target_pose: &'b na::Isometry3<T>,
57 ) -> Self {
58 robot.update_transforms();
59 let joints = robot.iter().collect();
60 Self {
61 detector,
62 target_shape,
63 target_pose,
64 joints,
65 index: 0,
66 }
67 }
68}
69
70impl<T> Iterator for EnvCollisionNames<'_, '_, T>
71where
72 T: RealField + Copy + k::SubsetOf<f64>,
73{
74 type Item = String;
75
76 fn next(&mut self) -> Option<String> {
77 if self.joints.len() <= self.index {
78 return None;
79 }
80 let joint = self.joints[self.index];
81 self.index += 1;
82
83 let joint_pose = joint.world_transform().unwrap();
85 let joint_name = &joint.joint().name;
86
87 match self.detector.name_collision_model_map.get(joint_name) {
88 Some(obj_vec) => {
89 for obj in obj_vec {
91 let obj_pose = joint_pose * obj.1;
92 let aabb1 = obj.0.aabb(&(obj_pose));
93 let aabb2 = self.target_shape.aabb(self.target_pose);
94
95 let bvt = BVT::new_balanced(vec![(0usize, aabb2)]);
96
97 let mut collector = Vec::<usize>::new();
98 let mut visitor = query::visitors::BoundingVolumeInterferencesCollector::new(
99 &aabb1,
100 &mut collector,
101 );
102
103 bvt.visit(&mut visitor);
104
105 if !collector.is_empty() {
106 let dist = query::distance(
108 &obj_pose,
109 &*obj.0,
110 self.target_pose,
111 self.target_shape,
112 );
113 if dist < self.detector.prediction {
115 debug!("name: {joint_name}, dist={dist}");
116 return Some(joint_name.to_owned());
117 }
118 }
119 }
120 }
121 None => {
122 debug!("collision model {joint_name} not found");
123 }
124 }
125 self.next()
126 }
127}
128
129pub struct SelfCollisionPairs<'a, T>
131where
132 T: RealField + Copy,
133{
134 detector: &'a CollisionDetector<T>,
135 robot: &'a k::Chain<T>,
136 self_collision_pairs: &'a [(String, String)],
137 index: usize,
138 used_duration: HashMap<String, Duration>,
139}
140
141impl<'a, T> SelfCollisionPairs<'a, T>
142where
143 T: RealField + Copy + k::SubsetOf<f64>,
144{
145 pub fn new(
146 detector: &'a CollisionDetector<T>,
147 robot: &'a k::Chain<T>,
148 self_collision_pairs: &'a [(String, String)],
149 ) -> Self {
150 robot.update_transforms();
151 Self {
152 detector,
153 robot,
154 self_collision_pairs,
155 index: 0,
156 used_duration: HashMap::new(),
157 }
158 }
159
160 pub fn used_duration(&self) -> &HashMap<String, Duration> {
162 &self.used_duration
163 }
164}
165
166impl<T> Iterator for SelfCollisionPairs<'_, T>
167where
168 T: RealField + Copy + k::SubsetOf<f64>,
169{
170 type Item = (String, String);
171
172 fn next(&mut self) -> Option<(String, String)> {
173 if self.self_collision_pairs.len() <= self.index {
174 return None;
175 }
176 let (j1, j2) = &self.self_collision_pairs[self.index];
177 self.index += 1;
178
179 let obj_vec1 = match self.detector.name_collision_model_map.get(j1) {
181 Some(v) => v,
182 None => {
183 warn!("Collision model {j1} not found");
184 return self.next();
185 }
186 };
187 let obj_vec2 = match self.detector.name_collision_model_map.get(j2) {
188 Some(v) => v,
189 None => {
190 warn!("Collision model {j2} not found");
191 return self.next();
192 }
193 };
194
195 let pose1 = match self.robot.find(j1) {
197 Some(v) => v.world_transform().unwrap(),
198 None => {
199 warn!("self_colliding: joint {j1} not found");
200 return self.next();
201 }
202 };
203 let pose2 = match self.robot.find(j2) {
204 Some(v) => v.world_transform().unwrap(),
205 None => {
206 warn!("self_colliding: joint {j2} not found");
207 return self.next();
208 }
209 };
210
211 let start_time = Instant::now();
213 for obj1 in obj_vec1 {
214 let aabb1 = obj1.0.aabb(&(pose1 * obj1.1));
215
216 let index_and_aabb = obj_vec2
217 .iter()
218 .enumerate()
219 .map(|(index, obj)| (index, obj.0.aabb(&(pose2 * obj.1))))
220 .collect();
221 let bvt = BVT::new_balanced(index_and_aabb);
222
223 let mut collector = Vec::<usize>::new();
224 let mut visitor =
225 query::visitors::BoundingVolumeInterferencesCollector::new(&aabb1, &mut collector);
226
227 bvt.visit(&mut visitor);
228
229 if !collector.is_empty() {
230 for index in collector {
232 let obj2 = &obj_vec2[index];
233
234 let dist =
236 query::distance(&(pose1 * obj1.1), &*obj1.0, &(pose2 * obj2.1), &*obj2.0);
237 debug!("name: {j1}, name: {j2} dist={dist}");
238 if dist < self.detector.prediction {
239 return Some((j1.to_owned(), j2.to_owned()));
240 }
241 }
242 }
243 }
244
245 let elapsed = start_time.elapsed();
247 *self
248 .used_duration
249 .entry(j1.to_owned())
250 .or_insert_with(|| Duration::from_nanos(0)) += elapsed;
251 *self
252 .used_duration
253 .entry(j2.to_owned())
254 .or_insert_with(|| Duration::from_nanos(0)) += elapsed;
255
256 self.next()
257 }
258}
259
260#[derive(Clone)]
261pub struct CollisionDetector<T>
263where
264 T: RealField + Copy,
265{
266 name_collision_model_map: NameShapeMap<T>,
267 pub prediction: T,
269}
270
271impl<T> CollisionDetector<T>
272where
273 T: RealField + Copy + k::SubsetOf<f64>,
274{
275 pub fn new(name_collision_model_map: NameShapeMap<T>, prediction: T) -> Self {
277 CollisionDetector {
278 name_collision_model_map,
279 prediction,
280 }
281 }
282
283 pub fn from_urdf_robot(urdf_robot: &urdf_rs::Robot, prediction: T) -> Self {
285 Self::from_urdf_robot_with_base_dir(urdf_robot, None, prediction)
286 }
287
288 pub fn from_urdf_robot_with_base_dir(
292 urdf_robot: &urdf_rs::Robot,
293 base_dir: Option<&Path>,
294 prediction: T,
295 ) -> Self {
296 let mut name_collision_model_map = HashMap::new();
297 let link_joint_map = k::urdf::link_to_joint_map(urdf_robot);
298 for l in &urdf_robot.links {
299 let col_pose_vec = l
300 .collision
301 .iter()
302 .filter_map(|collision| {
303 urdf_geometry_to_shape_handle(&collision.geometry, base_dir)
304 .map(|col| (col, k::urdf::isometry_from(&collision.origin)))
305 })
306 .collect::<Vec<_>>();
307 debug!("name={}, ln={}", l.name, col_pose_vec.len());
308 if !col_pose_vec.is_empty() {
309 if let Some(joint_name) = link_joint_map.get(&l.name) {
310 name_collision_model_map.insert(joint_name.to_owned(), col_pose_vec);
311 }
312 }
313 }
314 CollisionDetector {
315 name_collision_model_map,
316 prediction,
317 }
318 }
319
320 pub fn from_robot(robot: &k::Chain<T>, prediction: T) -> Self {
322 let mut name_collision_model_map = HashMap::new();
323 for node in robot.iter() {
324 let link = match node.link().clone() {
325 Some(v) => v,
326 None => break,
327 };
328
329 let col_pose_vec = link
330 .collisions
331 .iter()
332 .filter_map(|c| {
333 k_link_geometry_to_shape_handle(&c.geometry).map(|col| (col, *c.origin()))
334 })
335 .collect::<Vec<_>>();
336
337 debug!("name={}, ln={}", link.name, col_pose_vec.len());
338 if !col_pose_vec.is_empty() {
339 name_collision_model_map.insert(node.joint().name.to_owned(), col_pose_vec);
340 }
341 }
342 CollisionDetector {
343 name_collision_model_map,
344 prediction,
345 }
346 }
347
348 pub fn detect_env<'a>(
354 &'a self,
355 robot: &'a k::Chain<T>,
356 target_shape: &'a dyn Shape<T>,
357 target_pose: &'a na::Isometry3<T>,
358 ) -> EnvCollisionNames<'a, 'a, T> {
359 robot.update_transforms();
360 EnvCollisionNames::new(self, robot, target_shape, target_pose)
361 }
362
363 pub fn detect_self<'a>(
368 &'a self,
369 robot: &'a k::Chain<T>,
370 self_collision_pairs: &'a [(String, String)],
371 ) -> SelfCollisionPairs<'a, T> {
372 robot.update_transforms();
373 SelfCollisionPairs::new(self, robot, self_collision_pairs)
374 }
375}
376
377#[cfg(test)]
378mod tests {
379 use na::{Isometry3, Vector3};
380 use ncollide3d::shape::Cuboid;
381
382 use super::*;
383
384 #[test]
385 fn test_environmental_collision_detection() {
386 let urdf_robot = urdf_rs::read_file("sample.urdf").unwrap();
387 let robot = k::Chain::<f32>::from(&urdf_robot);
388 robot.set_joint_positions(&[0.0; 16]).unwrap();
389 let detector = CollisionDetector::from_urdf_robot(&urdf_robot, 0.01);
390
391 let target = Cuboid::new(Vector3::new(0.2, 0.3, 0.1));
393 let target_pose = Isometry3::new(Vector3::new(0.7, 0.0, 0.6), na::zero());
394
395 assert!(detector
396 .detect_env(&robot, &target, &target_pose)
397 .next()
398 .is_none());
399
400 let angles = [
401 -0.7, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
402 ];
403 robot.set_joint_positions(&angles).unwrap();
404 let names: Vec<String> = detector.detect_env(&robot, &target, &target_pose).collect();
405 assert_eq!(
406 names,
407 vec![
408 "l_wrist_yaw",
409 "l_wrist_pitch",
410 "l_gripper_linear1",
411 "l_gripper_linear2"
412 ]
413 );
414
415 let target_pose = Isometry3::new(Vector3::new(0.0, 0.0, 0.0), na::zero());
416 let names: Vec<String> = detector.detect_env(&robot, &target, &target_pose).collect();
417 assert_eq!(names, vec!["root"]);
418 }
419
420 #[test]
421 fn test_self_collision_detection() {
422 let urdf_robot = urdf_rs::read_file("sample.urdf").unwrap();
423 let robot = k::Chain::<f32>::from(&urdf_robot);
424 let detector = CollisionDetector::from_urdf_robot(&urdf_robot, 0.01);
425
426 let collision_check_pairs = parse_colon_separated_pairs(&[
427 "root:l_shoulder_roll".to_owned(),
428 "root:l_elbow_pitch".to_owned(),
429 "root:l_wrist_yaw".to_owned(),
430 "root:l_wrist_pitch".to_owned(),
431 ])
432 .unwrap();
433 let (correct_collisions, _) = collision_check_pairs.split_at(2);
434
435 let angles = [0.0; 16];
436 robot.set_joint_positions(&angles).unwrap();
437 assert!(detector
438 .detect_self(&robot, &collision_check_pairs)
439 .next()
440 .is_none());
441
442 let angles = [
443 -1.57, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
444 ];
445 robot.set_joint_positions(&angles).unwrap();
446 let result: Vec<(String, String)> = detector
447 .detect_self(&robot, &collision_check_pairs)
448 .collect();
449 assert_eq!(result, correct_collisions.to_vec());
450 }
451}
452
453pub trait FromUrdf {
455 fn from_urdf_robot_with_base_dir(robot: &urdf_rs::Robot, path: Option<&Path>) -> Self;
456 fn from_urdf_robot(robot: &urdf_rs::Robot) -> Self
457 where
458 Self: std::marker::Sized,
459 {
460 Self::from_urdf_robot_with_base_dir(robot, None::<&Path>)
461 }
462 fn from_urdf_file(path: impl AsRef<Path>) -> ::std::result::Result<Self, urdf_rs::UrdfError>
463 where
464 Self: ::std::marker::Sized,
465 {
466 Ok(Self::from_urdf_robot_with_base_dir(
467 &urdf_rs::read_file(&path)?,
468 path.as_ref().parent(),
469 ))
470 }
471}
472
473pub fn parse_colon_separated_pairs(pair_strs: &[String]) -> Result<Vec<(String, String)>> {
486 let mut pairs = Vec::new();
487 for pair_str in pair_strs {
488 let mut sp = pair_str.split(':');
489 if let Some(p1) = sp.next() {
490 if let Some(p2) = sp.next() {
491 pairs.push((p1.to_owned(), p2.to_owned()));
492 } else {
493 return Err(Error::ParseError(pair_str.to_owned()));
494 }
495 } else {
496 return Err(Error::ParseError(pair_str.to_owned()));
497 }
498 }
499 Ok(pairs)
500}
501
502#[cfg(test)]
503mod test {
504 use super::parse_colon_separated_pairs;
505 #[test]
506 fn test_parse_colon_separated_pairs() {
507 let pairs = parse_colon_separated_pairs(&["j0:j1".to_owned(), "j2:j0".to_owned()]).unwrap();
508 assert_eq!(pairs.len(), 2);
509 assert_eq!(pairs[0].0, "j0");
510 assert_eq!(pairs[0].1, "j1");
511 assert_eq!(pairs[1].0, "j2");
512 assert_eq!(pairs[1].1, "j0");
513 }
514}
515
516impl FromUrdf for Compound<f64> {
521 fn from_urdf_robot_with_base_dir(
522 urdf_obstacle: &urdf_rs::Robot,
523 base_path: Option<&Path>,
524 ) -> Self {
525 let compound_data = urdf_obstacle
526 .links
527 .iter()
528 .flat_map(|l| {
529 l.collision
530 .iter()
531 .map(|collision| {
532 urdf_geometry_to_shape_handle(&collision.geometry, base_path)
533 .map(|col| (k::urdf::isometry_from(&collision.origin), col))
534 })
535 .collect::<Vec<_>>()
536 })
537 .flatten()
538 .collect::<Vec<_>>();
539 Compound::new(compound_data)
540 }
541}