urdf_viz/
viewer.rs

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    /// if `assimp` feature is disabled, this method does nothing.
63    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                        // set initial origin offset
137                        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            // The x2 factor should be removed for kiss3d >= 0.36
250            // See: https://github.com/sebcrozet/kiss3d/issues/98
251            &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    //https://docs.rs/kiss3d/latest/kiss3d/scene/struct.SceneNode.html#method.add_cube
313    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        // kiss3d::scene::SceneNode and kiss3d::text::Font don't implement Debug.
333        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}