1use crate::camera_controller::{camera_controller_system, CameraController};
44use crate::collisions::SafetyDistances;
45use crate::kinematic_traits::{Joints, Kinematics, Pose, ENV_START_IDX, J_BASE, J_TOOL};
46use crate::kinematics_with_shape::KinematicsWithShape;
47use crate::utils;
48use bevy::prelude::*;
49use bevy_egui::{egui, EguiContexts, EguiPlugin};
50use nalgebra::{Isometry3, Translation3, UnitQuaternion, Vector3};
51use parry3d::shape::TriMesh;
52use std::collections::HashSet;
53use std::ops::RangeInclusive;
54use std::time::Instant;
55use bevy::render::render_asset::RenderAssetUsages;
56
57fn trimesh_to_bevy_mesh(trimesh: &TriMesh) -> Mesh {
59 let mut mesh = Mesh::new(bevy::render::mesh::PrimitiveTopology::TriangleList, RenderAssetUsages::RENDER_WORLD);
60
61 let vertices: Vec<_> = trimesh.vertices().iter().map(|v| [v.x, v.y, v.z]).collect();
63 let indices: Vec<_> = trimesh
64 .indices()
65 .iter()
66 .flat_map(|i| i.to_vec())
67 .collect::<Vec<_>>();
68
69 let mut normals: Vec<[f32; 3]> = vec![[0.0, 0.0, 0.0]; vertices.len()];
71
72 for triangle in trimesh.indices() {
74 let i0 = triangle[0] as usize;
75 let i1 = triangle[1] as usize;
76 let i2 = triangle[2] as usize;
77
78 let v0 = nalgebra::Vector3::new(vertices[i0][0], vertices[i0][1], vertices[i0][2]);
80 let v1 = nalgebra::Vector3::new(vertices[i1][0], vertices[i1][1], vertices[i1][2]);
81 let v2 = nalgebra::Vector3::new(vertices[i2][0], vertices[i2][1], vertices[i2][2]);
82
83 let edge1 = v1 - v0;
85 let edge2 = v2 - v0;
86
87 let normal = edge1.cross(&edge2).normalize();
89
90 for &i in &[i0, i1, i2] {
91 normals[i][0] += normal.x;
92 normals[i][1] += normal.y;
93 normals[i][2] += normal.z;
94 }
95 }
96
97 for normal in normals.iter_mut() {
99 let length = (normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2]).sqrt();
100 if length > 0.0 {
101 normal[0] /= length;
102 normal[1] /= length;
103 normal[2] /= length;
104 }
105 }
106
107 mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vertices);
109 mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals); mesh.insert_indices(bevy::render::mesh::Indices::U32(indices));
113
114 mesh
115}
116
117#[derive(Resource)]
119struct RobotControls {
120 joint_angles: [f32; 6],
121 tcp: [f64; 6],
122 tcp_box: [RangeInclusive<f64>; 3], initial_joint_angles: [f32; 6], previous_joint_angles: [f32; 6], previous_tcp: [f64; 6],
126 safety_distance: f32,
127 previous_safety_distance: f32
128}
129
130impl RobotControls {
131 fn set_tcp_from_pose(&mut self, pose: &Isometry3<f64>) {
132 let (roll, pitch, yaw) = pose.rotation.to_rotation_matrix().euler_angles();
133 self.tcp = [
134 pose.translation.x,
135 pose.translation.y,
136 pose.translation.z,
137 roll.to_degrees(),
138 pitch.to_degrees(),
139 yaw.to_degrees(),
140 ];
141 }
142
143 fn pose(&self) -> Pose {
144 fn quat_from_euler(this: &RobotControls) -> UnitQuaternion<f64> {
145 let roll = this.tcp[3].to_radians();
146 let pitch = this.tcp[4].to_radians();
147 let yaw = this.tcp[5].to_radians();
148
149 UnitQuaternion::from_axis_angle(&Vector3::z_axis(), roll)
151 * UnitQuaternion::from_axis_angle(&Vector3::y_axis(), pitch)
152 * UnitQuaternion::from_axis_angle(&Vector3::x_axis(), yaw)
153 }
154
155 Isometry3::from_parts(
156 Translation3::new(self.tcp[0], self.tcp[1], self.tcp[2]),
157 quat_from_euler(&self),
158 )
159 }
160}
161
162#[derive(Resource)]
164struct Robot {
165 kinematics: KinematicsWithShape,
166 safety: SafetyDistances,
167 joint_meshes: Option<[Handle<Mesh>; 6]>, material: Option<Handle<StandardMaterial>>,
169 tool_material: Option<Handle<StandardMaterial>>,
170 base_material: Option<Handle<StandardMaterial>>,
171 environment_material: Option<Handle<StandardMaterial>>,
172 colliding_material: Option<Handle<StandardMaterial>>, tool: Option<Handle<Mesh>>,
174 base: Option<Handle<Mesh>>,
175 environment: Vec<Handle<Mesh>>, }
177
178pub fn visualize_robot(
182 robot: KinematicsWithShape,
183 intial_angles: [f32; 6],
184 tcp_box: [RangeInclusive<f64>; 3],
185) {
186 let safety = robot.body.safety.clone();
187 visualize_robot_with_safety(
188 robot,
189 intial_angles,
190 tcp_box,
191 &safety
192 )
193}
194
195pub fn visualize_robot_with_safety(
196 robot: KinematicsWithShape,
197 intial_angles: [f32; 6],
198 tcp_box: [RangeInclusive<f64>; 3],
199 safety_distances: &SafetyDistances,
200) {
201 App::new()
202 .add_plugins((DefaultPlugins, EguiPlugin)) .insert_resource(RobotControls {
204 initial_joint_angles: intial_angles.clone(),
205 joint_angles: intial_angles.clone(),
206 tcp: [0.0; 6],
207 tcp_box: tcp_box,
208 previous_joint_angles: intial_angles.clone(),
209 previous_tcp: [0.0; 6],
210 safety_distance: 0.05,
211 previous_safety_distance: 0.0
212 })
213 .insert_resource(Robot {
214 kinematics: robot,
215 safety: safety_distances.clone(),
216 joint_meshes: None,
217 tool: None,
218 base: None,
219 environment: Vec::new(),
220 material: None,
221 tool_material: None,
222 base_material: None,
223 environment_material: None,
224 colliding_material: None,
225 })
226 .add_systems(Startup, setup) .add_systems(
228 Update,
229 (update_robot, camera_controller_system, control_panel),
230 ) .run();
232}
233
234fn setup(
235 mut commands: Commands,
236 mut meshes: ResMut<Assets<Mesh>>,
237 mut materials: ResMut<Assets<StandardMaterial>>,
238 mut robot_controls: ResMut<RobotControls>,
239 mut robot: ResMut<Robot>,
240) {
241 fn mesh_for_joint(
242 meshes: &mut ResMut<Assets<Mesh>>,
243 robot: &ResMut<Robot>,
244 k: usize,
245 ) -> Handle<Mesh> {
246 meshes.add(trimesh_to_bevy_mesh(&robot.kinematics.body.joint_meshes[k]))
247 }
248
249 robot.joint_meshes = Some([
251 mesh_for_joint(&mut meshes, &robot, 0),
252 mesh_for_joint(&mut meshes, &robot, 1),
253 mesh_for_joint(&mut meshes, &robot, 2),
254 mesh_for_joint(&mut meshes, &robot, 3),
255 mesh_for_joint(&mut meshes, &robot, 4),
256 mesh_for_joint(&mut meshes, &robot, 5),
257 ]);
258
259 robot.environment = robot
260 .kinematics
261 .body
262 .collision_environment
263 .iter()
264 .map(|x| meshes.add(trimesh_to_bevy_mesh(&x.mesh)))
265 .collect();
266
267 if let Some(tool) = robot.kinematics.body.tool.as_ref() {
268 robot.tool = Some(meshes.add(trimesh_to_bevy_mesh(&tool)));
269 robot.tool_material = Some(materials.add(StandardMaterial {
270 base_color: Color::srgb(0.8, 1.0, 0.8),
271 metallic: 0.7,
272 perceptual_roughness: 0.05,
273 ..default()
274 }));
275 }
276
277 if let Some(base) = robot.kinematics.body.base.as_ref() {
278 robot.base = Some(meshes.add(trimesh_to_bevy_mesh(&base.mesh)));
279 robot.base_material = Some(materials.add(StandardMaterial {
280 base_color: Color::srgb(0.8, 1.0, 0.8),
281 metallic: 0.7,
282 perceptual_roughness: 0.05,
283 ..default()
284 }));
285 }
286
287 robot.material = Some(materials.add(StandardMaterial {
288 base_color: Color::srgb(1.0, 1.0, 0.0),
289 metallic: 0.7,
290 perceptual_roughness: 0.1,
291 ..default()
292 }));
293
294 robot.environment_material = Some(materials.add(StandardMaterial {
295 base_color: Color::srgb(0.5, 0.5, 0.5),
296 metallic: 1.0,
297 perceptual_roughness: 1.0,
298 ..default()
299 }));
300
301 robot.colliding_material = Some(materials.add(StandardMaterial {
302 base_color: Color::srgb(1.0, 0.1, 0.1),
303 metallic: 1.0,
304 perceptual_roughness: 0.1,
305 ..default()
306 }));
307
308 let angles = utils::joints(&robot_controls.initial_joint_angles);
310 visualize_robot_joints(&mut commands, & mut robot, &angles, robot_controls.safety_distance);
311 let cartesian = robot.kinematics.kinematics.forward(&angles);
312 robot_controls.set_tcp_from_pose(&cartesian);
313
314 commands.spawn(DirectionalLightBundle {
316 directional_light: DirectionalLight {
317 illuminance: 30000.0,
318 ..default()
319 },
320 transform: Transform::from_xyz(5.0, 8.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
321 ..default()
322 });
323
324 commands.spawn(DirectionalLightBundle {
325 directional_light: DirectionalLight {
326 illuminance: 30000.0,
327 ..default()
328 },
329 transform: Transform::from_xyz(-5.0, 0.0, -5.0).looking_at(Vec3::ZERO, Vec3::Y),
330 ..default()
331 });
332
333 commands.spawn((
334 Camera3dBundle {
335 transform: Transform {
336 translation: Vec3::new(0.0, 5.0, 20.0), rotation: Quat::from_rotation_x(std::f32::consts::FRAC_PI_2),
339 ..default()
340 },
341 ..default()
342 },
343 CameraController::default(), ));
345}
346
347fn visualize_robot_joints(commands: &mut Commands, robot: &mut ResMut<Robot>, angles: &Joints, safety_distance: f32) {
357 fn as_bevy(transform: &Isometry3<f32>) -> (Vec3, Quat) {
359 let translation = transform.translation.vector;
360 let rotation = transform.rotation;
361 (
362 Vec3::new(translation.x, translation.y, translation.z),
363 Quat::from_xyzw(rotation.i, rotation.j, rotation.k, rotation.w),
364 )
365 }
366
367 fn spawn_joint(
369 commands: &mut Commands,
370 mesh: &Handle<Mesh>,
371 material: Handle<StandardMaterial>,
372 pose: &Isometry3<f32>,
373 ) {
374 let (translation, rotation) = as_bevy(&pose);
375 commands.spawn(PbrBundle {
376 mesh: mesh.clone(),
377 material,
378 transform: Transform {
379 translation,
380 rotation,
381 ..default()
382 },
383 ..default()
384 });
385 }
386
387 let start = Instant::now();
389 robot.safety.to_environment = safety_distance;
390 robot.safety.to_robot_default = safety_distance;
391 let collisions = robot.kinematics.near(&angles, &robot.safety);
392 println!("Time for collision check: {:?}", start.elapsed());
393
394 let colliding_segments: HashSet<_> = collisions.iter().flat_map(|(x, y)| [*x, *y]).collect();
395
396 let positioned_robot = robot.kinematics.positioned_robot(angles);
398 for (j, positioned_joint) in positioned_robot.joints.iter().enumerate() {
399 spawn_joint(
400 commands,
401 &robot.joint_meshes.as_ref().unwrap()[j],
402 maybe_colliding_material(&robot, &robot.material, &colliding_segments, &j),
403 &positioned_joint.transform,
404 );
405 }
406
407 if let (Some(tool), Some(tool_joint)) = (&robot.tool, positioned_robot.tool.as_ref()) {
409 spawn_joint(
410 commands,
411 tool,
412 maybe_colliding_material(&robot, &robot.tool_material, &colliding_segments, &J_TOOL),
413 &tool_joint.transform,
414 );
415 }
416
417 if let (Some(base), Some(base_joint)) = (&robot.base, robot.kinematics.body.base.as_ref()) {
419 spawn_joint(
420 commands,
421 base,
422 maybe_colliding_material(&robot, &robot.base_material, &colliding_segments, &J_BASE),
423 &base_joint.base_pose,
424 );
425 }
426
427 for (i, environment_joint) in positioned_robot.environment.iter().enumerate() {
429 spawn_joint(
430 commands,
431 &robot.environment[i],
432 maybe_colliding_material(
433 &robot,
434 &robot.environment_material,
435 &colliding_segments,
436 &(ENV_START_IDX + i),
437 ),
438 &environment_joint.pose,
439 );
440 }
441}
442
443fn maybe_colliding_material(
445 robot: &ResMut<Robot>,
446 material: &Option<Handle<StandardMaterial>>,
447 colliding_segments: &HashSet<usize>,
448 joint: &usize,
449) -> Handle<StandardMaterial> {
450 let selected_material = if colliding_segments.contains(joint) {
451 &robot.colliding_material
452 } else {
453 material
454 };
455 selected_material.as_ref().unwrap().clone()
456}
457
458fn update_robot(
460 mut commands: Commands,
461 mut controls: ResMut<RobotControls>,
462 mut robot: ResMut<Robot>,
463 query: Query<Entity, With<Handle<Mesh>>>, ) {
465 if controls.safety_distance != controls.previous_safety_distance {
466 for entity in query.iter() {
467 commands.entity(entity).despawn();
468 }
469 let angles = utils::joints(&controls.joint_angles);
470 visualize_robot_joints(&mut commands, &mut robot, &angles, controls.safety_distance);
471 controls.previous_safety_distance = controls.safety_distance;
472 } else if controls.joint_angles != controls.previous_joint_angles {
473 for entity in query.iter() {
475 commands.entity(entity).despawn();
476 }
477
478 let angles = utils::joints(&controls.joint_angles);
481 visualize_robot_joints(&mut commands, &mut robot, &angles, controls.safety_distance);
482 controls.previous_joint_angles = controls.joint_angles.clone();
483
484 let pose = robot.kinematics.kinematics.forward(&angles);
486 controls.set_tcp_from_pose(&pose);
487 controls.previous_tcp = controls.tcp;
488 controls.previous_safety_distance = controls.safety_distance;
489 } else if controls.tcp != controls.previous_tcp {
490 let angles = utils::joints(&controls.joint_angles);
492 let pose = controls.pose();
493
494 let start = Instant::now();
497 let ik = robot.kinematics.inverse_continuing(&pose, &angles);
498 println!("Time for inverse kinematics: {:?}", start.elapsed());
499 if ik.len() > 0 {
500 for entity in query.iter() {
501 commands.entity(entity).despawn();
502 }
503 let angles = ik[0];
504 visualize_robot_joints(&mut commands, &mut robot, &angles, controls.safety_distance);
505
506 controls.joint_angles = utils::to_degrees(&angles);
508 } else {
509 println!(
510 " no solution for pose {:.1} {:.1} {:.1} rotation {:.1} {:.1} {:.1}",
511 controls.tcp[0],
512 controls.tcp[1],
513 controls.tcp[2],
514 controls.tcp[3],
515 controls.tcp[4],
516 controls.tcp[5]
517 );
518 }
519 controls.previous_tcp = controls.tcp.clone();
520 controls.previous_joint_angles = controls.joint_angles;
521 controls.previous_safety_distance = controls.safety_distance;
522 }
523}
524
525fn control_panel(mut egui_contexts: EguiContexts, mut controls: ResMut<RobotControls>) {
527 bevy_egui::egui::Window::new("Robot Controls").show(egui_contexts.ctx_mut(), |ui| {
528 ui.label("Joint rotations");
529 for (i, angle) in controls.joint_angles.iter_mut().enumerate() {
530 ui.add(egui::Slider::new(angle, -225.0..=225.0).text(format!("Joint {}", i + 1)));
531 }
532
533 let tcp_x_range = controls.tcp_box[0].clone();
534 let tcp_y_range = controls.tcp_box[1].clone();
535 let tcp_z_range = controls.tcp_box[2].clone();
536
537 ui.add_space(10.0);
538 ui.label("Tool center point (TCP)");
539 ui.add(egui::Slider::new(&mut controls.tcp[0], tcp_x_range).text("X"));
540 ui.add(egui::Slider::new(&mut controls.tcp[1], tcp_y_range).text("Y"));
541 ui.add(egui::Slider::new(&mut controls.tcp[2], tcp_z_range).text("Z"));
542
543 ui.add_space(10.0);
544 ui.label("TCP Euler angles");
545 ui.add(egui::Slider::new(&mut controls.tcp[3], -90.0..=90.0).text("Roll"));
546 ui.add(egui::Slider::new(&mut controls.tcp[4], -90.0..=90.0).text("Pitch"));
547 ui.add(egui::Slider::new(&mut controls.tcp[5], -90.0..=90.0).text("Yaw"));
548
549 ui.add_space(10.0);
550 ui.label("Safety distance");
551 ui.add(egui::Slider::new(&mut controls.safety_distance, 0.0..=0.5).text("Max"));
552 });
553}