1use glfw::{Action, Context, Glfw, GlfwReceiver, Key, Modifiers, MouseButton, PWindow, WindowEvent};
2
3use std::time::Instant;
4
5use crate::mujoco_c::*;
6
7#[cfg(feature = "cpp-viewer")]
8use std::ffi::CString;
9
10use crate::prelude::{MjrContext, MjrRectangle};
11use crate::wrappers::mj_visualization::*;
12use crate::wrappers::mj_model::MjModel;
13use crate::wrappers::mj_data::MjData;
14
15const MJ_VIEWER_DEFAULT_SIZE_PX: (u32, u32) = (1280, 720);
19const MJ_VIEWER_DEFAULT_TITLE: &str = "MuJoCo Viewer (Rust)";
20const DOUBLE_CLICK_WINDOW_MS: u128 = 250;
21
22
23#[derive(Debug)]
24pub enum MjViewerError {
25 GlfwInitError (glfw::InitError),
26 WindowCreationError
27}
28
29#[derive(Debug)]
42pub struct MjViewer<'m> {
43 scene: MjvScene<'m>,
45 context: MjrContext,
46 camera: MjvCamera,
47
48 model: &'m MjModel,
50 pert: MjvPerturb,
51
52 last_x: mjtNum,
54 last_y: mjtNum,
55 left_click: bool,
56 last_bnt_press_time: Instant,
57 rect_view: MjrRectangle,
58 rect_full: MjrRectangle,
59
60 glfw: Glfw,
62 window: PWindow,
63 events: GlfwReceiver<(f64, WindowEvent)>
64}
65
66impl<'m> MjViewer<'m> {
67 pub fn launch_passive(model: &'m MjModel, scene_max_ngeom: usize) -> Result<Self, MjViewerError> {
70 let mut glfw = glfw::init_no_callbacks()
71 .map_err(|err| MjViewerError::GlfwInitError(err))?;
72 let (w, h) = MJ_VIEWER_DEFAULT_SIZE_PX;
73 let (mut window, events) = match glfw.create_window(
74 w, h, MJ_VIEWER_DEFAULT_TITLE, glfw::WindowMode::Windowed
75 ) {
76 Some(x) => Ok(x),
77 None => Err(MjViewerError::WindowCreationError)
78 }?;
79
80 window.make_current();
82 window.set_all_polling(true);
83 glfw.set_swap_interval(glfw::SwapInterval::None);
84
85 let scene = MjvScene::new(model, scene_max_ngeom);
86 let context= MjrContext::new(model);
87 let camera = MjvCamera::new(0, MjtCamera::mjCAMERA_FREE, model);
88 let pert = MjvPerturb::default();
89 Ok(Self {
90 scene,
91 context,
92 camera,
93 model,
94 pert,
95 glfw,
96 window,
97 events,
98 last_x: 0.0,
99 last_y: 0.0,
100 left_click: false,
101 last_bnt_press_time: Instant::now(),
102 rect_view: MjrRectangle::default(),
103 rect_full: MjrRectangle::default(),
104 })
105 }
106
107 pub fn running(&self) -> bool {
109 !self.window.should_close()
110 }
111
112 pub fn sync(&mut self, data: &mut MjData) {
113 self.process_events(data);
114 self.update(data);
115 }
116
117
118 fn update(&mut self, data: &mut MjData) {
120 let mut viewport = MjrRectangle::default();
122 let (width, height) = self.window.get_framebuffer_size();
123 viewport.width = width;
124 viewport.height = height;
125
126 self.update_rectangles((width, height));
127
128 let opt = MjvOption::default();
130 self.scene.update(data, &opt, &self.pert, &mut self.camera);
131 self.scene.render(&viewport, &self.context);
132
133 self.window.swap_buffers();
135 }
136
137 fn update_rectangles(&mut self, viewport_size: (i32, i32)) {
140 self.rect_view.width = viewport_size.0;
142 self.rect_view.height = viewport_size.1;
143
144 self.rect_full.width = viewport_size.0;
145 self.rect_full.height = viewport_size.1;
146 }
147
148 fn process_events(&mut self, data: &mut MjData) {
150 self.glfw.poll_events();
151 while let Some((_, event)) = self.events.receive() {
152 match event {
153 WindowEvent::Key(Key::Q, _, _, modifier) if modifier == Modifiers::Control => {
154 self.window.set_should_close(true);
155 break; },
157 WindowEvent::Key(Key::Escape, _, _, _) => {
158 self.camera.free();
159 },
160 WindowEvent::Scroll(_, change) => {
161 self.process_scroll(change);
162 }
163 WindowEvent::CursorPos(x, y) => {
164 self.process_cursor_pos(x, y);
165 },
166
167 WindowEvent::MouseButton(MouseButton::Left, action, modifiers) => {
169 self.process_left_click(data, &action, &modifiers);
170 }
171 _ => {} }
173 }
174 }
175
176 fn process_scroll(&mut self, change: f64) {
177 self.camera.move_(MjtMouse::mjMOUSE_ZOOM, self.model, 0.0, -0.05 * change, &self.scene);
178 }
179
180 fn process_cursor_pos(&mut self, x: f64, y: f64) {
181 let dx = x - self.last_x;
183 let dy = y - self.last_y;
184 self.last_x = x;
185 self.last_y = y;
186
187 let action;
189 let shift = self.window.get_key(Key::LeftShift) == Action::Press;
190
191 if self.window.get_mouse_button(MouseButton::Left) == Action::Press {
192 action = if shift {MjtMouse::mjMOUSE_ROTATE_H} else {MjtMouse::mjMOUSE_ROTATE_V};
193 }
194 else if self.window.get_mouse_button(MouseButton::Right) == Action::Press {
195 action = if shift {MjtMouse::mjMOUSE_MOVE_H} else {MjtMouse::mjMOUSE_MOVE_V};
196 }
197 else if self.window.get_mouse_button(MouseButton::Middle) == Action::Press {
198 action = MjtMouse::mjMOUSE_ZOOM;
199 }
200 else {
201 return; }
203
204 let height = self.window.get_size().1 as mjtNum;
205 self.camera.move_(action, self.model, dx / height, dy / height, &self.scene);
206 }
207
208 fn process_left_click(&mut self, data: &mut MjData, action: &Action, modifiers: &Modifiers) {
209 self.left_click = match action {
210 Action::Press => {
211 if !self.left_click && self.last_bnt_press_time.elapsed().as_millis() < DOUBLE_CLICK_WINDOW_MS {
213 let (mut x, mut y) = self.window.get_cursor_pos();
214
215 let buffer_ratio = self.window.get_framebuffer_size().0 as mjtNum / self.window.get_size().0 as mjtNum;
217 x *= buffer_ratio;
218 y *= buffer_ratio;
219 y = self.rect_full.height as mjtNum - y; let rect: &mjrRect_ = &self.rect_view;
223 let (body_id, _, flex_id, skin_id, xyz) = self.scene.find_selection(
224 data, &MjvOption::default(),
225 rect.width as mjtNum / rect.height as mjtNum,
226 (x - rect.left as mjtNum) / rect.width as mjtNum,
227 (y - rect.bottom as mjtNum) / rect.height as mjtNum
228 );
229
230 self.pert.select = body_id;
232 self.pert.flexselect = flex_id;
233 self.pert.skinselect = skin_id;
234 self.pert.active = 0;
235
236 let mut tmp = [0.0; 3];
237 unsafe {
238 mju_sub3(tmp.as_mut_ptr(), xyz.as_ptr(), data.ffi().xpos.add((3 *self.pert.select) as usize));
239 mju_mulMatTVec(self.pert.localpos.as_mut_ptr(), data.ffi().xmat.add((9*self.pert.select) as usize), tmp.as_ptr(), 3, 3);
240 }
241
242 if modifiers == &Modifiers::Control && body_id >= 0 {
244 self.camera.track(body_id as u32);
245 }
246 }
247 self.last_bnt_press_time = Instant::now();
248 true
249 },
250 Action::Release => false,
251 Action::Repeat => self.left_click
252 };
253 }
254}
255
256#[cfg(feature = "cpp-viewer")]
261pub struct MjViewerCpp<'m> {
262 sim: *mut mujoco_Simulate,
263 running: bool,
264
265 _cam: Box<MjvCamera>,
268 _opt: Box<MjvOption>,
269 _pert: Box<MjvPerturb>,
270 _user_scn: Box<MjvScene<'m>>,
271 _glfw: glfw::Glfw
272}
273
274#[cfg(feature = "cpp-viewer")]
275impl<'m> MjViewerCpp<'m> {
276 #[inline]
277 pub fn running(&self) -> bool {
278 self.running
279 }
280
281 #[inline]
282 pub fn user_scn_mut(&mut self) -> &mut MjvScene<'m> {
283 &mut self._user_scn
284 }
285
286 pub fn launch_passive(model: &'m MjModel, data: &MjData, scene_max_ngeom: usize) -> Self {
287 let mut _glfw = glfw::init(glfw::fail_on_errors).unwrap();
288
289 let mut _cam = Box::new(MjvCamera::default());
291 let mut _opt: Box<MjvOption> = Box::new(MjvOption::default());
292 let mut _pert = Box::new(MjvPerturb::default());
293 let mut _user_scn = Box::new(MjvScene::new(&model, scene_max_ngeom));
294 let sim;
295 unsafe {
296 sim = new_simulate(&mut *_cam, &mut *_opt, &mut *_pert, _user_scn.ffi_mut(), true);
297 (*sim).RenderInit();
298 (*sim).Load(model.__raw(), data.__raw(), CString::new("file.xml").unwrap().as_ptr());
299 (*sim).RenderStep(true);
300 }
301
302 Self {sim, running: true, _cam, _opt, _pert, _glfw, _user_scn}
303 }
304
305 pub fn __raw(&self) -> *mut mujoco_Simulate {
307 self.sim
308 }
309
310 pub fn render(&mut self, update_timer: bool) {
317 unsafe {
318 assert!(self.running, "render called after viewer has been closed!");
319 self.running = (*self.sim).RenderStep(update_timer);
320 }
321 }
322
323 pub fn sync(&mut self) {
324 unsafe {
325 (*self.sim).Sync(false);
326 }
327 }
328}
329
330#[cfg(feature = "cpp-viewer")]
331impl Drop for MjViewerCpp<'_> {
332 fn drop(&mut self) {
333 unsafe {
334 (*self.sim).RenderCleanup();
335 free_simulate(self.sim);
336 }
337 }
338}