1use crate::urdf::*;
2use k::nalgebra as na;
3use kiss3d::camera::ArcBall;
4use kiss3d::camera::Camera;
5use kiss3d::ncollide3d::simba::scalar::SubsetOf;
6use kiss3d::scene::SceneNode;
7use kiss3d::window::Window;
8use std::collections::HashMap;
9use std::fmt;
10use std::path::Path;
11use std::rc::Rc;
12use tracing::*;
13
14const DEFAULT_CYLINDER_RADIUS: f32 = 0.01;
15
16pub struct Viewer {
17 scenes: HashMap<String, SceneNode>,
18 pub arc_ball: ArcBall,
19 font: Rc<kiss3d::text::Font>,
20 original_colors: HashMap<String, Vec<na::Point3<f32>>>,
21 is_texture_enabled: bool,
22 is_assimp_enabled: bool,
23 link_joint_map: HashMap<String, String>,
24}
25
26impl Viewer {
27 pub fn new(title: &str) -> (Viewer, Window) {
28 Self::with_background_color(title, (0.0, 0.0, 0.3))
29 }
30
31 pub fn with_background_color(title: &str, color: (f32, f32, f32)) -> (Viewer, Window) {
32 let eye = na::Point3::new(3.0f32, 1.0, 1.0);
33 let at = na::Point3::new(0.0f32, 0.0, 0.25);
34 let mut window = kiss3d::window::Window::new_with_size(title, 1400, 1000);
35 window.set_light(kiss3d::light::Light::StickToCamera);
36 window.set_background_color(color.0, color.1, color.2);
37 let mut arc_ball = ArcBall::new(eye, at);
38 arc_ball.set_up_axis(na::Vector3::z());
39 arc_ball.set_dist_step(0.99);
40 let font = kiss3d::text::Font::default();
41 let viewer = Viewer {
42 scenes: HashMap::new(),
43 arc_ball,
44 font,
45 original_colors: HashMap::new(),
46 is_texture_enabled: true,
47 is_assimp_enabled: cfg!(feature = "assimp"),
48 link_joint_map: HashMap::new(),
49 };
50 (viewer, window)
51 }
52 pub fn disable_texture(&mut self) {
53 self.is_texture_enabled = false;
54 }
55 pub fn enable_texture(&mut self) {
56 self.is_texture_enabled = true;
57 }
58
59 pub fn disable_assimp(&mut self) {
60 self.is_assimp_enabled = false;
61 }
62 pub fn enable_assimp(&mut self) {
64 self.is_assimp_enabled = cfg!(feature = "assimp");
65 }
66
67 pub fn add_robot(
68 &mut self,
69 window: &mut Window,
70 urdf_robot: &urdf_rs::Robot,
71 package_path: &HashMap<String, String>,
72 ) {
73 self.add_robot_with_base_dir(window, urdf_robot, None, package_path);
74 }
75 pub fn add_robot_with_base_dir(
76 &mut self,
77 window: &mut Window,
78 urdf_robot: &urdf_rs::Robot,
79 base_dir: Option<&Path>,
80 package_path: &HashMap<String, String>,
81 ) {
82 self.add_robot_with_base_dir_and_collision_flag(
83 window,
84 urdf_robot,
85 base_dir,
86 false,
87 package_path,
88 );
89 }
90 pub fn add_robot_with_base_dir_and_collision_flag(
91 &mut self,
92 window: &mut Window,
93 urdf_robot: &urdf_rs::Robot,
94 base_dir: Option<&Path>,
95 is_collision: bool,
96 package_path: &HashMap<String, String>,
97 ) {
98 self.link_joint_map = k::urdf::link_to_joint_map(urdf_robot);
99
100 for l in &urdf_robot.links {
101 let num = if is_collision {
102 l.collision.len()
103 } else {
104 l.visual.len()
105 };
106 if num == 0 {
107 continue;
108 }
109 let mut scene_group = window.add_group();
110 let mut colors = Vec::new();
111 for i in 0..num {
112 let (geom_element, origin_element) = if is_collision {
113 (&l.collision[i].geometry, &l.collision[i].origin)
114 } else {
115 (&l.visual[i].geometry, &l.visual[i].origin)
116 };
117 let mut opt_color = None;
118 if l.visual.len() > i {
119 let rgba = rgba_from_visual(urdf_robot, &l.visual[i]);
120 let color = na::Point3::new(rgba[0] as f32, rgba[1] as f32, rgba[2] as f32);
121 if color[0] > 0.001 || color[1] > 0.001 || color[2] > 0.001 {
122 opt_color = Some(color);
123 }
124 colors.push(color);
125 }
126 match add_geometry(
127 geom_element,
128 &opt_color,
129 base_dir,
130 &mut scene_group,
131 self.is_texture_enabled,
132 self.is_assimp_enabled,
133 package_path,
134 ) {
135 Ok(mut base_group) => {
136 base_group.set_local_transformation(k::urdf::isometry_from(origin_element));
138 }
139 Err(e) => {
140 error!("failed to create for link '{}': {e}", l.name);
141 }
142 }
143 }
144 let joint_name = self
145 .link_joint_map
146 .get(&l.name)
147 .unwrap_or_else(|| panic!("joint for link '{}' not found", l.name));
148 self.scenes.insert(joint_name.to_owned(), scene_group);
149 self.original_colors.insert(joint_name.to_owned(), colors);
150 }
151 }
152 pub fn remove_robot(&mut self, window: &mut Window, urdf_robot: &urdf_rs::Robot) {
153 for l in &urdf_robot.links {
154 let joint_name = self
155 .link_joint_map
156 .get(&l.name)
157 .unwrap_or_else(|| panic!("{} not found", l.name));
158 if let Some(scene) = self.scenes.get_mut(joint_name) {
159 window.remove_node(scene);
160 }
161 }
162 }
163 pub fn add_axis_cylinders(&mut self, window: &mut Window, name: &str, size: f32) {
164 self.add_axis_cylinders_with_scale(window, name, size, 1.0);
165 }
166 pub fn add_axis_cylinders_with_scale(
167 &mut self,
168 window: &mut Window,
169 name: &str,
170 size: f32,
171 scale: f32,
172 ) {
173 let mut axis_group = window.add_group();
174 let radius = DEFAULT_CYLINDER_RADIUS * scale;
175 let size = size * scale;
176 let mut x = axis_group.add_cylinder(radius, size);
177 x.set_color(0.0, 0.0, 1.0);
178 let mut y = axis_group.add_cylinder(radius, size);
179 y.set_color(0.0, 1.0, 0.0);
180 let mut z = axis_group.add_cylinder(radius, size);
181 z.set_color(1.0, 0.0, 0.0);
182 let rot_x = na::UnitQuaternion::from_axis_angle(&na::Vector3::x_axis(), 1.57);
183 let rot_y = na::UnitQuaternion::from_axis_angle(&na::Vector3::y_axis(), 1.57);
184 let rot_z = na::UnitQuaternion::from_axis_angle(&na::Vector3::z_axis(), 1.57);
185 x.append_translation(&na::Translation3::new(0.0, 0.0, size * 0.5));
186 y.append_translation(&na::Translation3::new(0.0, size * 0.5, 0.0));
187 z.append_translation(&na::Translation3::new(size * 0.5, 0.0, 0.0));
188 x.set_local_rotation(rot_x);
189 y.set_local_rotation(rot_y);
190 z.set_local_rotation(rot_z);
191 self.scenes.insert(name.to_owned(), axis_group);
192 }
193 pub fn scene_node(&self, name: &str) -> Option<&SceneNode> {
194 self.scenes.get(name)
195 }
196 pub fn scene_node_mut(&mut self, name: &str) -> Option<&mut SceneNode> {
197 self.scenes.get_mut(name)
198 }
199 pub fn update<T>(&mut self, robot: &k::Chain<T>)
200 where
201 T: k::RealField + k::SubsetOf<f64> + kiss3d::ncollide3d::simba::scalar::SubsetOf<f32>,
202 {
203 robot.update_transforms();
204 for link in robot.iter() {
205 let trans = link.world_transform().unwrap();
206 let link_name = &link.joint().name;
207 let trans_f32: na::Isometry3<f32> = na::Isometry3::to_superset(&trans);
208 match self.scenes.get_mut(link_name) {
209 Some(obj) => {
210 obj.set_local_transformation(trans_f32);
211 }
212 None => {
213 debug!("{link_name} not found");
214 }
215 }
216 }
217 }
218 pub fn draw_text(
219 &mut self,
220 window: &mut Window,
221 text: &str,
222 size: f32,
223 pos: &na::Point2<f32>,
224 color: &na::Point3<f32>,
225 ) {
226 window.draw_text(text, pos, size, &self.font, color);
227 }
228
229 pub fn draw_text_from_3d(
230 &mut self,
231 window: &mut Window,
232 text: &str,
233 size: f32,
234 pos: &na::Point3<f32>,
235 color: &na::Point3<f32>,
236 ) {
237 let height = window.height() as f32;
238 let width = window.width() as f32;
239 let text_position_in_2d: na::Matrix<
240 f32,
241 na::Const<2>,
242 na::Const<1>,
243 na::ArrayStorage<f32, 2, 1>,
244 > = self.arc_ball.project(pos, &na::Vector2::new(width, height));
245 self.draw_text(
246 window,
247 text,
248 size,
249 &na::Point2::new(
252 text_position_in_2d.x * 2.0,
253 (height - text_position_in_2d.y) * 2.0,
254 ),
255 color,
256 );
257 }
258
259 pub fn set_temporal_color(&mut self, link_name: &str, r: f32, g: f32, b: f32) {
260 if let Some(obj) = self.scenes.get_mut(link_name) {
261 obj.set_color(r, g, b);
262 }
263 }
264 pub fn reset_temporal_color(&mut self, link_name: &str) {
265 if let Some(colors) = self.original_colors.get(link_name) {
266 if let Some(obj) = self.scenes.get_mut(link_name) {
267 for color in colors {
268 obj.apply_to_scene_nodes_mut(&mut |o| {
269 o.set_color(color[0], color[1], color[2]);
270 });
271 }
272 }
273 }
274 }
275 pub fn add_ground(
276 &mut self,
277 window: &mut Window,
278 ground_height: f32,
279 panel_size: f32,
280 half_panel_num: i32,
281 ground_color1: (f32, f32, f32),
282 ground_color2: (f32, f32, f32),
283 ) -> Vec<SceneNode> {
284 let mut panels = Vec::new();
285 const PANEL_HEIGHT: f32 = 0.0001;
286
287 for i in -half_panel_num..half_panel_num {
288 for j in -half_panel_num..half_panel_num {
289 let mut c0 = window.add_cube(panel_size, panel_size, PANEL_HEIGHT);
290 if (i + j) % 2 == 0 {
291 c0.set_color(ground_color1.0, ground_color1.1, ground_color1.2);
292 } else {
293 c0.set_color(ground_color2.0, ground_color2.1, ground_color2.2);
294 }
295 let x_ind = j as f32 + 0.5;
296 let y_ind = i as f32 + 0.5;
297 let trans = na::Isometry3::from_parts(
298 na::Translation3::new(
299 panel_size * x_ind,
300 panel_size * y_ind,
301 ground_height - PANEL_HEIGHT * 0.5,
302 ),
303 na::UnitQuaternion::identity(),
304 );
305 c0.set_local_transformation(trans);
306 panels.push(c0);
307 }
308 }
309 panels
310 }
311
312 pub(crate) fn add_cube(&mut self, window: &mut Window, name: &str, wx: f32, wy: f32, wz: f32) {
314 let cube = window.add_cube(wx, wy, wz);
315 self.scenes.insert(name.to_owned(), cube);
316 }
317
318 pub(crate) fn add_capsule(
319 &mut self,
320 window: &mut Window,
321 name: &str,
322 radius: f32,
323 height: f32,
324 ) {
325 let cube = window.add_capsule(radius, height);
326 self.scenes.insert(name.to_owned(), cube);
327 }
328}
329
330impl fmt::Debug for Viewer {
331 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
332 f.debug_struct("Viewer")
334 .field("scenes", &self.scenes.keys())
335 .field("original_colors", &self.original_colors)
336 .field("is_texture_enabled", &self.is_texture_enabled)
337 .field("link_joint_map", &self.link_joint_map)
338 .finish()
339 }
340}