mujoco_rs/
viewer.rs

1//! Module related to implementation of the [`MjViewer`]. For implementation of the C++ wrapper,
2//! see [`crate::cpp_viewer::MjViewerCpp`].
3#[cfg(feature = "viewer-ui")] use glutin::display::GetGlDisplay;
4use glutin::prelude::PossiblyCurrentGlContext;
5use glutin::surface::GlSurface;
6
7use winit::event::{ElementState, KeyEvent, Modifiers, MouseButton, MouseScrollDelta, WindowEvent};
8use winit::platform::pump_events::EventLoopExtPumpEvents;
9use winit::keyboard::{KeyCode, PhysicalKey};
10use winit::event_loop::EventLoop;
11use winit::dpi::PhysicalPosition;
12use winit::window::Fullscreen;
13
14use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
15use std::time::{Duration, Instant};
16use std::ops::{Deref, DerefMut};
17use std::marker::PhantomData;
18use std::num::NonZero;
19use std::error::Error;
20use std::fmt::Display;
21use std::borrow::Cow;
22
23use bitflags::bitflags;
24
25use crate::prelude::{MjrContext, MjrRectangle, MjtFont, MjtGridPos};
26use crate::winit_gl_base::{RenderBaseGlState, RenderBase};
27use crate::wrappers::mj_data::{MjData, MjtState};
28use crate::{builder_setters, get_mujoco_version};
29use crate::wrappers::mj_primitive::MjtNum;
30use crate::wrappers::mj_visualization::*;
31use crate::wrappers::mj_model::MjModel;
32use crate::vis_common::sync_geoms;
33
34
35#[cfg(feature = "viewer-ui")]
36mod ui;
37
38// Re-export egui for user convenience when using custom UI callbacks
39#[cfg(feature = "viewer-ui")]
40pub use egui;
41
42
43/****************************************** */
44// Rust native viewer
45/****************************************** */
46const MJ_VIEWER_DEFAULT_SIZE_PX: (u32, u32) = (1280, 720);
47const DOUBLE_CLICK_WINDOW_MS: u128 = 250;
48const TOUCH_BAR_ZOOM_FACTOR: f64 = 0.1;
49const FPS_SMOOTHING_FACTOR: f64 = 0.1;
50const REALTIME_FACTOR_SMOOTHING_FACTOR: f64 = 0.1;
51const REALTIME_FACTOR_DISPLAY_THRESHOLD: f64 = 0.02;
52
53/// How much extra room to create in the internal [`MjvScene`]. Useful for drawing labels, etc.
54pub(crate) const EXTRA_SCENE_GEOM_SPACE: usize = 2000;
55
56const HELP_MENU_TITLES: &str = concat!(
57    "Toggle help\n",
58    "Toggle info\n",
59    "Toggle v-sync\n",
60    "Toggle realtime check\n",
61    "Toggle full screen\n",
62    "Free camera\n",
63    "Track camera\n",
64    "Camera orbit\n",
65    "Camera pan\n",
66    "Camera look at\n",
67    "Zoom\n",
68    "Object select\n",
69    "Selection rotate\n",
70    "Selection translate\n",
71    "Exit\n",
72    "Reset simulation\n",
73    "Cycle cameras\n",
74    "Visualization toggles",
75);
76
77const HELP_MENU_VALUES: &str = concat!(
78    "F1\n",
79    "F2\n",
80    "F3\n",
81    "F4\n",
82    "F5\n",
83    "Escape\n",
84    "Control + Alt + double-left click\n",
85    "Left drag\n",
86    "Right [+Shift] drag\n",
87    "Alt + double-left click\n",
88    "Zoom, middle drag\n",
89    "Double-left click\n",
90    "Control + [Shift] + drag\n",
91    "Control + Alt + [Shift] + drag\n",
92    "Control + Q\n",
93    "Backspace\n",
94    "[ ]\n",
95    "See MjViewer docs"
96);
97
98#[derive(Debug)]
99pub enum MjViewerError {
100    EventLoopError(winit::error::EventLoopError),
101    GlutinError(glutin::error::Error)
102}
103
104impl Display for MjViewerError {
105    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106        match self {
107            Self::EventLoopError(e) => write!(f, "failed to initialize event_loop: {}", e),
108            Self::GlutinError(e) => write!(f, "glutin raised an error: {}", e)
109        }
110    }
111}
112
113impl Error for MjViewerError {
114    fn source(&self) -> Option<&(dyn Error + 'static)> {
115        match self {
116            Self::EventLoopError(e) => Some(e),
117            Self::GlutinError(e) => Some(e)
118        }
119    }
120}
121
122
123/// Internal state that is used by [`MjViewer`] to store
124/// [`MjData`]-related state. This is separate from [`MjViewer`]
125/// to allow use in multi-threaded programs, where the physics part
126/// runs in another thread and syncs the state with the viewer
127/// running in the main thread.
128/// 
129/// The state can be obtained through [`MjViewer::state`], which will return an `Arc<Mutex<ViewerSharedState>>`
130/// instance. For scoped access, you may also use [`MjViewer::with_state_lock`].
131#[derive(Debug)]
132pub struct ViewerSharedState<M: Deref<Target = MjModel>>{
133    /// This attribute, [`ViewerSharedState::data_passive`] and [`ViewerSharedState::data_passive_state_old`]
134    /// are used together to detect changes made to the state within the viewer.
135    /// This can happen due to changes made through the UI to joints, equalities, actuators, etc.
136    data_passive_state: Box<[MjtNum]>,
137    data_passive_state_old: Box<[MjtNum]>,
138    data_passive: MjData<M>,
139    pert: MjvPerturb,
140    running: bool,
141    user_scene: MjvScene<M>,
142
143    /* Internals */
144    last_sync_time: Instant,
145    /// Time factor representing the ratio of viewer syncs with model's selected timestep.
146    realtime_factor_smooth: f64,
147    /// Preallocated buffer for storing the state of new [`MjData`] state.
148    data_state_buffer: Box<[MjtNum]>,
149}
150
151impl<M: Deref<Target = MjModel> + Clone> ViewerSharedState<M> {
152    fn new(model: M, max_user_geom: usize) -> Self {
153        // Tracking of changes made between syncs
154        let state_size = model.state_size(MjtState::mjSTATE_INTEGRATION as u32) as usize;
155        let data_passive_state = vec![0.0; state_size].into_boxed_slice();
156        let data_passive_state_old = data_passive_state.clone();
157        let data_passive = MjData::new(model.clone());
158        let data_state_buffer = data_passive_state.clone();
159        Self {
160            data_passive_state,
161            data_passive_state_old,
162            data_passive,
163            pert: MjvPerturb::default(),
164            running: true,
165            user_scene: MjvScene::new(model, max_user_geom),
166
167            /* Internal */
168            last_sync_time: Instant::now(),
169            realtime_factor_smooth: 1.0,
170            data_state_buffer
171        }
172    }
173
174    /// Checks whether the viewer is still running or is supposed to run.
175    pub fn running(&self) -> bool {
176        self.running
177    }
178
179    /// Returns an immutable reference to a user scene for drawing custom visual-only geoms.
180    /// Geoms in the user scene are preserved between calls to [`ViewerSharedState::sync_data`].
181    pub fn user_scene(&self) -> &MjvScene<M> {
182        &self.user_scene
183    }
184
185    /// Returns a mutable reference to a user scene for drawing custom visual-only geoms.
186    /// Geoms in the user scene are preserved between calls to [`ViewerSharedState::sync_data`].
187    pub fn user_scene_mut(&mut self) -> &mut MjvScene<M> {
188        &mut self.user_scene
189    }
190
191    /// Same as [`ViewerSharedState::sync_data`], except it copies the entire [`MjData`]
192    /// struct (including large Jacobian and other arrays), not just the state needed for visualization.
193    pub fn sync_data_full(&mut self, data: &mut MjData<M>) {
194        self._sync_data(data, true);
195    }
196
197    /// Syncs the state of viewer's internal [`MjData`] with `data`.
198    /// Synchronization happens in two steps.
199    /// First the viewer checks if any changes have been made to the internal [`MjData`]
200    /// since the last call to this method (since the last sync). Any changes made are
201    /// directly copied to the parameter `data`.
202    /// Then the `data`'s state overwrites the internal [`MjData`]'s state.
203    /// 
204    /// Note that users must afterward call [`MjViewer::render`] for the scene
205    /// to be rendered and the UI to be processed.
206    /// 
207    /// <div class="warning">
208    /// Synchronization of data is performed via mjv_copyData, which only copies fields
209    /// required for visualization purposes.
210    /// 
211    /// If you require everything to be synced for use in a UI callback,
212    /// you need to call appropriate functions/methods to calculate them (e.g., data.forward()).
213    /// Alternatively, you can opt-in into syncing the entire [`MjData`] struct by calling
214    /// [`ViewerSharedState::sync_data_full`] instead.
215    /// 
216    /// The following are **NOT SYNCHRONIZED**:
217    /// - Jacobian matrices;
218    /// - mass matrices.
219    /// </div>
220    /// 
221    pub fn sync_data(&mut self, data: &mut MjData<M>) {
222        self._sync_data(data, false);
223    }
224
225    /// Data sync implementation.
226    fn _sync_data(&mut self, data: &mut MjData<M>, full_sync: bool) {
227        /* Update statistics */
228        let passive_time = self.data_passive.time();
229        let active_time = data.time();
230        if passive_time > 0.0 && active_time > passive_time {  // time = 0 means data was reset
231            let time_elapsed_sim = active_time - passive_time;
232            let elapsed_sync = self.last_sync_time.elapsed();
233            if !elapsed_sync.is_zero() {
234                self.realtime_factor_smooth += REALTIME_FACTOR_SMOOTHING_FACTOR * (
235                    time_elapsed_sim / elapsed_sync.as_secs_f64()
236                    - self.realtime_factor_smooth
237                );
238            }
239        } else {  // simulation was reset
240            self.realtime_factor_smooth = 1.0;
241        }
242
243        self.last_sync_time = Instant::now();
244
245        /* Sync */
246        self.data_passive.read_state_into(
247            MjtState::mjSTATE_INTEGRATION as u32,
248            &mut self.data_passive_state
249        );
250        if self.data_passive_state != self.data_passive_state_old {
251            data.read_state_into(MjtState::mjSTATE_INTEGRATION as u32, &mut self.data_state_buffer);
252            for ((new, passive), passive_old) in self.data_state_buffer.iter_mut()
253                .zip(&mut self.data_passive_state)
254                .zip(&mut self.data_passive_state_old)
255            {
256                if *passive_old != *passive {
257                    *new = *passive;
258                }
259            }
260
261            data.set_state(&self.data_state_buffer, MjtState::mjSTATE_INTEGRATION as u32);
262        }
263
264        if full_sync {
265            // Copy everything.
266            data.copy_to(&mut self.data_passive);
267        } else {
268            // Copy only visually-required information to the internal passive data.
269            data.copy_visual_to(&mut self.data_passive);
270        }
271
272        // Make both saved states the same.
273        // If any modification is made through the viewer
274        // between syncs, then the above if block will trigger a transfer.
275        self.data_passive.read_state_into(  // read to match the synced passive MjData
276            MjtState::mjSTATE_INTEGRATION as u32,
277            &mut self.data_passive_state
278        );
279        self.data_passive_state_old.copy_from_slice(&self.data_passive_state);
280
281        // Apply perturbations
282        self.pert.apply(self.data_passive.model(), data);
283    }
284}
285
286
287/// A Rust-native implementation of the MuJoCo viewer. To confirm to rust safety rules,
288/// the viewer doesn't store a mutable reference to the [`MjData`] struct, but it instead
289/// accepts it as a parameter at its methods.
290/// 
291/// The [`MjViewer::sync`] method must be called to sync the state of [`MjViewer`] and [`MjData`].
292/// 
293/// # Shortcuts
294/// Main keyboard and mouse shortcuts can be viewed by pressing ``F1``.
295/// Additionally, some visualization toggles are included, but not displayed
296/// in the ``F1`` help menu:
297/// - C: camera,
298/// - U: actuator,
299/// - J: joint,
300/// - M: center of mass,
301/// - H: convex hull,
302/// - Z: light,
303/// - T: transparent,
304/// - I: inertia,
305/// - E: constraint.
306/// 
307/// # Safety
308/// Due to the nature of OpenGL, this should only be run in the **main thread**.
309#[derive(Debug)]
310pub struct MjViewer<M: Deref<Target = MjModel> + Clone> {
311    /* MuJoCo rendering */
312    scene: MjvScene<M>,
313    context: MjrContext,
314    camera: MjvCamera,
315
316    /* Other MuJoCo related */
317    model: M,
318    opt: MjvOption,
319
320    /* Internal state */
321    last_x: f64,
322    last_y: f64,
323    last_bnt_press_time: Instant,
324    rect_view: MjrRectangle,
325    rect_full: MjrRectangle,
326    fps_timer: Instant,
327    fps_smooth: f64,
328
329    /* OpenGL */
330    adapter: RenderBase,
331    event_loop: EventLoop<()>,
332    modifiers: Modifiers,
333    buttons_pressed: ButtonsPressed,
334    raw_cursor_position: (f64, f64),
335
336    /* External interaction */
337    user_scene: MjvScene<M>,
338    shared_state: Arc<Mutex<ViewerSharedState<M>>>,
339
340    /* User interface */
341    #[cfg(feature = "viewer-ui")]
342    ui: ui::ViewerUI<M>,
343
344    status: ViewerStatusBit
345}
346
347impl<M: Deref<Target = MjModel> + Clone> MjViewer<M> {
348    /// Launches the MuJoCo viewer. A [`Result`] struct is returned that either contains
349    /// [`MjViewer`] or a [`MjViewerError`]. The `max_user_geom` parameter
350    /// defines how much space will be allocated for additional, user-defined visual-only geoms.
351    /// It can thus be set to 0 if no additional geoms will be drawn by the user.
352    /// 
353    /// Note that the use of [`MjViewerBuilder`] is preferred, because it is more flexible.
354    /// Call [`MjViewer::builder`] to create a [`MjViewerBuilder`] instance.
355    pub fn launch_passive(model: M, max_user_geom: usize) -> Result<Self, MjViewerError> {
356        MjViewerBuilder::new()
357            .max_user_geoms(max_user_geom)
358            .build_passive(model)
359    }
360
361    /// A shortcut for creating an instance of [`MjViewerBuilder`].
362    /// The builder can be used to build the viewer after configuring it.
363    /// It allows better configuration than [`MjViewer::launch_passive`], which
364    /// is fixed to achieve backward compatibility.
365    pub fn builder() -> MjViewerBuilder<M> {
366        MjViewerBuilder::new()
367    }
368
369    /// Checks whether the window is still open.
370    pub fn running(&self) -> bool {
371        self.shared_state.lock().unwrap().running()
372    }
373
374    /// Returns a reference to the shared state [`ViewerSharedState`].
375    /// This struct can be used to sync the state of the viewer with
376    /// the simulation, possibly running in another thread.
377    pub fn state(&self) -> &Arc<Mutex<ViewerSharedState<M>>> {
378        &self.shared_state
379    }
380
381    /// Acquires a Mutex lock on the [`MjViewer`]'s shared state ([`MjViewer::state`]).
382    /// The acquired lock is passed to the function/closure `fun`.
383    /// 
384    /// # Errors
385    /// Returns [`PoisonError`] if the mutex holding the shared state has panicked, thus poisoning
386    /// the lock.
387    ///
388    /// # Example
389    /// ```no_run
390    /// # use mujoco_rs::viewer::MjViewer;
391    /// # use mujoco_rs::prelude::*;
392    /// # let model = MjModel::from_xml_string("<mujoco/>").unwrap();
393    /// let mut viewer = MjViewer::builder().max_user_geoms(1)
394    ///     .build_passive(&model).unwrap();
395    /// viewer.with_state_lock(|mut lock| {
396    ///     let scene = lock.user_scene_mut();
397    ///     scene.create_geom(MjtGeom::mjGEOM_BOX, Some([1.0, 1.0, 1.0]), Some([0.0, 0.0, 0.0]), None, None);
398    /// }).unwrap();
399    /// ```
400    pub fn with_state_lock<F, R>(&self, fun: F) -> Result<R, PoisonError<MutexGuard<'_, ViewerSharedState<M>>>>
401        where F: FnOnce(MutexGuard<ViewerSharedState<M>>) -> R
402    {
403        Ok(fun(self.shared_state.lock()?))
404    }
405
406    /// **DEPRECATED** method for reading the state.
407    /// This will be removed in 3.0.0, in favor of [`ViewerSharedState::user_scene`],
408    /// which allows usage from multiple threads.
409    /// 
410    /// [`ViewerSharedState`] can be obtained via [`MjViewer::state`], which returns `Arc<Mutex<ViewerSharedState>>`.
411    /// 
412    /// # Note
413    /// There is no way to make a fully compatible proxy method to [`ViewerSharedState::user_scene`]
414    /// through the viewer as a reference to the scene is returned, thus this method currently
415    /// uses a temporary user scene, part of [`MjViewer`]. The viewer then syncs both
416    /// [`MjViewer::user_scene`] and [`ViewerSharedState::user_scene`] to achieve backward compatibility,
417    /// however we strongly urge you to use the latter as the **FORMER** will be **REMOVED IN THE FUTURE**.
418    #[deprecated(since = "2.2.0", note = "use viewer.state().lock().unwrap().user_scene()")]
419    pub fn user_scene(&self) -> &MjvScene<M>{
420        &self.user_scene
421    }
422
423    /// **DEPRECATED** method for reading the state.
424    /// This will be removed in 3.0.0, in favor of [`ViewerSharedState::user_scene_mut`],
425    /// which allows usage from multiple threads.
426    /// 
427    /// [`ViewerSharedState`] can be obtained via [`MjViewer::state`], which returns `Arc<Mutex<ViewerSharedState>>`.
428    /// 
429    /// # Note
430    /// There is no way to make a fully compatible proxy method to [`ViewerSharedState::user_scene_mut`]
431    /// through the viewer as a reference to the scene is returned, thus this method currently
432    /// uses a temporary user scene, part of [`MjViewer`]. The viewer then syncs both
433    /// [`MjViewer::user_scene_mut`] and [`ViewerSharedState::user_scene_mut`] to achieve backward compatibility,
434    /// however we strongly urge you to use the latter as the **FORMER** will be **REMOVED IN THE FUTURE**.
435    #[deprecated(since = "2.2.0", note = "use viewer.state().lock().unwrap().user_scene_mut()")]
436    pub fn user_scene_mut(&mut self) -> &mut MjvScene<M>{
437        &mut self.user_scene
438    }
439
440    #[deprecated(since = "1.3.0", note = "use viewer.state().lock().unwrap().user_scene()")]
441    pub fn user_scn(&self) -> &MjvScene<M> {
442        self.user_scene()
443    }
444
445    #[deprecated(since = "1.3.0", note = "use viewer.state().lock().unwrap().user_scene_mut()")]
446    pub fn user_scn_mut(&mut self) -> &mut MjvScene<M> {
447        self.user_scene_mut()
448    }
449
450    /// Adds a user-defined UI callback for custom widgets in the viewer's UI.
451    /// The callback receives an [`egui::Context`] reference and can be used to create
452    /// custom windows, panels, or other UI elements.
453    /// It also receives a mutable reference to [`MjData`], which can be used to read
454    /// and modify simulation state. Note that the model can be accessed through [`MjData::model`].
455    ///
456    /// This method is only available when the `viewer-ui` feature is enabled.
457    ///
458    /// # Example
459    /// ```no_run
460    /// # use mujoco_rs::prelude::*;
461    /// # use mujoco_rs::viewer::MjViewer;
462    /// # let model = MjModel::from_xml_string("<mujoco/>").unwrap();
463    /// # let mut viewer = MjViewer::launch_passive(&model, 0).unwrap();
464    /// viewer.add_ui_callback(|ctx, data| {
465    ///     use mujoco_rs::viewer::egui;
466    ///     egui::Window::new("Custom controls")
467    ///         .scroll(true)
468    ///         .show(ctx, |ui| {
469    ///             ui.label("Custom UI element");
470    ///         });
471    /// });
472    /// ```
473    #[cfg(feature = "viewer-ui")]
474    pub fn add_ui_callback<F>(&mut self, callback: F)
475    where
476        F: FnMut(&egui::Context, &mut MjData<M>) + 'static
477    {
478        self.ui.add_ui_callback(callback);
479    }
480
481    /// Deprecated synchronization and rendering method.
482    /// Users should use [`MjViewer::sync_data`] instead, which is a proxy
483    /// to [`ViewerSharedState::sync_data`], and afterwards call [`MjViewer::render`].
484    /// # Migration to new API
485    /// To achieve identical behavior, replace the call of this method with
486    /// a call to [`MjViewer::sync_data`] and afterwards [`MjViewer::render`].
487    /// 
488    /// [`MjViewer::render`] must be called by the user, because syncing no longer
489    /// processes the UI and renders the scene. This was changed for the purposes of
490    /// allowing multithreading --- i.e., rendering in main thread and everything else in a separate thread.
491    #[deprecated(since = "2.2.0", note = "replaced with calls to sync_data and render")]
492    pub fn sync(&mut self, data: &mut MjData<M>) {
493        self.shared_state.lock().unwrap().sync_data(data);
494        self.render();
495    }
496
497    /// Same as [`MjViewer::sync_data`], except it copies the entire [`MjData`]
498    /// struct (including large Jacobian and other arrays), not just the state needed for visualization.
499    /// This is a proxy to [`ViewerSharedState::sync_data_full`].
500    pub fn sync_data_full(&mut self, data: &mut MjData<M>) {
501        self.shared_state.lock().unwrap().sync_data_full(data);
502    }
503
504    /// Syncs the state of viewer's internal [`MjData`] with `data`.
505    /// This is a proxy to [`ViewerSharedState::sync_data`].
506    /// 
507    /// Additionally, any changes made to the internal [`MjData`] in between syncs,
508    /// get copied back to `data` before the actual sync.
509    /// This includes object perturbations.
510    /// 
511    /// Note that users must afterward call [`MjViewer::render`] for the scene
512    /// to be rendered and the UI to be processed.
513    /// 
514    /// <div class="warning">
515    /// Synchronization of data is performed via mjv_copyData, which only copies fields
516    /// required for visualization purposes.
517    /// 
518    /// If you require everything to be synced for use in a UI callback,
519    /// you need to call appropriate functions/methods to calculate them (e.g., data.forward()).
520    /// Alternatively, you can opt-in into syncing the entire [`MjData`] struct by calling
521    /// [`MjViewer::sync_data_full`] instead.
522    /// 
523    /// The following are **NOT SYNCHRONIZED**:
524    /// - Jacobian matrices;
525    /// - mass matrices.
526    /// </div>
527    /// 
528    /// # Example
529    /// ```no_run
530    /// # use mujoco_rs::prelude::*;
531    /// # use mujoco_rs::viewer::MjViewer;
532    /// # let model = MjModel::from_xml("/path/scene.xml").unwrap();
533    /// # let mut viewer = MjViewer::builder().build_passive(&model).unwrap();
534    /// # let mut data = MjData::new(&model);
535    /// viewer.sync_data(&mut data);  // sync the data
536    /// viewer.render();  // render the scene and process the user interface
537    /// ```
538    pub fn sync_data(&mut self, data: &mut MjData<M>) {
539        self.shared_state.lock().unwrap().sync_data(data);
540    }
541
542    /// Processes the UI (when enabled), processes events, draws the scene
543    /// and swaps buffers in OpenGL.
544    pub fn render(&mut self) {
545        let RenderBaseGlState {
546            gl_context,
547            gl_surface,
548            ..
549        } = self.adapter.state.as_ref().unwrap();
550
551        /* Make sure everything is done on the viewer's window */
552        gl_context.make_current(gl_surface).expect("could not make OpenGL context current");
553
554        /* Read the screen size */
555        self.update_rectangles(self.adapter.state.as_ref().unwrap().window.inner_size().into());
556
557        /* Process mouse and keyboard events */
558        self.process_events();
559
560        /* Update the scene from data and render */
561        self.update_scene();
562
563        /* Draw the user menu on top */
564        #[cfg(feature = "viewer-ui")]
565        self.process_user_ui();
566
567        /* Update the user menu state and overlays */
568        self.update_menus();
569
570        /* Flush to the GPU */
571        self.swap_buffers();
572    }
573
574    /// Perform OpenGL buffer swap.
575    fn swap_buffers(&self) {
576        let RenderBaseGlState {
577            gl_context,
578            gl_surface,
579            ..
580        } = self.adapter.state.as_ref().unwrap();
581
582        /* Swap OpenGL buffers (render to screen) */
583        gl_surface.swap_buffers(gl_context).expect("buffer swap in OpenGL failed");
584    }
585
586    fn update_smooth_fps(&mut self) {
587        let elapsed = self.fps_timer.elapsed();
588
589        let fps = if elapsed.is_zero() {
590            self.fps_smooth
591        } else {
592            1.0 / elapsed.as_secs_f64()
593        };
594
595        self.fps_timer = Instant::now();
596        self.fps_smooth += FPS_SMOOTHING_FACTOR * (fps - self.fps_smooth);
597    }
598
599    /// Updates the scene and draws it to the display.
600    fn update_scene(&mut self) {
601        /* Update the scene from the MjData state */
602        let lock = &mut self.shared_state.lock().unwrap();
603        let ViewerSharedState { data_passive, pert, .. } = lock.deref_mut();
604        self.scene.update(data_passive, &self.opt, pert, &mut self.camera);
605
606        // Temporary check until 3.0.0. Geom syncing will fail if the target scene is smaller than
607        // the requested number of user scenes.
608        let new_user_scene = lock.user_scene();
609        let old_user_scene = &self.user_scene;
610        if !new_user_scene.geoms().is_empty() && !old_user_scene.geoms().is_empty() {
611            panic!(
612                "Both the new ViewerSharedState::user_scene and the deprecated MjViewer::user_scene are non-empty. \
613                 Please update your code to fully use ViewerSharedState::user_scene."
614            );
615        }
616
617        // Draw geoms drawn through the user scene.
618        sync_geoms(new_user_scene, &mut self.scene)
619            .expect("could not sync the user scene with the internal scene; this is a bug, please report it.");
620
621        // Temporary (until MuJoCo-rs 3.0.0) sync. Used only for backward compatibility.
622        sync_geoms(old_user_scene, &mut self.scene)
623            .expect("could not sync the user scene with the internal scene; this is a bug, please report it.");
624
625        self.scene.render(&self.rect_full, &self.context);
626    }
627
628    /// Draws the user menu
629    fn update_menus(&mut self) {
630        let rectangle_from_ui = self.rect_view;
631        let rectangle_full = self.rect_full;
632
633        /* Overlay section */
634        if self.status.contains(ViewerStatusBit::HELP) {  // Help
635            self.context.overlay(
636                MjtFont::mjFONT_NORMAL, MjtGridPos::mjGRID_TOPLEFT,
637                rectangle_from_ui,
638                HELP_MENU_TITLES,
639                Some(HELP_MENU_VALUES)
640            );
641        }
642
643        // Read later-required information and then drop the mutex lock
644        let (
645            time,
646            memory_pct,
647            mut total_memory,
648            realtime_factor
649        ) = {
650            let state_lock = self.shared_state.lock().unwrap();
651            let data_lock = &state_lock.data_passive;
652            let memory_total = data_lock.narena().max(1) as f64;
653            (
654                data_lock.time(),
655                100.0 * data_lock.maxuse_arena() as f64 / memory_total, memory_total,
656                state_lock.realtime_factor_smooth
657            )
658        };
659
660        if self.status.contains(ViewerStatusBit::INFO) {  // Info
661            self.update_smooth_fps();
662
663            // Overlay headers
664            let headers = concat!(
665                "FPS\n",
666                "Time\n",
667                "Memory\n",
668                "Realtime factor"
669            );
670
671            // Calculate the amount of memory used and represent with SI units.
672            let mut memory_unit = ' ';
673            if total_memory > 1e6 {
674                total_memory /= 1e6;
675                memory_unit = 'M';
676            } else if total_memory > 1e3 {
677                total_memory /= 1e3;
678                memory_unit = 'k';
679            }
680
681            // Format values of the overlay.
682            let values = format!(
683                concat!(
684                    "{:.1}\n",
685                    "{:.1}\n",
686                    "{:.1} % out of {:.1} {}\n",
687                    "{:.1} %"
688                ),
689                self.fps_smooth,
690                time,
691                memory_pct, total_memory, memory_unit,
692                realtime_factor * 100.0
693            );
694
695            self.context.overlay(
696                MjtFont::mjFONT_NORMAL,
697                MjtGridPos::mjGRID_BOTTOMLEFT,
698                rectangle_from_ui,
699                &headers,
700                Some(&values)
701            );
702        }
703
704        // Check for slowdowns
705        if self.status.contains(ViewerStatusBit::WARN_REALTIME) {
706            if (realtime_factor - 1.0).abs() > REALTIME_FACTOR_DISPLAY_THRESHOLD {
707                self.context.overlay(
708                    MjtFont::mjFONT_BIG,
709                    MjtGridPos::mjGRID_BOTTOMRIGHT,
710                    rectangle_full,
711                    &format!("Realtime factor: {:.1} %", realtime_factor * 100.0),
712                    None
713                );
714            }
715        }
716    }
717
718    /// Draws the user UI
719    #[cfg(feature = "viewer-ui")]
720    fn process_user_ui(&mut self) {
721        /* Draw the user interface */
722
723        use crate::viewer::ui::UiEvent;
724        let RenderBaseGlState {window, ..} = &self.adapter.state.as_ref().unwrap();
725
726        let inner_size = window.inner_size();
727        let left = self.ui.process(
728            window, &mut self.status,
729            &mut self.scene, &mut self.opt,
730            &mut self.camera, &mut self.shared_state.lock().unwrap().data_passive
731        );
732
733        /* Adjust the viewport so MuJoCo doesn't draw over the UI */
734        self.rect_view.left = left as i32;
735        self.rect_view.width = inner_size.width as i32;
736
737        /* Reset some OpenGL settings so that MuJoCo can still draw */
738        self.ui.reset();
739
740        /* Process events made in the user UI */
741        while let Some(event) = self.ui.drain_events() {
742            use UiEvent::*;
743            match event {
744                Close => self.shared_state.lock().unwrap().running = false,
745                Fullscreen => self.toggle_full_screen(),
746                ResetSimulation => {
747                    let mut lock = self.shared_state.lock().unwrap();
748                    lock.data_passive.reset();
749                    lock.data_passive.forward();
750                },
751                AlignCamera => {
752                    self.camera = MjvCamera::new_free(&self.model);
753                },
754                VSyncToggle => {
755                    self.update_vsync();
756                }
757            }
758        }
759    }
760
761    /// Reads the state of requested vsync setting and makes appropriate calls to [`glutin`].
762    fn update_vsync(&self) {
763        let RenderBaseGlState {
764            gl_surface, gl_context, ..
765        } = &self.adapter.state.as_ref().unwrap();
766
767        if self.status.contains(ViewerStatusBit::VSYNC) {
768            gl_surface.set_swap_interval(
769                gl_context, glutin::surface::SwapInterval::Wait(NonZero::new(1).unwrap())
770            ).expect("failed to enable vsync");
771        } else {
772            gl_surface.set_swap_interval(
773                gl_context, glutin::surface::SwapInterval::DontWait
774            ).expect("failed to disable vsync");
775        }
776    }
777
778    /// Updates the dimensions of the rectangles defining the dimensions of
779    /// the user interface, as well as the actual scene viewer.
780    fn update_rectangles(&mut self, viewport_size: (i32, i32)) {
781        // The scene (middle) rectangle
782        self.rect_view.width = viewport_size.0;
783        self.rect_view.height = viewport_size.1;
784
785        self.rect_full.width = viewport_size.0;
786        self.rect_full.height = viewport_size.1;
787    }
788
789    /// Processes user input events.
790    fn process_events(&mut self) {
791        self.event_loop.pump_app_events(Some(Duration::ZERO), &mut self.adapter);
792        while let Some(window_event) = self.adapter.queue.pop_front() {
793            #[cfg(feature = "viewer-ui")]
794            {
795                let window: &winit::window::Window = &self.adapter.state.as_ref().unwrap().window;
796                self.ui.handle_events(window, &window_event);
797            }
798
799            match window_event {
800                WindowEvent::ModifiersChanged(modifiers) => self.modifiers = modifiers,
801                WindowEvent::MouseInput {state, button, .. } => {
802                    let is_pressed = state == ElementState::Pressed;
803                    
804                    #[cfg(feature = "viewer-ui")]
805                    if self.ui.covered() && is_pressed {
806                        continue;
807                    }
808
809                    let index = match button {
810                        MouseButton::Left => {
811                            self.process_left_click(state);
812                            ButtonsPressed::LEFT
813                        },
814                        MouseButton::Middle => ButtonsPressed::MIDDLE,
815                        MouseButton::Right => ButtonsPressed::RIGHT,
816                        _ => return
817                    };
818
819                    self.buttons_pressed.set(index, is_pressed);
820                }
821
822                WindowEvent::CursorMoved { position, .. } => {
823                    let PhysicalPosition { x, y } = position;
824
825                    // The UI might not be detected as covered as dragging can happen slightly outside
826                    // of a (popup) window. This might seem like an ad-hoc solution, but is at the time the
827                    // shortest and most efficient one.
828                    #[cfg(feature = "viewer-ui")]
829                    if self.ui.dragged() {
830                        continue;
831                    }
832
833                    self.process_cursor_pos(x, y);
834                }
835
836                // Set the viewer's state to pending exit.
837                WindowEvent::KeyboardInput {
838                    event: KeyEvent {
839                        physical_key: PhysicalKey::Code(KeyCode::KeyQ),
840                        state: ElementState::Pressed, ..
841                    }, ..
842                } if self.modifiers.state().control_key()  => {
843                    self.shared_state.lock().unwrap().running = false;
844                }
845
846                // Also set the viewer's state to pending exit if the window no longer exists.
847                WindowEvent::CloseRequested => { self.shared_state.lock().unwrap().running = false }
848
849                // Free the camera from tracking.
850                WindowEvent::KeyboardInput {
851                    event: KeyEvent {
852                        physical_key: PhysicalKey::Code(KeyCode::Escape),
853                        state: ElementState::Pressed, ..
854                    }, ..
855                } => {
856                    #[cfg(feature = "viewer-ui")]
857                    if self.ui.focused() {
858                        continue;
859                    }
860                    self.camera.free();
861                }
862
863                // Toggle help menu
864                WindowEvent::KeyboardInput {
865                    event: KeyEvent {
866                        physical_key: PhysicalKey::Code(KeyCode::F1),
867                        state: ElementState::Pressed, ..
868                    }, ..
869                } => {
870                    self.status.toggle(ViewerStatusBit::HELP);
871                }
872
873                 // Toggle info menu
874                WindowEvent::KeyboardInput {
875                    event: KeyEvent {
876                        physical_key: PhysicalKey::Code(KeyCode::F2),
877                        state: ElementState::Pressed, ..
878                    }, ..
879                } => {
880                    self.status.toggle(ViewerStatusBit::INFO);
881                }
882
883                // Toggle VSync
884                WindowEvent::KeyboardInput {
885                    event: KeyEvent {physical_key: PhysicalKey::Code(KeyCode::F3), state: ElementState::Pressed, ..},
886                    ..
887                } => {
888                    self.status.toggle(ViewerStatusBit::VSYNC);
889                    self.update_vsync();
890                }
891
892                // Non-realtime warnings
893                WindowEvent::KeyboardInput {
894                    event: KeyEvent {physical_key: PhysicalKey::Code(KeyCode::F4), state: ElementState::Pressed, ..},
895                    ..
896                } => {
897                    self.status.toggle(ViewerStatusBit::WARN_REALTIME);
898                }
899
900                // Full screen
901                WindowEvent::KeyboardInput {
902                    event: KeyEvent {
903                        physical_key: PhysicalKey::Code(KeyCode::F5),
904                        state: ElementState::Pressed, ..
905                    }, ..
906                } => {
907                    self.toggle_full_screen();
908                }
909
910                // Reset the simulation (the data).
911                WindowEvent::KeyboardInput {
912                    event: KeyEvent {
913                        physical_key: PhysicalKey::Code(KeyCode::Backspace),
914                        state: ElementState::Pressed, ..
915                    }, ..
916                } => {
917                    #[cfg(feature = "viewer-ui")]
918                    if self.ui.focused() {
919                        continue;
920                    }
921                    let mut lock = self.shared_state.lock().unwrap();
922                    lock.data_passive.reset();
923                    lock.data_passive.forward();
924                }
925
926                // Cycle to the next camera
927                WindowEvent::KeyboardInput {
928                    event: KeyEvent {
929                        physical_key: PhysicalKey::Code(KeyCode::BracketRight),
930                        state: ElementState::Pressed, ..
931                    }, ..
932                } => {
933                    self.cycle_camera(1);
934                }
935
936                // Cycle to the previous camera
937                WindowEvent::KeyboardInput {
938                    event: KeyEvent {
939                        physical_key: PhysicalKey::Code(KeyCode::BracketLeft),
940                        state: ElementState::Pressed, ..
941                    }, ..
942                } => {
943                    self.cycle_camera(-1);
944                }
945
946                // Toggles camera visualization.
947                WindowEvent::KeyboardInput {
948                    event: KeyEvent {physical_key: PhysicalKey::Code(KeyCode::KeyC), state: ElementState::Pressed, ..},
949                    ..
950                } => self.toggle_opt_flag(MjtVisFlag::mjVIS_CAMERA),
951
952                WindowEvent::KeyboardInput {
953                    event: KeyEvent {physical_key: PhysicalKey::Code(KeyCode::KeyU), state: ElementState::Pressed, ..},
954                    ..
955                } => self.toggle_opt_flag(MjtVisFlag::mjVIS_ACTUATOR),
956
957                WindowEvent::KeyboardInput {
958                    event: KeyEvent {physical_key: PhysicalKey::Code(KeyCode::KeyJ), state: ElementState::Pressed, ..},
959                    ..
960                } => self.toggle_opt_flag(MjtVisFlag::mjVIS_JOINT),
961
962                WindowEvent::KeyboardInput {
963                    event: KeyEvent {physical_key: PhysicalKey::Code(KeyCode::KeyM), state: ElementState::Pressed, ..},
964                    ..
965                } => self.toggle_opt_flag(MjtVisFlag::mjVIS_COM),
966
967                WindowEvent::KeyboardInput {
968                    event: KeyEvent {physical_key: PhysicalKey::Code(KeyCode::KeyH), state: ElementState::Pressed, ..},
969                    ..
970                } => self.toggle_opt_flag(MjtVisFlag::mjVIS_CONVEXHULL),
971
972                WindowEvent::KeyboardInput {
973                    event: KeyEvent {physical_key: PhysicalKey::Code(KeyCode::KeyZ), state: ElementState::Pressed, ..},
974                    ..
975                } => self.toggle_opt_flag(MjtVisFlag::mjVIS_LIGHT),
976
977                WindowEvent::KeyboardInput {
978                    event: KeyEvent {physical_key: PhysicalKey::Code(KeyCode::KeyT), state: ElementState::Pressed, ..},
979                    ..
980                } => self.toggle_opt_flag(MjtVisFlag::mjVIS_TRANSPARENT),
981
982                WindowEvent::KeyboardInput {
983                    event: KeyEvent {physical_key: PhysicalKey::Code(KeyCode::KeyI), state: ElementState::Pressed, ..},
984                    ..
985                } => self.toggle_opt_flag(MjtVisFlag::mjVIS_INERTIA),
986
987                WindowEvent::KeyboardInput {
988                    event: KeyEvent {physical_key: PhysicalKey::Code(KeyCode::KeyE), state: ElementState::Pressed, ..},
989                    ..
990                } => self.toggle_opt_flag(MjtVisFlag::mjVIS_CONSTRAINT),
991
992                #[cfg(feature = "viewer-ui")]
993                WindowEvent::KeyboardInput {
994                    event: KeyEvent {physical_key: PhysicalKey::Code(KeyCode::KeyX), state: ElementState::Pressed, ..},
995                    ..
996                } => self.status.toggle(ViewerStatusBit::UI),
997
998                // Zoom in/out
999                WindowEvent::MouseWheel {delta, ..} => {
1000                    #[cfg(feature = "viewer-ui")]
1001                    if self.ui.covered() {
1002                        continue;
1003                    }
1004
1005                    let value = match delta {
1006                        MouseScrollDelta::LineDelta(_, down) => down as f64,
1007                        MouseScrollDelta::PixelDelta(PhysicalPosition {y, ..}) => y * TOUCH_BAR_ZOOM_FACTOR
1008                    };
1009                    self.process_scroll(value);
1010                }
1011
1012                _ => {}  // ignore other events
1013            }
1014        }
1015    }
1016
1017    /// Toggles visualization options.
1018    fn toggle_opt_flag(&mut self, flag: MjtVisFlag) {
1019        let index = flag as usize;
1020        self.opt.flags[index] = 1 - self.opt.flags[index];
1021    }
1022
1023    /// Cycle MJCF defined cameras.
1024    fn cycle_camera(&mut self, direction: i32) {
1025        let n_cam = self.model.ffi().ncam;
1026        if n_cam == 0 {  // No cameras, ignore.
1027            return;
1028        }
1029
1030        self.camera.fix((self.camera.fixedcamid + direction).rem_euclid(n_cam) as u32);
1031    }
1032
1033    /// Toggles full screen mode.
1034    fn toggle_full_screen(&mut self) {
1035        let window = &self.adapter.state.as_ref().unwrap().window;
1036        if window.fullscreen().is_some() {
1037            window.set_fullscreen(None);
1038        }
1039        else {
1040            window.set_fullscreen(Some(Fullscreen::Borderless(None)));
1041        }
1042    }
1043
1044    /// Processes scrolling events.
1045    fn process_scroll(&mut self, change: f64) {
1046        self.camera.move_(MjtMouse::mjMOUSE_ZOOM, &self.model, 0.0, -0.05 * change, &self.scene);
1047    }
1048
1049    /// Processes camera and perturbation movements.
1050    fn process_cursor_pos(&mut self, x: f64, y: f64) {
1051        self.raw_cursor_position = (x, y);
1052        /* Calculate the change in mouse position since last call */
1053        let dx = x - self.last_x;
1054        let dy = y - self.last_y;
1055        self.last_x = x;
1056        self.last_y = y;
1057        let window = &self.adapter.state.as_ref().unwrap().window;
1058        let modifiers = &self.modifiers.state();
1059        let buttons = &self.buttons_pressed;
1060        let shift = modifiers.shift_key();
1061
1062        /* Check mouse presses and move the camera if any of them is pressed */
1063        let action;
1064        let height = window.outer_size().height as f64;
1065
1066        let mut lock = self.shared_state.lock().unwrap();
1067        let ViewerSharedState {data_passive, pert, ..} = lock.deref_mut();
1068        if buttons.contains(ButtonsPressed::LEFT) {
1069            if pert.active == MjtPertBit::mjPERT_TRANSLATE as i32 {
1070                action = if shift {MjtMouse::mjMOUSE_MOVE_H} else {MjtMouse::mjMOUSE_MOVE_V};
1071            }
1072            else {
1073                action = if shift {MjtMouse::mjMOUSE_ROTATE_H} else {MjtMouse::mjMOUSE_ROTATE_V};
1074            }
1075        }
1076        else if buttons.contains(ButtonsPressed::RIGHT) {
1077            action = if shift {MjtMouse::mjMOUSE_MOVE_H} else {MjtMouse::mjMOUSE_MOVE_V};
1078        }
1079        else if buttons.contains(ButtonsPressed::MIDDLE) {
1080            action = MjtMouse::mjMOUSE_ZOOM;
1081        }
1082        else {
1083            return;  // If buttons aren't pressed, ignore.
1084        }
1085
1086        /* When the perturbation isn't active, move the camera */
1087        if pert.active == 0 {
1088            self.camera.move_(action, &self.model, dx / height, dy / height, &self.scene);
1089        }
1090        else {  // When the perturbation is active, move apply the perturbation.
1091            pert.move_(&self.model, data_passive, action, dx / height, dy / height, &self.scene);
1092        }
1093    }
1094
1095    /// Processes left clicks and double left clicks.
1096    fn process_left_click(&mut self, state: ElementState) {
1097        let modifier_state = self.modifiers.state();
1098        let mut lock = self.shared_state.lock().unwrap();
1099        let ViewerSharedState {data_passive, pert, ..} = lock.deref_mut();
1100        match state {
1101            ElementState::Pressed => {
1102                /* Clicking and holding applies perturbation */
1103                if pert.select > 0 && modifier_state.control_key() {
1104                    let type_ = if modifier_state.alt_key() {
1105                        MjtPertBit::mjPERT_TRANSLATE
1106                    } else {
1107                        MjtPertBit::mjPERT_ROTATE
1108                    };
1109                    pert.start(type_, &self.model, data_passive, &self.scene);
1110                }
1111
1112                /* Double click detection */
1113                if self.last_bnt_press_time.elapsed().as_millis() < DOUBLE_CLICK_WINDOW_MS {
1114                    let cp = self.raw_cursor_position;
1115                    let x = cp.0;
1116                    let y = self.rect_full.height as f64 - cp.1;
1117
1118                    /* Obtain the selection */ 
1119                    let rect = &self.rect_full;
1120                    let (body_id, _, flex_id, skin_id, xyz) = self.scene.find_selection(
1121                        data_passive, &self.opt,
1122                        rect.width as MjtNum / rect.height as MjtNum,
1123                        (x - rect.left as MjtNum) / rect.width as MjtNum,
1124                        (y - rect.bottom as MjtNum) / rect.height as MjtNum
1125                    );
1126
1127                    /* Set tracking camera */
1128                    if modifier_state.alt_key() {
1129                        if body_id >= 0 {
1130                            self.camera.lookat = xyz;
1131                            if modifier_state.control_key() {
1132                                self.camera.track(body_id as u32);
1133                            }
1134                        }
1135                    }
1136                    else {
1137                        /* Mark selection */
1138                        if body_id >= 0 {
1139                            pert.select = body_id;
1140                            pert.flexselect = flex_id;
1141                            pert.skinselect = skin_id;
1142                            pert.active = 0;
1143                            pert.update_local_pos(xyz, data_passive);
1144                        }
1145                        else {
1146                            pert.select = 0;
1147                            pert.flexselect = -1;
1148                            pert.skinselect = -1;
1149                        }
1150                    }
1151                }
1152                self.last_bnt_press_time = Instant::now();
1153            },
1154            ElementState::Released => {
1155                // Clear perturbation when left click is released.
1156                pert.active = 0;
1157            },
1158        };
1159    }
1160}
1161
1162
1163/// Builder for [`MjViewer`].
1164/// ### Default settings:
1165/// - `window_name`: MuJoCo Rust Viewer (MuJoCo \<MuJoCo version here\>)
1166/// - `max_user_geoms`: 0
1167/// - `vsync`: false
1168/// - `warn_non_realtime`: false
1169/// 
1170pub struct MjViewerBuilder<M: Deref<Target = MjModel> + Clone> {
1171    /// The name shown on the window decoration.
1172    window_name: Cow<'static, str>,
1173    /// Maximum number of geoms that can be given by the user for custom visualization.
1174    max_user_geoms: usize,
1175    /// Start the viewer with vertical synchronization. This should be used only if rendering
1176    /// and simulation are separated by threads and you are ok with [`MjViewer::render`]
1177    /// blocking to achieve the correct refresh rate (of your monitor).
1178    vsync: bool,
1179
1180    /// Start the viewer with warnings enabled for non-realtime synchronization.
1181    /// When this is enabled and the simulation state isn't synced in realtime, an overlay will be displayed
1182    /// in the bottom right corner indicating the realtime percentage.
1183    /// The warning will only be shown if the deviation is 2 % from realtime or more.
1184    warn_non_realtime: bool,
1185
1186    /* Miscellaneous */
1187    /// Used to store the model type only. Useful for type inference.
1188    model_type: PhantomData<M>,
1189}
1190
1191impl<M: Deref<Target = MjModel> + Clone> MjViewerBuilder<M> {
1192    builder_setters! {
1193        window_name: S where S: Into<Cow<'static, str>>; "text shown in the title of the window.";
1194        max_user_geoms: usize; "maximum number of geoms that can be drawn by the user in addition to the regular geoms.";
1195        vsync: bool; "enable vertical synchronization by default.";
1196        warn_non_realtime: bool; "enable showing an overlay when the simulation state isn't synced in realtime (deviation larger than 2 %).";
1197    }
1198}
1199
1200impl<M: Deref<Target = MjModel> + Clone> MjViewerBuilder<M> {
1201    pub fn new() -> Self {
1202        Self { 
1203            window_name: Cow::Owned(format!("MuJoCo Rust Viewer (MuJoCo {})", get_mujoco_version())),
1204            max_user_geoms: 0, vsync: false, warn_non_realtime: false,
1205            model_type: PhantomData
1206        }
1207    }
1208
1209    pub fn build_passive(&self, model: M) -> Result<MjViewer<M>, MjViewerError> {
1210        let (w, h) = MJ_VIEWER_DEFAULT_SIZE_PX;
1211        let mut event_loop = EventLoop::new().map_err(MjViewerError::EventLoopError)?;
1212        let adapter = RenderBase::new(
1213            w, h,
1214            self.window_name.to_string(),
1215            &mut event_loop,
1216            true  // process events
1217        );
1218
1219        /* Initialize the OpenGL related things */
1220        let RenderBaseGlState {
1221            gl_context,
1222            gl_surface,
1223            #[cfg(feature = "viewer-ui")] window,
1224            ..
1225        } = adapter.state.as_ref().unwrap();
1226        gl_context.make_current(gl_surface).map_err(MjViewerError::GlutinError)?;
1227
1228        // Configure vertical synchronization
1229        if self.vsync {
1230            gl_surface.set_swap_interval(
1231                gl_context,
1232                glutin::surface::SwapInterval::Wait(NonZero::new(1).unwrap())
1233            ).map_err(|e| MjViewerError::GlutinError(e))?;
1234        } else {
1235            gl_surface.set_swap_interval(gl_context, glutin::surface::SwapInterval::DontWait).map_err(
1236                |e| MjViewerError::GlutinError(e)
1237            )?;
1238        }
1239
1240        event_loop.set_control_flow(winit::event_loop::ControlFlow::Poll);
1241
1242        let ngeom = model.ffi().ngeom as usize;
1243        let scene = MjvScene::new(model.clone(), ngeom + self.max_user_geoms + EXTRA_SCENE_GEOM_SPACE);
1244        let context = MjrContext::new(&model);
1245        let camera  = MjvCamera::new_free(&model);
1246
1247        // Tracking of changes made between syncs
1248        let shared_state = Arc::new(Mutex::new(ViewerSharedState::new(model.clone(), self.max_user_geoms)));
1249        let user_scene = MjvScene::new(model.clone(), self.max_user_geoms);
1250
1251        // User interface
1252        #[cfg(feature = "viewer-ui")]
1253        let ui = ui::ViewerUI::new(model.clone(), &window, &gl_surface.display());
1254        #[cfg(feature = "viewer-ui")]
1255        let mut status = ViewerStatusBit::UI;
1256        #[cfg(not(feature = "viewer-ui"))]
1257        let mut status = ViewerStatusBit::HELP;
1258
1259        status.set(ViewerStatusBit::VSYNC, self.vsync);
1260        status.set(ViewerStatusBit::WARN_REALTIME, self.warn_non_realtime);
1261
1262        Ok(MjViewer {
1263            model,
1264            scene,
1265            context,
1266            camera,
1267            opt: MjvOption::default(),
1268            user_scene,  // TEMPORARY! TODO: Drop in 3.0.0
1269            shared_state,
1270            last_x: 0.0,
1271            last_y: 0.0,
1272            last_bnt_press_time: Instant::now(),
1273            fps_timer: Instant::now(),
1274            fps_smooth: 60.0,
1275            rect_view: MjrRectangle::default(),
1276            rect_full: MjrRectangle::default(),
1277            adapter,
1278            event_loop,
1279            modifiers: Modifiers::default(),
1280            buttons_pressed: ButtonsPressed::empty(),
1281            raw_cursor_position: (0.0, 0.0),
1282            #[cfg(feature = "viewer-ui")] ui,
1283            status
1284        })
1285    }
1286}
1287
1288impl<M: Deref<Target = MjModel> + Clone> Default for MjViewerBuilder<M> {
1289    fn default() -> Self {
1290        MjViewerBuilder::new()
1291    }
1292}
1293
1294bitflags! {
1295    #[derive(Debug)]
1296    struct ViewerStatusBit: u8 {
1297        const HELP = 1 << 0;
1298        const VSYNC = 1 << 1;
1299        const INFO = 1 << 2;
1300        const WARN_REALTIME = 1 << 3;
1301        #[cfg(feature = "viewer-ui")] const UI = 1 << 4;
1302    }
1303}
1304
1305bitflags! {
1306    /// Boolean flags for tracking button press events.
1307    #[derive(Debug)]
1308    struct ButtonsPressed: u8 {
1309        const LEFT = 1 << 0;
1310        const MIDDLE = 1 << 1;
1311        const RIGHT = 1 << 2;
1312    }
1313}