1use crate::{array_slice_dyn, getter_setter, mujoco_c::*};
3use crate::error::MjrContextError;
4
5use super::mj_model::{MjModel, MjtTexture, MjtTextureRole};
6
7use std::ffi::CString;
8use std::ptr;
9
10pub type MjtGridPos = mjtGridPos;
15
16pub type MjtFramebuffer = mjtFramebuffer;
18
19pub type MjtDepthMap = mjtDepthMap;
23
24pub type MjtFontScale = mjtFontScale;
27
28pub type MjtFont = mjtFont;
30pub type MjrRectangle = mjrRect;
38impl MjrRectangle {
39 pub const fn new(left: i32, bottom: i32, width: i32, height: i32) -> Self {
42 Self {
43 left,
44 bottom,
45 width,
46 height,
47 }
48 }
49}
50
51impl PartialEq for MjrRectangle {
52 fn eq(&self, other: &Self) -> bool {
53 self.left == other.left && self.bottom == other.bottom
54 && self.width == other.width && self.height == other.height
55 }
56}
57impl Eq for MjrRectangle {}
58
59#[allow(clippy::derivable_impls)] impl Default for MjrRectangle {
61 fn default() -> Self {
62 Self {
63 left: 0,
64 bottom: 0,
65 width: 0,
66 height: 0,
67 }
68 }
69}
70
71
72#[derive(Debug)]
87pub struct MjrContext {
88 ffi: Box<mjrContext>
89}
90
91impl MjrContext {
92 pub unsafe fn new(model: &MjModel) -> Self {
101 unsafe {
105 let mut c = Box::new_uninit();
106 mjr_defaultContext(c.as_mut_ptr());
107 mjr_makeContext(model.ffi(), c.as_mut_ptr(), MjtFontScale::mjFONTSCALE_100 as i32);
108 Self {ffi: c.assume_init()}
109 }
110 }
111
112 pub fn offscreen(&mut self) -> &mut Self {
114 unsafe {
116 mjr_setBuffer(MjtFramebuffer::mjFB_OFFSCREEN as i32, self.ffi.as_mut());
117 }
118 self
119 }
120
121 pub fn window(&mut self) -> &mut Self {
123 unsafe {
125 mjr_setBuffer(MjtFramebuffer::mjFB_WINDOW as i32, self.ffi.as_mut());
126 }
127 self
128 }
129
130 pub fn change_font(&mut self, fontscale: MjtFontScale) {
132 unsafe { mjr_changeFont(fontscale as i32, self.ffi_mut()) }
134 }
135
136 pub fn add_aux(&mut self, index: usize, width: u32, height: u32, samples: usize) -> Result<(), MjrContextError> {
140 if index >= mjNAUX as usize {
141 return Err(MjrContextError::IndexOutOfBounds { id: index, len: mjNAUX as usize });
142 }
143 unsafe { mjr_addAux(index as i32, width as i32, height as i32, samples as i32, self.ffi_mut()); }
145 Ok(())
146 }
147
148 pub fn resize_offscreen(&mut self, width: u32, height: u32) {
150 unsafe { mjr_resizeOffscreen(width as i32, height as i32, self.ffi_mut()); }
152 }
153
154 pub fn upload_texture(&self, model: &MjModel, texture_id: usize) -> Result<(), MjrContextError> {
159 self.upload_x(model, texture_id, model.ntex() as usize, mjr_uploadTexture)
160 }
161
162 pub fn upload_mesh(&self, model: &MjModel, mesh_id: usize) -> Result<(), MjrContextError> {
167 self.upload_x(model, mesh_id, model.nmesh() as usize, mjr_uploadMesh)
168 }
169
170 pub fn upload_hfield(&self, model: &MjModel, hfield_id: usize) -> Result<(), MjrContextError> {
175 self.upload_x(model, hfield_id, model.nhfield() as usize, mjr_uploadHField)
176 }
177
178 pub fn restore_buffer(&mut self) {
180 unsafe { mjr_restoreBuffer(self.ffi_mut()); }
182 }
183
184 pub fn set_buffer(&mut self, framebuffer: i32) {
187 unsafe { mjr_setBuffer(framebuffer, self.ffi_mut()); }
189 }
190
191 pub fn read_pixels(
199 &self,
200 rgb: Option<&mut [u8]>,
201 depth: Option<&mut [f32]>,
202 viewport: &MjrRectangle,
203 ) -> Result<(), MjrContextError> {
204 if viewport.width < 0 || viewport.height < 0 {
205 return Err(MjrContextError::InvalidViewport {
206 width: viewport.width,
207 height: viewport.height,
208 });
209 }
210 let size = viewport.width as usize * viewport.height as usize;
211 if let Some(buf) = rgb.as_ref() {
212 let needed = size * 3;
213 if buf.len() < needed {
214 return Err(MjrContextError::BufferTooSmall {
215 name: "rgb",
216 got: buf.len(),
217 needed,
218 });
219 }
220 }
221 if let Some(buf) = depth.as_ref()
222 && buf.len() < size
223 {
224 return Err(MjrContextError::BufferTooSmall {
225 name: "depth",
226 got: buf.len(),
227 needed: size,
228 });
229 }
230
231 unsafe {
234 mjr_readPixels(
235 rgb.map_or(ptr::null_mut(), |x| x.as_mut_ptr()),
236 depth.map_or(ptr::null_mut(), |x| x.as_mut_ptr()),
237 *viewport, self.ffi()
238 )
239 }
240 Ok(())
241 }
242
243 pub fn set_aux(&mut self, index: usize) -> Result<(), MjrContextError> {
247 if index >= mjNAUX as usize {
248 return Err(MjrContextError::IndexOutOfBounds { id: index, len: mjNAUX as usize });
249 }
250 unsafe { mjr_setAux(index as i32, self.ffi_mut()); }
252 Ok(())
253 }
254
255 pub fn overlay(&mut self, font: MjtFont, gridpos: MjtGridPos, viewport: MjrRectangle, overlay: &str, overlay2: Option<&str>) {
259 let c_overlay = CString::new(overlay).unwrap();
260 let c_overlay2 = overlay2.map(|x| CString::new(x).unwrap());
261
262 unsafe { mjr_overlay(
265 font as i32, gridpos as i32, viewport,
266 c_overlay.as_ptr(),
267 c_overlay2.as_ref().map_or(std::ptr::null(), |x| x.as_ptr()),
268 self.ffi()
269 ); }
270 }
271
272 pub fn ffi(&self) -> &mjrContext {
274 &self.ffi
275 }
276
277 pub unsafe fn ffi_mut(&mut self) -> &mut mjrContext {
283 &mut self.ffi
284 }
285
286 fn upload_x(
289 &self, model: &MjModel, item_id: usize, n_items: usize,
290 upload_fn: unsafe extern "C" fn (m: *const mjModel, con: *const mjrContext, id: ::std::ffi::c_int)
291 ) -> Result<(), MjrContextError>
292 {
293 if item_id >= n_items {
294 return Err(MjrContextError::IndexOutOfBounds { id: item_id, len: n_items });
295 }
296 unsafe { upload_fn(model.ffi(), self.ffi(), item_id as i32); }
298 Ok(())
299 }
300}
301
302impl MjrContext {
304 array_slice_dyn! {
305 (mut = unsafe) textureType: as_ptr as_mut_ptr &[MjtTexture [force]; "type of texture"; ffi().ntexture],
306 (mut = unsafe) skinvertVBO: &[u32; "skin vertex position VBOs"; ffi().nskin],
307 (mut = unsafe) skinnormalVBO: &[u32; "skin vertex normal VBOs"; ffi().nskin],
308 (mut = unsafe) skintexcoordVBO: &[u32; "skin vertex texture coordinate VBOs"; ffi().nskin],
309 (mut = unsafe) skinfaceVBO: &[u32; "skin face index VBOs"; ffi().nskin]
310 }
311}
312
313impl MjrContext {
314 getter_setter! {get, [
315 [ffi] lineWidth: f32; "line width for wireframe rendering.";
316 [ffi] shadowClip: f32; "clipping radius for directional lights.";
317 [ffi] shadowScale: f32; "fraction of light cutoff for spot lights.";
318 [ffi] fogStart: f32; "fog start = stat.extent * vis.map.fogstart.";
319 [ffi] fogEnd: f32; "fog end = stat.extent * vis.map.fogend.";
320 [ffi] shadowSize: i32; "size of shadow map texture.";
321 [ffi] offWidth: i32; "width of offscreen buffer.";
322 [ffi] offHeight: i32; "height of offscreen buffer.";
323 [ffi] offSamples: i32; "number of offscreen buffer multisamples.";
324 [ffi] fontScale: i32; "font scale.";
325 [ffi] offFBO: u32; "offscreen framebuffer object.";
326 [ffi] offFBO_r: u32; "offscreen framebuffer for resolving multisamples.";
327 [ffi] offColor: u32; "offscreen color buffer.";
328 [ffi] offColor_r: u32; "offscreen color buffer for resolving multisamples.";
329 [ffi] offDepthStencil: u32; "offscreen depth and stencil buffer.";
330 [ffi] offDepthStencil_r: u32; "offscreen depth and stencil buffer for multisamples.";
331 [ffi] shadowFBO: u32; "shadow map framebuffer object.";
332 [ffi] shadowTex: u32; "shadow map texture.";
333 [ffi] ntexture: i32; "number of allocated textures.";
334 [ffi] basePlane: u32; "all planes from model.";
335 [ffi] baseMesh: u32; "all meshes from model.";
336 [ffi] baseHField: u32; "all height fields from model.";
337 [ffi] baseBuiltin: u32; "all builtin geoms, with quality from model.";
338 [ffi] baseFontNormal: u32; "normal font.";
339 [ffi] baseFontShadow: u32; "shadow font.";
340 [ffi] baseFontBig: u32; "big font.";
341 [ffi] rangePlane: i32; "all planes from model.";
342 [ffi] rangeMesh: i32; "all meshes from model.";
343 [ffi] rangeHField: i32; "all hfields from model.";
344 [ffi] rangeBuiltin: i32; "all builtin geoms, with quality from model.";
345 [ffi] rangeFont: i32; "all characters in font.";
346 [ffi] nskin: i32; "number of skins.";
347 [ffi] charHeight: i32; "character heights: normal and shadow.";
348 [ffi] charHeightBig: i32; "character heights: big.";
349 [ffi] glInitialized: i32; "is OpenGL initialized.";
350 [ffi] windowAvailable: i32; "is default/window framebuffer available.";
351 [ffi] windowSamples: i32; "number of samples for default/window framebuffer.";
352 [ffi] windowStereo: i32; "is stereo available for default/window framebuffer.";
353 [ffi] windowDoublebuffer: i32; "is default/window framebuffer double buffered.";
354 [ffi] currentBuffer: i32; "currently active framebuffer: mjFB_WINDOW or mjFB_OFFSCREEN.";
355 [ffi] readPixelFormat: i32; "default color pixel format for mjr_readPixels.";
356 [ffi] readDepthMap: i32; "depth mapping: mjDEPTH_ZERONEAR or mjDEPTH_ZEROFAR.";
357 ]}
358
359 getter_setter! {get, [
360 [ffi] (allow_mut = false) fogRGBA: &[f32; 4]; "fog rgba.";
361 [ffi] (allow_mut = false) auxWidth: &[i32; mjNAUX as usize]; "auxiliary buffer width.";
362 [ffi] (allow_mut = false) auxHeight: &[i32; mjNAUX as usize]; "auxiliary buffer height.";
363 [ffi] (allow_mut = false) auxSamples: &[i32; mjNAUX as usize]; "auxiliary buffer multisamples.";
364 [ffi] (allow_mut = false) auxFBO: &[u32; mjNAUX as usize]; "auxiliary framebuffer object.";
365 [ffi] (allow_mut = false) auxFBO_r: &[u32; mjNAUX as usize]; "auxiliary framebuffer object for resolving.";
366 [ffi] (allow_mut = false) auxColor: &[u32; mjNAUX as usize]; "auxiliary color buffer.";
367 [ffi] (allow_mut = false) auxColor_r: &[u32; mjNAUX as usize]; "auxiliary color buffer for resolving.";
368 [ffi] (allow_mut = false) mat_texid: &[i32; (mjMAXMATERIAL * MjtTextureRole::mjNTEXROLE as u32) as usize]; "material texture ids (-1: no texture).";
369 [ffi] (allow_mut = false) mat_texuniform: &[i32; mjMAXMATERIAL as usize]; "uniform cube mapping.";
370 [ffi] (allow_mut = false) mat_texrepeat: &[f32; (mjMAXMATERIAL * 2) as usize]; "texture repetition for 2d mapping.";
371 [ffi] (allow_mut = false) texture: &[u32; mjMAXTEXTURE as usize]; "texture names.";
372 [ffi] (allow_mut = false) charWidth: &[i32; 127]; "character widths: normal and shadow.";
373 [ffi] (allow_mut = false) charWidthBig: &[i32; 127]; "character widths: big.";
374 ]}
375}
376
377impl Drop for MjrContext {
378 fn drop(&mut self) {
379 unsafe {
381 mjr_freeContext(self.ffi.as_mut());
382 }
383 }
384}