mujoco_rs/
renderer.rs

1//! Module related to implementation of the [`MjRenderer`].
2use crate::render_base::{GlState, RenderBase, sync_geoms};
3use crate::wrappers::mj_visualization::MjvScene;
4use crate::wrappers::mj_rendering::MjrContext;
5use crate::builder_setters;
6use crate::prelude::*;
7
8
9
10use bitflags::bitflags;
11use glutin::prelude::PossiblyCurrentGlContext;
12use glutin::surface::GlSurface;
13use png::Encoder;
14use winit::event_loop::EventLoop;
15
16use std::io::{self, BufWriter, ErrorKind, Write};
17use std::fmt::Display;
18use std::error::Error;
19use std::marker::PhantomData;
20use std::ops::Deref;
21use std::path::Path;
22use std::fs::File;
23
24const RGB_NOT_FOUND_ERR_STR: &str = "RGB rendering is not enabled (renderer.with_rgb_rendering(true))";
25const DEPTH_NOT_FOUND_ERR_STR: &str = "depth rendering is not enabled (renderer.with_depth_rendering(true))";
26const INVALID_INPUT_SIZE: &str = "the input width and height don't match the renderer's configuration";
27const EXTRA_INTERNAL_VISUAL_GEOMS: u32 = 100;
28
29
30/// A builder for [`MjRenderer`].
31#[derive(Debug)]
32pub struct MjRendererBuilder<M: Deref<Target = MjModel> + Clone> {
33    width: u32,
34    height: u32,
35    num_visual_internal_geom: u32,
36    num_visual_user_geom: u32,
37    rgb: bool,
38    depth: bool,
39    font_scale: MjtFontScale,
40    camera: MjvCamera,
41    opts: MjvOption,
42    model_type: PhantomData<M>
43}
44
45impl<M: Deref<Target = MjModel> + Clone> MjRendererBuilder<M> {
46    /// Create a builder with default configuration.
47    /// Defaults are:
48    /// - `width` and `height`: use offwidth and offheight of MuJoCo's visual/global settings from the model,
49    /// - `num_visual_internal_geom`: 100,
50    /// - `num_visual_user_geom`: 0,
51    /// - `rgb`: true,
52    /// - `depth`: false.
53    pub fn new() -> Self {
54        Self {
55            width: 0, height: 0,
56            num_visual_internal_geom: EXTRA_INTERNAL_VISUAL_GEOMS, num_visual_user_geom: 0,
57            rgb: true, depth: false, font_scale: MjtFontScale::mjFONTSCALE_100,
58            camera: MjvCamera::default(), opts: MjvOption::default(), model_type: PhantomData
59        }
60    }
61
62    builder_setters! {
63        width: u32; "
64image width.
65
66<div class=\"warning\">
67
68The width must be less or equal to the offscreen buffer width,
69which can be configured at the top of the model's XML like so:
70
71```xml
72<visual>
73    <global offwidth=\"1920\" .../>
74</visual>
75```
76
77</div>";
78
79        height: u32; "\
80image height.
81
82<div class=\"warning\">
83
84The height must be less or equal to the offscreen buffer height,
85which can be configured at the top of the model's XML like so:
86
87```xml
88<visual>
89    <global offheight=\"1080\" .../>
90</visual>
91```
92
93</div>";
94
95        num_visual_internal_geom: u32; "\
96            maximum number of additional visual-only internal geoms to allocate for.
97            Note that the total number of geoms in the internal scene will be
98            `num_visual_internal_geom` + `num_visual_user_geom`.";
99
100        num_visual_user_geom: u32;      "maximum number of additional visual-only user geoms (drawn by the user).";
101        rgb: bool;                      "RGB rendering enabled (true) or disabled (false).";
102        depth: bool;                    "depth rendering enabled (true) or disabled (false).";
103        font_scale: MjtFontScale;       "font scale of drawn text (with [MjrContext]).";
104        camera: MjvCamera;              "camera used for drawing.";
105        opts: MjvOption;                "visualization options.";
106
107    }
108
109    /// Builds a [`MjRenderer`].
110    pub fn build(self, model: M) -> Result<MjRenderer<M>, RendererError> {
111        // Assume model's maximum should be used
112        let mut height = self.height;
113        let mut width = self.width;
114        if width == 0 && height == 0 {
115            let global = &model.vis().global;
116            height = global.offheight as u32;
117            width = global.offwidth as u32;
118        }
119
120        let mut event_loop = EventLoop::new().map_err(RendererError::EventLoopError)?;
121        let adapter = RenderBase::new(
122            width, height,
123            "".to_string(),
124            &mut event_loop,
125            false  // don't process events
126        );
127
128        /* Initialize the OpenGL related things */
129        if let Some (GlState { window, gl_context, gl_surface }) = 
130            adapter.state.as_ref()
131        {
132            window.set_visible(false);
133            gl_surface.set_swap_interval(gl_context, glutin::surface::SwapInterval::DontWait)
134                .map_err(RendererError::GlutinError)?;
135        }
136
137        event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll);
138
139        // Initialize the rendering context to render to the offscreen buffer.
140        let mut context = MjrContext::new(&model);
141        context.offscreen();
142
143        // The 3D scene for visualization
144        let scene = MjvScene::new(
145            model.clone(),
146            model.ffi().ngeom as usize + self.num_visual_internal_geom as usize
147            + self.num_visual_user_geom as usize
148        );
149
150        let user_scene = MjvScene::new(
151            model.clone(),
152            self.num_visual_user_geom as usize
153        );
154
155        // Construct the renderer and create allocated buffers.
156        let renderer = MjRenderer {
157            scene, user_scene, context, model, camera: self.camera, option: self.opts,
158            flags: RendererFlags::empty(), rgb: None, depth: None,
159            width: width as usize, height: height as usize,
160            adapter, _event_loop: event_loop
161        }   // These require special care
162            .with_rgb_rendering(self.rgb)
163            .with_depth_rendering(self.depth);
164
165        Ok(renderer)
166    }
167}
168
169
170impl<M: Deref<Target = MjModel> + Clone> Default for MjRendererBuilder<M> {
171    fn default() -> Self {
172        Self::new()
173    }
174}
175
176/// A renderer for rendering 3D scenes.
177/// By default, RGB rendering is enabled and depth rendering is disabled.
178pub struct MjRenderer<M: Deref<Target = MjModel> + Clone> {
179    scene: MjvScene<M>,
180    user_scene: MjvScene<M>,
181    context: MjrContext,
182    model: M,
183
184    /* OpenGL */
185    adapter: RenderBase,
186    _event_loop: EventLoop<()>,
187
188    /* Configuration */
189    camera: MjvCamera,
190    option: MjvOption,
191    flags: RendererFlags,
192
193    /* Storage */
194    // Use Box to allow less space to be used
195    // when rgb or depth rendering is disabled
196    rgb: Option<Box<[u8]>>,
197    depth: Option<Box<[f32]>>,
198
199    width: usize,
200    height: usize,
201}
202
203impl<M: Deref<Target = MjModel> + Clone> MjRenderer<M> {
204    /// Construct a new renderer.
205    /// The `max_user_geom` parameter
206    /// defines how much space will be allocated for additional, user-defined visual-only geoms.
207    /// It can thus be set to 0 if no additional geoms will be drawn by the user.
208    /// # Scene allocation
209    /// The renderer uses two scenes:
210    /// - the internal scene: used by the renderer to draw the model's state.
211    /// - the user scene: used by the user to add additional geoms to the internal scene
212    /// 
213    /// The **internal scene** allocates the amount of space needed to fit every pre-existing
214    /// model geom + user visual-only geoms + additional visual-only geoms that aren't from the user (e.g., tendons).
215    /// By default, the renderer reserves 100 extra geom slots for drawing the additional visual-only geoms.
216    /// If that is not enough or it is too much, you can construct [`MjRenderer`] via its builder
217    /// ([`MjRenderer::builder`]), which allows more configuration. 
218    /// 
219    /// <div class="warning">
220    /// 
221    /// Parameters `width` and `height` must be less or equal to the offscreen buffer size,
222    /// which can be configured at the top of the model's XML like so:
223    /// 
224    /// ```xml
225    /// <visual>
226    ///    <global offwidth="1920" offheight="1080"/>
227    /// </visual>
228    /// ```
229    /// 
230    /// </div>
231    pub fn new(model: M, width: usize, height: usize, max_user_geom: usize) -> Result<Self, RendererError> {
232        let builder = Self::builder()
233            .width(width as u32).height(height as u32).num_visual_user_geom(max_user_geom as u32);
234        builder.build(model)
235    }
236
237    /// Create a [`MjRendererBuilder`] to configure [`MjRenderer`].
238    pub fn builder() -> MjRendererBuilder<M> {
239        MjRendererBuilder::new()
240    }
241
242    /// Return an immutable reference to the internal scene.
243    pub fn scene(&self) -> &MjvScene<M>{
244        &self.scene
245    }
246
247    /// Return an immutable reference to a user scene for drawing custom visual-only geoms.
248    pub fn user_scene(&self) -> &MjvScene<M>{
249        &self.user_scene
250    }
251
252    /// Return a mutable reference to a user scene for drawing custom visual-only geoms.
253    pub fn user_scene_mut(&mut self) -> &mut MjvScene<M>{
254        &mut self.user_scene
255    }
256
257    /// Return an immutable reference to visualization options.
258    pub fn opts(&self) -> &MjvOption {
259        &self.option
260    }
261
262    /// Return a mutable reference to visualization options.
263    pub fn opts_mut(&mut self) -> &mut MjvOption {
264        &mut self.option
265    }
266
267    /// Return an immutable reference to the camera.
268    pub fn camera(&self) -> &MjvCamera {
269        &self.camera
270    }
271
272    /// Return a mutable reference to the camera.
273    pub fn camera_mut(&mut self) -> &mut MjvCamera {
274        &mut self.camera
275    }
276
277    /// Check if RGB rendering is enabled.
278    pub fn rgb_enabled(&self) -> bool {
279        self.flags.contains(RendererFlags::RENDER_RGB)
280    }
281
282    /// Check if depth rendering is enabled.
283    pub fn depth_enabled(&self) -> bool {
284        self.flags.contains(RendererFlags::RENDER_DEPTH)
285    }
286
287    /// Sets the font size.
288    pub fn set_font_scale(&mut self, font_scale: MjtFontScale) {
289        self.context.change_font(font_scale);
290    }
291
292    /// Update the visualization options and return a reference to self.
293    pub fn set_opts(&mut self, options: MjvOption) {
294        self.option = options;
295    }
296
297    /// Set the camera used for rendering.
298    pub fn set_camera(&mut self, camera: MjvCamera)  {
299        self.camera = camera;
300    }
301
302    /// Enables/disables RGB rendering.
303    pub fn set_rgb_rendering(&mut self, enable: bool) {
304        self.flags.set(RendererFlags::RENDER_RGB, enable);
305        self.rgb = if enable { Some(vec![0; 3 * self.width * self.height].into_boxed_slice()) } else { None } ;
306    }
307
308    /// Enables/disables depth rendering.
309    pub fn set_depth_rendering(&mut self, enable: bool) {
310        self.flags.set(RendererFlags::RENDER_DEPTH, enable);
311        self.depth = if enable { Some(vec![0.0; self.width * self.height].into_boxed_slice()) } else { None } ;
312    }
313
314    /// Sets the font size. To be used on construction.
315    pub fn with_font_scale(mut self, font_scale: MjtFontScale) -> Self {
316        self.set_font_scale(font_scale);
317        self
318    }
319
320    /// Update the visualization options and return a reference to self. To be used on construction.
321    pub fn with_opts(mut self, options: MjvOption) -> Self {
322        self.set_opts(options);
323        self
324    }
325
326    /// Set the camera used for rendering. To be used on construction.
327    pub fn with_camera(mut self, camera: MjvCamera) -> Self  {
328        self.set_camera(camera);
329        self
330    }
331
332    /// Enables/disables RGB rendering. To be used on construction.
333    pub fn with_rgb_rendering(mut self, enable: bool) -> Self {
334        self.set_rgb_rendering(enable);
335        self
336    }
337
338    /// Enables/disables depth rendering. To be used on construction.
339    pub fn with_depth_rendering(mut self, enable: bool) -> Self {
340        self.set_depth_rendering(enable);
341        self
342    }
343
344    /// Update the scene with new data from data.
345    pub fn sync(&mut self, data: &mut MjData<M>) {
346        let model_data_ptr = unsafe {  data.model().__raw() };
347        let bound_model_ptr = unsafe { self.model.__raw() };
348        assert_eq!(model_data_ptr, bound_model_ptr, "'data' must be created from the same model as the renderer.");
349
350        self.scene.update(data, &self.option, &MjvPerturb::default(), &mut self.camera);
351
352        /* Draw user scene geoms */
353        sync_geoms(&self.user_scene, &mut self.scene)
354            .expect("could not sync the user scene with the internal scene; this is a bug, please report it.");
355
356        self.render();
357    }
358
359    /// Return a flattened RGB image of the scene.
360    pub fn rgb_flat(&self) -> Option<&[u8]> {
361        self.rgb.as_deref()
362    }
363
364    /// Return an RGB image of the scene. This methods accepts two generic parameters <WIDTH, HEIGHT>
365    /// that define the shape of the output slice.
366    pub fn rgb<const WIDTH: usize, const HEIGHT: usize>(&self) -> io::Result<&[[[u8; 3]; WIDTH]; HEIGHT]> {
367        if let Some(flat) = self.rgb_flat() {
368            if flat.len() == WIDTH * HEIGHT * 3 {
369                let p_shaped = flat.as_ptr() as *const [[[u8; 3]; WIDTH]; HEIGHT];
370
371                // SAFETY: The alignment of the output is the same as the original.
372                // The lifetime also matches  'a in &'a self, which prevents data races.
373                // Length (number of elements) matches the output's.
374                Ok(unsafe { p_shaped.as_ref().unwrap() })
375            }
376            else {
377                Err(io::Error::new(io::ErrorKind::InvalidInput, INVALID_INPUT_SIZE))
378            }
379        }
380        else {
381            Err(io::Error::new(io::ErrorKind::NotFound, RGB_NOT_FOUND_ERR_STR))
382        }
383    }
384
385    /// Return a flattened depth image of the scene.
386    pub fn depth_flat(&self) -> Option<&[f32]> {
387        self.depth.as_deref()
388    }
389
390    /// Return a depth image of the scene. This methods accepts two generic parameters <WIDTH, HEIGHT>
391    /// that define the shape of the output slice.
392    pub fn depth<const WIDTH: usize, const HEIGHT: usize>(&self) -> io::Result<&[[f32; WIDTH]; HEIGHT]> {
393        if let Some(flat) = self.depth_flat() {
394            if flat.len() == WIDTH * HEIGHT {
395                let p_shaped = flat.as_ptr() as *const [[f32; WIDTH]; HEIGHT];
396
397                // SAFETY: The alignment of the output is the same as the original.
398                // The lifetime matches  'a in &'a self, which prevents data races.
399                // Length (number of elements) matches the output's.
400                Ok(unsafe { p_shaped.as_ref().unwrap() })
401            }
402            else {
403                Err(io::Error::new(io::ErrorKind::InvalidInput, INVALID_INPUT_SIZE))
404            }
405        }
406        else {
407            Err(io::Error::new(io::ErrorKind::NotFound, DEPTH_NOT_FOUND_ERR_STR))
408        }
409    }
410
411    /// Save an RGB image of the scene to a path.
412    /// # Errors
413    /// - [`ErrorKind::NotFound`] when RGB rendering is disabled,
414    /// - other errors related to write.
415    pub fn save_rgb<T: AsRef<Path>>(&self, path: T) -> io::Result<()> {
416        if let Some(rgb) = &self.rgb {
417            let file = File::create(path.as_ref())?;
418            let w = BufWriter::new(file);
419
420            let mut encoder = Encoder::new(w, self.width as u32, self.height as u32);
421            encoder.set_color(png::ColorType::Rgb);
422            encoder.set_depth(png::BitDepth::Eight);
423            encoder.set_compression(png::Compression::NoCompression);
424
425            let mut writer = encoder.write_header()?;
426            writer.write_image_data(rgb)?;
427            Ok(())
428        }
429        else {
430            Err(io::Error::new(ErrorKind::NotFound, RGB_NOT_FOUND_ERR_STR))
431        }
432    }
433
434    /// Save a depth image of the scene to a path. The image is 16-bit PNG, which
435    /// can be converted into depth (distance) data by dividing the grayscale values by
436    /// 65535.0 and applying inverse normalization (if it was enabled with `normalize`): `depth = min + (grayscale / 65535.0) * (max - min)`.
437    /// If `normalize` is `true`, then the data is normalized with min-max normalization.
438    /// Use of [`MjRenderer::save_depth_raw`] is recommended if performance is critical, as
439    /// it skips PNG encoding and also saves the true depth values directly.
440    /// # Returns
441    /// An [`Ok`]`((min, max))` is returned, where min and max represent the normalization parameters.
442    /// # Errors
443    /// - [`ErrorKind::NotFound`] when depth rendering is disabled,
444    /// - other errors related to write.
445    pub fn save_depth<T: AsRef<Path>>(&self, path: T, normalize: bool) -> io::Result<(f32, f32)> {
446        if let Some(depth) = &self.depth {
447            let file = File::create(path.as_ref())?;
448            let w = BufWriter::new(file);
449
450            let mut encoder = Encoder::new(w, self.width as u32, self.height as u32);
451            encoder.set_color(png::ColorType::Grayscale);
452            encoder.set_depth(png::BitDepth::Sixteen);
453            encoder.set_compression(png::Compression::NoCompression);
454
455            let (norm, min, max) =
456            if normalize {
457                let max = depth.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
458                let min = depth.iter().cloned().fold(f32::INFINITY, f32::min);
459                (depth.iter().flat_map(|&x| (((x - min) / (max - min) * 65535.0).min(65535.0) as u16).to_be_bytes()).collect::<Box<_>>(), min, max)
460            }
461            else {
462                (depth.iter().flat_map(|&x| ((x * 65535.0).min(65535.0) as u16).to_be_bytes()).collect::<Box<_>>(), 0.0, 1.0)
463            };
464
465            let mut writer = encoder.write_header()?;
466            writer.write_image_data(&norm)?;
467            Ok((min, max))
468        }
469        else {
470            Err(io::Error::new(ErrorKind::NotFound, DEPTH_NOT_FOUND_ERR_STR))
471        }
472    }
473
474    /// Save the raw depth data to the `path`. The data is encoded
475    /// as a sequence of bytes, where groups of four represent a single f32 value.
476    /// The lower bytes of individual f32 appear first (low-endianness).
477    /// # Errors
478    /// - [`ErrorKind::NotFound`] when depth rendering is disabled,
479    /// - other errors related to write.
480    pub fn save_depth_raw<T: AsRef<Path>>(&self, path: T) -> io::Result<()> {
481        if let Some(depth) = &self.depth {
482            let file = File::create(path.as_ref())?;
483            let mut writer = BufWriter::new(file);
484
485            /* Fast conversion to a byte slice to prioritize performance */
486            let p = unsafe { std::slice::from_raw_parts(
487                depth.as_ptr() as *const u8,
488                std::mem::size_of::<f32>() * depth.len()
489            ) };
490
491            writer.write_all(p)?;
492            Ok(())
493        }
494        else {
495            Err(io::Error::new(ErrorKind::NotFound, DEPTH_NOT_FOUND_ERR_STR))
496        }
497    }
498
499    /// Draws the scene to internal arrays.
500    /// Use [`MjRenderer::rgb`] or [`MjRenderer::depth`] to obtain the rendered image.
501    fn render(&mut self) {
502        let GlState {gl_context, gl_surface, .. }
503            = self.adapter.state.as_ref().unwrap();
504
505        gl_context.make_current(gl_surface).expect("failed to make OpenGL context current");
506        let vp = MjrRectangle::new(0, 0, self.width as i32, self.height as i32);
507        self.scene.render(&vp, &self.context);
508
509        /* Fully flatten everything */
510        let flat_rgb = self.rgb.as_deref_mut();
511        let flat_depth = self.depth.as_deref_mut();
512
513        /* Read to whatever is enabled */
514        self.context.read_pixels(
515            flat_rgb,
516            flat_depth,
517            &vp
518        );
519
520        /* Make depth values be the actual distance in meters */
521        if let Some(depth) = self.depth.as_deref_mut() {
522            let map = &self.model.vis().map;
523            let near = map.znear;
524            let far = map.zfar;
525            for value in depth {
526                let z_ndc = 2.0 * *value - 1.0;
527                *value = 2.0 * near * far / (far + near - z_ndc * (far - near));
528            }
529        }
530    }
531}
532
533
534#[derive(Debug)]
535pub enum RendererError {
536    EventLoopError(winit::error::EventLoopError),
537    GlutinError(glutin::error::Error)
538}
539
540impl Display for RendererError {
541    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
542        match self {
543            Self::EventLoopError(e) => write!(f, "event loop failed to initialize: {}", e),
544            Self::GlutinError(e) => write!(f, "glutin failed to initialize: {}", e)
545        }
546    }
547}
548
549impl Error for RendererError {
550    fn source(&self) -> Option<&(dyn Error + 'static)> {
551        match self {
552            Self::EventLoopError(e) => Some(e),
553            Self::GlutinError(e) => Some(e)
554        }
555    }
556}
557
558bitflags! { 
559    /// Flags that enable features of the renderer.
560    struct RendererFlags: u8 {
561        const RENDER_RGB = 1 << 0;
562        const RENDER_DEPTH = 1 << 1;
563    }
564}
565
566
567
568/* 
569** Don't run any tests as OpenGL hates if anything
570** runs outside the main thread.
571*/
572
573// #[cfg(test)]
574// mod test {
575//     use super::*;
576
577//     const MODEL: &str = "
578//         <mujoco>
579//             <worldbody>
580//             </worldbody>
581//         </mujoco>
582//     ";
583
584//     // #[test]
585//     // fn test_update_normal() {
586//     //     // let model = MjModel::from_xml_string(MODEL).unwrap();
587//     //     // let model2 = MjModel::from_xml_string(MODEL).unwrap();
588//     //     // let mut data = model.make_data();
589//     //     // let mut renderer = Renderer::new(&model, 720, 1280).unwrap();
590        
591//     //     // /* Check if scene updates without errors. */
592//     //     // renderer.update_scene(&mut data);
593//     // }
594// }
595