use std::default::Default;
use std::mem::MaybeUninit;
use std::ops::Deref;
use std::ptr;
use super::mj_rendering::{MjrContext, MjrRectangle};
use super::mj_primitive::{MjtNum, MjtByte, MjtSize};
use super::mj_model::{MjModel, MjtGeom};
use super::mj_data::MjData;
use crate::{array_slice_dyn, c_str_as_str_method};
use crate::error::MjSceneError;
use crate::getter_setter;
use crate::mujoco_c::*;
#[derive(Debug, Clone, PartialEq)]
pub struct SceneSelection {
pub body_id: Option<usize>,
pub geom_id: Option<usize>,
pub flex_id: Option<usize>,
pub skin_id: Option<usize>,
pub point: [MjtNum; 3],
}
impl Default for SceneSelection {
fn default() -> Self {
Self {
body_id: None,
geom_id: None,
flex_id: None,
skin_id: None,
point: [0.0; 3],
}
}
}
pub type MjtCatBit = mjtCatBit;
pub type MjtMouse = mjtMouse;
pub type MjtPertBit = mjtPertBit;
pub type MjtCamera = mjtCamera;
const _: () = {
assert!(MjtCamera::mjCAMERA_FREE as i32 == 0);
assert!(MjtCamera::mjCAMERA_TRACKING as i32 == 1);
assert!(MjtCamera::mjCAMERA_FIXED as i32 == 2);
assert!(MjtCamera::mjCAMERA_USER as i32 == 3);
};
impl TryFrom<i32> for MjtCamera {
type Error = MjSceneError;
fn try_from(value: i32) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::mjCAMERA_FREE),
1 => Ok(Self::mjCAMERA_TRACKING),
2 => Ok(Self::mjCAMERA_FIXED),
3 => Ok(Self::mjCAMERA_USER),
_ => Err(MjSceneError::InvalidCameraType(value))
}
}
}
pub type MjtLabel = mjtLabel;
pub type MjtFrame = mjtFrame;
pub type MjtVisFlag = mjtVisFlag;
pub type MjtRndFlag = mjtRndFlag;
pub type MjtStereo = mjtStereo;
pub type MjvPerturb = mjvPerturb;
impl Default for MjvPerturb {
fn default() -> Self {
unsafe {
let mut pert = MaybeUninit::uninit();
mjv_defaultPerturb(pert.as_mut_ptr());
pert.assume_init()
}
}
}
impl MjvPerturb {
pub fn start<M: Deref<Target = MjModel>>(&mut self, type_: MjtPertBit, data: &mut MjData<M>, scene: &MjvScene) {
let model_ffi = data.model().ffi();
unsafe { mjv_initPerturb(model_ffi, data.ffi_mut(), scene.ffi(), self); }
self.active = type_ as i32;
}
pub fn move_<M: Deref<Target = MjModel>>(&mut self, data: &MjData<M>, action: MjtMouse, dx: MjtNum, dy: MjtNum, scene: &MjvScene) {
let nbody = data.model().nbody();
assert!(
self.select >= 0 && (self.select as MjtSize) < nbody,
"selected perturbation body id {} is out of range for a model with {} bodies",
self.select, nbody
);
unsafe { mjv_movePerturb(data.model().ffi(), data.ffi(), action as i32, dx, dy, scene.ffi(), self); }
}
pub fn apply<M: Deref<Target = MjModel>>(&mut self, data: &mut MjData<M>) {
data.xfrc_applied_mut().fill([0.0; 6]);
let model_ffi = data.model().ffi();
unsafe { mjv_applyPerturbPose(model_ffi, data.ffi_mut(), self, 0); }
let model_ffi = data.model().ffi();
unsafe { mjv_applyPerturbForce(model_ffi, data.ffi_mut(), self); }
}
pub fn update_local_pos<M: Deref<Target = MjModel>>(&mut self, selection_xyz: &[MjtNum; 3], data: &MjData<M>) {
debug_assert!(self.select >= 0, "invalid selecting when calling update_local_pos");
let select = self.select as usize;
let body_xpos = &data.xpos()[select];
let body_xmat = &data.xmat()[select];
let tmp = [
selection_xyz[0] - body_xpos[0],
selection_xyz[1] - body_xpos[1],
selection_xyz[2] - body_xpos[2],
];
self.localpos = [
body_xmat[0] * tmp[0] + body_xmat[3] * tmp[1] + body_xmat[6] * tmp[2],
body_xmat[1] * tmp[0] + body_xmat[4] * tmp[1] + body_xmat[7] * tmp[2],
body_xmat[2] * tmp[0] + body_xmat[5] * tmp[1] + body_xmat[8] * tmp[2],
];
}
}
pub type MjvCamera = mjvCamera;
impl MjvCamera {
pub fn new_free(model: &MjModel) -> Self {
let mut camera: mjvCamera_ = Self::default();
unsafe { mjv_defaultFreeCamera(model.ffi(), &mut camera); }
camera
}
pub fn new_fixed(camera_id: usize) -> Self {
debug_assert!(camera_id <= i32::MAX as usize, "camera_id exceeds i32::MAX");
mjvCamera_ {
type_: MjtCamera::mjCAMERA_FIXED as i32,
fixedcamid: camera_id as i32,
..Self::default()
}
}
pub fn new_tracking(tracking_id: usize) -> Self {
debug_assert!(tracking_id <= i32::MAX as usize, "tracking_id exceeds i32::MAX");
mjvCamera_ {
type_: MjtCamera::mjCAMERA_TRACKING as i32,
trackbodyid: tracking_id as i32,
..Self::default()
}
}
pub fn new_user() -> Self {
mjvCamera_ {
type_: MjtCamera::mjCAMERA_USER as i32,
..Self::default()
}
}
pub fn track(&mut self, tracking_id: usize) {
self.type_ = MjtCamera::mjCAMERA_TRACKING as i32;
self.fixedcamid = -1;
self.trackbodyid = tracking_id as i32;
}
pub fn free(&mut self) {
self.trackbodyid = -1;
self.type_ = MjtCamera::mjCAMERA_FREE as i32;
}
pub fn fix(&mut self, camera_id: usize) {
self.type_ = MjtCamera::mjCAMERA_FIXED as i32;
self.fixedcamid = camera_id as i32;
self.trackbodyid = -1;
}
pub fn move_(&mut self, action: MjtMouse, model: &MjModel, dx: MjtNum, dy: MjtNum, scene: &MjvScene) {
unsafe { mjv_moveCamera(model.ffi(), action as i32, dx, dy, scene.ffi(), self); };
}
pub fn frame<M: Deref<Target = MjModel>>(&self, data: &MjData<M>) -> ([MjtNum; 3], [MjtNum; 3], [MjtNum; 3], [MjtNum; 3]) {
if self.type_ == MjtCamera::mjCAMERA_FIXED as i32 {
let ncam = data.model().ncam();
assert!(
self.fixedcamid >= 0 && (self.fixedcamid as i64) < ncam,
"fixed camera id {} is out of range for a model with {} cameras",
self.fixedcamid, ncam
);
}
let mut headpos = [0.0; 3];
let mut forward = [0.0; 3];
let mut up = [0.0; 3];
let mut right = [0.0; 3];
unsafe {
mjv_cameraFrame(
&mut headpos, &mut forward, &mut up, &mut right,
data.ffi(), self
);
}
(headpos, forward, up, right)
}
pub fn frustum(&self, model: &MjModel) -> ([f32; 2], [f32; 2], [f32; 2]) {
let mut zver = [0.0; 2];
let mut zhor = [0.0; 2];
let mut zclip = [0.0; 2];
unsafe {
mjv_cameraFrustum(
&mut zver, &mut zhor, &mut zclip,
model.ffi(), self
);
}
(zver, zhor, zclip)
}
}
impl Default for MjvCamera {
fn default() -> Self {
unsafe {
let mut c = MaybeUninit::uninit();
mjv_defaultCamera(c.as_mut_ptr());
c.assume_init()
}
}
}
pub type MjvGLCamera = mjvGLCamera;
impl MjvGLCamera {
pub fn average_camera(&self, other: &Self) -> Self {
unsafe { mjv_averageCamera (self, other) }
}
}
pub type MjvGeom = mjvGeom;
impl MjvGeom {
pub fn connect(&mut self, width: MjtNum, from: [MjtNum; 3], to: [MjtNum; 3]) {
unsafe {
mjv_connector(self, self.type_, width, &from, &to);
}
}
pub fn label(&self) -> String {
let len = self.label.iter().position(|&c| c == 0).unwrap_or(self.label.len());
let bytes: &[u8] = bytemuck::cast_slice(&self.label[..len]);
String::from_utf8_lossy(bytes).to_string()
}
pub fn set_label(&mut self, s: &str) -> Result<(), MjSceneError> {
if !s.is_ascii() {
return Err(MjSceneError::NonAsciiLabel);
}
let capacity = self.label.len() - 1;
if s.len() > capacity {
return Err(MjSceneError::LabelTooLong { len: s.len(), capacity });
}
let target: &mut [u8] = bytemuck::cast_slice_mut(&mut self.label[..s.len()]);
target.copy_from_slice(s.as_bytes());
self.label[s.len()] = 0;
Ok(())
}
}
pub type MjvLight = mjvLight;
pub type MjvOption = mjvOption;
impl Default for MjvOption {
fn default() -> Self {
let mut opt = MaybeUninit::uninit();
unsafe {
mjv_defaultOption(opt.as_mut_ptr());
opt.assume_init()
}
}
}
pub type MjvFigure = mjvFigure;
impl Default for MjvFigure {
fn default() -> Self {
*Self::new_boxed()
}
}
impl MjvFigure {
pub fn new_boxed() -> Box<Self> {
let mut opt = Box::new(MaybeUninit::uninit());
unsafe {
mjv_defaultFigure(opt.as_mut_ptr());
opt.assume_init()
}
}
#[deprecated(since = "3.0.0", note = "use `new_boxed` instead")]
pub fn new() -> Box<Self> {
Self::new_boxed()
}
pub fn draw(&mut self, viewport: MjrRectangle, context: &MjrContext) {
unsafe { mjr_figure(viewport, self, context.ffi()) };
}
}
impl MjvFigure {
getter_setter! {with, get, set, [
flg_legend: bool; "whether to show legend.";
flg_extend: bool; "whether to automatically extend axis ranges to fit data.";
flg_barplot: bool; "whether to isolate line segments.";
flg_selection: bool; "whether to show vertical selection line.";
flg_symmetric: bool; "whether to make y-axis symmetric.";
]}
getter_setter! {with, [
gridsize: [i32; 2]; "number of grid points in (x, y).";
gridrgb: [f32; 3]; "grid line RGB color.";
figurergba: [f32; 4]; "figure RGBA color.";
panergba: [f32; 4]; "pane RGBA color.";
legendrgba: [f32; 4]; "legend RGBA color.";
textrgb: [f32; 3]; "text RGB color.";
linergb: [[f32; 3]; mjMAXLINE as usize]; "line colors.";
range: [[f32; 2]; 2]; "axis ranges (min >= max means automatic).";
]}
c_str_as_str_method! {with, get, set {
xlabel; "the x-axis label.";
title; "the title.";
xformat; "the x-axis C's printf format (e.g., `%.1f`).";
yformat; "the y-axis C's printf format (e.g., `%.1f`).";
linename [plot_index: usize]; "the line name of plot with `plot_index`.";
}}
}
impl MjvFigure {
pub fn full(&self, plot_index: usize) -> bool {
self.try_full(plot_index).unwrap()
}
pub fn try_full(&self, plot_index: usize) -> Result<bool, MjSceneError> {
if plot_index >= mjMAXLINE as usize {
return Err(MjSceneError::InvalidPlotIndex { plot_index, max_plots: mjMAXLINE as usize });
}
Ok(self.linepnt[plot_index] >= (self.linedata[plot_index].len() / 2) as i32)
}
pub fn empty(&self, plot_index: usize) -> bool {
self.try_empty(plot_index).unwrap()
}
pub fn try_empty(&self, plot_index: usize) -> Result<bool, MjSceneError> {
if plot_index >= mjMAXLINE as usize {
return Err(MjSceneError::InvalidPlotIndex { plot_index, max_plots: mjMAXLINE as usize });
}
Ok(self.linepnt[plot_index] == 0)
}
pub fn push(&mut self, plot_index: usize, x: f32, y: f32) -> Result<(), MjSceneError> {
if plot_index >= mjMAXLINE as usize {
return Err(MjSceneError::InvalidPlotIndex { plot_index, max_plots: mjMAXLINE as usize });
}
let plot = &mut self.linedata[plot_index];
let capacity = plot.len() / 2;
let point_index = self.linepnt[plot_index] as usize;
if point_index >= capacity {
return Err(MjSceneError::FigureBufferFull { plot_index, capacity });
}
plot[2 * point_index] = x;
plot[2 * point_index + 1] = y;
self.linepnt[plot_index] += 1;
Ok(())
}
pub fn set_at(
&mut self,
plot_index: usize,
point_index: usize,
x: f32,
y: f32,
) -> Result<(), MjSceneError> {
if plot_index >= mjMAXLINE as usize {
return Err(MjSceneError::InvalidPlotIndex { plot_index, max_plots: mjMAXLINE as usize });
}
let current_len = self.linepnt[plot_index] as usize;
if point_index >= current_len {
return Err(MjSceneError::FigureIndexOutOfBounds {
plot_index,
point_index,
current_len,
});
}
let plot = &mut self.linedata[plot_index];
plot[2 * point_index] = x;
plot[2 * point_index + 1] = y;
Ok(())
}
pub fn clear(&mut self, maybe_plot_index: Option<usize>) {
if let Some(plot_index) = maybe_plot_index {
self.linepnt[plot_index] = 0;
} else {
self.linepnt.fill(0);
}
}
pub fn pop_front(&mut self, plot_index: usize) -> Option<(f32, f32)> {
self.try_pop_front(plot_index).unwrap()
}
pub fn try_pop_front(&mut self, plot_index: usize) -> Result<Option<(f32, f32)>, MjSceneError> {
if plot_index >= mjMAXLINE as usize {
return Err(MjSceneError::InvalidPlotIndex { plot_index, max_plots: mjMAXLINE as usize });
}
let len = self.linepnt[plot_index];
if len <= 0 {
return Ok(None);
}
let plot_data = &mut self.linedata[plot_index];
let first = (plot_data[0], plot_data[1]);
plot_data.copy_within(2..len as usize * 2, 0);
self.linepnt[plot_index] -= 1;
Ok(Some(first))
}
pub fn pop_back(&mut self, plot_index: usize) -> Option<(f32, f32)> {
self.try_pop_back(plot_index).unwrap()
}
pub fn try_pop_back(&mut self, plot_index: usize) -> Result<Option<(f32, f32)>, MjSceneError> {
if plot_index >= mjMAXLINE as usize {
return Err(MjSceneError::InvalidPlotIndex { plot_index, max_plots: mjMAXLINE as usize });
}
let old_len = self.linepnt[plot_index];
if old_len <= 0 {
return Ok(None);
}
let plot_data = &mut self.linedata[plot_index];
let new_start = ((old_len - 1) * 2) as usize;
self.linepnt[plot_index] -= 1;
Ok(Some((plot_data[new_start], plot_data[new_start + 1])))
}
pub fn cut_front(&mut self, plot_index: usize, n: usize) -> Result<(), MjSceneError> {
if plot_index >= mjMAXLINE as usize {
return Err(MjSceneError::InvalidPlotIndex { plot_index, max_plots: mjMAXLINE as usize });
}
let len = self.linepnt[plot_index];
if len < 0 || (len as usize) < n {
return Ok(());
}
self.linedata[plot_index].copy_within(2 * n..(len as usize * 2), 0);
self.linepnt[plot_index] -= n as i32;
Ok(())
}
pub fn cut_end(&mut self, plot_index: usize, n: usize) -> Result<(), MjSceneError> {
if plot_index >= mjMAXLINE as usize {
return Err(MjSceneError::InvalidPlotIndex { plot_index, max_plots: mjMAXLINE as usize });
}
let len = self.linepnt[plot_index];
if len < 0 || (len as usize) < n {
return Ok(());
}
self.linepnt[plot_index] -= n as i32;
Ok(())
}
}
#[derive(Debug)]
pub struct MjvScene {
ffi: Box<mjvScene>,
signature: u64,
nflexedge: MjtSize,
nflexvert: MjtSize,
nskinvert: MjtSize,
}
impl MjvScene {
pub fn new<M: Deref<Target = MjModel>>(model: M, max_geom: usize) -> Self {
debug_assert!(max_geom <= i32::MAX as usize, "max_geom exceeds i32::MAX");
let model_ffi = model.ffi();
let nflexedge = model_ffi.nflexedge;
let nflexvert = model_ffi.nflexvert;
let nskinvert = model_ffi.nskinvert;
let signature = model.signature();
let scn = unsafe {
let mut t = Box::new_uninit();
mjv_defaultScene(t.as_mut_ptr());
mjv_makeScene(model_ffi, t.as_mut_ptr(), max_geom as i32);
t.assume_init()
};
Self { ffi: scn, signature, nflexedge, nflexvert, nskinvert }
}
pub fn signature(&self) -> u64 {
self.signature
}
fn assert_signature(&self, data_sig: u64) {
assert_eq!(
self.signature, data_sig,
"model signature mismatch: scene {:#X}, data model {:#X}",
self.signature, data_sig
);
}
pub fn update_with_catmask<M: Deref<Target = MjModel>>(
&mut self, data: &mut MjData<M>, opt: &MjvOption, perturb: &MjvPerturb,
cam: &mut MjvCamera, catmask: i32,
) {
self.assert_signature(data.model().signature());
if cam.type_ == MjtCamera::mjCAMERA_FIXED as i32 {
let ncam = data.model().ncam();
assert!(
cam.fixedcamid >= 0 && (cam.fixedcamid as i64) < ncam,
"fixed camera id {} is out of range for a model with {} cameras",
cam.fixedcamid, ncam
);
}
assert!((perturb.select as MjtSize) < data.model().nbody(), "selected perturbated body ID is outside the valid range");
unsafe {
mjv_updateScene(
data.model().ffi(), data.ffi_mut(), opt, perturb,
cam, catmask, self.ffi.as_mut()
);
}
}
pub fn update<M: Deref<Target = MjModel>>(&mut self, data: &mut MjData<M>, opt: &MjvOption, perturb: &MjvPerturb, cam: &mut MjvCamera) {
self.update_with_catmask(data, opt, perturb, cam, MjtCatBit::mjCAT_ALL as i32);
}
pub fn create_geom(
&mut self, geom_type: MjtGeom, size: Option<[MjtNum; 3]>,
pos: Option<[MjtNum; 3]>, mat: Option<[MjtNum; 9]>, rgba: Option<[f32; 4]>
) -> &mut MjvGeom {
self.try_create_geom(geom_type, size, pos, mat, rgba).expect("create_geom failed: scene full")
}
pub fn try_create_geom(
&mut self, geom_type: MjtGeom, size: Option<[MjtNum; 3]>,
pos: Option<[MjtNum; 3]>, mat: Option<[MjtNum; 9]>, rgba: Option<[f32; 4]>
) -> Result<&mut MjvGeom, MjSceneError> {
if self.ffi.ngeom >= self.ffi.maxgeom {
return Err(MjSceneError::SceneFull { capacity: self.ffi.maxgeom });
}
let size_ptr = size.as_ref().map_or(ptr::null(), |x| x);
let pos_ptr = pos.as_ref().map_or(ptr::null(), |x| x);
let mat_ptr = mat.as_ref().map_or(ptr::null(), |x| x);
let rgba_ptr = rgba.as_ref().map_or(ptr::null(), |x| x);
unsafe {
let p_geom = self.ffi.geoms.add(self.ffi.ngeom as usize);
mjv_initGeom(p_geom, geom_type as i32, size_ptr, pos_ptr, mat_ptr, rgba_ptr);
self.ffi.ngeom += 1;
Ok(&mut *p_geom)
}
}
pub fn clear_geom(&mut self) {
self.ffi.ngeom = 0;
}
pub fn pop_geom(&mut self) {
if self.ffi.ngeom == 0 {
return;
}
self.ffi.ngeom -= 1;
}
pub fn render(&mut self, viewport: &MjrRectangle, context: &MjrContext){
unsafe {
mjr_render(*viewport, self.ffi_mut(), context.ffi());
}
}
pub fn find_selection<M: Deref<Target = MjModel>>(
&self, data: &mut MjData<M>, option: &MjvOption,
aspect_ratio: MjtNum, relx: MjtNum, rely: MjtNum,
) -> SceneSelection {
self.assert_signature(data.model().signature());
let (mut geom_id, mut flex_id, mut skin_id) = (-1 , -1, -1);
let mut selpnt = [0.0; 3];
let body_id = unsafe {
mjv_select(
data.model().ffi(), data.ffi(), option,
aspect_ratio, relx, rely, self.ffi(), &mut selpnt,
&mut geom_id, &mut flex_id, &mut skin_id
)
};
let to_opt = |v| if v >= 0 { Some(v as usize) } else { None };
SceneSelection { body_id: to_opt(body_id), geom_id: to_opt(geom_id), flex_id: to_opt(flex_id), skin_id: to_opt(skin_id), point: selpnt }
}
pub fn ffi(&self) -> &mjvScene {
&self.ffi
}
pub unsafe fn ffi_mut(&mut self) -> &mut mjvScene {
&mut self.ffi
}
}
impl MjvScene {
array_slice_dyn! {
(mut = unsafe) flexedge: &[[i32; 2] [force]; "flex edge data"; nflexedge],
flexvert: &[[f32; 3] [force]; "flex vertices"; nflexvert],
skinvert: &[[f32; 3] [force]; "skin vertex data"; nskinvert],
skinnormal: &[[f32; 3] [force]; "skin normal data"; nskinvert],
(mut = unsafe) geoms: &[MjvGeom; "buffer for geoms"; ffi.ngeom],
geomorder: &[i32; "buffer for ordering geoms by distance to camera"; ffi.ngeom],
(mut = unsafe) flexedgeadr: &[i32; "address of flex edges"; ffi.nflex],
(mut = unsafe) flexedgenum: &[i32; "number of edges in flex"; ffi.nflex],
(mut = unsafe) flexvertadr: &[i32; "address of flex vertices"; ffi.nflex],
(mut = unsafe) flexvertnum: &[i32; "number of vertices in flex"; ffi.nflex],
(mut = unsafe) flexfaceadr: &[i32; "address of flex faces"; ffi.nflex],
(mut = unsafe) flexfacenum: &[i32; "number of flex faces allocated"; ffi.nflex],
(mut = unsafe) flexfaceused: &[i32; "number of flex faces currently in use"; ffi.nflex],
(mut = unsafe) skinfacenum: &[i32; "number of faces in skin"; ffi.nskin],
(mut = unsafe) skinvertadr: &[i32; "address of skin vertices"; ffi.nskin],
(mut = unsafe) skinvertnum: &[i32; "number of vertices in skin"; ffi.nskin],
lights: as_ptr as_mut_ptr &[MjvLight; "buffer for lights"; ffi.nlight]
}
array_slice_dyn! {
summed {
flexface: &[f32; "flex faces vertices"; [9; (ffi.flexfacenum); (ffi.nflex)]],
flexnormal: &[f32; "flex face normals"; [9; (ffi.flexfacenum); (ffi.nflex)]],
flextexcoord: &[f32; "flex face texture coordinates"; [6; (ffi.flexfacenum); (ffi.nflex)]]
}
}
}
impl MjvScene {
getter_setter! {get, [
[ffi] maxgeom: i32; "size of allocated geom buffer.";
[ffi] ngeom: i32; "number of geoms currently in buffer.";
[ffi] nflex: i32; "number of flexes.";
[ffi] nskin: i32; "number of skins.";
[ffi] nlight: i32; "number of lights currently in buffer.";
[ffi] status: i32; "status; 0: ok, 1: geoms exhausted.";
]}
getter_setter! {get, [
[ffi] flexvertopt: bool; "copy of mjVIS_FLEXVERT mjvOption flag.";
[ffi] flexedgeopt: bool; "copy of mjVIS_FLEXEDGE mjvOption flag.";
[ffi] flexfaceopt: bool; "copy of mjVIS_FLEXFACE mjvOption flag.";
[ffi] flexskinopt: bool; "copy of mjVIS_FLEXSKIN mjvOption flag.";
]}
getter_setter! {with, get, set, [
[ffi, ffi_mut] stereo: MjtStereo [force]; "stereoscopic rendering.";
]}
getter_setter! {with, get, set, [
[ffi, ffi_mut] scale: f32; "model scaling.";
[ffi, ffi_mut] framewidth: i32; "frame pixel width; 0: disable framing.";
]}
getter_setter! {with, get, set, [
[ffi, ffi_mut] enabletransform: bool; "enable model transformation.";
]}
getter_setter! {with, get, [
[ffi, ffi_mut] camera: &[MjvGLCamera; 2]; "left and right camera.";
[ffi, ffi_mut] translate: &[f32; 3]; "model translation.";
[ffi, ffi_mut] rotate: &[f32; 4]; "model quaternion rotation.";
[ffi, ffi_mut] framergb: &[f32; 3]; "frame color.";
]}
getter_setter! {get, [
[ffi, ffi_mut] flags: &[MjtByte; MjtRndFlag::mjNRNDFLAG as usize]; "rendering flags (indexed by mjtRndFlag).";
]}
}
impl Drop for MjvScene {
fn drop(&mut self) {
unsafe {
mjv_freeScene(self.ffi.as_mut());
}
}
}
unsafe impl Send for MjvScene {}
unsafe impl Sync for MjvScene {}
#[cfg(test)]
mod tests {
use super::*;
const EXAMPLE_MODEL: &str = "
<mujoco>
<worldbody>
<light ambient=\"0.2 0.2 0.2\"/>
<body name=\"ball\">
<geom name=\"green_sphere\" pos=\".2 .2 .2\" size=\".1\" rgba=\"0 1 0 1\"/>
<joint type=\"free\"/>
</body>
<geom name=\"floor\" type=\"plane\" size=\"10 10 1\" euler=\"5 0 0\"/>
</worldbody>
</mujoco>
";
fn load_model() -> MjModel {
MjModel::from_xml_string(EXAMPLE_MODEL).unwrap()
}
#[test]
#[allow(non_snake_case)]
fn test_MjvGeom() {
let model = load_model();
let mut scene = MjvScene::new(&model, 1000);
let geom = scene.create_geom(MjtGeom::mjGEOM_SPHERE, None, None, None, None);
let label = "Hello World";
geom.set_label(label).unwrap();
assert_eq!(geom.label(), label);
}
#[test]
#[should_panic(expected = "out of range")]
fn test_camera_frame_fixed_id_out_of_range_panics() {
let model = load_model(); let data = model.make_data();
let camera = MjvCamera::new_fixed(0); let _ = camera.frame(&data);
}
#[test]
#[should_panic(expected = "out of range")]
fn test_scene_update_fixed_camera_out_of_range_panics() {
let model = load_model(); let mut scene = MjvScene::new(&model, 1000);
let mut data = model.make_data();
let (opt, pert) = (MjvOption::default(), MjvPerturb::default());
let mut camera = MjvCamera::new_fixed(9999);
scene.update(&mut data, &opt, &pert, &mut camera);
}
#[test]
#[should_panic(expected = "out of range")]
fn test_perturb_move_select_out_of_range_panics() {
let model = load_model(); let data = model.make_data();
let scene = MjvScene::new(&model, 100);
let mut pert = MjvPerturb { select: 1_000_000, ..Default::default() }; pert.move_(&data, MjtMouse::mjMOUSE_MOVE_H_REL, 0.1, 0.1, &scene);
}
#[test]
fn test_scene_geom_pop() {
const N_GEOM: usize = 10;
const N_GEOM_POP: usize = 11;
let model = load_model();
let mut scene = MjvScene::new(&model, 1000);
for _ in 0..N_GEOM {
scene.create_geom(MjtGeom::mjGEOM_SPHERE, None, None, None, None);
}
assert_eq!(scene.geoms().len(), N_GEOM);
for _ in 0..N_GEOM_POP {
scene.pop_geom();
}
assert_eq!(scene.geoms().len(), 0);
}
#[test]
fn test_scene_slices() {
let model = load_model();
let scene = MjvScene::new(&model, 100);
assert_eq!(scene.lights().len(), scene.ffi().nlight as usize);
assert_eq!(scene.geomorder().len(), scene.ffi().ngeom as usize);
assert_eq!(scene.geoms().len(), scene.ffi().ngeom as usize);
}
#[test]
fn test_figure() {
let mut fig = MjvFigure::new_boxed();
let plot = 0;
assert!(fig.empty(plot));
assert_eq!(fig.pop_front(plot), None);
assert_eq!(fig.pop_back(plot), None);
fig.push(plot, 1.0, 2.0).unwrap();
fig.push(plot, 3.0, 4.0).unwrap();
assert!(!fig.empty(plot));
let first = fig.pop_front(plot);
assert_eq!(first, Some((1.0, 2.0)));
let last = fig.pop_back(plot);
assert_eq!(last, Some((3.0, 4.0)));
assert!(fig.empty(plot));
assert_eq!(fig.pop_front(plot), None);
assert_eq!(fig.pop_back(plot), None);
}
#[test]
fn test_bool_getter_setter_roundtrip() {
let model = load_model();
let mut scene = MjvScene::new(&model, 100);
assert!(!scene.enabletransform());
scene.set_enabletransform(true);
assert!(scene.enabletransform());
scene.set_enabletransform(false);
assert!(!scene.enabletransform());
}
#[test]
fn test_force_enum_getter_setter_roundtrip() {
let model = load_model();
let mut scene = MjvScene::new(&model, 100);
let original = scene.stereo();
assert_eq!(original, MjtStereo::mjSTEREO_NONE);
scene.set_stereo(MjtStereo::mjSTEREO_QUADBUFFERED);
assert_eq!(scene.stereo(), MjtStereo::mjSTEREO_QUADBUFFERED);
scene.set_stereo(MjtStereo::mjSTEREO_SIDEBYSIDE);
assert_eq!(scene.stereo(), MjtStereo::mjSTEREO_SIDEBYSIDE);
}
#[test]
fn test_scene_full_error() {
let model = load_model();
let mut scene = MjvScene::new(&model, 2);
scene.create_geom(MjtGeom::mjGEOM_SPHERE, None, None, None, None);
scene.create_geom(MjtGeom::mjGEOM_SPHERE, None, None, None, None);
let err = scene.try_create_geom(MjtGeom::mjGEOM_SPHERE, None, None, None, None).unwrap_err();
assert!(matches!(err, MjSceneError::SceneFull { capacity: 2 }));
}
#[test]
fn test_geom_label_boundary() {
let model = load_model();
let mut scene = MjvScene::new(&model, 10);
let geom = scene.create_geom(MjtGeom::mjGEOM_BOX, None, None, None, None);
let capacity = geom.label.len() - 1;
let max_label: String = "A".repeat(capacity);
geom.set_label(&max_label).unwrap();
assert_eq!(geom.label(), max_label);
let too_long: String = "B".repeat(capacity + 1);
let err = geom.set_label(&too_long).unwrap_err();
assert!(matches!(err, MjSceneError::LabelTooLong { .. }));
geom.set_label("").unwrap();
assert_eq!(geom.label(), "");
}
#[test]
fn test_figure_push_overflow() {
let mut fig = MjvFigure::new_boxed();
let plot = 0;
let capacity = fig.linedata[plot].len() / 2;
for i in 0..capacity {
fig.push(plot, i as f32, i as f32).unwrap();
}
assert!(fig.full(plot));
let err = fig.push(plot, 0.0, 0.0).unwrap_err();
assert!(matches!(err, MjSceneError::FigureBufferFull { plot_index: 0, .. }));
fig.pop_back(plot);
assert!(!fig.full(plot));
fig.push(plot, 99.0, 99.0).unwrap();
assert!(fig.full(plot));
}
#[test]
fn test_c_str_as_str_method_title_roundtrip() {
let mut fig = MjvFigure::new_boxed();
assert_eq!(fig.title(), "");
fig.set_title("hello");
assert_eq!(fig.title(), "hello");
fig.set_title("world");
assert_eq!(fig.title(), "world");
}
#[test]
fn test_array_slice_dyn_summed_flex_empty() {
let model = load_model();
let scene = MjvScene::new(&model, 1000);
assert!(scene.flexface().is_empty(), "flexface must be empty with no flex bodies");
assert!(scene.flexnormal().is_empty(), "flexnormal must be empty with no flex bodies");
assert!(scene.flextexcoord().is_empty(), "flextexcoord must be empty with no flex bodies");
}
}