1use 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#[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 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 pub fn build(self, model: M) -> Result<MjRenderer<M>, RendererError> {
111 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 );
127
128 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 let mut context = MjrContext::new(&model);
141 context.offscreen();
142
143 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 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 } .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
176pub struct MjRenderer<M: Deref<Target = MjModel> + Clone> {
179 scene: MjvScene<M>,
180 user_scene: MjvScene<M>,
181 context: MjrContext,
182 model: M,
183
184 adapter: RenderBase,
186 _event_loop: EventLoop<()>,
187
188 camera: MjvCamera,
190 option: MjvOption,
191 flags: RendererFlags,
192
193 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 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 pub fn builder() -> MjRendererBuilder<M> {
239 MjRendererBuilder::new()
240 }
241
242 pub fn scene(&self) -> &MjvScene<M>{
244 &self.scene
245 }
246
247 pub fn user_scene(&self) -> &MjvScene<M>{
249 &self.user_scene
250 }
251
252 pub fn user_scene_mut(&mut self) -> &mut MjvScene<M>{
254 &mut self.user_scene
255 }
256
257 pub fn opts(&self) -> &MjvOption {
259 &self.option
260 }
261
262 pub fn opts_mut(&mut self) -> &mut MjvOption {
264 &mut self.option
265 }
266
267 pub fn camera(&self) -> &MjvCamera {
269 &self.camera
270 }
271
272 pub fn camera_mut(&mut self) -> &mut MjvCamera {
274 &mut self.camera
275 }
276
277 pub fn rgb_enabled(&self) -> bool {
279 self.flags.contains(RendererFlags::RENDER_RGB)
280 }
281
282 pub fn depth_enabled(&self) -> bool {
284 self.flags.contains(RendererFlags::RENDER_DEPTH)
285 }
286
287 pub fn set_font_scale(&mut self, font_scale: MjtFontScale) {
289 self.context.change_font(font_scale);
290 }
291
292 pub fn set_opts(&mut self, options: MjvOption) {
294 self.option = options;
295 }
296
297 pub fn set_camera(&mut self, camera: MjvCamera) {
299 self.camera = camera;
300 }
301
302 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 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 pub fn with_font_scale(mut self, font_scale: MjtFontScale) -> Self {
316 self.set_font_scale(font_scale);
317 self
318 }
319
320 pub fn with_opts(mut self, options: MjvOption) -> Self {
322 self.set_opts(options);
323 self
324 }
325
326 pub fn with_camera(mut self, camera: MjvCamera) -> Self {
328 self.set_camera(camera);
329 self
330 }
331
332 pub fn with_rgb_rendering(mut self, enable: bool) -> Self {
334 self.set_rgb_rendering(enable);
335 self
336 }
337
338 pub fn with_depth_rendering(mut self, enable: bool) -> Self {
340 self.set_depth_rendering(enable);
341 self
342 }
343
344 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 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 pub fn rgb_flat(&self) -> Option<&[u8]> {
361 self.rgb.as_deref()
362 }
363
364 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 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 pub fn depth_flat(&self) -> Option<&[f32]> {
387 self.depth.as_deref()
388 }
389
390 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 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 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 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 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 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 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 let flat_rgb = self.rgb.as_deref_mut();
511 let flat_depth = self.depth.as_deref_mut();
512
513 self.context.read_pixels(
515 flat_rgb,
516 flat_depth,
517 &vp
518 );
519
520 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 struct RendererFlags: u8 {
561 const RENDER_RGB = 1 << 0;
562 const RENDER_DEPTH = 1 << 1;
563 }
564}
565
566
567
568