1use crate::{
3 view_creator, info_method, info_with_view,
4 array_slice_dyn, getter_setter
5};
6use crate::util::{assert_mujoco_version, checked_c_len, ERROR_BUF_LEN};
7use crate::error::{MjDataError, MjModelError};
8use crate::wrappers::mj_option::MjOption;
9use crate::wrappers::mj_data::MjData;
10use crate::mujoco_c::*;
11
12use super::mj_auxiliary::{MjVfs, MjVisual, MjStatistic};
13use super::mj_editing::encode;
14use super::mj_primitive::*;
15
16use bytemuck::must_cast_slice;
17use log::debug;
18
19use std::fmt::{Formatter, Debug, Result as FmtResult};
20use std::ffi::{c_char, CStr, CString, c_int, c_void};
21use std::sync::{OnceLock, Arc};
22use std::ptr::{self, NonNull};
23use std::path::Path;
24
25pub mod traits;
26
27
28pub type MjtDisableBit = mjtDisableBit;
34
35pub type MjtEnableBit = mjtEnableBit;
39
40pub type MjtJoint = mjtJoint;
44
45pub type MjtGeom = mjtGeom;
49
50pub type MjtProjection = mjtProjection;
52
53pub type MjtCamLight = mjtCamLight;
56
57pub type MjtLightType = mjtLightType;
60
61pub type MjtTexture = mjtTexture;
63
64pub type MjtTextureRole = mjtTextureRole;
67
68pub type MjtColorSpace = mjtColorSpace;
70
71pub type MjtLRMode = mjtLRMode;
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
82#[repr(i32)]
83pub enum MjtCubeFace {
84 Right = 0,
86 Left = 1,
88 Up = 2,
90 Down = 3,
92 Front = 4,
94 Back = 5,
96}
97
98pub type MjtIntegrator = mjtIntegrator;
100
101pub type MjtCone = mjtCone;
103
104pub type MjtJacobian = mjtJacobian;
106
107pub type MjtSolver = mjtSolver;
109
110pub type MjtEq = mjtEq;
112
113pub type MjtWrap = mjtWrap;
115
116pub type MjtTrn = mjtTrn;
118
119pub type MjtDyn = mjtDyn;
121
122pub type MjtGain = mjtGain;
124
125pub type MjtBias = mjtBias;
127
128pub type MjtCtrlChart = mjtCtrlChart;
130
131pub type MjtCtrlInput = mjtCtrlInput;
134
135pub type MjtObj = mjtObj;
138
139pub type MjtSensor = mjtSensor;
141
142pub type MjtStage = mjtStage;
145
146pub type MjtDataType = mjtDataType;
148
149pub type MjtConDataField = mjtConDataField;
151
152pub type MjtSameFrame = mjtSameFrame;
155
156pub type MjtSleepPolicy = mjtSleepPolicy;
159
160pub type MjtFlexSelf = mjtFlexSelf;
162
163pub type MjtSDFType = mjtSDFType;
165
166pub type MjtRayDataField = mjtRayDataField;
168
169pub type MjtCamOutBit = mjtCamOutBit;
171
172unsafe impl bytemuck::Zeroable for mjtTrn {}
178unsafe impl bytemuck::Zeroable for mjtDyn {}
179unsafe impl bytemuck::Zeroable for mjtGain {}
180unsafe impl bytemuck::Zeroable for mjtBias {}
181unsafe impl bytemuck::Zeroable for mjtObj {}
182unsafe impl bytemuck::Zeroable for mjtSameFrame {}
183unsafe impl bytemuck::Zeroable for mjtCamLight {}
184unsafe impl bytemuck::Zeroable for mjtProjection {}
185unsafe impl bytemuck::Zeroable for mjtEq {}
186unsafe impl bytemuck::Zeroable for mjtGeom {}
187unsafe impl bytemuck::Zeroable for mjtJoint {}
188unsafe impl bytemuck::Zeroable for mjtLightType {}
189unsafe impl bytemuck::Zeroable for mjtSensor {}
190unsafe impl bytemuck::Zeroable for mjtDataType {}
191unsafe impl bytemuck::Zeroable for mjtStage {}
192unsafe impl bytemuck::Zeroable for mjtTexture {}
193unsafe impl bytemuck::Zeroable for mjtColorSpace {}
194unsafe impl bytemuck::Zeroable for mjtAlignFree {}
195unsafe impl bytemuck::Zeroable for mjtBuiltin {}
196unsafe impl bytemuck::Zeroable for mjtConflict {}
197unsafe impl bytemuck::Zeroable for mjtConstraint {}
198unsafe impl bytemuck::Zeroable for mjtConstraintState {}
199unsafe impl bytemuck::Zeroable for mjtDepthMap {}
200unsafe impl bytemuck::Zeroable for mjtFlexSelf {}
201unsafe impl bytemuck::Zeroable for mjtInertiaFromGeom {}
202unsafe impl bytemuck::Zeroable for mjtLimited {}
203unsafe impl bytemuck::Zeroable for mjtLogLevel {}
204unsafe impl bytemuck::Zeroable for mjtLogTopic {}
205unsafe impl bytemuck::Zeroable for mjtMark {}
206unsafe impl bytemuck::Zeroable for mjtSleepPolicy {}
207unsafe impl bytemuck::Zeroable for mjtSleepState {}
208unsafe impl bytemuck::Zeroable for mjtStereo {}
209unsafe impl bytemuck::Zeroable for mjtWrap {}
210
211unsafe impl bytemuck::NoUninit for mjtJoint {}
215unsafe impl bytemuck::NoUninit for mjtGeom {}
216unsafe impl bytemuck::NoUninit for mjtEq {}
217unsafe impl bytemuck::NoUninit for mjtObj {}
218unsafe impl bytemuck::NoUninit for mjtWrap {}
219unsafe impl bytemuck::NoUninit for mjtTrn {}
220unsafe impl bytemuck::NoUninit for mjtDyn {}
221unsafe impl bytemuck::NoUninit for mjtGain {}
222unsafe impl bytemuck::NoUninit for mjtBias {}
223unsafe impl bytemuck::NoUninit for mjtSensor {}
224unsafe impl bytemuck::NoUninit for mjtDataType {}
225unsafe impl bytemuck::NoUninit for mjtStage {}
226unsafe impl bytemuck::NoUninit for mjtTexture {}
227
228const ASSET_SPLIT_TABLES: usize = 11;
232
233const ELEMENT_SPLIT_TABLES: usize = 32;
235
236#[derive(Debug, Clone, Eq)]
246#[expect(non_snake_case, reason = "the fields keep the MuJoCo size symbol names")]
247pub(crate) struct MjModelLayout {
248 signature: u64,
249
250 nexclude: MjtSize, nmat: MjtSize, npair: MjtSize, nskin: MjtSize,
253 nkey: MjtSize,
254
255 nmocap: MjtSize, nuserdata: MjtSize, nhistory: MjtSize, nbvh: MjtSize,
259 nbvhdynamic: MjtSize, nflexedge: MjtSize, nflexstiffness: MjtSize, nJmom: MjtSize,
260 nJfe: MjtSize, nJfv: MjtSize, nC: MjtSize, nD: MjtSize,
261 ntree: MjtSize,
262
263 narena: MjtSize,
266
267 nmeshgraph: MjtSize,
271
272 nuser_body: MjtSize, nuser_jnt: MjtSize, nuser_geom: MjtSize, nuser_site: MjtSize,
274 nuser_cam: MjtSize, nuser_tendon: MjtSize, nuser_actuator: MjtSize, nuser_sensor: MjtSize,
275
276 split: MjSplitTables,
281}
282
283impl PartialEq for MjModelLayout {
284 fn eq(&self, other: &Self) -> bool {
287 self.nexclude == other.nexclude && self.nmat == other.nmat && self.npair == other.npair && self.nskin == other.nskin &&
288 self.nkey == other.nkey && self.nmocap == other.nmocap && self.nuserdata == other.nuserdata && self.nhistory == other.nhistory &&
289 self.nbvh == other.nbvh && self.nbvhdynamic == other.nbvhdynamic && self.nflexedge == other.nflexedge && self.nflexstiffness == other.nflexstiffness &&
290 self.nJmom == other.nJmom && self.nJfe == other.nJfe && self.nJfv == other.nJfv && self.nC == other.nC &&
291 self.nD == other.nD && self.ntree == other.ntree && self.narena == other.narena && self.nmeshgraph == other.nmeshgraph &&
292 self.nuser_body == other.nuser_body && self.nuser_jnt == other.nuser_jnt && self.nuser_geom == other.nuser_geom && self.nuser_site == other.nuser_site &&
293 self.nuser_cam == other.nuser_cam && self.nuser_tendon == other.nuser_tendon && self.nuser_actuator == other.nuser_actuator && self.nuser_sensor == other.nuser_sensor &&
294 self.split == other.split
295 }
296}
297
298impl MjModelLayout {
299 pub(crate) fn signature(&self) -> u64 {
301 self.signature
302 }
303
304 fn asset_split(&self) -> &[Box<[u8]>; ASSET_SPLIT_TABLES] {
306 &self.split.assets
307 }
308}
309
310impl From<&MjModel> for MjModelLayout {
311 fn from(model: &MjModel) -> Self {
312 let m = model.ffi();
313 Self {
314 signature: m.signature,
315 nexclude: m.nexclude, nmat: m.nmat, npair: m.npair, nskin: m.nskin, nkey: m.nkey,
316 nmocap: m.nmocap, nuserdata: m.nuserdata, nhistory: m.nhistory, nbvh: m.nbvh,
317 nbvhdynamic: m.nbvhdynamic, nflexedge: m.nflexedge, nflexstiffness: m.nflexstiffness,
318 nJmom: m.nJmom, nJfe: m.nJfe, nJfv: m.nJfv, nC: m.nC, nD: m.nD, ntree: m.ntree,
319 narena: m.narena,
320 nmeshgraph: m.nmeshgraph,
321 nuser_body: m.nuser_body, nuser_jnt: m.nuser_jnt, nuser_geom: m.nuser_geom, nuser_site: m.nuser_site,
322 nuser_cam: m.nuser_cam, nuser_tendon: m.nuser_tendon, nuser_actuator: m.nuser_actuator,
323 nuser_sensor: m.nuser_sensor,
324 split: model.split_tables(),
325 }
326 }
327}
328
329
330#[derive(Clone, PartialEq, Eq)]
336pub(crate) struct MjSplitTables {
337 assets: [Box<[u8]>; ASSET_SPLIT_TABLES],
338 elements: [Box<[u8]>; ELEMENT_SPLIT_TABLES],
339}
340
341impl Debug for MjSplitTables {
342 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
343 let assets = self.assets.each_ref().map(|table| table.len());
344 let elements = self.elements.each_ref().map(|table| table.len());
345 f.debug_struct("MjSplitTables")
346 .field("assets", &assets)
347 .field("elements", &elements)
348 .finish_non_exhaustive()
349 }
350}
351
352
353#[derive(Debug)]
356pub struct MjModel {
357 ptr: NonNull<mjModel>,
358 layout: OnceLock<Arc<MjModelLayout>>,
360}
361
362unsafe impl Send for MjModel {}
365unsafe impl Sync for MjModel {}
366
367
368impl MjModel {
369 pub fn from_xml<T: AsRef<Path>>(path: T) -> Result<Self, MjModelError> {
380 Self::from_xml_file(path, None)
381 }
382
383 pub fn from_xml_vfs<T: AsRef<Path>>(path: T, vfs: &MjVfs) -> Result<Self, MjModelError> {
394 Self::from_xml_file(path, Some(vfs))
395 }
396
397 fn from_xml_file<T: AsRef<Path>>(path: T, vfs: Option<&MjVfs>) -> Result<Self, MjModelError> {
398 assert_mujoco_version();
399
400 let mut error_buffer = [0; ERROR_BUF_LEN];
401 let path_str = path.as_ref().to_str()
402 .ok_or(MjModelError::InvalidUtf8Path)?;
403 let path = CString::new(path_str).unwrap();
404 let raw_ptr = unsafe { mj_loadXML(
405 path.as_ptr(), vfs.map_or(ptr::null(), |v| v.ffi()),
406 error_buffer.as_mut_ptr(), error_buffer.len() as c_int
407 ) };
408
409 Self::check_raw_model(raw_ptr, &error_buffer)
410 .inspect(|_| debug!("loaded the model from \"{path_str}\""))
411 }
412
413 pub fn from_xml_string(data: &str) -> Result<Self, MjModelError> {
423 assert_mujoco_version();
424
425 let mut vfs = MjVfs::new();
426 let filename = "model.xml";
427
428 vfs.add_from_buffer(filename, data.as_bytes())?;
430
431 let mut error_buffer = [0; ERROR_BUF_LEN];
432 let filename_c = CString::new(filename).unwrap();
433 let raw_ptr = unsafe { mj_loadXML(
434 filename_c.as_ptr(), vfs.ffi(),
435 error_buffer.as_mut_ptr(), error_buffer.len() as c_int
436 ) };
437
438 Self::check_raw_model(raw_ptr, &error_buffer)
439 }
440
441 pub fn from_buffer(data: &[u8]) -> Result<Self, MjModelError> {
451 assert_mujoco_version();
452 unsafe {
453 Self::from_raw(mj_loadModelBuffer(data.as_ptr() as *const c_void, checked_c_len(data.len())))
454 }
455 }
456
457 pub fn from_mjb<T: AsRef<Path>>(path: T) -> Result<Self, MjModelError> {
469 Self::from_mjb_file(path, None)
470 }
471
472 pub fn from_mjb_vfs<T: AsRef<Path>>(path: T, vfs: &MjVfs) -> Result<Self, MjModelError> {
483 Self::from_mjb_file(path, Some(vfs))
484 }
485
486 fn from_mjb_file<T: AsRef<Path>>(path: T, vfs: Option<&MjVfs>) -> Result<Self, MjModelError> {
487 assert_mujoco_version();
488
489 let path_str = path.as_ref().to_str()
490 .ok_or(MjModelError::InvalidUtf8Path)?;
491 let path = CString::new(path_str).unwrap();
492 let raw_ptr = unsafe { mj_loadModel(
493 path.as_ptr(), vfs.map_or(ptr::null(), |v| v.ffi())
494 ) };
495
496 Self::from_raw(raw_ptr)
497 .inspect(|_| debug!("loaded the model from \"{path_str}\""))
498 }
499
500 pub(crate) fn from_raw(ptr: *mut mjModel) -> Result<Self, MjModelError> {
502 Self::check_raw_model(ptr, &[0])
503 }
504
505 pub fn save_last_xml<T: AsRef<Path>>(&self, filename: T) -> Result<(), MjModelError> {
515 let path_str = filename.as_ref().to_str()
516 .ok_or(MjModelError::InvalidUtf8Path)?;
517 let mut error = [0; ERROR_BUF_LEN];
518 let cstring = CString::new(path_str).unwrap();
519 let result = unsafe { mj_saveLastXML(
520 cstring.as_ptr(), self.ffi(),
521 error.as_mut_ptr(), error.len() as i32
522 ) };
523 match result {
524 1 => {
525 debug!("saved the last loaded XML to \"{path_str}\"");
526 Ok(())
527 },
528 _ => {
529 let cstr_error = unsafe { CStr::from_ptr(error.as_ptr()) }
533 .to_string_lossy()
534 .into_owned();
535 Err(MjModelError::SaveFailed(cstr_error))
536 },
537 }
538 }
539
540 pub fn encode(&self, filepath: impl AsRef<Path>, content_type: &str) -> Result<(), MjModelError> {
562 self.encode_impl(filepath, content_type, None)
563 }
564
565 pub fn encode_with_vfs(&self, filepath: impl AsRef<Path>, content_type: &str, vfs: &MjVfs) -> Result<(), MjModelError> {
573 self.encode_impl(filepath, content_type, Some(vfs))
574 }
575
576 fn encode_impl(
578 &self,
579 filepath: impl AsRef<Path>, content_type: &str,
580 maybe_vfs: Option<&MjVfs>
581 ) -> Result<(), MjModelError> {
582 let filepath = filepath.as_ref().to_str().ok_or(MjModelError::InvalidUtf8Path)?;
583 encode(None, Some(self), filepath, content_type, maybe_vfs).map_err(MjModelError::SaveFailed)
584 }
585
586 pub fn make_data(&self) -> MjData<&Self> {
592 MjData::new(self)
593 }
594
595 pub fn try_make_data(&self) -> Result<MjData<&Self>, MjDataError> {
601 MjData::try_new(self)
602 }
603
604 fn check_raw_model(ptr_model: *mut mjModel, error_buffer: &[c_char]) -> Result<Self, MjModelError> {
607 match NonNull::new(ptr_model) {
608 Some(nn) => Ok(Self { ptr: nn, layout: OnceLock::new() }),
609 None => {
610 let message = unsafe { CStr::from_ptr(error_buffer.as_ptr()) }
613 .to_string_lossy()
614 .into_owned();
615 Err(MjModelError::LoadFailed(message))
616 }
617 }
618 }
619
620 info_method! { Model, actuator,
621 [trntype: 1, dyntype: 1, gaintype: 1, biastype: 1, ctrladr: 1, ctrlnum: 1, ctrlspec: 1,
622 outadr: 1, outnum: 1, trnid: 2, actadr: 1, actnum: 1,
623 group: 1, history: 2, historyadr: 1, delay: 1, forcelimited: 1, actlimited: 1, dynprm: mjNDYN as usize, gainprm: mjNGAIN as usize, biasprm: mjNBIAS as usize,
624 actearly: 1, forcerange: 2, actrange: 2, damping: 1,
625 dampingpoly: mjNPOLY as usize, armature: 1, cranklength: 1, plugin: 1],
626 [user: nuser_actuator],
627 [ctrllimited: nu, ctrlrange: nu * 2, gear: nout * 6, acc0: nout, length0: nout, lengthrange: nout * 2]
628 }
629
630 info_method! { Model, body,
631 [parentid: 1, rootid: 1, weldid: 1, mocapid: 1, jntnum: 1, jntadr: 1,
632 dofnum: 1, dofadr: 1, treeid: 1, geomnum: 1, geomadr: 1, simple: 1,
633 sameframe: 1, pos: 3, quat: 4, ipos: 3, iquat: 4, mass: 1, subtreemass: 1,
634 inertia: 3, invweight0: 2, gravcomp: 1, margin: 1, plugin: 1,
635 contype: 1, conaffinity: 1, bvhadr: 1, bvhnum: 1],
636 [user: nuser_body],
637 []
638 }
639
640 info_method! { Model, camera,
641 [mode: 1, bodyid: 1, targetbodyid: 1,
642 pos: 3, quat: 4, poscom0: 3,
643 pos0: 3, mat0: 9, projection: 1, fovy: 1,
644 ipd: 1, resolution: 2, output: 1, sensorsize: 2, intrinsic: 4],
645 [user: nuser_cam],
646 []
647 }
648
649 info_method! { Model, joint,
650 [r#type: 1, qposadr: 1, dofadr: 1, group: 1,
651 limited: 1, actfrclimited: 1, actgravcomp: 1, solref: mjNREF as usize, solimp: mjNIMP as usize,
652 pos: 3, axis: 3, stiffness: 1, stiffnesspoly: mjNPOLY as usize,
653 range: 2, actfrcrange: 2, margin: 1, bodyid: 1, actuatorid: 1],
654 [user: nuser_jnt],
655 [qpos0: nq, qpos_spring: nq, jntid: nv,
656 dof_bodyid: nv, parentid: nv, dof_treeid: nv, Madr: nv, simplenum: nv, frictionloss: nv,
657 armature: nv, damping: nv, dampingpoly: nv * mjNPOLY as usize, invweight0: nv, M0: nv]
658 }
659
660
661 info_method! { Model, equality,
662 [r#type: 1, obj1id: 1,
663 obj2id: 1, active0: 1,
664 solref: mjNREF as usize, solimp: mjNIMP as usize,
665 data: mjNEQDATA as usize, objtype: 1],
666 [],
667 []
668 }
669
670 info_method! { Model, exclude,
671 [signature: 1],
672 [],
673 []
674 }
675
676 info_method! { Model, geom,
677 [r#type: 1, contype: 1, conaffinity: 1, condim: 1, bodyid: 1, dataid: 1, matid: 1,
678 group: 1, priority: 1, plugin: 1, sameframe: 1, solmix: 1, solref: mjNREF as usize, solimp: mjNIMP as usize, size: 3,
679 aabb: 6, rbound: 1, pos: 3, quat: 4, friction: 3, margin: 1, gap: 1, surfacevel: 6, adhesion: 1, fluid: mjNFLUID as usize, rgba: 4],
680 [user: nuser_geom],
681 []
682 }
683
684 info_method! { Model, hfield,
685 [size: 4,
686 nrow: 1,
687 ncol: 1,
688 adr: 1,
689 pathadr: 1],
690 [],
691 [data: nhfielddata]
692 }
693
694 info_method! { Model, light,
695 [mode: 1, bodyid: 1, targetbodyid: 1, r#type: 1, texid: 1, castshadow: 1,
696 bulbradius: 1, intensity: 1, range: 1,
697 active: 1, pos: 3, dir: 3, poscom0: 3, pos0: 3,
698 dir0: 3, attenuation: 3, cutoff: 1, softness: 1, exponent: 1, ambient: 3,
699 diffuse: 3, specular: 3],
700 [],
701 []
702 }
703
704 info_method! { Model, material,
705 [texid: MjtTextureRole::mjNTEXROLE as usize, texuniform: 1,
706 texrepeat: 2, emission: 1,
707 specular: 1, shininess: 1,
708 reflectance: 1, rgba: 4, metallic: 1, roughness: 1],
709 [],
710 []
711 }
712
713 info_method! { Model, mesh,
714 [vertadr: 1, vertnum: 1,
715 texcoordadr: 1, faceadr: 1,
716 facenum: 1, graphadr: 1, extrema: 27,
717 normaladr: 1, normalnum: 1, texcoordnum: 1,
718 bvhadr: 1, bvhnum: 1, octadr: 1, octnum: 1,
719 pathadr: 1, polynum: 1, polyadr: 1,
720 scale: 3, pos: 3, quat: 4],
721 [],
722 []
723 }
724
725 info_method! { Model, numeric,
726 [adr: 1,
727 size: 1],
728 [],
729 [data: nnumericdata]
730 }
731
732 info_method! { Model, pair,
733 [dim: 1, geom1: 1, geom2: 1,
734 signature: 1, solref: mjNREF as usize, solimp: mjNIMP as usize,
735 margin: 1, gap: 1, adhesion: 1, friction: 5, solreffriction: mjNREF as usize],
736 [],
737 []
738 }
739
740 info_method! { Model, sensor,
741 [r#type: 1, datatype: 1, needstage: 1,
742 objtype: 1, objid: 1, reftype: 1,
743 refid: 1, intprm: mjNSENS as usize, dim: 1, adr: 1,
744 cutoff: 1, noise: 1, history: 2, historyadr: 1, delay: 1, interval: 2, plugin: 1],
745 [user: nuser_sensor],
746 []
747 }
748
749 info_method! { Model, site,
750 [r#type: 1, bodyid: 1, matid: 1,
751 group: 1, sameframe: 1, size: 3,
752 pos: 3, quat: 4, rgba: 4],
753 [user: nuser_site],
754 []
755 }
756
757 info_method! { Model, skin,
758 [matid: 1, group: 1, rgba: 4, inflate: 1,
759 vertadr: 1, vertnum: 1, texcoordadr: 1,
760 faceadr: 1, facenum: 1, boneadr: 1,
761 bonenum: 1, pathadr: 1],
762 [],
763 []
764 }
765
766 info_method! { Model, tendon,
767 [adr: 1, num: 1, matid: 1, actuatorid: 1, group: 1, treenum: 1, treeid: 2,
768 limited: 1, actfrclimited: 1, width: 1,
769 solref_lim: mjNREF as usize, solimp_lim: mjNIMP as usize, solref_fri: mjNREF as usize, solimp_fri: mjNIMP as usize, range: 2, actfrcrange: 2, margin: 1,
770 stiffness: 1, stiffnesspoly: mjNPOLY as usize, damping: 1, dampingpoly: mjNPOLY as usize, armature: 1,
771 frictionloss: 1, lengthspring: 2, length0: 1, invweight0: 1, J_rownnz: 1, J_rowadr: 1, rgba: 4],
772 [user: nuser_tendon],
773 [J_colind: nJten]
774 }
775
776 info_method! { Model, texture,
777 [r#type: 1, colorspace: 1, height: 1,
778 width: 1, nchannel: 1,
779 adr: 1, pathadr: 1],
780 [],
781 [data: ntexdata]
782 }
783
784 info_method! { Model, tuple,
785 [adr: 1,
786 size: 1],
787 [],
788 [objtype: ntupledata,
789 objid: ntupledata,
790 objprm: ntupledata]
791 }
792
793 info_method! { Model, key,
794 [time: 1],
795 [qpos: nq, qvel: nv,
796 act: na, mpos: nmocap*3,
797 mquat: nmocap*4, ctrl: nu],
798 []
799 }
800
801 pub fn name_to_id(&self, type_: MjtObj, name: &str) -> Option<usize> {
807 let c_string = CString::new(name).unwrap();
808 let id = unsafe {
809 mj_name2id(self.ffi(), type_ as i32, c_string.as_ptr())
810 };
811 if id == -1 { None } else { Some(id as usize) }
812 }
813
814 #[deprecated(
825 since = "6.0.0",
826 note = "always returns Ok; use `clone`"
827 )]
828 pub fn try_clone(&self) -> Result<MjModel, MjModelError> {
829 let ptr = unsafe { mj_copyModel(ptr::null_mut(), self.ffi()) };
830 NonNull::new(ptr)
831 .map(|ptr| MjModel { ptr, layout: self.layout.clone() })
833 .ok_or(MjModelError::AllocationFailed)
834 }
835
836 pub fn save_to_file<T: AsRef<Path>>(&self, filename: T) -> Result<(), MjModelError> {
849 let path_str = filename.as_ref().to_str()
850 .ok_or(MjModelError::InvalidUtf8Path)?;
851 let c_filename = CString::new(path_str).unwrap();
852 unsafe { mj_saveModel(
853 self.ffi(), c_filename.as_ptr(),
854 ptr::null_mut(), 0
855 ) };
856 debug!("saved the model to \"{path_str}\"");
857 Ok(())
858 }
859
860 pub fn save_to_buffer(&self, buffer: &mut [u8]) -> Result<(), MjModelError> {
867 let needed = self.size();
868 if buffer.len() < needed {
869 return Err(MjModelError::BufferTooSmall {
870 needed,
871 available: buffer.len(),
872 });
873 }
874 unsafe { mj_saveModel(
875 self.ffi(), ptr::null(),
876 buffer.as_mut_ptr() as *mut c_void, buffer.len() as i32
877 ) };
878 Ok(())
879 }
880
881 pub fn size(&self) -> usize {
884 unsafe { mj_sizeModel(self.ffi()) as usize }
885 }
886
887 pub fn print_formatted<T: AsRef<Path>>(&self, filename: T, float_format: &str) -> Result<(), MjModelError> {
900 let path_str = filename.as_ref().to_str()
901 .ok_or(MjModelError::InvalidUtf8Path)?;
902 let c_filename = CString::new(path_str).unwrap();
903 let c_float_format = CString::new(float_format).unwrap();
904 unsafe { mj_printFormattedModel(self.ffi(), c_filename.as_ptr(), c_float_format.as_ptr()) }
905 Ok(())
906 }
907
908 pub fn print<T: AsRef<Path>>(&self, filename: T) -> Result<(), MjModelError> {
920 let path_str = filename.as_ref().to_str()
921 .ok_or(MjModelError::InvalidUtf8Path)?;
922 let c_filename = CString::new(path_str).unwrap();
923 unsafe { mj_printModel(self.ffi(), c_filename.as_ptr()) }
924 Ok(())
925 }
926
927 pub fn state_size(&self, spec: u32) -> usize {
932 unsafe { mj_stateSize(self.ffi(), spec as i32) as usize }
933 }
934
935 pub fn extract_state(&self, src: &[MjtNum], src_spec: u32, dst_spec: u32) -> Box<[MjtNum]> {
950 self.try_extract_state(src, src_spec, dst_spec).unwrap()
951 }
952
953 pub fn try_extract_state(&self, src: &[MjtNum], src_spec: u32, dst_spec: u32) -> Result<Box<[MjtNum]>, MjModelError> {
965 let expected = self.state_size(src_spec);
966 if src.len() != expected {
967 return Err(MjModelError::StateSliceLengthMismatch { expected, got: src.len() });
968 }
969
970 if (dst_spec & src_spec) != dst_spec {
971 return Err(MjModelError::SpecNotSubset);
972 }
973
974 let required_size = self.state_size(dst_spec);
975 let mut dst = Vec::with_capacity(required_size);
976
977 unsafe {
981 mj_extractState(
982 self.ffi(),
983 src.as_ptr(), src_spec as i32,
984 dst.as_mut_ptr(), dst_spec as i32
985 );
986
987 dst.set_len(required_size);
988 Ok(dst.into_boxed_slice())
989 }
990 }
991
992 pub fn extract_state_into(&self, src: &[MjtNum], src_spec: u32, dst: &mut [MjtNum], dst_spec: u32) -> usize {
1008 self.try_extract_state_into(src, src_spec, dst, dst_spec).unwrap()
1009 }
1010
1011 pub fn try_extract_state_into(&self, src: &[MjtNum], src_spec: u32, dst: &mut [MjtNum], dst_spec: u32) -> Result<usize, MjModelError> {
1024 let expected = self.state_size(src_spec);
1025 if src.len() != expected {
1026 return Err(MjModelError::StateSliceLengthMismatch { expected, got: src.len() });
1027 }
1028
1029 if (dst_spec & src_spec) != dst_spec {
1030 return Err(MjModelError::SpecNotSubset);
1031 }
1032
1033 let required_size = self.state_size(dst_spec);
1034 let available_size = dst.len();
1035
1036 if available_size < required_size {
1037 return Err(MjModelError::BufferTooSmall { needed: required_size, available: available_size });
1038 }
1039
1040 unsafe {
1041 mj_extractState(
1042 self.ffi(),
1043 src.as_ptr(), src_spec as i32,
1044 dst.as_mut_ptr(), dst_spec as i32
1045 );
1046 }
1047
1048 Ok(required_size)
1049 }
1050
1051 pub fn differentiate_pos(&self, dt: MjtNum, qpos1: &[MjtNum], qpos2: &[MjtNum]) -> Box<[MjtNum]> {
1059 self.try_differentiate_pos(dt, qpos1, qpos2).unwrap()
1060 }
1061
1062 pub fn try_differentiate_pos(&self, dt: MjtNum, qpos1: &[MjtNum], qpos2: &[MjtNum]) -> Result<Box<[MjtNum]>, MjModelError> {
1073 let mut qvel = vec![0 as MjtNum; self.ffi().nv as usize];
1074 self.try_differentiate_pos_into(dt, qpos1, qpos2, &mut qvel)?;
1075 Ok(qvel.into_boxed_slice())
1076 }
1077
1078 pub fn differentiate_pos_into(&self, dt: MjtNum, qpos1: &[MjtNum], qpos2: &[MjtNum], qvel: &mut [MjtNum]) {
1089 self.try_differentiate_pos_into(dt, qpos1, qpos2, qvel).unwrap()
1090 }
1091
1092 pub fn try_differentiate_pos_into(&self, dt: MjtNum, qpos1: &[MjtNum], qpos2: &[MjtNum], qvel: &mut [MjtNum]) -> Result<(), MjModelError> {
1102 let nq = self.ffi().nq as usize;
1103 if qpos1.len() != nq {
1104 return Err(MjModelError::LengthMismatch { name: "qpos1", expected: nq, got: qpos1.len() });
1105 }
1106
1107 if qpos2.len() != nq {
1108 return Err(MjModelError::LengthMismatch { name: "qpos2", expected: nq, got: qpos2.len() });
1109 }
1110
1111 let nv = self.ffi().nv as usize;
1112 if qvel.len() < nv {
1113 return Err(MjModelError::BufferTooSmall { needed: nv, available: qvel.len() });
1114 }
1115
1116 unsafe {
1119 mj_differentiatePos(
1120 self.ffi(),
1121 qvel.as_mut_ptr(), dt,
1122 qpos1.as_ptr(), qpos2.as_ptr()
1123 )
1124 };
1125
1126 Ok(())
1127 }
1128
1129 pub fn integrate_pos(&self, qpos: &mut [MjtNum], qvel: &[MjtNum], dt: MjtNum) {
1136 assert_eq!(qpos.len(), self.ffi().nq as usize, "qpos must hold nq elements");
1138 assert_eq!(qvel.len(), self.ffi().nv as usize, "qvel must hold nv elements");
1139
1140 unsafe { mj_integratePos(self.ffi(), qpos.as_mut_ptr(), qvel.as_ptr(), dt) }
1143 }
1144
1145 pub fn normalize_quat(&self, qpos: &mut [MjtNum]) {
1151 assert_eq!(qpos.len(), self.ffi().nq as usize, "qpos must hold nq elements");
1153
1154 unsafe { mj_normalizeQuat(self.ffi(), qpos.as_mut_ptr()) }
1157 }
1158
1159 pub fn is_pyramidal(&self) -> bool {
1162 unsafe { mj_isPyramidal(self.ffi()) == 1 }
1163 }
1164
1165 pub fn is_sparse(&self) -> bool {
1168 unsafe { mj_isSparse(self.ffi()) == 1 }
1169 }
1170
1171 pub fn is_dual(&self) -> bool {
1175 unsafe { mj_isDual(self.ffi()) == 1 }
1176 }
1177
1178 pub fn id_to_name(&self, type_: MjtObj, id: usize) -> Option<&str> {
1183 let ptr = unsafe { mj_id2name(self.ffi(), type_ as i32, id as i32) };
1184 if ptr.is_null() {
1185 None
1186 }
1187 else {
1188 let cstr = unsafe { CStr::from_ptr(ptr).to_str().unwrap() };
1191 Some(cstr)
1192 }
1193 }
1194
1195 pub fn totalmass(&self) -> MjtNum {
1198 unsafe { mj_getTotalmass(self.ffi()) }
1199 }
1200
1201 pub fn set_totalmass(&mut self, newmass: MjtNum) {
1204 unsafe { mj_setTotalmass(self.ffi_mut(), newmass) }
1205 }
1206
1207 pub fn max_contacts(&self, geom1: usize, geom2: usize, has_margin: Option<bool>) -> u32 {
1217 self.try_max_contacts(geom1, geom2, has_margin).unwrap()
1218 }
1219
1220 pub fn try_max_contacts(&self, geom1: usize, geom2: usize, has_margin: Option<bool>) -> Result<u32, MjModelError> {
1226 let ngeom = self.ngeom() as usize;
1227
1228 if geom1 >= ngeom {
1229 return Err(MjModelError::IndexOutOfBounds { id: geom1, len: ngeom });
1230 }
1231
1232 if geom2 >= ngeom {
1233 return Err(MjModelError::IndexOutOfBounds { id: geom2, len: ngeom });
1234 }
1235
1236 Ok(unsafe { mj_maxContact(
1237 self.ffi(),
1238 geom1 as i32, geom2 as i32,
1239 has_margin.map(|m| m as i32).unwrap_or(-1)
1241 ) as u32 })
1242 }
1243
1244 pub fn actuator_input_name(&self, id: usize, input: usize) -> Option<&'static str> {
1254 unsafe {
1257 let c_ptr = mj_actuatorInputName(self.ffi(), id as i32, input as i32);
1258 (!c_ptr.is_null()).then(|| CStr::from_ptr(c_ptr).to_str().unwrap())
1259 }
1260 }
1261
1262 pub fn ffi(&self) -> &mjModel {
1265 unsafe { self.ptr.as_ref() }
1268 }
1269
1270 pub unsafe fn ffi_mut(&mut self) -> &mut mjModel {
1279 unsafe { self.ptr.as_mut() }
1280 }
1281
1282 #[cfg(feature = "cpp-viewer")]
1285 pub(crate) fn as_raw_ptr(&self) -> *mut mjModel {
1286 self.ptr.as_ptr()
1287 }
1288}
1289
1290
1291impl MjModel {
1293 pub fn signature(&self) -> u64 {
1295 self.ffi().signature
1296 }
1297
1298 pub fn is_compatible_with_model(&self, other: &MjModel) -> bool {
1300 self.layout() == other.layout()
1301 }
1302
1303 pub fn is_asset_compatible_with_model(&self, other: &MjModel) -> bool {
1305 self.layout().nmeshgraph == other.layout().nmeshgraph
1306 && self.layout().asset_split() == other.layout().asset_split()
1307 }
1308
1309 fn element_split_tables(&self) -> [&[u8]; ELEMENT_SPLIT_TABLES] {
1312 [
1313 must_cast_slice(self.sensor_dim()), must_cast_slice(self.numeric_size()),
1314 must_cast_slice(self.tuple_size()), must_cast_slice(self.actuator_actnum()),
1315 must_cast_slice(self.actuator_ctrlnum()), must_cast_slice(self.actuator_outnum()),
1316 must_cast_slice(self.ten_j_rownnz()),
1318 must_cast_slice(self.flex_dim()), must_cast_slice(self.flex_vertnum()),
1321 must_cast_slice(self.flex_elemnum()), must_cast_slice(self.plugin()),
1322 must_cast_slice(self.plugin_statenum()),
1324 must_cast_slice(self.jnt_type()),
1327 must_cast_slice(self.geom_type()), must_cast_slice(self.eq_type()),
1328 must_cast_slice(self.eq_objtype()), must_cast_slice(self.wrap_type()),
1329 must_cast_slice(self.actuator_trntype()), must_cast_slice(self.actuator_dyntype()),
1330 must_cast_slice(self.actuator_gaintype()), must_cast_slice(self.actuator_biastype()),
1331 must_cast_slice(self.sensor_type()), must_cast_slice(self.sensor_objtype()),
1332 must_cast_slice(self.sensor_reftype()), must_cast_slice(self.sensor_datatype()),
1333 must_cast_slice(self.sensor_needstage()),
1334 must_cast_slice(self.body_parentid()), must_cast_slice(self.jnt_bodyid()),
1337 must_cast_slice(self.geom_bodyid()), must_cast_slice(self.site_bodyid()),
1338 must_cast_slice(self.cam_bodyid()), must_cast_slice(self.light_bodyid()),
1339 ]
1340 }
1341
1342 fn asset_split_tables(&self) -> [&[u8]; ASSET_SPLIT_TABLES] {
1345 [
1346 must_cast_slice(self.mesh_vertnum()), must_cast_slice(self.mesh_normalnum()),
1347 must_cast_slice(self.mesh_texcoordnum()), must_cast_slice(self.mesh_facenum()),
1348 must_cast_slice(self.mesh_graphadr()),
1351 must_cast_slice(self.tex_width()), must_cast_slice(self.tex_height()),
1352 must_cast_slice(self.tex_nchannel()),
1353 must_cast_slice(self.tex_type()),
1357 must_cast_slice(self.hfield_nrow()), must_cast_slice(self.hfield_ncol()),
1358 ]
1359 }
1360
1361 fn split_tables(&self) -> MjSplitTables {
1363 MjSplitTables {
1364 assets: self.asset_split_tables().map(Box::from),
1365 elements: self.element_split_tables().map(Box::from),
1366 }
1367 }
1368
1369 pub(crate) fn layout(&self) -> &Arc<MjModelLayout> {
1371 self.layout.get_or_init(|| Arc::new(MjModelLayout::from(self)))
1372 }
1373
1374 getter_setter! {get, [
1375 [ffi] nq: MjtSize; "number of generalized coordinates = dim(qpos).";
1376 [ffi] nv: MjtSize; "number of degrees of freedom = dim(qvel).";
1377 [ffi] nu: MjtSize; "number of scalar controls = dim(ctrl).";
1378 [ffi] nactuator: MjtSize; "number of actuators.";
1379 [ffi] nout: MjtSize; "number of force outputs, derived from transmission type.";
1380 [ffi] na: MjtSize; "number of activation states = dim(act).";
1381 [ffi] nbody: MjtSize; "number of bodies.";
1382 [ffi] nbvh: MjtSize; "number of total bounding volumes in all bodies.";
1383 [ffi] nbvhstatic: MjtSize; "number of static bounding volumes (aabb stored in mjModel).";
1384 [ffi] nbvhdynamic: MjtSize; "number of dynamic bounding volumes (aabb stored in mjData).";
1385 [ffi] noct: MjtSize; "number of total octree cells in all meshes.";
1386 [ffi] njnt: MjtSize; "number of joints.";
1387 [ffi] ntree: MjtSize; "number of kinematic trees under world body.";
1388 [ffi] nM: MjtSize; "number of non-zeros in sparse inertia matrix.";
1389 [ffi] nB: MjtSize; "number of non-zeros in sparse body-dof matrix.";
1390 [ffi] nC: MjtSize; "number of non-zeros in sparse reduced dof-dof matrix.";
1391 [ffi] nD: MjtSize; "number of non-zeros in sparse dof-dof matrix.";
1392 [ffi] ngeom: MjtSize; "number of geoms.";
1393 [ffi] nsite: MjtSize; "number of sites.";
1394 [ffi] ncam: MjtSize; "number of cameras.";
1395 [ffi] nlight: MjtSize; "number of lights.";
1396 [ffi] nflex: MjtSize; "number of flexes.";
1397 [ffi] nflexnode: MjtSize; "number of dofs in all flexes.";
1398 [ffi] nflexvert: MjtSize; "number of vertices in all flexes.";
1399 [ffi] nflexedge: MjtSize; "number of edges in all flexes.";
1400 [ffi] nflexelem: MjtSize; "number of elements in all flexes.";
1401 [ffi] nflexelemdata: MjtSize; "number of element vertex ids in all flexes.";
1402 [ffi] nflexstiffness: MjtSize; "number of stiffness parameters in all flexes.";
1403 [ffi] nflexbending: MjtSize; "number of bending parameters in all flexes";
1404 [ffi] nefm0dof: MjtSize; "number of dofs covered by the constant metric factor.";
1405 [ffi] nefm0L: MjtSize; "number of non-zeros in the constant metric factor.";
1406 [ffi] nflexelemedge: MjtSize; "number of element edge ids in all flexes.";
1407 [ffi] nflexshelldata: MjtSize; "number of shell fragment vertex ids in all flexes.";
1408 [ffi] nflexevpair: MjtSize; "number of element-vertex pairs in all flexes.";
1409 [ffi] nflextexcoord: MjtSize; "number of vertices with texture coordinates.";
1410 [ffi] nJfe: MjtSize; "number of non-zeros in sparse flexedge Jacobian matrix.";
1411 [ffi] nJfv: MjtSize; "number of non-zeros in sparse flexvert Jacobian matrix.";
1412 [ffi] nmesh: MjtSize; "number of meshes.";
1413 [ffi] nmeshvert: MjtSize; "number of vertices in all meshes.";
1414 [ffi] nmeshnormal: MjtSize; "number of normals in all meshes.";
1415 [ffi] nmeshtexcoord: MjtSize; "number of texcoords in all meshes.";
1416 [ffi] nmeshface: MjtSize; "number of triangular faces in all meshes.";
1417 [ffi] nmeshgraph: MjtSize; "number of ints in mesh auxiliary data.";
1418 [ffi] nmeshpoly: MjtSize; "number of polygons in all meshes.";
1419 [ffi] nmeshpolyvert: MjtSize; "number of vertices in all polygons.";
1420 [ffi] nmeshpolymap: MjtSize; "number of polygons in vertex map.";
1421 [ffi] nskin: MjtSize; "number of skins.";
1422 [ffi] nskinvert: MjtSize; "number of vertices in all skins.";
1423 [ffi] nskintexvert: MjtSize; "number of vertices with texcoords in all skins.";
1424 [ffi] nskinface: MjtSize; "number of triangular faces in all skins.";
1425 [ffi] nskinbone: MjtSize; "number of bones in all skins.";
1426 [ffi] nskinbonevert: MjtSize; "number of vertices in all skin bones.";
1427 [ffi] nhfield: MjtSize; "number of heightfields.";
1428 [ffi] nhfielddata: MjtSize; "number of data points in all heightfields.";
1429 [ffi] ntex: MjtSize; "number of textures.";
1430 [ffi] ntexdata: MjtSize; "number of bytes in texture rgb data.";
1431 [ffi] nmat: MjtSize; "number of materials.";
1432 [ffi] npair: MjtSize; "number of predefined geom pairs.";
1433 [ffi] nexclude: MjtSize; "number of excluded geom pairs.";
1434 [ffi] neq: MjtSize; "number of equality constraints.";
1435 [ffi] ntendon: MjtSize; "number of tendons.";
1436 [ffi] nJten: MjtSize; "number of non-zeros in sparse tendon Jacobian matrix.";
1437 [ffi] nwrap: MjtSize; "number of wrap objects in all tendon paths.";
1438 [ffi] nsensor: MjtSize; "number of sensors.";
1439 [ffi] nnumeric: MjtSize; "number of numeric custom fields.";
1440 [ffi] nnumericdata: MjtSize; "number of mjtNums in all numeric fields.";
1441 [ffi] ntext: MjtSize; "number of text custom fields.";
1442 [ffi] ntextdata: MjtSize; "number of mjtBytes in all text fields.";
1443 [ffi] ntuple: MjtSize; "number of tuple custom fields.";
1444 [ffi] ntupledata: MjtSize; "number of objects in all tuple fields.";
1445 [ffi] nkey: MjtSize; "number of keyframes.";
1446 [ffi] nmocap: MjtSize; "number of mocap bodies.";
1447 [ffi] nplugin: MjtSize; "number of plugin instances.";
1448 [ffi] npluginattr: MjtSize; "number of chars in all plugin config attributes.";
1449 [ffi] nuser_body: MjtSize; "number of mjtNums in body_user.";
1450 [ffi] nuser_jnt: MjtSize; "number of mjtNums in jnt_user.";
1451 [ffi] nuser_geom: MjtSize; "number of mjtNums in geom_user.";
1452 [ffi] nuser_site: MjtSize; "number of mjtNums in site_user.";
1453 [ffi] nuser_cam: MjtSize; "number of mjtNums in cam_user.";
1454 [ffi] nuser_tendon: MjtSize; "number of mjtNums in tendon_user.";
1455 [ffi] nuser_actuator: MjtSize; "number of mjtNums in actuator_user.";
1456 [ffi] nuser_sensor: MjtSize; "number of mjtNums in sensor_user.";
1457 [ffi] nnames: MjtSize; "number of chars in all names.";
1458 [ffi] npaths: MjtSize; "number of chars in all paths.";
1459 [ffi] nnames_map: MjtSize; "number of slots in the names hash map.";
1460 [ffi] nJmom: MjtSize; "number of non-zeros in sparse actuator_moment matrix.";
1461 [ffi] ngravcomp: MjtSize; "number of bodies with nonzero gravcomp.";
1462 [ffi] nemax: MjtSize; "number of potential equality-constraint rows.";
1463 [ffi] njmax: MjtSize; "number of available rows in constraint Jacobian (legacy).";
1464 [ffi] nconmax: MjtSize; "number of potential contacts in contact list (legacy).";
1465 [ffi] npolygonmax: MjtSize; "maximum number of vertices in a mesh polygon.";
1466 [ffi] nmeshdegmax: MjtSize; "maximum number of edges adjacent to a mesh vertex.";
1467 [ffi] nuserdata: MjtSize; "number of mjtNums reserved for the user.";
1468 [ffi] nsensordata: MjtSize; "number of mjtNums in sensor data vector.";
1469 [ffi] npluginstate: MjtSize; "number of mjtNums in plugin state vector.";
1470 [ffi] nhistory: MjtSize; "number of mjtNums in history buffer.";
1471 [ffi] narena: MjtSize; "number of bytes in the mjData arena (inclusive of stack).";
1472 [ffi] nbuffer: MjtSize; "number of bytes in buffer.";
1473 [ffi] flg_gravcomp: MjtBool; "whether any body has nonzero gravcomp.";
1474 [ffi] flg_surfacevel: MjtBool; "whether any geom has nonzero surfacevel.";
1475 [ffi] flg_adhesion: MjtBool; "whether any geom or pair has nonzero adhesion.";
1476 ]}
1477
1478 getter_setter! {get, [
1479 [ffi, ffi_mut] opt: &MjOption; "physics options.";
1480 [ffi, ffi_mut] vis: &MjVisual; "visualization options.";
1481 [ffi, ffi_mut] stat: &MjStatistic; "model statistics.";
1482 ]}
1483}
1484
1485impl MjModel {
1487 array_slice_dyn! {
1488 probe = probe_dynamic_arrays;
1489 qpos0: &[MjtNum; "qpos values at default pose"; ffi().nq],
1490 qpos_spring: &[MjtNum; "reference pose for springs"; ffi().nq],
1491 (mut = unsafe) body_parentid: &[i32; "id of body's parent"; ffi().nbody],
1492 (mut = unsafe) body_rootid: &[i32; "ancestor that is direct child of world"; ffi().nbody],
1493 (mut = unsafe) body_weldid: &[i32; "top dof-less ancestor; mocap: own root"; ffi().nbody],
1494 (mut = unsafe) body_mocapid: &[i32; "id of mocap data; -1: none"; ffi().nbody],
1495 (mut = unsafe) body_jntnum: &[i32; "number of joints for this body"; ffi().nbody],
1496 (mut = unsafe) body_jntadr: &[i32; "start addr of joints; -1: no joints"; ffi().nbody],
1497 (mut = unsafe) body_dofnum: &[i32; "number of motion degrees of freedom"; ffi().nbody],
1498 (mut = unsafe) body_dofadr: &[i32; "start addr of dofs; -1: no dofs"; ffi().nbody],
1499 (mut = unsafe) body_treeid: &[i32; "id of body's kinematic tree; -1: static"; ffi().nbody],
1500 (mut = unsafe) body_geomnum: &[i32; "number of geoms"; ffi().nbody],
1501 (mut = unsafe) body_geomadr: &[i32; "start addr of geoms; -1: no geoms"; ffi().nbody],
1502 body_simple: &[MjtByte; "1: diag M; 2: diag M, sliders only"; ffi().nbody],
1503 body_sameframe: &[MjtSameFrame [force]; "same frame as inertia"; ffi().nbody],
1504 body_pos: &[[MjtNum; 3] [force]; "position offset rel. to parent body"; ffi().nbody],
1505 body_quat: &[[MjtNum; 4] [force]; "orientation offset rel. to parent body"; ffi().nbody],
1506 body_ipos: &[[MjtNum; 3] [force]; "local position of center of mass"; ffi().nbody],
1507 body_iquat: &[[MjtNum; 4] [force]; "local orientation of inertia ellipsoid"; ffi().nbody],
1508 body_mass: &[MjtNum; "mass"; ffi().nbody],
1509 body_subtreemass: &[MjtNum; "mass of subtree starting at this body"; ffi().nbody],
1510 body_inertia: &[[MjtNum; 3] [force]; "diagonal inertia in ipos/iquat frame"; ffi().nbody],
1511 body_invweight0: &[[MjtNum; 2] [force]; "mean inv inert in qpos0 (trn, rot)"; ffi().nbody],
1512 body_gravcomp: &[MjtNum; "antigravity force, units of body weight"; ffi().nbody],
1513 body_margin: &[MjtNum; "MAX over all geom margins+gaps"; ffi().nbody],
1514 (mut = unsafe) body_plugin: &[i32; "plugin instance id; -1: not in use"; ffi().nbody],
1515 body_contype: &[i32; "OR over all geom contypes"; ffi().nbody],
1516 body_conaffinity: &[i32; "OR over all geom conaffinities"; ffi().nbody],
1517 (mut = unsafe) body_bvhadr: &[i32; "address of bvh root"; ffi().nbody],
1518 (mut = unsafe) body_bvhnum: &[i32; "number of bounding volumes"; ffi().nbody],
1519 bvh_depth: &[i32; "depth in the bounding volume hierarchy"; ffi().nbvh],
1520 (mut = unsafe) bvh_child: &[[i32; 2] [force]; "left and right children in tree"; ffi().nbvh],
1521 (mut = unsafe) bvh_nodeid: &[i32; "geom or elem id of node; -1: non-leaf"; ffi().nbvh],
1522 bvh_aabb: &[[MjtNum; 6] [force]; "local bounding box (center, size)"; ffi().nbvhstatic],
1523 oct_depth: &[i32; "depth in the octree"; ffi().noct],
1524 (mut = unsafe) oct_child: &[[i32; 8] [force]; "children of octree node"; ffi().noct],
1525 oct_aabb: &[[MjtNum; 6] [force]; "octree node bounding box (center, size)"; ffi().noct],
1526 oct_coeff: &[[MjtNum; 8] [force]; "octree interpolation coefficients"; ffi().noct],
1527 (mut = unsafe) jnt_type: &[MjtJoint [force]; "type of joint"; ffi().njnt],
1528 (mut = unsafe) jnt_qposadr: &[i32; "start addr in 'qpos' for joint's data"; ffi().njnt],
1529 (mut = unsafe) jnt_dofadr: &[i32; "start addr in 'qvel' for joint's data"; ffi().njnt],
1530 (mut = unsafe) jnt_bodyid: &[i32; "id of joint's body"; ffi().njnt],
1531 (mut = unsafe) jnt_actuatorid: &[i32; "actuator contributing damping / armature"; ffi().njnt],
1532 jnt_group: &[i32; "group for visibility"; ffi().njnt],
1533 jnt_limited: &[MjtBool; "does joint have limits"; ffi().njnt],
1534 jnt_actfrclimited: &[MjtBool; "does joint have actuator force limits"; ffi().njnt],
1535 jnt_actgravcomp: &[MjtBool; "is gravcomp force applied via actuators"; ffi().njnt],
1536 jnt_solref: &[[MjtNum; mjNREF as usize] [force]; "constraint solver reference: limit"; ffi().njnt],
1537 jnt_solimp: &[[MjtNum; mjNIMP as usize] [force]; "constraint solver impedance: limit"; ffi().njnt],
1538 jnt_pos: &[[MjtNum; 3] [force]; "local anchor position"; ffi().njnt],
1539 jnt_axis: &[[MjtNum; 3] [force]; "local joint axis"; ffi().njnt],
1540 jnt_stiffness: &[MjtNum; "linear stiffness coefficient"; ffi().njnt],
1541 jnt_stiffnesspoly: &[[MjtNum; mjNPOLY as usize] [force]; "high-order stiffness coefficients"; ffi().njnt],
1542 jnt_range: &[[MjtNum; 2] [force]; "joint limits"; ffi().njnt],
1543 jnt_actfrcrange: &[[MjtNum; 2] [force]; "range of total actuator force"; ffi().njnt],
1544 jnt_margin: &[MjtNum; "min distance for limit detection"; ffi().njnt],
1545 (mut = unsafe) dof_bodyid: &[i32; "id of dof's body"; ffi().nv],
1546 (mut = unsafe) dof_jntid: &[i32; "id of dof's joint"; ffi().nv],
1547 (mut = unsafe) dof_parentid: &[i32; "id of dof's parent; -1: none"; ffi().nv],
1548 (mut = unsafe) dof_treeid: &[i32; "id of dof's kinematic tree"; ffi().nv],
1549 (mut = unsafe) dof_Madr: &[i32; "dof address in M-diagonal"; ffi().nv],
1550 dof_simplenum: &[i32; "number of consecutive simple dofs"; ffi().nv],
1551 dof_solref: &[[MjtNum; mjNREF as usize] [force]; "constraint solver reference:frictionloss"; ffi().nv],
1552 dof_solimp: &[[MjtNum; mjNIMP as usize] [force]; "constraint solver impedance:frictionloss"; ffi().nv],
1553 dof_frictionloss: &[MjtNum; "dof friction loss"; ffi().nv],
1554 dof_armature: &[MjtNum; "dof armature inertia/mass"; ffi().nv],
1555 dof_damping: &[MjtNum; "linear damping coefficient"; ffi().nv],
1556 dof_dampingpoly: &[[MjtNum; mjNPOLY as usize] [force]; "high-order damping coefficients"; ffi().nv],
1557 dof_invweight0: &[MjtNum; "diag. inverse inertia in qpos0"; ffi().nv],
1558 dof_M0: &[MjtNum; "diag. inertia in qpos0"; ffi().nv],
1559 dof_length: &[MjtNum; "linear: 1; angular: approx. length scale"; ffi().nv],
1560 (mut = unsafe) tree_bodyadr: &[i32; "start addr of bodies"; ffi().ntree],
1561 (mut = unsafe) tree_bodynum: &[i32; "number of bodies in tree"; ffi().ntree],
1562 (mut = unsafe) tree_dofadr: &[i32; "start addr of dofs"; ffi().ntree],
1563 (mut = unsafe) tree_dofnum: &[i32; "number of dofs in tree"; ffi().ntree],
1564 tree_sleep_policy: &[MjtSleepPolicy [force]; "sleep policy"; ffi().ntree],
1565 (mut = unsafe) geom_type: &[MjtGeom [force]; "geometric type"; ffi().ngeom],
1566 geom_contype: &[i32; "geom contact type"; ffi().ngeom],
1567 geom_conaffinity: &[i32; "geom contact affinity"; ffi().ngeom],
1568 (mut = unsafe) geom_condim: &[i32; "contact dimensionality (1, 3, 4, 6)"; ffi().ngeom],
1569 (mut = unsafe) geom_bodyid: &[i32; "id of geom's body"; ffi().ngeom],
1570 (mut = unsafe) geom_dataid: &[i32; "id of geom's mesh/hfield; -1: none"; ffi().ngeom],
1571 (mut = unsafe) geom_matid: &[i32; "material id for rendering; -1: none"; ffi().ngeom],
1572 geom_group: &[i32; "group for visibility"; ffi().ngeom],
1573 geom_priority: &[i32; "geom contact priority"; ffi().ngeom],
1574 (mut = unsafe) geom_plugin: &[i32; "plugin instance id; -1: not in use"; ffi().ngeom],
1575 geom_sameframe: &[MjtSameFrame [force]; "same frame as body"; ffi().ngeom],
1576 geom_solmix: &[MjtNum; "mixing coef for solref/imp in geom pair"; ffi().ngeom],
1577 geom_solref: &[[MjtNum; mjNREF as usize] [force]; "constraint solver reference: contact"; ffi().ngeom],
1578 geom_solimp: &[[MjtNum; mjNIMP as usize] [force]; "constraint solver impedance: contact"; ffi().ngeom],
1579 geom_size: &[[MjtNum; 3] [force]; "geom-specific size parameters"; ffi().ngeom],
1580 geom_aabb: &[[MjtNum; 6] [force]; "bounding box, (center, size)"; ffi().ngeom],
1581 geom_rbound: &[MjtNum; "radius of bounding sphere"; ffi().ngeom],
1582 geom_pos: &[[MjtNum; 3] [force]; "local position offset rel. to body"; ffi().ngeom],
1583 geom_quat: &[[MjtNum; 4] [force]; "local orientation offset rel. to body"; ffi().ngeom],
1584 geom_friction: &[[MjtNum; 3] [force]; "friction for (slide, spin, roll)"; ffi().ngeom],
1585 geom_margin: &[MjtNum; "geometric inflation for contact"; ffi().ngeom],
1586 geom_gap: &[MjtNum; "additional contact detection buffer"; ffi().ngeom],
1587 geom_surfacevel: &[[MjtNum; 6] [force]; "surface velocity in local frame: lin,ang"; ffi().ngeom],
1588 geom_adhesion: &[MjtNum; "adhesive force of contacts"; ffi().ngeom],
1589 geom_fluid: &[[MjtNum; mjNFLUID as usize] [force]; "fluid interaction parameters"; ffi().ngeom],
1590 geom_rgba: &[[f32; 4] [force]; "rgba when material is omitted"; ffi().ngeom],
1591 site_type: &[MjtGeom [force]; "geom type for rendering"; ffi().nsite],
1592 (mut = unsafe) site_bodyid: &[i32; "id of site's body"; ffi().nsite],
1593 (mut = unsafe) site_matid: &[i32; "material id for rendering; -1: none"; ffi().nsite],
1594 site_group: &[i32; "group for visibility"; ffi().nsite],
1595 site_sameframe: &[MjtSameFrame [force]; "same frame as body"; ffi().nsite],
1596 site_size: &[[MjtNum; 3] [force]; "geom size for rendering"; ffi().nsite],
1597 site_pos: &[[MjtNum; 3] [force]; "local position offset rel. to body"; ffi().nsite],
1598 site_quat: &[[MjtNum; 4] [force]; "local orientation offset rel. to body"; ffi().nsite],
1599 site_rgba: &[[f32; 4] [force]; "rgba when material is omitted"; ffi().nsite],
1600 cam_mode: &[MjtCamLight [force]; "camera tracking mode"; ffi().ncam],
1601 (mut = unsafe) cam_bodyid: &[i32; "id of camera's body"; ffi().ncam],
1602 (mut = unsafe) cam_targetbodyid: &[i32; "id of targeted body; -1: none"; ffi().ncam],
1603 cam_pos: &[[MjtNum; 3] [force]; "position rel. to body frame"; ffi().ncam],
1604 cam_quat: &[[MjtNum; 4] [force]; "orientation rel. to body frame"; ffi().ncam],
1605 cam_poscom0: &[[MjtNum; 3] [force]; "global position rel. to sub-com in qpos0"; ffi().ncam],
1606 cam_pos0: &[[MjtNum; 3] [force]; "global position rel. to body in qpos0"; ffi().ncam],
1607 cam_mat0: &[[MjtNum; 9] [force]; "global orientation in qpos0"; ffi().ncam],
1608 cam_projection: &[MjtProjection [force]; "projection type"; ffi().ncam],
1609 cam_fovy: &[MjtNum; "y field-of-view (ortho ? len : deg)"; ffi().ncam],
1610 cam_ipd: &[MjtNum; "inter-pupillary distance"; ffi().ncam],
1611 (mut = unsafe) cam_resolution: &[[i32; 2] [force]; "resolution: pixels [width, height]"; ffi().ncam],
1612 cam_output: &[i32; "output types (MjtCamOutBit bit flags)"; ffi().ncam],
1613 cam_sensorsize: &[[f32; 2] [force]; "sensor size: length [width, height]"; ffi().ncam],
1614 cam_intrinsic: &[[f32; 4] [force]; "[focal length; principal point]"; ffi().ncam],
1615 light_mode: &[MjtCamLight [force]; "light tracking mode"; ffi().nlight],
1616 (mut = unsafe) light_bodyid: &[i32; "id of light's body"; ffi().nlight],
1617 (mut = unsafe) light_targetbodyid: &[i32; "id of targeted body; -1: none"; ffi().nlight],
1618 light_type: &[MjtLightType [force]; "spot, directional, etc."; ffi().nlight],
1619 (mut = unsafe) light_texid: &[i32; "texture id for image lights"; ffi().nlight],
1620 light_castshadow: &[MjtBool; "does light cast shadows"; ffi().nlight],
1621 light_bulbradius: &[f32; "light radius for soft shadows"; ffi().nlight],
1622 light_intensity: &[f32; "intensity, in candela"; ffi().nlight],
1623 light_range: &[f32; "range of effectiveness"; ffi().nlight],
1624 light_active: &[MjtBool; "is light on"; ffi().nlight],
1625 light_pos: &[[MjtNum; 3] [force]; "position rel. to body frame"; ffi().nlight],
1626 light_dir: &[[MjtNum; 3] [force]; "direction rel. to body frame"; ffi().nlight],
1627 light_poscom0: &[[MjtNum; 3] [force]; "global position rel. to sub-com in qpos0"; ffi().nlight],
1628 light_pos0: &[[MjtNum; 3] [force]; "global position rel. to body in qpos0"; ffi().nlight],
1629 light_dir0: &[[MjtNum; 3] [force]; "global direction in qpos0"; ffi().nlight],
1630 light_attenuation: &[[f32; 3] [force]; "OpenGL attenuation (quadratic model)"; ffi().nlight],
1631 light_cutoff: &[f32; "OpenGL cutoff"; ffi().nlight],
1632 light_softness: &[f32; "spotlight edge softness"; ffi().nlight],
1633 light_exponent: &[f32; "OpenGL exponent"; ffi().nlight],
1634 light_ambient: &[[f32; 3] [force]; "ambient rgb (alpha=1)"; ffi().nlight],
1635 light_diffuse: &[[f32; 3] [force]; "diffuse rgb (alpha=1)"; ffi().nlight],
1636 light_specular: &[[f32; 3] [force]; "specular rgb (alpha=1)"; ffi().nlight],
1637 flex_contype: &[i32; "flex contact type"; ffi().nflex],
1638 flex_conaffinity: &[i32; "flex contact affinity"; ffi().nflex],
1639 (mut = unsafe) flex_condim: &[i32; "contact dimensionality (1, 3, 4, 6)"; ffi().nflex],
1640 flex_priority: &[i32; "flex contact priority"; ffi().nflex],
1641 flex_solmix: &[MjtNum; "mix coef for solref/imp in contact pair"; ffi().nflex],
1642 flex_solref: &[[MjtNum; mjNREF as usize] [force]; "constraint solver reference: contact"; ffi().nflex],
1643 flex_solimp: &[[MjtNum; mjNIMP as usize] [force]; "constraint solver impedance: contact"; ffi().nflex],
1644 flex_friction: &[[MjtNum; 3] [force]; "friction for (slide, spin, roll)"; ffi().nflex],
1645 flex_margin: &[MjtNum; "geometric inflation for contact"; ffi().nflex],
1646 flex_gap: &[MjtNum; "additional contact detection buffer"; ffi().nflex],
1647 flex_internal: &[MjtBool; "internal flex collision enabled"; ffi().nflex],
1648 flex_selfcollide: &[MjtFlexSelf [force]; "self collision mode"; ffi().nflex],
1649 flex_activelayers: &[i32; "number of active element layers, 3D only"; ffi().nflex],
1650 flex_passive: &[i32; "passive collisions enabled"; ffi().nflex],
1651 (mut = unsafe) flex_dim: &[i32; "1: lines, 2: triangles, 3: tetrahedra"; ffi().nflex],
1652 (mut = unsafe) flex_matid: &[i32; "material id for rendering"; ffi().nflex],
1653 flex_group: &[i32; "group for visibility"; ffi().nflex],
1654 (mut = unsafe) flex_interp: &[i32; "interpolation (0: vertex, 1: nodes)"; ffi().nflex],
1655 (mut = unsafe) flex_cellnum: &[[i32; 3] [force]; "finite cell num per dimension"; ffi().nflex],
1656 (mut = unsafe) flex_nodeadr: &[i32; "first node address"; ffi().nflex],
1657 (mut = unsafe) flex_nodenum: &[i32; "number of nodes"; ffi().nflex],
1658 (mut = unsafe) flex_vertadr: &[i32; "first vertex address"; ffi().nflex],
1659 (mut = unsafe) flex_vertnum: &[i32; "number of vertices"; ffi().nflex],
1660 (mut = unsafe) flex_edgeadr: &[i32; "first edge address"; ffi().nflex],
1661 (mut = unsafe) flex_edgenum: &[i32; "number of edges"; ffi().nflex],
1662 (mut = unsafe) flex_elemadr: &[i32; "first element address"; ffi().nflex],
1663 (mut = unsafe) flex_elemnum: &[i32; "number of elements"; ffi().nflex],
1664 (mut = unsafe) flex_elemdataadr: &[i32; "first element vertex id address"; ffi().nflex],
1665 (mut = unsafe) flex_stiffnessadr: &[i32; "stiffness matrix address"; ffi().nflex],
1666 (mut = unsafe) flex_elemedgeadr: &[i32; "first element edge id address"; ffi().nflex],
1667 (mut = unsafe) flex_bendingadr: &[i32; "first bending data address"; ffi().nflex],
1668 (mut = unsafe) flex_shellnum: &[i32; "number of shells"; ffi().nflex],
1669 (mut = unsafe) flex_shelldataadr: &[i32; "first shell data address"; ffi().nflex],
1670 (mut = unsafe) flex_evpairadr: &[i32; "first evpair address"; ffi().nflex],
1671 (mut = unsafe) flex_evpairnum: &[i32; "number of evpairs"; ffi().nflex],
1672 (mut = unsafe) flex_texcoordadr: &[i32; "address in flex_texcoord; -1: none"; ffi().nflex],
1673 (mut = unsafe) flex_nodebodyid: &[i32; "node body ids"; ffi().nflexnode],
1674 (mut = unsafe) flex_vertbodyid: &[i32; "vertex body ids"; ffi().nflexvert],
1675 (mut = unsafe) flex_vertedgeadr: &[i32; "first edge address"; ffi().nflexvert],
1676 (mut = unsafe) flex_vertedgenum: &[i32; "number of edges"; ffi().nflexvert],
1677 (mut = unsafe) flex_vertedge: &[[i32; 2] [force]; "edge indices"; ffi().nflexedge],
1678 (mut = unsafe) flex_edge: &[[i32; 2] [force]; "edge vertex ids (2 per edge)"; ffi().nflexedge],
1679 (mut = unsafe) flex_edgeflap: &[[i32; 2] [force]; "adjacent vertex ids (dim=2 only)"; ffi().nflexedge],
1680 (mut = unsafe) flex_elem: &[i32; "element vertex ids (dim+1 per elem)"; ffi().nflexelemdata],
1681 (mut = unsafe) flex_elemtexcoord: &[i32; "element texture coordinates (dim+1)"; ffi().nflexelemdata],
1682 (mut = unsafe) flex_elemedge: &[i32; "element edge ids"; ffi().nflexelemedge],
1683 (mut = unsafe) flex_elemlayer: &[i32; "element distance from surface, 3D only"; ffi().nflexelem],
1684 (mut = unsafe) flex_shell: &[i32; "shell fragment vertex ids (dim per frag)"; ffi().nflexshelldata],
1685 (mut = unsafe) flex_evpair: &[[i32; 2] [force]; "(element, vertex) collision pairs"; ffi().nflexevpair],
1686 flex_vert: &[[MjtNum; 3] [force]; "vertex positions in local body frames"; ffi().nflexvert],
1687 flex_vert0: &[[MjtNum; 3] [force]; "vertex positions in qpos0 on [0, 1]^d"; ffi().nflexvert],
1688 flex_vertmetric: &[[MjtNum; 4] [force]; "inverse of reference shape matrix"; ffi().nflexvert],
1689 flex_node: &[[MjtNum; 3] [force]; "node positions in local body frames"; ffi().nflexnode],
1690 flex_node0: &[[MjtNum; 3] [force]; "Cartesian node positions in qpos0"; ffi().nflexnode],
1691 flexedge_length0: &[MjtNum; "edge lengths in qpos0"; ffi().nflexedge],
1692 flexedge_invweight0: &[MjtNum; "edge inv. weight in qpos0"; ffi().nflexedge],
1693 flex_radius: &[MjtNum; "radius around primitive element"; ffi().nflex],
1694 flex_size: &[[MjtNum; 3] [force]; "vertex bounding box half sizes in qpos0"; ffi().nflex],
1695 flex_stiffness: &[MjtNum; "finite element stiffness matrix"; ffi().nflexstiffness],
1696 flex_bending: &[MjtNum; "bending stiffness"; ffi().nflexbending],
1697 (mut = unsafe) efm0_dofid: &[i32; "constant metric factor row->dof address"; ffi().nefm0dof],
1698 (mut = unsafe) efm0_L_rownnz: &[i32; "constant metric factor row nonzeros"; ffi().nefm0dof],
1699 (mut = unsafe) efm0_L_rowadr: &[i32; "constant metric factor row addresses"; ffi().nefm0dof],
1700 (mut = unsafe) efm0_L_colind: &[i32; "constant metric factor column indices"; ffi().nefm0L],
1701 efm0_L: &[MjtNum; "factor of M + (dt^2+dt*d)*K_bend"; ffi().nefm0L],
1702 flex_damping: &[MjtNum; "Rayleigh's damping coefficient"; ffi().nflex],
1703 flex_edgestiffness: &[MjtNum; "edge stiffness"; ffi().nflex],
1704 flex_edgedamping: &[MjtNum; "edge damping"; ffi().nflex],
1705 flex_edgeequality: &[i32; "0: none, 1: edges, 2: vertices, 3: strain"; ffi().nflex],
1706 flex_rigid: &[MjtBool; "are all vertices in the same body"; ffi().nflex],
1707 flexedge_rigid: &[MjtBool; "are both edge vertices in same body"; ffi().nflexedge],
1708 flex_centered: &[MjtBool; "are all vertex coordinates (0,0,0)"; ffi().nflex],
1709 flex_flatskin: &[MjtBool; "render flex skin with flat shading"; ffi().nflex],
1710 (mut = unsafe) flex_bvhadr: &[i32; "address of bvh root; -1: no bvh"; ffi().nflex],
1711 (mut = unsafe) flex_bvhnum: &[i32; "number of bounding volumes"; ffi().nflex],
1712 (mut = unsafe) flexedge_J_rownnz: &[i32; "number of non-zeros in Jacobian row"; ffi().nflexedge],
1713 (mut = unsafe) flexedge_J_rowadr: &[i32; "row start address in colind array"; ffi().nflexedge],
1714 (mut = unsafe) flexedge_J_colind: &[i32; "column indices in sparse Jacobian"; ffi().nJfe],
1715 (mut = unsafe) flexvert_J_rownnz: &[[i32; 2] [force]; "number of non-zeros in Jacobian row"; ffi().nflexvert],
1716 (mut = unsafe) flexvert_J_rowadr: &[[i32; 2] [force]; "row start address in colind array"; ffi().nflexvert],
1717 (mut = unsafe) flexvert_J_colind: &[[i32; 2] [force]; "column indices in sparse Jacobian"; ffi().nJfv],
1718 flex_rgba: &[[f32; 4] [force]; "rgba when material is omitted"; ffi().nflex],
1719 flex_texcoord: &[[f32; 2] [force]; "vertex texture coordinates"; ffi().nflextexcoord],
1720 (mut = unsafe) mesh_vertadr: &[i32; "first vertex address"; ffi().nmesh],
1721 (mut = unsafe) mesh_vertnum: &[i32; "number of vertices"; ffi().nmesh],
1722 (mut = unsafe) mesh_faceadr: &[i32; "first face address"; ffi().nmesh],
1723 (mut = unsafe) mesh_facenum: &[i32; "number of faces"; ffi().nmesh],
1724 (mut = unsafe) mesh_bvhadr: &[i32; "address of bvh root"; ffi().nmesh],
1725 (mut = unsafe) mesh_bvhnum: &[i32; "number of bvh"; ffi().nmesh],
1726 (mut = unsafe) mesh_octadr: &[i32; "address of octree root"; ffi().nmesh],
1727 (mut = unsafe) mesh_octnum: &[i32; "number of octree nodes"; ffi().nmesh],
1728 (mut = unsafe) mesh_normaladr: &[i32; "first normal address"; ffi().nmesh],
1729 (mut = unsafe) mesh_normalnum: &[i32; "number of normals"; ffi().nmesh],
1730 (mut = unsafe) mesh_texcoordadr: &[i32; "texcoord data address; -1: no texcoord"; ffi().nmesh],
1731 (mut = unsafe) mesh_texcoordnum: &[i32; "number of texcoord"; ffi().nmesh],
1732 (mut = unsafe) mesh_graphadr: &[i32; "graph data address; -1: no graph"; ffi().nmesh],
1733 (mut = unsafe) mesh_extrema: &[[i32; 27] [force]; "extremum vertices in 3x3x3 directions"; ffi().nmesh],
1734 mesh_vert: &[[f32; 3] [force]; "vertex positions for all meshes"; ffi().nmeshvert],
1735 mesh_normal: &[[f32; 3] [force]; "normals for all meshes"; ffi().nmeshnormal],
1736 mesh_texcoord: &[[f32; 2] [force]; "vertex texcoords for all meshes"; ffi().nmeshtexcoord],
1737 (mut = unsafe) mesh_face: &[[i32; 3] [force]; "vertex face data"; ffi().nmeshface],
1738 (mut = unsafe) mesh_facenormal: &[[i32; 3] [force]; "normal face data"; ffi().nmeshface],
1739 (mut = unsafe) mesh_facetexcoord: &[[i32; 3] [force]; "texture face data"; ffi().nmeshface],
1740 (mut = unsafe) mesh_graph: &[i32; "convex graph data"; ffi().nmeshgraph],
1741 mesh_scale: &[[MjtNum; 3] [force]; "scaling applied to asset vertices"; ffi().nmesh],
1742 mesh_pos: &[[MjtNum; 3] [force]; "translation applied to asset vertices"; ffi().nmesh],
1743 mesh_quat: &[[MjtNum; 4] [force]; "rotation applied to asset vertices"; ffi().nmesh],
1744 (mut = unsafe) mesh_pathadr: &[i32; "address of asset path for mesh; -1: none"; ffi().nmesh],
1745 (mut = unsafe) mesh_polynum: &[i32; "number of polygons per mesh"; ffi().nmesh],
1746 (mut = unsafe) mesh_polyadr: &[i32; "first polygon address per mesh"; ffi().nmesh],
1747 mesh_polynormal: &[[MjtNum; 3] [force]; "all polygon normals"; ffi().nmeshpoly],
1748 (mut = unsafe) mesh_polyvertadr: &[i32; "polygon vertex start address"; ffi().nmeshpoly],
1749 (mut = unsafe) mesh_polyvertnum: &[i32; "number of vertices per polygon"; ffi().nmeshpoly],
1750 (mut = unsafe) mesh_polyvert: &[i32; "all polygon vertices"; ffi().nmeshpolyvert],
1751 (mut = unsafe) mesh_polymapadr: &[i32; "first polygon address per vertex"; ffi().nmeshvert],
1752 (mut = unsafe) mesh_polymapnum: &[i32; "number of polygons per vertex"; ffi().nmeshvert],
1753 (mut = unsafe) mesh_polymap: &[i32; "vertex to polygon map"; ffi().nmeshpolymap],
1754 (mut = unsafe) skin_matid: &[i32; "skin material id; -1: none"; ffi().nskin],
1755 skin_group: &[i32; "group for visibility"; ffi().nskin],
1756 skin_rgba: &[[f32; 4] [force]; "skin rgba"; ffi().nskin],
1757 skin_inflate: &[f32; "inflate skin in normal direction"; ffi().nskin],
1758 (mut = unsafe) skin_vertadr: &[i32; "first vertex address"; ffi().nskin],
1759 (mut = unsafe) skin_vertnum: &[i32; "number of vertices"; ffi().nskin],
1760 (mut = unsafe) skin_texcoordadr: &[i32; "texcoord data address; -1: no texcoord"; ffi().nskin],
1761 (mut = unsafe) skin_faceadr: &[i32; "first face address"; ffi().nskin],
1762 (mut = unsafe) skin_facenum: &[i32; "number of faces"; ffi().nskin],
1763 (mut = unsafe) skin_boneadr: &[i32; "first bone in skin"; ffi().nskin],
1764 (mut = unsafe) skin_bonenum: &[i32; "number of bones in skin"; ffi().nskin],
1765 skin_vert: &[[f32; 3] [force]; "vertex positions for all skin meshes"; ffi().nskinvert],
1766 skin_texcoord: &[[f32; 2] [force]; "vertex texcoords for all skin meshes"; ffi().nskintexvert],
1767 (mut = unsafe) skin_face: &[[i32; 3] [force]; "triangle faces for all skin meshes"; ffi().nskinface],
1768 (mut = unsafe) skin_bonevertadr: &[i32; "first vertex in each bone"; ffi().nskinbone],
1769 (mut = unsafe) skin_bonevertnum: &[i32; "number of vertices in each bone"; ffi().nskinbone],
1770 skin_bonebindpos: &[[f32; 3] [force]; "bind pos of each bone"; ffi().nskinbone],
1771 skin_bonebindquat: &[[f32; 4] [force]; "bind quat of each bone"; ffi().nskinbone],
1772 (mut = unsafe) skin_bonebodyid: &[i32; "body id of each bone"; ffi().nskinbone],
1773 (mut = unsafe) skin_bonevertid: &[i32; "mesh ids of vertices in each bone"; ffi().nskinbonevert],
1774 skin_bonevertweight: &[f32; "weights of vertices in each bone"; ffi().nskinbonevert],
1775 (mut = unsafe) skin_pathadr: &[i32; "address of asset path for skin; -1: none"; ffi().nskin],
1776 hfield_size: &[[MjtNum; 4] [force]; "(x, y, z_top, z_bottom)"; ffi().nhfield],
1777 (mut = unsafe) hfield_nrow: &[i32; "number of rows in grid"; ffi().nhfield],
1778 (mut = unsafe) hfield_ncol: &[i32; "number of columns in grid"; ffi().nhfield],
1779 (mut = unsafe) hfield_adr: &[i32; "address in hfield_data"; ffi().nhfield],
1780 hfield_data: &[f32; "elevation data"; ffi().nhfielddata],
1781 (mut = unsafe) hfield_pathadr: &[i32; "address of hfield asset path; -1: none"; ffi().nhfield],
1782 (mut = unsafe) tex_type: &[MjtTexture [force]; "texture type"; ffi().ntex],
1783 tex_colorspace: &[MjtColorSpace [force]; "texture colorspace"; ffi().ntex],
1784 (mut = unsafe) tex_height: &[i32; "number of rows in texture image"; ffi().ntex],
1785 (mut = unsafe) tex_width: &[i32; "number of columns in texture image"; ffi().ntex],
1786 (mut = unsafe) tex_nchannel: &[i32; "number of channels in texture image"; ffi().ntex],
1787 (mut = unsafe) tex_adr: &[MjtSize; "start address in tex_data"; ffi().ntex],
1788 tex_data: &[MjtByte; "pixel values"; ffi().ntexdata],
1789 (mut = unsafe) tex_pathadr: &[i32; "address of texture asset path; -1: none"; ffi().ntex],
1790 (mut = unsafe) mat_texid: &[[i32; MjtTextureRole::mjNTEXROLE as usize] [force]; "indices of textures; -1: none"; ffi().nmat],
1791 mat_texuniform: &[MjtBool; "make texture cube uniform"; ffi().nmat],
1792 mat_texrepeat: &[[f32; 2] [force]; "texture repetition for 2d mapping"; ffi().nmat],
1793 mat_emission: &[f32; "emission (x rgb)"; ffi().nmat],
1794 mat_specular: &[f32; "specular (x white)"; ffi().nmat],
1795 mat_shininess: &[f32; "shininess coef"; ffi().nmat],
1796 mat_reflectance: &[f32; "reflectance (0: disable)"; ffi().nmat],
1797 mat_metallic: &[f32; "metallic coef"; ffi().nmat],
1798 mat_roughness: &[f32; "roughness coef"; ffi().nmat],
1799 mat_rgba: &[[f32; 4] [force]; "rgba"; ffi().nmat],
1800 (mut = unsafe) pair_dim: &[i32; "contact dimensionality"; ffi().npair],
1801 (mut = unsafe) pair_geom1: &[i32; "id of geom1"; ffi().npair],
1802 (mut = unsafe) pair_geom2: &[i32; "id of geom2"; ffi().npair],
1803 pair_signature: &[i32; "body1 << 16 + body2"; ffi().npair],
1804 pair_solref: &[[MjtNum; mjNREF as usize] [force]; "solver reference: contact normal"; ffi().npair],
1805 pair_solreffriction: &[[MjtNum; mjNREF as usize] [force]; "solver reference: contact friction"; ffi().npair],
1806 pair_solimp: &[[MjtNum; mjNIMP as usize] [force]; "solver impedance: contact"; ffi().npair],
1807 pair_margin: &[MjtNum; "geometric inflation for contact"; ffi().npair],
1808 pair_gap: &[MjtNum; "additional contact detection buffer"; ffi().npair],
1809 pair_adhesion: &[MjtNum; "adhesive force of contacts"; ffi().npair],
1810 pair_friction: &[[MjtNum; 5] [force]; "tangent1, 2, spin, roll1, 2"; ffi().npair],
1811 exclude_signature: &[i32; "body1 << 16 + body2"; ffi().nexclude],
1812 (mut = unsafe) eq_type: &[MjtEq [force]; "constraint type"; ffi().neq],
1813 (mut = unsafe) eq_obj1id: &[i32; "id of object 1"; ffi().neq],
1814 (mut = unsafe) eq_obj2id: &[i32; "id of object 2"; ffi().neq],
1815 (mut = unsafe) eq_objtype: &[MjtObj [force]; "type of both objects"; ffi().neq],
1816 eq_active0: &[MjtBool; "initial enable/disable constraint state"; ffi().neq],
1817 eq_solref: &[[MjtNum; mjNREF as usize] [force]; "constraint solver reference"; ffi().neq],
1818 eq_solimp: &[[MjtNum; mjNIMP as usize] [force]; "constraint solver impedance"; ffi().neq],
1819 eq_data: &[[MjtNum; mjNEQDATA as usize] [force]; "numeric data for constraint"; ffi().neq],
1820 (mut = unsafe) tendon_adr: &[i32; "address of first object in tendon's path"; ffi().ntendon],
1821 (mut = unsafe) tendon_num: &[i32; "number of objects in tendon's path"; ffi().ntendon],
1822 (mut = unsafe) tendon_matid: &[i32; "material id for rendering"; ffi().ntendon],
1823 (mut = unsafe) tendon_actuatorid: &[i32; "actuator contributing damping / armature"; ffi().ntendon],
1824 tendon_group: &[i32; "group for visibility"; ffi().ntendon],
1825 tendon_treenum: &[i32; "number of trees along tendon's path"; ffi().ntendon],
1826 (mut = unsafe) tendon_treeid: &[[i32; 2] [force]; "first two trees along tendon's path"; ffi().ntendon],
1827 (mut = unsafe) ten_J_rownnz: &[i32; "number of non-zeros in Jacobian row"; ffi().ntendon],
1828 (mut = unsafe) ten_J_rowadr: &[i32; "row start address in colind array"; ffi().ntendon],
1829 (mut = unsafe) ten_J_colind: &[i32; "column indices in sparse Jacobian"; ffi().nJten],
1830 tendon_limited: &[MjtBool; "does tendon have length limits"; ffi().ntendon],
1831 tendon_actfrclimited: &[MjtBool; "does tendon have actuator force limits"; ffi().ntendon],
1832 tendon_width: &[MjtNum; "width for rendering"; ffi().ntendon],
1833 tendon_solref_lim: &[[MjtNum; mjNREF as usize] [force]; "constraint solver reference: limit"; ffi().ntendon],
1834 tendon_solimp_lim: &[[MjtNum; mjNIMP as usize] [force]; "constraint solver impedance: limit"; ffi().ntendon],
1835 tendon_solref_fri: &[[MjtNum; mjNREF as usize] [force]; "constraint solver reference: friction"; ffi().ntendon],
1836 tendon_solimp_fri: &[[MjtNum; mjNIMP as usize] [force]; "constraint solver impedance: friction"; ffi().ntendon],
1837 tendon_range: &[[MjtNum; 2] [force]; "tendon length limits"; ffi().ntendon],
1838 tendon_actfrcrange: &[[MjtNum; 2] [force]; "range of total actuator force"; ffi().ntendon],
1839 tendon_margin: &[MjtNum; "min distance for limit detection"; ffi().ntendon],
1840 tendon_stiffness: &[MjtNum; "linear stiffness coefficient"; ffi().ntendon],
1841 tendon_stiffnesspoly: &[[MjtNum; mjNPOLY as usize] [force]; "high-order stiffness coefficients"; ffi().ntendon],
1842 tendon_damping: &[MjtNum; "linear damping coefficient"; ffi().ntendon],
1843 tendon_dampingpoly: &[[MjtNum; mjNPOLY as usize] [force]; "high-order damping coefficients"; ffi().ntendon],
1844 tendon_armature: &[MjtNum; "inertia associated with tendon velocity"; ffi().ntendon],
1845 tendon_frictionloss: &[MjtNum; "loss due to friction"; ffi().ntendon],
1846 tendon_lengthspring: &[[MjtNum; 2] [force]; "spring resting length range"; ffi().ntendon],
1847 tendon_length0: &[MjtNum; "tendon length in qpos0"; ffi().ntendon],
1848 tendon_invweight0: &[MjtNum; "inv. weight in qpos0"; ffi().ntendon],
1849 tendon_rgba: &[[f32; 4] [force]; "rgba when material is omitted"; ffi().ntendon],
1850 (mut = unsafe) wrap_type: &[MjtWrap [force]; "wrap object type"; ffi().nwrap],
1851 (mut = unsafe) wrap_objid: &[i32; "object id: geom, site, joint"; ffi().nwrap],
1852 (mut = unsafe) wrap_prm: &[MjtNum; "divisor, joint coef, or site id"; ffi().nwrap],
1853 (mut = unsafe) actuator_trntype: &[MjtTrn [force]; "transmission type"; ffi().nactuator],
1854 (mut = unsafe) actuator_dyntype: &[MjtDyn [force]; "dynamics type"; ffi().nactuator],
1855 (mut = unsafe) actuator_gaintype: &[MjtGain [force]; "gain type"; ffi().nactuator],
1856 actuator_biastype: &[MjtBias [force]; "bias type"; ffi().nactuator],
1857 (mut = unsafe) actuator_ctrladr: &[i32; "address of first control"; ffi().nactuator],
1858 (mut = unsafe) actuator_ctrlnum: &[i32; "number of controls"; ffi().nactuator],
1859 (mut = unsafe) actuator_ctrlspec: &[i32; "input signature, scoped by gaintype"; ffi().nactuator],
1860 (mut = unsafe) actuator_outadr: &[i32; "address of first force output"; ffi().nactuator],
1861 (mut = unsafe) actuator_outnum: &[i32; "number of force outputs, from trntype"; ffi().nactuator],
1862 (mut = unsafe) actuator_trnid: &[[i32; 2] [force]; "transmission id: joint, tendon, site"; ffi().nactuator],
1863 (mut = unsafe) actuator_actadr: &[i32; "first activation address; -1: stateless"; ffi().nactuator],
1864 (mut = unsafe) actuator_actnum: &[i32; "number of activation variables"; ffi().nactuator],
1865 actuator_group: &[i32; "group for visibility"; ffi().nactuator],
1866 (mut = unsafe) actuator_history: &[[i32; 2] [force]; "history buffer: [nsample, interp]"; ffi().nactuator],
1867 (mut = unsafe) actuator_historyadr: &[i32; "address in history buffer; -1: none"; ffi().nactuator],
1868 actuator_delay: &[MjtNum; "delay time in seconds; 0: no delay"; ffi().nactuator],
1869 actuator_ctrllimited: &[MjtBool; "is control limited"; ffi().nu],
1870 actuator_forcelimited: &[MjtBool; "is force limited"; ffi().nactuator],
1871 actuator_actlimited: &[MjtBool; "is activation limited"; ffi().nactuator],
1872 actuator_dynprm: &[[MjtNum; mjNDYN as usize] [force]; "dynamics parameters"; ffi().nactuator],
1873 actuator_gainprm: &[[MjtNum; mjNGAIN as usize] [force]; "gain parameters"; ffi().nactuator],
1874 actuator_biasprm: &[[MjtNum; mjNBIAS as usize] [force]; "bias parameters"; ffi().nactuator],
1875 actuator_actearly: &[MjtBool; "step activation before force"; ffi().nactuator],
1876 actuator_ctrlrange: &[[MjtNum; 2] [force]; "range of controls"; ffi().nu],
1877 actuator_forcerange: &[[MjtNum; 2] [force]; "range of forces"; ffi().nactuator],
1878 actuator_actrange: &[[MjtNum; 2] [force]; "range of activations"; ffi().nactuator],
1879 actuator_damping: &[MjtNum; "linear damping coefficient"; ffi().nactuator],
1880 actuator_dampingpoly: &[[MjtNum; mjNPOLY as usize] [force]; "high-order damping coefficients"; ffi().nactuator],
1881 actuator_armature: &[MjtNum; "armature added to target (joint, tendon)"; ffi().nactuator],
1882 actuator_gear: &[[MjtNum; 6] [force]; "scale length and transmitted force"; ffi().nout],
1883 actuator_cranklength: &[MjtNum; "crank length for slider-crank"; ffi().nactuator],
1884 actuator_acc0: &[MjtNum; "acceleration from unit force in qpos0"; ffi().nout],
1885 actuator_length0: &[MjtNum; "actuator length in qpos0"; ffi().nout],
1886 actuator_lengthrange: &[[MjtNum; 2] [force]; "feasible actuator length range"; ffi().nout],
1887 (mut = unsafe) actuator_plugin: &[i32; "plugin instance id; -1: not a plugin"; ffi().nactuator],
1888 (mut = unsafe) sensor_type: &[MjtSensor [force]; "sensor type"; ffi().nsensor],
1889 sensor_datatype: &[MjtDataType [force]; "numeric data type"; ffi().nsensor],
1890 sensor_needstage: &[MjtStage [force]; "required compute stage"; ffi().nsensor],
1891 (mut = unsafe) sensor_objtype: &[MjtObj [force]; "type of sensorized object"; ffi().nsensor],
1892 (mut = unsafe) sensor_objid: &[i32; "id of sensorized object"; ffi().nsensor],
1893 (mut = unsafe) sensor_reftype: &[MjtObj [force]; "type of reference frame"; ffi().nsensor],
1894 (mut = unsafe) sensor_refid: &[i32; "id of reference frame; -1: global frame"; ffi().nsensor],
1895 (mut = unsafe) sensor_intprm: &[[i32; mjNSENS as usize] [force]; "sensor parameters"; ffi().nsensor],
1896 (mut = unsafe) sensor_dim: &[i32; "number of scalar outputs"; ffi().nsensor],
1897 (mut = unsafe) sensor_adr: &[i32; "address in sensor array"; ffi().nsensor],
1898 sensor_cutoff: &[MjtNum; "cutoff for real and positive; 0: ignore"; ffi().nsensor],
1899 sensor_noise: &[MjtNum; "noise standard deviation"; ffi().nsensor],
1900 (mut = unsafe) sensor_history: &[[i32; 2] [force]; "history buffer: [nsample, interp]"; ffi().nsensor],
1901 (mut = unsafe) sensor_historyadr: &[i32; "address in history buffer; -1: none"; ffi().nsensor],
1902 sensor_delay: &[MjtNum; "delay time in seconds; 0: no delay"; ffi().nsensor],
1903 sensor_interval: &[[MjtNum; 2] [force]; "interval: [period, phase] in seconds"; ffi().nsensor],
1904 (mut = unsafe) sensor_plugin: &[i32; "plugin instance id; -1: not a plugin"; ffi().nsensor],
1905 (mut = unsafe) plugin: &[i32; "globally registered plugin slot number"; ffi().nplugin],
1906 (mut = unsafe) plugin_stateadr: &[i32; "address in the plugin state array"; ffi().nplugin],
1907 (mut = unsafe) plugin_statenum: &[i32; "number of states in the plugin instance"; ffi().nplugin],
1908 (mut = unsafe) plugin_attr: &[c_char; "config attributes of plugin instances"; ffi().npluginattr],
1909 (mut = unsafe) plugin_attradr: &[i32; "address to each instance's config attrib"; ffi().nplugin],
1910 (mut = unsafe) numeric_adr: &[i32; "address of field in numeric_data"; ffi().nnumeric],
1911 (mut = unsafe) numeric_size: &[i32; "size of numeric field"; ffi().nnumeric],
1912 numeric_data: &[MjtNum; "array of all numeric fields"; ffi().nnumericdata],
1913 (mut = unsafe) text_adr: &[i32; "address of text in text_data"; ffi().ntext],
1914 (mut = unsafe) text_size: &[i32; "size of text field (strlen+1)"; ffi().ntext],
1915 (mut = unsafe) text_data: &[c_char; "array of all text fields (0-terminated)"; ffi().ntextdata],
1916 (mut = unsafe) tuple_adr: &[i32; "address of tuple in tuple_objtype/objid/objprm"; ffi().ntuple],
1917 (mut = unsafe) tuple_size: &[i32; "number of objects in tuple"; ffi().ntuple],
1918 tuple_objtype: &[MjtObj [force]; "array of object types in all tuples"; ffi().ntupledata],
1919 (mut = unsafe) tuple_objid: &[i32; "array of object ids in all tuples"; ffi().ntupledata],
1920 tuple_objprm: &[MjtNum; "array of object params in all tuples"; ffi().ntupledata],
1921 key_time: &[MjtNum; "key time"; ffi().nkey],
1922 (mut = unsafe) name_bodyadr: &[i32; "body name pointers"; ffi().nbody],
1923 (mut = unsafe) name_jntadr: &[i32; "joint name pointers"; ffi().njnt],
1924 (mut = unsafe) name_geomadr: &[i32; "geom name pointers"; ffi().ngeom],
1925 (mut = unsafe) name_siteadr: &[i32; "site name pointers"; ffi().nsite],
1926 (mut = unsafe) name_camadr: &[i32; "camera name pointers"; ffi().ncam],
1927 (mut = unsafe) name_lightadr: &[i32; "light name pointers"; ffi().nlight],
1928 (mut = unsafe) name_flexadr: &[i32; "flex name pointers"; ffi().nflex],
1929 (mut = unsafe) name_meshadr: &[i32; "mesh name pointers"; ffi().nmesh],
1930 (mut = unsafe) name_skinadr: &[i32; "skin name pointers"; ffi().nskin],
1931 (mut = unsafe) name_hfieldadr: &[i32; "hfield name pointers"; ffi().nhfield],
1932 (mut = unsafe) name_texadr: &[i32; "texture name pointers"; ffi().ntex],
1933 (mut = unsafe) name_matadr: &[i32; "material name pointers"; ffi().nmat],
1934 (mut = unsafe) name_pairadr: &[i32; "geom pair name pointers"; ffi().npair],
1935 (mut = unsafe) name_excludeadr: &[i32; "exclude name pointers"; ffi().nexclude],
1936 (mut = unsafe) name_eqadr: &[i32; "equality constraint name pointers"; ffi().neq],
1937 (mut = unsafe) name_tendonadr: &[i32; "tendon name pointers"; ffi().ntendon],
1938 (mut = unsafe) name_actuatoradr: &[i32; "actuator name pointers"; ffi().nactuator],
1939 (mut = unsafe) name_sensoradr: &[i32; "sensor name pointers"; ffi().nsensor],
1940 (mut = unsafe) name_numericadr: &[i32; "numeric name pointers"; ffi().nnumeric],
1941 (mut = unsafe) name_textadr: &[i32; "text name pointers"; ffi().ntext],
1942 (mut = unsafe) name_tupleadr: &[i32; "tuple name pointers"; ffi().ntuple],
1943 (mut = unsafe) name_keyadr: &[i32; "keyframe name pointers"; ffi().nkey],
1944 (mut = unsafe) name_pluginadr: &[i32; "plugin instance name pointers"; ffi().nplugin],
1945 (mut = unsafe) names: &[c_char; "names of all objects, 0-terminated"; ffi().nnames],
1946 (mut = unsafe) names_map: &[i32; "internal hash map of names"; ffi().nnames_map],
1947 paths: &[c_char; "paths to assets, 0-terminated"; ffi().npaths],
1948 (mut = unsafe) B_rownnz: &[i32; "body-dof: non-zeros in each row"; ffi().nbody],
1949 (mut = unsafe) B_rowadr: &[i32; "body-dof: row addresses"; ffi().nbody],
1950 (mut = unsafe) B_colind: &[i32; "body-dof: column indices"; ffi().nB],
1951 (mut = unsafe) M_rownnz: &[i32; "reduced inertia: non-zeros in each row"; ffi().nv],
1952 (mut = unsafe) M_rowadr: &[i32; "reduced inertia: row addresses"; ffi().nv],
1953 (mut = unsafe) M_colind: &[i32; "reduced inertia: column indices"; ffi().nC],
1954 (mut = unsafe) mapM2M: &[i32; "index mapping from qM to M"; ffi().nC],
1955 (mut = unsafe) D_rownnz: &[i32; "full inertia: non-zeros in each row"; ffi().nv],
1956 (mut = unsafe) D_rowadr: &[i32; "full inertia: row addresses"; ffi().nv],
1957 (mut = unsafe) D_diag: &[i32; "full inertia: index of diagonal element"; ffi().nv],
1958 (mut = unsafe) D_colind: &[i32; "full inertia: column indices"; ffi().nD],
1959 (mut = unsafe) mapM2D: &[i32; "index mapping from M to D"; ffi().nD],
1960 (mut = unsafe) mapD2M: &[i32; "index mapping from D to M"; ffi().nC]
1961 }
1962
1963 array_slice_dyn! {
1964 sublen_dep {
1965 key_qpos: &[[MjtNum; ffi().nq] [force]; "key position"; ffi().nkey],
1966 key_qvel: &[[MjtNum; ffi().nv] [force]; "key velocity"; ffi().nkey],
1967 key_act: &[[MjtNum; ffi().na] [force]; "key activation"; ffi().nkey],
1968 key_mpos: &[[MjtNum; ffi().nmocap * 3] [force]; "key mocap position"; ffi().nkey],
1969 key_mquat: &[[MjtNum; ffi().nmocap * 4] [force]; "key mocap quaternion"; ffi().nkey],
1970 key_ctrl: &[[MjtNum; ffi().nu] [force]; "key control"; ffi().nkey],
1971
1972 sensor_user: &[[MjtNum; ffi().nuser_sensor] [force]; "user data"; ffi().nsensor],
1973 actuator_user: &[[MjtNum; ffi().nuser_actuator] [force]; "user data"; ffi().nactuator],
1974 tendon_user: &[[MjtNum; ffi().nuser_tendon] [force]; "user data"; ffi().ntendon],
1975 cam_user: &[[MjtNum; ffi().nuser_cam] [force]; "user data"; ffi().ncam],
1976 site_user: &[[MjtNum; ffi().nuser_site] [force]; "user data"; ffi().nsite],
1977 geom_user: &[[MjtNum; ffi().nuser_geom] [force]; "user data"; ffi().ngeom],
1978 jnt_user: &[[MjtNum; ffi().nuser_jnt] [force]; "user data"; ffi().njnt],
1979 body_user: &[[MjtNum; ffi().nuser_body] [force]; "user data"; ffi().nbody]
1980 }
1981 }
1982}
1983
1984impl Clone for MjModel {
1985 #[expect(deprecated, reason = "try_clone keeps the implementation until it is removed")]
1988 fn clone(&self) -> Self {
1989 self.try_clone().expect("failed to clone model")
1990 }
1991}
1992
1993impl Drop for MjModel {
1994 fn drop(&mut self) {
1995 unsafe {
1997 mj_deleteModel(self.ptr.as_ptr());
1998 }
1999 }
2000}
2001
2002info_with_view!(Model, actuator,
2003 [[actuator_] group: i32,
2004 [actuator_] delay: MjtNum, [actuator_] ctrllimited: MjtBool,
2005 [actuator_] forcelimited: MjtBool, [actuator_] actlimited: MjtBool,
2006 [actuator_] dynprm: MjtNum, [actuator_] gainprm: MjtNum,
2007 [actuator_] biasprm: MjtNum, [actuator_] actearly: MjtBool,
2008 [actuator_] ctrlrange: MjtNum, [actuator_] forcerange: MjtNum,
2009 [actuator_] actrange: MjtNum, [actuator_] gear: MjtNum,
2010 [actuator_] damping: MjtNum, [actuator_] dampingpoly: MjtNum,
2011 [actuator_] armature: MjtNum,
2012 [actuator_] cranklength: MjtNum, [actuator_] acc0: MjtNum,
2013 [actuator_] length0: MjtNum, [actuator_] lengthrange: MjtNum,
2014 [actuator_] user: MjtNum, [actuator_] biastype: MjtBias [force],
2015 [actuator_] plugin: i32],
2016 [[actuator_] trntype: MjtTrn [force], [actuator_] dyntype: MjtDyn [force],
2017 [actuator_] ctrladr: i32, [actuator_] ctrlnum: i32, [actuator_] ctrlspec: i32,
2018 [actuator_] gaintype: MjtGain [force],
2019 [actuator_] outadr: i32, [actuator_] outnum: i32,
2020 [actuator_] trnid: i32, [actuator_] actadr: i32,
2021 [actuator_] actnum: i32, [actuator_] history: i32,
2022 [actuator_] historyadr: i32],
2023 []);
2024
2025info_with_view!(Model, body,
2026 [[body_] sameframe: MjtSameFrame [force],
2027 [body_] pos: MjtNum,
2028 [body_] quat: MjtNum, [body_] ipos: MjtNum,
2029 [body_] iquat: MjtNum, [body_] mass: MjtNum,
2030 [body_] subtreemass: MjtNum, [body_] inertia: MjtNum,
2031 [body_] invweight0: MjtNum, [body_] gravcomp: MjtNum,
2032 [body_] margin: MjtNum,
2033 [body_] contype: i32, [body_] conaffinity: i32,
2034 [body_] user: MjtNum,
2035 [body_] simple: MjtByte, [body_] plugin: i32],
2036 [[body_] parentid: i32, [body_] rootid: i32,
2037 [body_] weldid: i32, [body_] mocapid: i32,
2038 [body_] jntnum: i32, [body_] jntadr: i32,
2039 [body_] dofnum: i32, [body_] dofadr: i32,
2040 [body_] treeid: i32, [body_] geomnum: i32,
2041 [body_] geomadr: i32,
2042 [body_] bvhadr: i32, [body_] bvhnum: i32],
2043 []);
2044
2045info_with_view!(Model, camera,
2046 [[cam_] mode: MjtCamLight [force],
2047 [cam_] pos: MjtNum,
2048 [cam_] quat: MjtNum,
2049 [cam_] poscom0: MjtNum,
2050 [cam_] pos0: MjtNum,
2051 [cam_] mat0: MjtNum,
2052 [cam_] projection: MjtProjection [force],
2053 [cam_] fovy: MjtNum,
2054 [cam_] ipd: MjtNum,
2055 [cam_] output: i32,
2056 [cam_] sensorsize: f32,
2057 [cam_] intrinsic: f32,
2058 [cam_] user: MjtNum],
2059 [[cam_] bodyid: i32,
2060 [cam_] targetbodyid: i32,
2061 [cam_] resolution: i32],
2062 []);
2063
2064info_with_view!(Model, equality,
2065 [[eq_] active0: MjtBool,
2066 [eq_] solref: MjtNum,
2067 [eq_] solimp: MjtNum,
2068 [eq_] data: MjtNum],
2069 [[eq_] r#type: MjtEq [force],
2070 [eq_] obj1id: i32,
2071 [eq_] obj2id: i32,
2072 [eq_] objtype: MjtObj [force]],
2073 []);
2074
2075info_with_view!(Model, exclude,
2076 [[exclude_] signature: i32],
2077 [],
2078 []);
2079
2080info_with_view!(Model, geom,
2081 [[geom_] contype: i32,
2082 [geom_] conaffinity: i32,
2083 [geom_] group: i32,
2084 [geom_] priority: i32, [geom_] sameframe: MjtSameFrame [force],
2085 [geom_] solmix: MjtNum, [geom_] solref: MjtNum,
2086 [geom_] solimp: MjtNum, [geom_] size: MjtNum,
2087 [geom_] aabb: MjtNum,
2088 [geom_] rbound: MjtNum, [geom_] pos: MjtNum,
2089 [geom_] quat: MjtNum, [geom_] friction: MjtNum,
2090 [geom_] margin: MjtNum, [geom_] gap: MjtNum,
2091 [geom_] surfacevel: MjtNum, [geom_] adhesion: MjtNum, [geom_] fluid: MjtNum,
2092 [geom_] user: MjtNum, [geom_] rgba: f32],
2093 [[geom_] r#type: MjtGeom [force], [geom_] condim: i32,
2094 [geom_] bodyid: i32, [geom_] dataid: i32,
2095 [geom_] matid: i32, [geom_] plugin: i32],
2096 []);
2097
2098info_with_view!(Model, hfield,
2099 [[hfield_] size: MjtNum],
2100 [[hfield_] nrow: i32,
2101 [hfield_] ncol: i32,
2102 [hfield_] adr: i32,
2103 [hfield_] pathadr: i32],
2104 [[hfield_] data: f32]);
2105
2106info_with_view!(Model, joint,
2107 [qpos0: MjtNum, qpos_spring: MjtNum,
2108 [jnt_] group: i32,
2109 [jnt_] limited: MjtBool, [jnt_] actfrclimited: MjtBool, [jnt_] actgravcomp: MjtBool,
2110 [jnt_] solref: MjtNum, [jnt_] solimp: MjtNum,
2111 [jnt_] pos: MjtNum,
2112 [jnt_] axis: MjtNum, [jnt_] stiffness: MjtNum,
2113 [jnt_] stiffnesspoly: MjtNum,
2114 [jnt_] range: MjtNum, [jnt_] actfrcrange: MjtNum, [jnt_] margin: MjtNum,
2115 [jnt_] user: MjtNum,
2116 [dof_] frictionloss: MjtNum, [dof_] armature: MjtNum,
2117 [dof_] damping: MjtNum,
2118 [dof_] dampingpoly: MjtNum,
2119 [dof_] invweight0: MjtNum,
2120 [dof_] M0: MjtNum,
2121 [dof_] simplenum: i32],
2122 [[jnt_] r#type: MjtJoint [force],
2123 [jnt_] qposadr: i32,
2124 [jnt_] dofadr: i32, [jnt_] bodyid: i32, [jnt_] actuatorid: i32,
2125 dof_bodyid: i32, [dof_] jntid: i32,
2126 [dof_] parentid: i32, dof_treeid: i32,
2127 [dof_] Madr: i32],
2128 []);
2129
2130info_with_view!(Model, light,
2131 [[light_] mode: MjtCamLight [force],
2132 [light_] r#type: MjtLightType [force],
2133 [light_] castshadow: MjtBool,
2134 [light_] bulbradius: f32,
2135 [light_] intensity: f32,
2136 [light_] range: f32,
2137 [light_] active: MjtBool,
2138 [light_] pos: MjtNum,
2139 [light_] dir: MjtNum,
2140 [light_] poscom0: MjtNum,
2141 [light_] pos0: MjtNum,
2142 [light_] dir0: MjtNum,
2143 [light_] attenuation: f32,
2144 [light_] cutoff: f32,
2145 [light_] softness: f32,
2146 [light_] exponent: f32,
2147 [light_] ambient: f32,
2148 [light_] diffuse: f32,
2149 [light_] specular: f32],
2150 [[light_] bodyid: i32,
2151 [light_] targetbodyid: i32,
2152 [light_] texid: i32],
2153 []);
2154
2155info_with_view!(Model, material,
2156 [[mat_] texuniform: MjtBool,
2157 [mat_] texrepeat: f32,
2158 [mat_] emission: f32,
2159 [mat_] specular: f32,
2160 [mat_] shininess: f32,
2161 [mat_] reflectance: f32,
2162 [mat_] rgba: f32,
2163 [mat_] metallic: f32,
2164 [mat_] roughness: f32],
2165 [[mat_] texid: i32],
2166 []);
2167
2168info_with_view!(Model, mesh,
2169 [[mesh_] scale: MjtNum,
2170 [mesh_] pos: MjtNum,
2171 [mesh_] quat: MjtNum],
2172 [[mesh_] vertadr: i32,
2173 [mesh_] vertnum: i32,
2174 [mesh_] texcoordadr: i32,
2175 [mesh_] faceadr: i32,
2176 [mesh_] facenum: i32,
2177 [mesh_] graphadr: i32,
2178 [mesh_] extrema: i32,
2179 [mesh_] normaladr: i32,
2180 [mesh_] normalnum: i32,
2181 [mesh_] texcoordnum: i32,
2182 [mesh_] bvhadr: i32,
2183 [mesh_] bvhnum: i32,
2184 [mesh_] octadr: i32,
2185 [mesh_] octnum: i32,
2186 [mesh_] pathadr: i32,
2187 [mesh_] polynum: i32,
2188 [mesh_] polyadr: i32],
2189 []);
2190
2191info_with_view!(Model, numeric,
2192 [],
2193 [[numeric_] adr: i32,
2194 [numeric_] size: i32],
2195 [[numeric_] data: MjtNum]);
2196
2197info_with_view!(Model, pair,
2198 [[pair_] solref: MjtNum,
2199 [pair_] solimp: MjtNum,
2200 [pair_] margin: MjtNum,
2201 [pair_] gap: MjtNum,
2202 [pair_] adhesion: MjtNum,
2203 [pair_] friction: MjtNum,
2204 [pair_] solreffriction: MjtNum,
2205 [pair_] signature: i32],
2206 [[pair_] dim: i32,
2207 [pair_] geom1: i32,
2208 [pair_] geom2: i32],
2209 []);
2210
2211info_with_view!(Model, sensor,
2212 [[sensor_] cutoff: MjtNum,
2213 [sensor_] noise: MjtNum,
2214 [sensor_] delay: MjtNum,
2215 [sensor_] interval: MjtNum,
2216 [sensor_] user: MjtNum,
2217 [sensor_] datatype: MjtDataType [force],
2218 [sensor_] needstage: MjtStage [force]],
2219 [[sensor_] intprm: i32,
2220 [sensor_] r#type: MjtSensor [force],
2221 [sensor_] objid: i32,
2222 [sensor_] refid: i32,
2223 [sensor_] objtype: MjtObj [force],
2224 [sensor_] reftype: MjtObj [force],
2225 [sensor_] dim: i32,
2226 [sensor_] adr: i32,
2227 [sensor_] history: i32,
2228 [sensor_] historyadr: i32,
2229 [sensor_] plugin: i32],
2230 []);
2231
2232info_with_view!(Model, site,
2233 [[site_] group: i32,
2234 [site_] sameframe: MjtSameFrame [force],
2235 [site_] size: MjtNum,
2236 [site_] pos: MjtNum,
2237 [site_] quat: MjtNum,
2238 [site_] user: MjtNum,
2239 [site_] rgba: f32,
2240 [site_] r#type: MjtGeom [force]],
2241 [[site_] bodyid: i32,
2242 [site_] matid: i32],
2243 []);
2244
2245info_with_view!(Model, skin,
2246 [[skin_] group: i32,
2247 [skin_] rgba: f32,
2248 [skin_] inflate: f32],
2249 [[skin_] matid: i32,
2250 [skin_] vertadr: i32,
2251 [skin_] vertnum: i32,
2252 [skin_] texcoordadr: i32,
2253 [skin_] faceadr: i32,
2254 [skin_] facenum: i32,
2255 [skin_] boneadr: i32,
2256 [skin_] bonenum: i32,
2257 [skin_] pathadr: i32],
2258 []);
2259
2260info_with_view!(Model, tendon,
2261 [[tendon_] group: i32,
2262 [tendon_] limited: MjtBool, [tendon_] actfrclimited: MjtBool, [tendon_] width: MjtNum,
2263 [tendon_] solref_lim: MjtNum, [tendon_] solimp_lim: MjtNum,
2264 [tendon_] solref_fri: MjtNum, [tendon_] solimp_fri: MjtNum,
2265 [tendon_] range: MjtNum, [tendon_] actfrcrange: MjtNum, [tendon_] margin: MjtNum,
2266 [tendon_] stiffness: MjtNum,
2267 [tendon_] stiffnesspoly: MjtNum,
2268 [tendon_] damping: MjtNum,
2269 [tendon_] dampingpoly: MjtNum,
2270 [tendon_] armature: MjtNum,
2271 [tendon_] frictionloss: MjtNum, [tendon_] lengthspring: MjtNum,
2272 [tendon_] length0: MjtNum, [tendon_] invweight0: MjtNum,
2273 [tendon_] user: MjtNum, [tendon_] rgba: f32,
2274 [tendon_] treenum: i32],
2275 [[tendon_] matid: i32, [tendon_] actuatorid: i32, [tendon_] treeid: i32,
2276 [tendon_] adr: i32, [tendon_] num: i32,
2277 [ten_] J_rownnz: i32, [ten_] J_rowadr: i32, [ten_] J_colind: i32],
2278 []);
2279
2280info_with_view!(Model, texture,
2281 [[tex_] colorspace: MjtColorSpace [force]],
2282 [[tex_] r#type: MjtTexture [force],
2283 [tex_] height: i32,
2284 [tex_] width: i32,
2285 [tex_] nchannel: i32,
2286 [tex_] adr: MjtSize,
2287 [tex_] pathadr: i32],
2288 [[tex_] data: MjtByte]);
2289
2290info_with_view!(Model, tuple,
2291 [[tuple_] objprm: MjtNum,
2292 [tuple_] objtype: MjtObj [force]],
2293 [[tuple_] adr: i32,
2294 [tuple_] size: i32,
2295 [tuple_] objid: i32],
2296 []);
2297
2298info_with_view!(Model, key,
2299 [[key_] time: MjtNum,
2300 [key_] qpos: MjtNum,
2301 [key_] qvel: MjtNum,
2302 [key_] act: MjtNum,
2303 [key_] mpos: MjtNum,
2304 [key_] mquat: MjtNum,
2305 [key_] ctrl: MjtNum],
2306 [],
2307 []);
2308
2309#[cfg(test)]
2310#[expect(clippy::needless_range_loop,
2311 reason = "the loop indices drive FFI pointer arithmetic, ptr.add(i * stride + j)")]
2312mod tests {
2313 use crate::assert_relative_eq;
2314
2315 use super::*;
2316 use std::fs;
2317
2318 const EXAMPLE_MODEL: &str = stringify!(
2319 <mujoco>
2320 <worldbody>
2321 <light name="lamp_light1"
2322 mode="fixed" type="directional" castshadow="false" bulbradius="0.5" intensity="250"
2323 range="10" active="true" pos="0 0 0" dir="0 0 -1" attenuation="0.1 0.05 0.01"
2324 cutoff="60" exponent="2" ambient="0.1 0.1 0.25" diffuse="0.5 1 1" specular="1 1.5 1"/>
2325
2326 <light name="lamp_light2"
2327 mode="fixed" type="spot" castshadow="true" bulbradius="0.2" intensity="500"
2328 range="10" active="true" pos="0 0 0" dir="0 0 -1" attenuation="0.1 0.05 0.01"
2329 cutoff="45" exponent="2" ambient="0.1 0.1 0.1" diffuse="1 1 1" specular="1 1 1"/>
2330
2331 <camera name="cam1" fovy="50" resolution="100 200"/>
2332
2333 <light ambient="0.2 0.2 0.2"/>
2334 <body name="ball">
2335 <geom name="green_sphere" pos=".2 .2 .2" size=".1" rgba="0 1 0 1"/>
2336 <joint name="ball" type="free" axis="1 1 1"/>
2337 <site name="touch" size="1" type="box"/>
2338 </body>
2339
2340 <body name="ball1" pos="-.5 0 0">
2341 <geom size=".1" rgba="0 1 0 1" mass="1"/>
2342 <joint type="free"/>
2343 <site name="ball1" size=".1 .1 .1" pos="0 0 0" rgba="0 1 0 0.2" type="box"/>
2344 <site name="ball12" size=".1 .1 .1" pos="0 0 0" rgba="0 1 1 0.2" type="box"/>
2345 <site name="ball13" size=".1 .1 .1" pos="0 0 0" rgba="0 1 1 0.2" type="box"/>
2346 </body>
2347
2348 <body name="ball2" pos=".5 0 0">
2349 <geom name="ball2" size=".5" rgba="0 1 1 1" mass="1"/>
2350 <joint name="ball2" type="free"/>
2351 <site name="ball2" size=".1 .1 .1" pos="0 0 0" rgba="0 1 1 0.2" type="box"/>
2352 <site name="ball22" size="0.5 0.25 0.5" pos="5 1 3" rgba="1 2 3 1" type="box"/>
2353 <site name="ball23" size=".1 .1 .1" pos="0 0 0" rgba="0 1 1 0.2" type="box"/>
2354 </body>
2355
2356 <geom name="floor" type="plane" size="10 10 1" euler="5 0 0"/>
2357
2358 <body name="slider">
2359 <geom name="rod" type="cylinder" size="1 10 0" euler="90 0 0" pos="0 0 10"/>
2360 <joint name="rod" type="slide" axis="0 1 0" range="0 1"/>
2361 </body>
2362
2363 <body name="ball3" pos="0 0 5">
2364 <geom name="ball31" size=".5" rgba="0 1 1 1" mass="1"/>
2365 <joint type="slide"/>
2366 </body>
2367
2368 <body name="ball32" pos="0 0 -5">
2369 <geom name="ball32" size=".5" rgba="0 1 1 1" mass="1"/>
2370 <joint type="slide"/>
2371 </body>
2372
2373 <body name="eq_body1" pos="0 0 0">
2374 <geom size="0.1"/>
2375 </body>
2376 <body name="eq_body2" pos="1 0 0">
2377 <geom size="0.1"/>
2378 </body>
2379 <body name="eq_body3" pos="0 0 0">
2380 <geom size="0.1"/>
2381 </body>
2382 <body name="eq_body4" pos="1 0 0">
2383 <geom size="0.1"/>
2384 </body>
2385 </worldbody>
2386
2387
2388 <equality>
2389 <connect name="eq1" body1="eq_body1" body2="eq_body2" anchor="15 0 10"/>
2390 <connect name="eq2" body1="eq_body2" body2="eq_body1" anchor="-5 0 10"/>
2391 <connect name="eq3" body1="eq_body3" body2="eq_body4" anchor="0 5 0"/>
2392 <connect name="eq4" body1="eq_body4" body2="eq_body3" anchor="5 5 10"/>
2393 </equality>
2394
2395
2396 <actuator>
2397 <general name="slider" joint="rod" biastype="affine" ctrlrange="0 1" dynprm="1 2 3 4 5 6 7 8 9 10" gaintype="fixed"/>
2398 <general name="slider2" joint="ball2" biastype="affine" ctrlrange="0 1" dynprm="10 9 8 7 6 5 4 3 2 1" gaintype="fixed"/>
2399 </actuator>
2400
2401 <sensor>
2402 <touch name="touch" site="touch"/>
2403 </sensor>
2404
2405 <tendon>
2406 <spatial name="tendon1" limited="false" range="0 5" rgba="0 .5 2 3" width=".5">
2407 <site site="ball1"/>
2408 <site site="ball2"/>
2409 </spatial>
2410 </tendon>
2411
2412 <tendon>
2413 <spatial name="tendon2" limited="true" range="0 1" rgba="0 .1 1 1" width=".005">
2414 <site site="ball1"/>
2415 <site site="ball2"/>
2416 </spatial>
2417 </tendon>
2418
2419 <tendon>
2420 <spatial name="tendon3" limited="false" range="0 5" rgba=".5 .2 .4 .3" width=".25">
2421 <site site="ball1"/>
2422 <site site="ball2"/>
2423 </spatial>
2424 </tendon>
2425
2426 <!-- Contact pair between the two geoms -->
2427 <contact>
2428 <pair name="geom_pair" geom1="ball31" geom2="ball32" condim="3" solref="0.02 1"
2429 solreffriction="0.01 0.5" solimp="0.0 0.95 0.001 0.5 2" margin="0.001" gap="0"
2430 friction="1.0 0.8 0.6 0.0 0.0">
2431 </pair>
2432 </contact>
2433
2434 <!-- A keyframe with qpos/qvel/ctrl etc. -->
2435 <keyframe>
2436 <!-- adjust nq/nv/nu in <default> or body definitions to match
2437 lengths in your test constants -->
2438 <key name="pose0"
2439 time="0.0"
2440 qpos="1.1 1.2 1.3 1.1 0.2 0.3 0.1 1.2 0.3 1.1 0.2 1.3 0.1 1.2 0.3 1.1 0.2 0.3 0.1 0.2 0.3 0.1 0.2 0.0"
2441 qvel="0.5 5.0 5.0 0.0 1.0 0.0 0.0 5.0 0.0 5.0 1.0 5.0 0.0 1.0 5.0 0.0 1.0 0.0 0.0 1.0 0.0"
2442 ctrl="0.5 0.5"/>
2443 <key name="pose1"
2444 time="1.5"
2445 qpos="0.1 0.2 0.3 0.1 0.2 0.3 0.1 0.2 0.3 0.1 0.2 0.3 0.1 0.2 0.3 0.1 0.2 0.3 0.1 0.2 0.3 0.1 0.2 0.0"
2446 qvel="0.0 1.0 0.0 0.0 1.0 0.0 0.0 1.0 0.0 0.0 1.0 0.0 0.0 1.0 0.0 0.0 1.0 0.0 0.0 1.0 0.0"
2447 ctrl="0.5 0.0"/>
2448 </keyframe>
2449
2450 <custom>
2451 <tuple name="tuple_example">
2452 <!-- First entry: a body -->
2453 <element objtype="body" objname="ball2" prm="0.5"/>
2454 <!-- Second entry: a site -->
2455 <element objtype="site" objname="ball1" prm="1.0"/>
2456 </tuple>
2457
2458 <!-- Numeric element with a single value -->
2459 <numeric name="gain_factor1" size="5" data="3.14159 0 0 0 3.14159"/>
2460 <numeric name="gain_factor2" size="3" data="1.25 5.5 10.0"/>
2461 </custom>
2462
2463 <!-- Texture definition -->
2464 <asset>
2465 <texture name="wall_tex"
2466 type="2d"
2467 colorspace="sRGB"
2468 width="128"
2469 height="128"
2470 nchannel="3"
2471 builtin="flat"
2472 rgb1="0.6 0.6 0.6"
2473 rgb2="0.6 0.6 0.6"
2474 mark="none"/>
2475
2476 <!-- Material definition -->
2477 <material name="wood_material"
2478 rgba="0.8 0.5 0.3 1"
2479 emission="0.1"
2480 specular="0.5"
2481 shininess="0.7"
2482 reflectance="0.2"
2483 metallic="0.3"
2484 roughness="0.4"
2485 texuniform="true"
2486 texrepeat="2 2"/>
2487
2488 <!-- Material definition -->
2489 <material name="also_wood_material"
2490 rgba="0.8 0.5 0.3 1"
2491 emission="0.1"
2492 specular="0.5"
2493 shininess="0.7"
2494 reflectance="0.2"
2495 metallic="0.3"
2496 roughness="0.5"
2497 texuniform="false"
2498 texrepeat="2 2"/>
2499
2500 <hfield name="hf1" nrow="2" ncol="3" size="1 1 1 0.1"/>
2501 <hfield name="hf2" nrow="5" ncol="3" size="1 1 1 5.25"/>
2502 <hfield name="hf3" nrow="2" ncol="3" size="1 1 1 0.1"/>
2503 </asset>
2504 </mujoco>
2505);
2506
2507 #[test]
2508 fn test_integrate_and_differentiate_pos() {
2509 const DT: MjtNum = 0.05;
2510
2511 let model = MjModel::from_xml_string(EXAMPLE_MODEL).expect("unable to load the model.");
2512 assert!(model.nq() > model.nv(), "the model must hold a quaternion joint");
2513
2514 let qvel: Vec<MjtNum> = (0..model.nv()).map(|i| 0.1 * (i as MjtNum + 1.0)).collect();
2515 let qpos1 = model.qpos0().to_vec();
2516 let mut qpos2 = qpos1.clone();
2517 model.integrate_pos(&mut qpos2, &qvel, DT);
2518 assert_ne!(qpos1, qpos2, "integration must move the configuration");
2519
2520 let recovered = model.differentiate_pos(DT, &qpos1, &qpos2);
2521 assert_eq!(recovered.len(), model.nv() as usize);
2522 for (got, expected) in recovered.iter().zip(&qvel) {
2523 assert_relative_eq!(*got, *expected, epsilon = 1e-9);
2524 }
2525 }
2526
2527 #[test]
2528 fn test_differentiate_pos_rejects_bad_length() {
2529 let model = MjModel::from_xml_string(EXAMPLE_MODEL).expect("unable to load the model.");
2530 let qpos = model.qpos0().to_vec();
2531 let short = &qpos[..qpos.len() - 1];
2532
2533 assert!(matches!(
2534 model.try_differentiate_pos(0.1, short, &qpos),
2535 Err(MjModelError::LengthMismatch { name: "qpos1", .. })
2536 ));
2537 assert!(matches!(
2538 model.try_differentiate_pos(0.1, &qpos, short),
2539 Err(MjModelError::LengthMismatch { name: "qpos2", .. })
2540 ));
2541 }
2542
2543 #[test]
2544 fn test_differentiate_pos_into_buffer_bounds() {
2545 let model = MjModel::from_xml_string(EXAMPLE_MODEL).expect("unable to load the model.");
2546 let nv = model.nv() as usize;
2547 let qpos1 = model.qpos0().to_vec();
2548 let mut qpos2 = qpos1.clone();
2549 model.integrate_pos(&mut qpos2, &vec![0.1; nv], 1.0);
2550
2551 let mut qvel = vec![MjtNum::NAN; nv + 2];
2552 model.differentiate_pos_into(1.0, &qpos1, &qpos2, &mut qvel);
2553 assert!(qvel[..nv].iter().all(|v| v.is_finite()), "every element below nv is written");
2554 assert!(qvel[nv..].iter().all(|v| v.is_nan()), "elements above nv keep their previous values");
2555
2556 assert!(matches!(
2557 model.try_differentiate_pos_into(1.0, &qpos1, &qpos2, &mut vec![0 as MjtNum; nv - 1]),
2558 Err(MjModelError::BufferTooSmall { needed, available }) if needed == nv && available == nv - 1
2559 ));
2560 }
2561
2562 #[test]
2563 fn test_normalize_quat() {
2564 let model = MjModel::from_xml_string(EXAMPLE_MODEL).expect("unable to load the model.");
2565 let free_adr = model.jnt_qposadr()[0] as usize;
2566 assert_eq!(model.jnt_type()[0], MjtJoint::mjJNT_FREE);
2567
2568 let mut qpos = model.qpos0().to_vec();
2569 qpos[free_adr + 3..free_adr + 7].copy_from_slice(&[2.0, 0.0, 0.0, 2.0]);
2570 model.normalize_quat(&mut qpos);
2571
2572 let quat = &qpos[free_adr + 3..free_adr + 7];
2573 let norm = quat.iter().map(|q| q * q).sum::<MjtNum>().sqrt();
2574 assert_relative_eq!(norm, 1.0, epsilon = 1e-12);
2575 assert_relative_eq!(quat[0], 0.5_f64.sqrt(), epsilon = 1e-12);
2576 assert_relative_eq!(quat[3], 0.5_f64.sqrt(), epsilon = 1e-12);
2577 }
2578
2579 #[test]
2581 fn test_model_load_save() {
2582 const MODEL_SAVE_XML_PATH: &str = "./__TMP_MODEL1.xml";
2583 const MODEL_INVALID_SAVE_XML_PATH: &str = "/some/non-existent/path/";
2584
2585 let model = MjModel::from_xml_string(EXAMPLE_MODEL).expect("unable to load the model.");
2586 model.save_last_xml(MODEL_SAVE_XML_PATH).expect("could not save the model XML.");
2587 fs::remove_file(MODEL_SAVE_XML_PATH).unwrap();
2588
2589 assert!(model.save_last_xml(MODEL_INVALID_SAVE_XML_PATH).is_err());
2591 }
2592
2593 #[test]
2594 fn test_model_encode() {
2595 const PATH_MJB: &str = "./__TMP_MODEL_ENCODE.mjb";
2596 const PATH_XML: &str = "./__TMP_MODEL_ENCODE.xml";
2597
2598 let model = MjModel::from_xml_string(EXAMPLE_MODEL).expect("unable to load the model.");
2599 model.encode(PATH_MJB, "").unwrap();
2600 model.encode_with_vfs(PATH_XML, "text/xml", &MjVfs::new()).unwrap();
2601
2602 let encoded = MjModel::from_buffer(&fs::read(PATH_MJB).unwrap()).unwrap();
2603 let reloaded = MjModel::from_xml(PATH_XML);
2606 fs::remove_file(PATH_MJB).unwrap();
2607 fs::remove_file(PATH_XML).unwrap();
2608
2609 assert!(model.is_compatible_with_model(&encoded));
2610 reloaded.unwrap();
2611
2612 assert!(model.encode("/some/non-existent/path/model.xml", "").is_err());
2613 }
2614
2615 #[test]
2616 fn test_actuator_model_view() {
2617 let mut model = MjModel::from_xml_string(EXAMPLE_MODEL).expect("unable to load the model.");
2618 let actuator_model_info = model.actuator("slider").unwrap();
2619 let view = actuator_model_info.view(&model);
2620
2621 assert_eq!(view.biastype[0], MjtBias::mjBIAS_AFFINE);
2623 assert_eq!(&view.ctrlrange[..], [0.0, 1.0]);
2624 assert!(view.ctrllimited[0]);
2625 assert!(!view.forcelimited[0]);
2626 assert_eq!(view.trntype[0], MjtTrn::mjTRN_JOINT);
2627 assert_eq!(view.gaintype[0], MjtGain::mjGAIN_FIXED);
2628
2629 assert_eq!(view.dynprm[..], model.actuator_dynprm()[actuator_model_info.id]);
2631
2632 let mut view_mut = actuator_model_info.view_mut(&mut model);
2634 view_mut.biastype[0] = MjtBias::mjBIAS_USER;
2635 view_mut.delay[0] = 3.0;
2636
2637 assert_eq!(view_mut.biastype[0], MjtBias::mjBIAS_USER);
2638 assert_eq!(view_mut.delay[0], 3.0);
2639 view_mut.zero();
2640
2641 assert_eq!(view_mut.delay[0], 0.0);
2642 assert_eq!(view_mut.biastype[0], MjtBias::mjBIAS_NONE);
2643 }
2644
2645 #[test]
2648 fn test_light_softness() {
2649 const XML: &str = stringify!(
2650 <mujoco>
2651 <worldbody>
2652 <light name="spot1" type="spot" cutoff="45" softness="0.25" exponent="2"/>
2653 <light name="spot2" type="spot" cutoff="60" softness="0.75" exponent="3"/>
2654 </worldbody>
2655 </mujoco>
2656 );
2657 let model = MjModel::from_xml_string(XML).unwrap();
2658 assert_eq!(model.light_softness(), [0.25, 0.75]);
2659
2660 let info = model.light("spot2").unwrap();
2661 assert_eq!(info.view(&model).softness[0], 0.75);
2662 }
2663
2664 #[test]
2665 fn test_sensor_model_view() {
2666 let mut model = MjModel::from_xml_string(EXAMPLE_MODEL).expect("unable to load the model.");
2667 let sensor_model_info = model.sensor("touch").unwrap();
2668 let view = sensor_model_info.view(&model);
2669
2670 assert_eq!(view.dim[0], 1);
2672 assert_eq!(view.objtype[0], MjtObj::mjOBJ_SITE);
2673 assert_eq!(view.noise[0], 0.0);
2674 assert_eq!(view.r#type[0], MjtSensor::mjSENS_TOUCH);
2675
2676 let mut view_mut = sensor_model_info.view_mut(&mut model);
2678 view_mut.noise[0] = 1.0;
2679 assert_eq!(view_mut.noise[0], 1.0);
2680 }
2681
2682 #[test]
2683 fn test_tendon_model_view() {
2684 let mut model = MjModel::from_xml_string(EXAMPLE_MODEL).expect("unable to load the model.");
2685 let tendon_model_info = model.tendon("tendon2").unwrap();
2686 let view = tendon_model_info.view(&model);
2687
2688 assert_eq!(&view.range[..], [0.0, 1.0]);
2690 assert!(view.limited[0]);
2691 assert_eq!(view.width[0], 0.005);
2692
2693 let tendon_id = tendon_model_info.id;
2695 assert_eq!(view.width[0], model.tendon_width()[tendon_id]);
2696 assert_eq!(*view.range, model.tendon_range()[tendon_id]);
2697 assert_eq!(view.limited[0], model.tendon_limited()[tendon_id]);
2698
2699 let mut view_mut = tendon_model_info.view_mut(&mut model);
2701 view_mut.frictionloss[0] = 5e-2;
2702 assert_eq!(view_mut.frictionloss[0], 5e-2);
2703 }
2704
2705 #[test]
2706 fn test_joint_model_view() {
2707 let mut model = MjModel::from_xml_string(EXAMPLE_MODEL).expect("unable to load the model.");
2708 let model_info = model.joint("rod").unwrap();
2709 let view = model_info.view(&model);
2710
2711 assert_eq!(view.r#type[0], MjtJoint::mjJNT_SLIDE);
2713 assert!(view.limited[0]);
2714 assert_eq!(&view.axis[..], [0.0, 1.0 , 0.0]);
2715 assert!(!view.dof_bodyid.is_empty());
2716 assert!(!view.dof_treeid.is_empty());
2717
2718 let dof_start = model.jnt_dofadr()[model_info.id] as usize;
2719 assert_eq!(view.dof_bodyid[0], model.dof_bodyid()[dof_start]);
2720 assert_eq!(view.dof_treeid[0], model.dof_treeid()[dof_start]);
2721
2722 let free_info = model.joint("ball2").unwrap();
2724 let free_view = free_info.view(&model);
2725 assert!(free_view.dof_bodyid.len() > 1);
2726 assert_eq!(free_view.dof_bodyid.len(), free_view.dof_treeid.len());
2727
2728 let free_dof_start = model.jnt_dofadr()[free_info.id] as usize;
2729 let free_dof_end = free_dof_start + free_view.dof_bodyid.len();
2730 assert_eq!(&free_view.dof_bodyid[..], &model.dof_bodyid()[free_dof_start..free_dof_end]);
2731 assert_eq!(&free_view.dof_treeid[..], &model.dof_treeid()[free_dof_start..free_dof_end]);
2732
2733 let mut view_mut = model_info.view_mut(&mut model);
2735 view_mut.axis.copy_from_slice(&[1.0, 0.0, 0.0]);
2736 assert_eq!(&view_mut.axis[..], [1.0, 0.0 , 0.0]);
2737 }
2738
2739 #[test]
2740 fn test_geom_model_view() {
2741 let mut model = MjModel::from_xml_string(EXAMPLE_MODEL).expect("unable to load the model.");
2742 let model_info = model.geom("ball2").unwrap();
2743 let view = model_info.view(&model);
2744
2745 assert_eq!(view.r#type[0], MjtGeom::mjGEOM_SPHERE);
2747 assert_eq!(view.size[0], 0.5);
2748
2749 let mut view_mut = model_info.view_mut(&mut model);
2751 view_mut.size[0] = 1.0;
2752 assert_eq!(view_mut.size[0], 1.0);
2753 }
2754
2755 #[test]
2756 fn test_body_model_view() {
2757 let mut model = MjModel::from_xml_string(EXAMPLE_MODEL).expect("unable to load the model.");
2758 let model_info = model.body("ball2").unwrap();
2759 let view = model_info.view(&model);
2760
2761 assert_eq!(view.pos[0], 0.5);
2763
2764 let body_id = model_info.id;
2766 assert_eq!(model.body_sameframe()[body_id], view.sameframe[0]);
2767
2768 let mut view_mut = model_info.view_mut(&mut model);
2770 view_mut.pos[0] = 1.0;
2771 assert_eq!(view_mut.pos[0], 1.0);
2772 }
2773
2774
2775 const COMPAT_MODEL: &str = "<mujoco>{extra}\
2778<worldbody><body name='b1'><joint name='j' type='hinge'/><geom size='0.1'/></body></worldbody>\
2779<actuator>{actuator}</actuator></mujoco>";
2780
2781 fn compat_model(extra: &str, actuator: &str) -> MjModel {
2782 let xml = COMPAT_MODEL.replace("{extra}", extra).replace("{actuator}", actuator);
2783 MjModel::from_xml_string(&xml).unwrap()
2784 }
2785
2786 #[test]
2787 fn test_is_compatible_with_model_rejects_unpinned_size() {
2788 let plain = compat_model("", "<motor joint='j'/>");
2789
2790 let userdata = compat_model("<size nuserdata='2000'/>", "<motor joint='j'/>");
2792 assert_eq!(plain.signature(), userdata.signature(), "the pair must share a signature");
2793 assert_ne!(plain.nuserdata(), userdata.nuserdata());
2794 assert!(!plain.is_compatible_with_model(&userdata));
2795
2796 let stateful = compat_model("", "<general joint='j' dyntype='integrator'/>");
2798 assert_eq!(plain.signature(), stateful.signature(), "the pair must share a signature");
2799 assert_ne!(plain.na(), stateful.na());
2800 assert!(!plain.is_compatible_with_model(&stateful));
2801 }
2802
2803 #[test]
2804 fn test_is_compatible_with_model_rejects_a_different_arena_size() {
2805 let plain = compat_model("", "<motor joint='j'/>");
2806
2807 let small = compat_model("<size memory='4M'/>", "<motor joint='j'/>");
2810 assert_eq!(plain.signature(), small.signature(), "the pair must share a signature");
2811 assert_ne!(plain.narena(), small.narena());
2812 assert!(!plain.is_compatible_with_model(&small));
2813 }
2814
2815 #[test]
2816 fn test_is_compatible_with_model_rejects_a_different_mesh_split() {
2817 const TET: &str = "0 0 0 1 0 0 0 1 0 0 0 1";
2818 const CUBE: &str = "0 0 0 1 0 0 1 1 0 0 1 0 0 0 1 1 0 1 1 1 1 0 1 1";
2819 let mesh_model = |first: &str, second: &str| MjModel::from_xml_string(&format!(
2820 "<mujoco><asset><mesh name='m1' vertex='{first}'/><mesh name='m2' vertex='{second}'/>\
2821 </asset><worldbody><body name='b'><joint name='j' type='hinge'/>\
2822 <geom type='mesh' mesh='m1'/><geom type='mesh' mesh='m2'/></body></worldbody></mujoco>"
2823 )).unwrap();
2824
2825 let model = mesh_model(TET, CUBE);
2826 let swapped = mesh_model(CUBE, TET);
2827 assert_eq!(model.signature(), swapped.signature(), "the pair must share a signature");
2828 assert_eq!(model.nmeshvert(), swapped.nmeshvert(), "the vertex totals must agree");
2829 assert_ne!(model.mesh_vertadr(), swapped.mesh_vertadr());
2830 assert!(!model.is_compatible_with_model(&swapped));
2831 assert_ne!(model.layout(), swapped.layout());
2833 assert!(model.is_compatible_with_model(&mesh_model(TET, CUBE)));
2834 }
2835
2836 #[test]
2837 fn test_is_compatible_with_model_accepts_a_renamed_element() {
2838 const LONG_NAME: &str = "name='a_body_name_long_enough_to_cross_the_model_buffer_padding_boundary'";
2839 let model = compat_model("", "<motor joint='j'/>");
2840 let renamed = MjModel::from_xml_string(
2841 &COMPAT_MODEL.replace("{extra}", "").replace("{actuator}", "<motor joint='j'/>")
2842 .replace("name='b1'", LONG_NAME)
2843 ).unwrap();
2844
2845 assert_eq!(model.signature(), renamed.signature(), "the pair must share a signature");
2846 assert_ne!(model.nbuffer(), renamed.nbuffer(), "the rename must move nbuffer");
2847 assert_eq!(model.narena(), renamed.narena(), "no mjData buffer may follow the name");
2848 assert!(model.is_compatible_with_model(&renamed));
2849 }
2850
2851 #[test]
2855 fn test_is_compatible_with_model_rejects_a_joint_type_change() {
2856 let joint_model = |kind: &str| MjModel::from_xml_string(&format!(
2857 "<mujoco><worldbody><body><joint type='{kind}'/><geom size='0.1'/></body>\
2858 </worldbody></mujoco>"
2859 )).unwrap();
2860
2861 let slide = joint_model("slide");
2862 let hinge = joint_model("hinge");
2863 assert_eq!(slide.signature(), hinge.signature(), "the pair must share a signature");
2864 assert_eq!(slide.nq(), hinge.nq(), "the qpos totals must agree");
2865 assert_eq!(slide.nv(), hinge.nv(), "the dof totals must agree");
2866 assert_eq!(slide.jnt_qposadr(), hinge.jnt_qposadr());
2867 assert_eq!(slide.jnt_dofadr(), hinge.jnt_dofadr());
2868 assert!(!slide.is_compatible_with_model(&hinge));
2869 }
2870
2871 #[test]
2874 fn test_is_compatible_with_model_rejects_a_different_body_tree() {
2875 const INNER: &str = "<body name='b2' pos='0.1 0 0'><joint type='hinge'/>\
2876 <geom size='0.05'/></body>";
2877 let nested = MjModel::from_xml_string(&format!(
2878 "<mujoco><worldbody><body name='b1'><joint type='hinge'/><geom size='0.1'/>\
2879 {INNER}</body></worldbody></mujoco>"
2880 )).unwrap();
2881 let siblings = MjModel::from_xml_string(&format!(
2882 "<mujoco><worldbody><body name='b1'><joint type='hinge'/><geom size='0.1'/></body>\
2883 {INNER}</worldbody></mujoco>"
2884 )).unwrap();
2885
2886 assert_eq!(nested.nbody(), siblings.nbody(), "the body counts must agree");
2887 assert_eq!(nested.nq(), siblings.nq(), "the qpos totals must agree");
2888 assert_eq!(nested.njnt(), siblings.njnt(), "the joint counts must agree");
2889 assert_ne!(nested.body_parentid(), siblings.body_parentid());
2890 assert_ne!(nested.element_split_tables(), siblings.element_split_tables());
2892 assert!(!nested.is_compatible_with_model(&siblings));
2893 }
2894
2895 #[test]
2896 fn test_is_compatible_with_model_rejects_a_different_sensor_split() {
2897 let sensor_model = |first: u32, second: u32| MjModel::from_xml_string(&format!(
2898 "<mujoco><worldbody><body name='b1'><joint name='j' type='hinge'/><geom size='0.1'/>\
2899 </body></worldbody><sensor><user name='u1' dim='{first}' objtype='body' objname='b1'/>\
2900 <user name='u2' dim='{second}' objtype='body' objname='b1'/></sensor></mujoco>"
2901 )).unwrap();
2902
2903 let model = sensor_model(3, 1);
2904 let swapped = sensor_model(1, 3);
2905 assert_eq!(model.signature(), swapped.signature(), "the pair must share a signature");
2906 assert_eq!(model.nsensordata(), swapped.nsensordata(), "the data totals must agree");
2907 assert_ne!(model.sensor_adr(), swapped.sensor_adr());
2908 assert!(!model.is_compatible_with_model(&swapped));
2909 assert!(model.is_compatible_with_model(&sensor_model(3, 1)));
2910 }
2911
2912 #[test]
2915 fn test_layout_rejects_a_different_flex_split() {
2916 let flex = |name: &str, bodies: &str, nvert: usize, element: &str| format!(
2917 "<flex name='{name}' dim='1' body='{bodies}' vertex='{}' element='{element}'>\
2918 <edge damping='1'/></flex>",
2919 "0 0 0 ".repeat(nvert)
2920 );
2921 let flex_model = |first: String, second: String| MjModel::from_xml_string(&format!(
2922 "<mujoco><worldbody>\
2923 <body name='v0'><freejoint/><geom size='0.01'/></body>\
2924 <body name='v1' pos='0.1 0 0'><freejoint/><geom size='0.01'/></body>\
2925 <body name='v2' pos='0.2 0 0'><freejoint/><geom size='0.01'/></body>\
2926 <body name='v3' pos='0.3 0 0'><freejoint/><geom size='0.01'/></body>\
2927 <body name='v4' pos='0.4 0 0'><freejoint/><geom size='0.01'/></body>\
2928 </worldbody><deformable>{first}{second}</deformable></mujoco>"
2929 )).unwrap();
2930
2931 let model = flex_model(flex("f1", "v0 v1 v2", 3, "0 1 1 2"), flex("f2", "v3 v4", 2, "0 1"));
2933 let swapped = flex_model(flex("f1", "v0 v1", 2, "0 1"), flex("f2", "v2 v3 v4", 3, "0 1 1 2"));
2934 assert_eq!(model.signature(), swapped.signature(), "the pair must share a signature");
2935 assert_eq!(model.nflexvert(), swapped.nflexvert(), "the vertex totals must agree");
2936 assert_ne!(model.flex_vertnum(), swapped.flex_vertnum());
2937 assert!(!model.is_compatible_with_model(&swapped));
2938 assert_ne!(model.layout(), swapped.layout());
2939 }
2940
2941 #[test]
2944 fn test_is_asset_compatible_with_model_ignores_the_data_sizes() {
2945 const TET: &str = "0 0 0 1 0 0 0 1 0 0 0 1";
2946 const CUBE: &str = "0 0 0 1 0 0 1 1 0 0 1 0 0 0 1 1 0 1 1 1 1 0 1 1";
2947 let asset_model = |extra: &str, first: &str, second: &str| MjModel::from_xml_string(&format!(
2948 "<mujoco>{extra}<asset><mesh name='m1' vertex='{first}'/><mesh name='m2' vertex='{second}'/>\
2949 </asset><worldbody><body name='b'><joint name='j' type='hinge'/>\
2950 <geom type='mesh' mesh='m1'/><geom type='mesh' mesh='m2'/></body></worldbody></mujoco>"
2951 )).unwrap();
2952
2953 let model = asset_model("", TET, CUBE);
2954 let more_userdata = asset_model("<size nuserdata='2000'/>", TET, CUBE);
2955 assert!(!model.is_compatible_with_model(&more_userdata), "the mjData buffers differ");
2956 assert!(model.is_asset_compatible_with_model(&more_userdata));
2957
2958 let swapped = asset_model("", CUBE, TET);
2960 assert_eq!(model.nmeshvert(), swapped.nmeshvert(), "the vertex totals must agree");
2961 assert!(!model.is_asset_compatible_with_model(&swapped));
2962
2963 let sensor = asset_model("<sensor><user dim='4' objtype='body' objname='b'/></sensor>", TET, CUBE);
2965 assert!(!model.is_compatible_with_model(&sensor), "the element tables differ");
2966 assert!(model.is_asset_compatible_with_model(&sensor));
2967 }
2968
2969 #[test]
2975 fn test_is_asset_compatible_with_model_rejects_a_different_texture_kind() {
2976 let textured = |kind: &str, width: u32, height: u32| MjModel::from_xml_string(&format!(
2977 "<mujoco><asset><texture name='t' type='{kind}' builtin='flat' width='{width}' \
2978 height='{height}' rgb1='1 1 1'/><material name='m' texture='t'/></asset>\
2979 <worldbody><geom size='0.1' material='m'/></worldbody></mujoco>"
2980 )).unwrap();
2981
2982 let flat = textured("2d", 8, 48);
2983 let cube = textured("cube", 8, 8);
2984 assert_eq!(flat.tex_width(), cube.tex_width(), "the widths must agree");
2985 assert_eq!(flat.tex_height(), cube.tex_height(), "the compiler gives the cube 6 * width");
2986 assert_eq!(flat.tex_nchannel(), cube.tex_nchannel(), "the channel counts must agree");
2987 assert_eq!(flat.ntexdata(), cube.ntexdata(), "the pair holds the same number of bytes");
2988 assert_ne!(flat.tex_type(), cube.tex_type());
2989
2990 assert!(!flat.is_asset_compatible_with_model(&cube));
2991 assert!(!flat.is_compatible_with_model(&cube));
2992 assert!(flat.is_asset_compatible_with_model(&textured("2d", 8, 48)));
2993 }
2994
2995 #[test]
3001 fn test_every_dropped_size_follows_from_a_count_table() {
3002 let model = MjModel::from_xml_string(
3003 "<mujoco><asset>\
3004 <texture name='t1' type='2d' builtin='checker' width='17' height='23' rgb1='1 0 0' rgb2='0 1 0'/>\
3005 <texture name='t2' type='cube' builtin='flat' width='8' height='8' rgb1='1 1 1'/>\
3006 <texture name='t3' type='2d' builtin='flat' width='4' height='6' nchannel='4' rgb1='1 1 1'/>\
3007 <material name='mat' texture='t1'/>\
3008 <hfield name='hf' nrow='7' ncol='11' size='1 1 1 0.1'/>\
3009 <mesh name='ms' vertex='0 0 0 1 0 0 0 1 0 0 0 1' texcoord='0 0 1 0 0 1 1 1'/>\
3010 </asset><worldbody>\
3011 <geom type='hfield' hfield='hf'/>\
3012 <body name='free'><freejoint/><geom size='0.1' type='mesh' mesh='ms' material='mat'/>\
3013 <site name='s1'/>\
3014 <body name='ball' pos='0 0 0.2'><joint name='jb' type='ball'/><geom size='0.1'/>\
3015 <body name='slide' pos='0 0 0.2'><joint name='js' type='slide'/><geom size='0.1'/>\
3016 <site name='s2'/><joint name='jh' type='hinge'/></body></body></body>\
3017 <body name='v0'><freejoint/><geom size='0.01'/></body>\
3018 <body name='v1' pos='0.1 0 0'><freejoint/><geom size='0.01'/></body>\
3019 <body name='v2' pos='0.2 0 0'><freejoint/><geom size='0.01'/></body>\
3020 </worldbody>\
3021 <deformable><flex name='f1' dim='1' body='v0 v1 v2' vertex='0 0 0 0 0 0 0 0 0' element='0 1 1 2'>\
3022 <edge damping='1'/></flex>\
3023 </deformable>\
3024 <tendon><spatial name='td'><site site='s1'/><site site='s2'/></spatial>\
3025 <fixed name='tf'><joint joint='js' coef='1'/><joint joint='jh' coef='2'/></fixed></tendon>\
3026 <actuator><motor joint='jh'/><position joint='js' kp='3'/>\
3027 <general joint='jh' dyntype='integrator'/><general joint='js' dyntype='filter' dynprm='0.1'/>\
3028 <general tendon='tf' dyntype='filterexact' dynprm='0.1'/>\
3029 <orientation site='s2' refsite='s1' kp='1' input='expmap'/>\
3030 <orientation site='s2' refsite='s1' kp='1' input='quat'/>\
3031 <pid joint='jh' kp='1' kv='1' input='pos vel'/></actuator>\
3032 <sensor><framepos objtype='site' objname='s1'/><framequat objtype='site' objname='s1'/>\
3033 <jointpos joint='jh'/><accelerometer site='s2'/><tendonpos tendon='td'/>\
3034 <user objtype='site' objname='s2' dim='5' needstage='vel'/></sensor></mujoco>"
3035 ).unwrap();
3036
3037 let sum = |table: &[i32]| table.iter().map(|&n| i64::from(n)).sum::<MjtSize>();
3038 let product = |first: &[i32], second: &[i32]| first.iter().zip(second)
3039 .map(|(&a, &b)| i64::from(a) * i64::from(b)).sum::<MjtSize>();
3040
3041 let qpos = |joint: &MjtJoint| match joint {
3043 MjtJoint::mjJNT_FREE => 7, MjtJoint::mjJNT_BALL => 4, _ => 1,
3044 };
3045 let dof = |joint: &MjtJoint| match joint {
3046 MjtJoint::mjJNT_FREE => 6, MjtJoint::mjJNT_BALL => 3, _ => 1,
3047 };
3048 assert_eq!(model.nq(), model.jnt_type().iter().map(qpos).sum::<MjtSize>());
3049 assert_eq!(model.nv(), model.jnt_type().iter().map(dof).sum::<MjtSize>());
3050
3051 let (mut qpos_adr, mut dof_adr) = (0, 0);
3054 for (id, joint) in model.jnt_type().iter().enumerate() {
3055 assert_eq!(i64::from(model.jnt_qposadr()[id]), qpos_adr, "joint {id}");
3056 assert_eq!(i64::from(model.jnt_dofadr()[id]), dof_adr, "joint {id}");
3057 qpos_adr += qpos(joint);
3058 dof_adr += dof(joint);
3059 }
3060
3061 assert_eq!(model.nsensordata(), sum(model.sensor_dim()));
3062 assert_eq!(model.na(), sum(model.actuator_actnum()));
3063 assert_eq!(model.nu(), sum(model.actuator_ctrlnum()));
3064 assert_eq!(model.nout(), sum(model.actuator_outnum()));
3065 assert_eq!(model.n_jten(), sum(model.ten_j_rownnz()));
3066 assert_eq!(model.nflexvert(), sum(model.flex_vertnum()));
3067 assert_eq!(model.nflexelem(), sum(model.flex_elemnum()));
3068
3069 assert_eq!(model.nmeshvert(), sum(model.mesh_vertnum()));
3070 assert_eq!(model.nmeshnormal(), sum(model.mesh_normalnum()));
3071 assert_eq!(model.nmeshtexcoord(), sum(model.mesh_texcoordnum()));
3072 assert_eq!(model.nmeshface(), sum(model.mesh_facenum()));
3073 assert_eq!(model.nhfielddata(), product(model.hfield_nrow(), model.hfield_ncol()));
3074 assert_eq!(model.ntexdata(), model.tex_width().iter().zip(model.tex_height())
3075 .zip(model.tex_nchannel())
3076 .map(|((&w, &h), &c)| i64::from(w) * i64::from(h) * i64::from(c)).sum::<MjtSize>());
3077
3078 assert!(model.na() > 0 && model.nu() > 0 && model.nout() > 0);
3080 assert!(model.nsensordata() > 6 && model.n_jten() > 0);
3081 assert!(model.nflexvert() > 0 && model.nflexelem() > 0 && model.nmeshvert() > 0);
3082 assert_eq!(model.nhfielddata(), 77, "the heightfield must survive the compiler");
3083 assert!(model.ntexdata() > 0 && model.nmeshtexcoord() > 0);
3084 assert!(model.actuator_ctrlnum().contains(&4) && model.actuator_outnum().contains(&3));
3087 assert!(model.tex_nchannel().contains(&3) && model.tex_nchannel().contains(&4));
3088 }
3089
3090 #[test]
3091 fn test_is_compatible_with_model_accepts_parameter_change() {
3092 let light = compat_model("", "<motor joint='j' gear='1'/>");
3093 let heavy = compat_model("", "<motor joint='j' gear='7'/>");
3094 assert!(light.is_compatible_with_model(&heavy));
3095 assert!(light.is_compatible_with_model(&light));
3096 }
3097
3098 #[test]
3099 fn test_try_view_model_signature_mismatch() {
3100 let model1 = MjModel::from_xml_string("<mujoco><worldbody><body name='b1'><joint type='free'/><geom size='0.1'/></body></worldbody></mujoco>").unwrap();
3101 let mut model2 = MjModel::from_xml_string("<mujoco><worldbody><body name='b1'><joint type='free'/><geom size='0.1'/></body><body name='extra'/></worldbody></mujoco>").unwrap();
3102
3103 let body_info = model1.body("b1").unwrap();
3104
3105 let err = body_info.try_view(&model2).unwrap_err();
3106 match err {
3107 MjModelError::IncompatibleModel { source, destination } => {
3108 assert_eq!(source, model1.signature());
3109 assert_eq!(destination, model2.signature());
3110 }
3111 other => panic!("expected IncompatibleModel, got {other:?}"),
3112 }
3113
3114 let err = body_info.try_view_mut(&mut model2).unwrap_err();
3115 match err {
3116 MjModelError::IncompatibleModel { source, destination } => {
3117 assert_eq!(source, model1.signature());
3118 assert_eq!(destination, model2.signature());
3119 }
3120 other => panic!("expected IncompatibleModel, got {other:?}"),
3121 }
3122 }
3123
3124 #[test]
3125 #[should_panic(expected = "the model is not compatible")]
3126 fn test_view_mut_model_signature_mismatch_panics() {
3127 let model1 = MjModel::from_xml_string("<mujoco><worldbody><body name='b1'><joint type='free'/><geom size='0.1'/></body></worldbody></mujoco>").unwrap();
3128 let mut model2 = MjModel::from_xml_string("<mujoco><worldbody><body name='b1'><joint type='free'/><geom size='0.1'/></body><body name='extra'/></worldbody></mujoco>").unwrap();
3129
3130 let body_info = model1.body("b1").unwrap();
3131 let _view = body_info.view_mut(&mut model2);
3132 }
3133
3134
3135 #[test]
3136 fn test_camera_model_view() {
3137 let mut model = MjModel::from_xml_string(EXAMPLE_MODEL).expect("unable to load the model.");
3138 let model_info = model.camera("cam1").unwrap();
3139 let view = model_info.view(&model);
3140
3141 assert_eq!(&view.resolution[..], [100, 200]);
3143 assert_eq!(view.fovy[0], 50.0);
3144
3145 let mut view_mut = model_info.view_mut(&mut model);
3147 view_mut.fovy[0] = 60.0;
3148 assert_eq!(view_mut.fovy[0], 60.0);
3149 }
3150
3151 #[test]
3152 fn test_id_2name_valid() {
3153 let model = MjModel::from_xml_string(EXAMPLE_MODEL).expect("unable to load the model.");
3154
3155 let name = model.id_to_name(MjtObj::mjOBJ_BODY, 1);
3157 assert_eq!(name, Some("ball"));
3158 }
3159
3160 #[test]
3161 fn test_model_prints() {
3162 const TMP_FILE: &str = "tmpprint.txt";
3163 let model = MjModel::from_xml_string(EXAMPLE_MODEL).expect("unable to load the model.");
3164 assert!(model.print(TMP_FILE).is_ok());
3165 fs::remove_file(TMP_FILE).unwrap();
3166
3167 assert!(model.print_formatted(TMP_FILE, "%.2f").is_ok());
3168 fs::remove_file(TMP_FILE).unwrap();
3169 }
3170
3171 #[test]
3172 fn test_id_2name_invalid() {
3173 let model = MjModel::from_xml_string(EXAMPLE_MODEL).expect("unable to load the model.");
3174
3175 let name = model.id_to_name(MjtObj::mjOBJ_BODY, 9999);
3177 assert_eq!(name, None);
3178 }
3179
3180 #[test]
3181 fn test_totalmass_set_and_get() {
3182 let mut model = MjModel::from_xml_string(EXAMPLE_MODEL).expect("unable to load the model.");
3183
3184 let mass_before = model.totalmass();
3185 model.set_totalmass(5.0);
3186 let mass_after = model.totalmass();
3187
3188 assert_relative_eq!(mass_after, 5.0, epsilon = 1e-9);
3189 assert_ne!(mass_before, mass_after);
3190 }
3191
3192 #[test]
3194 fn test_copy_model() {
3195 let model = MjModel::from_xml_string(EXAMPLE_MODEL).expect("unable to load the model.");
3196 let _cloned = model.clone();
3197 }
3198
3199 #[test]
3200 fn test_model_save() {
3201 const MODEL_SAVE_PATH: &str = "./__TMP_MODEL2.mjb";
3202 let model = MjModel::from_xml_string(EXAMPLE_MODEL).expect("unable to load the model.");
3203 model.save_to_file(MODEL_SAVE_PATH).unwrap();
3204
3205 let saved_data = fs::read(MODEL_SAVE_PATH).unwrap();
3206 let mut data = vec![0; saved_data.len()];
3207 model.save_to_buffer(&mut data).unwrap();
3208
3209 assert_eq!(saved_data, data);
3210 fs::remove_file(MODEL_SAVE_PATH).unwrap();
3211
3212 let model = MjModel::from_buffer(&saved_data).unwrap();
3214 assert!(model.light("lamp_light2").is_some());
3215 assert!(model.light("lamp_light-xyz").is_none());
3216 }
3217
3218 #[test]
3219 fn test_model_from_mjb() {
3220 const MODEL_SAVE_PATH: &str = "./__TMP_MODEL3.mjb";
3221 const MODEL_VFS_PATH: &str = "__TMP_MODEL3_VFS.mjb";
3222
3223 let model = MjModel::from_xml_string(EXAMPLE_MODEL).expect("unable to load the model.");
3224 model.save_to_file(MODEL_SAVE_PATH).unwrap();
3225
3226 let loaded = MjModel::from_mjb(MODEL_SAVE_PATH).unwrap();
3227 assert!(model.is_compatible_with_model(&loaded));
3228 assert!(loaded.light("lamp_light2").is_some());
3229
3230 let mut vfs = MjVfs::new();
3232 vfs.add_from_buffer(MODEL_VFS_PATH, &fs::read(MODEL_SAVE_PATH).unwrap()).unwrap();
3233 fs::remove_file(MODEL_SAVE_PATH).unwrap();
3235
3236 let loaded_vfs = MjModel::from_mjb_vfs(MODEL_VFS_PATH, &vfs).unwrap();
3237 assert!(model.is_compatible_with_model(&loaded_vfs));
3238 assert!(loaded_vfs.light("lamp_light2").is_some());
3239
3240 assert!(MjModel::from_mjb(MODEL_SAVE_PATH).is_err());
3241 }
3242
3243 #[test]
3244 fn test_site_view() {
3245 const BODY_NAME: &str = "ball2";
3247 const SITE_NAME: &str = "ball22";
3248 const SITE_SIZE: [f64; 3] = [0.5, 0.25, 0.5];
3249 const SITE_POS: [f64; 3] = [5.0, 1.0, 3.0];
3250 const SITE_RGBA: [f32; 4] = [1.0, 2.0, 3.0, 1.0];
3251 const SITE_TYPE: MjtGeom = MjtGeom::mjGEOM_BOX;
3252
3253 let model = MjModel::from_xml_string(EXAMPLE_MODEL).expect("unable to load the model.");
3254 let info_ball = model.body(BODY_NAME).unwrap();
3255 let info_site = model.site(SITE_NAME).unwrap();
3256 let view_site = info_site.view(&model);
3257
3258 assert_eq!(info_site.name, SITE_NAME);
3260 assert_eq!(view_site.size[..], SITE_SIZE);
3261 assert_eq!(view_site.pos[..], SITE_POS);
3262 assert_eq!(view_site.rgba[..], SITE_RGBA);
3263 assert_eq!(view_site.r#type[0], SITE_TYPE);
3264
3265 assert_eq!(view_site.bodyid[0] as usize, info_ball.id)
3266 }
3267
3268 #[test]
3269 fn test_pair_view() {
3270 const PAIR_NAME: &str = "geom_pair";
3271 const DIM: i32 = 3;
3272 const GEOM1_NAME: &str = "ball31";
3273 const GEOM2_NAME: &str = "ball32";
3274 const SOLREF: [f64; mjNREF as usize] = [0.02, 1.0];
3275 const SOLREFFRICTION: [f64; mjNREF as usize] = [0.01, 0.5];
3276 const SOLIMP: [f64; mjNIMP as usize] = [0., 0.95, 0.001, 0.5, 2.0];
3277 const MARGIN: f64 = 0.001;
3278 const GAP: f64 = 0.0;
3279 const FRICTION: [f64; 5] = [1.0, 0.8, 0.6, 0.0, 0.0];
3280
3281 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
3282 let info_pair = model.pair(PAIR_NAME).unwrap();
3283 let view_pair = info_pair.view(&model);
3284
3285 let geom1_info = model.geom(GEOM1_NAME).unwrap();
3286 let geom2_info = model.geom(GEOM2_NAME).unwrap();
3287
3288 let signature = ((geom1_info.view(&model).bodyid[0] as u32) << 16) + geom2_info.view(&model).bodyid[0] as u32;
3290
3291 assert_eq!(view_pair.dim[0], DIM);
3292 assert_eq!(view_pair.geom1[0] as usize, geom1_info.id);
3293 assert_eq!(view_pair.geom2[0] as usize, geom2_info.id);
3294 assert_eq!(view_pair.signature[0] as u32, signature);
3295 assert_eq!(view_pair.solref[..], SOLREF);
3296 assert_eq!(view_pair.solreffriction[..], SOLREFFRICTION);
3297 assert_eq!(view_pair.solimp[..], SOLIMP);
3298 assert_eq!(view_pair.margin[0], MARGIN);
3299 assert_eq!(view_pair.gap[0], GAP);
3300 assert_eq!(view_pair.friction[..], FRICTION);
3301 }
3302
3303 #[test]
3304 fn test_key_view() {
3305 const KEY_NAME: &str = "pose1";
3306 const TIME: f64 = 1.5;
3307 const QVEL: &[f64] = &[0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0];
3308 const ACT: &[f64] = &[];
3309 const CTRL: &[f64] = &[0.5, 0.0];
3310
3311 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
3312 let info_key = model.key(KEY_NAME).unwrap();
3313 let view_key = info_key.view(&model);
3314
3315 assert_eq!(view_key.time[0], TIME);
3316 assert_eq!(&view_key.qvel[..model.ffi().nv as usize], QVEL);
3319 assert_eq!(&view_key.act[..model.ffi().na as usize], ACT);
3320 assert_eq!(&view_key.ctrl[..model.ffi().nu as usize], CTRL);
3321
3322 let key_qvel = &model.key_qvel()[model.ffi().nv as usize..];
3323 assert_eq!(key_qvel, QVEL);
3324
3325 let key_act = &model.key_act()[model.ffi().na as usize..];
3326 assert_eq!(key_act, ACT);
3327
3328 let key_ctrl = &model.key_ctrl()[model.ffi().nu as usize..];
3329 assert_eq!(key_ctrl, CTRL);
3330 }
3331
3332 #[test]
3333 fn test_tuple_view() {
3334 const TUPLE_NAME: &str = "tuple_example";
3335 const SIZE: i32 = 2;
3336 const OBJTYPE: &[MjtObj] = &[MjtObj::mjOBJ_BODY, MjtObj::mjOBJ_SITE];
3337 const OBJPRM: &[f64] = &[0.5, 1.0];
3338
3339 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
3340 let info_tuple = model.tuple(TUPLE_NAME).unwrap();
3341 let view_tuple = info_tuple.view(&model);
3342
3343 let objid = &[
3344 model.body("ball2").unwrap().id as i32,
3345 model.site("ball1").unwrap().id as i32,
3346 ];
3347
3348 assert_eq!(view_tuple.size[0], SIZE);
3349 assert_eq!(&view_tuple.objtype[..SIZE as usize], OBJTYPE);
3350 assert_eq!(&view_tuple.objid[..SIZE as usize], objid);
3351 assert_eq!(&view_tuple.objprm[..SIZE as usize], OBJPRM);
3352 }
3353
3354 #[test]
3355 fn test_texture_view() {
3356 const TEX_NAME: &str = "wall_tex";
3357 const TYPE: MjtTexture = MjtTexture::mjTEXTURE_2D; const COLORSPACE: MjtColorSpace = MjtColorSpace::mjCOLORSPACE_SRGB; const HEIGHT: i32 = 128;
3360 const WIDTH: i32 = 128;
3361 const NCHANNEL: i32 = 3;
3362
3363 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
3364 let info_tex = model.texture(TEX_NAME).unwrap();
3365 let view_tex = info_tex.view(&model);
3366
3367 assert_eq!(view_tex.r#type[0], TYPE);
3368 assert_eq!(view_tex.colorspace[0], COLORSPACE);
3369 assert_eq!(view_tex.height[0], HEIGHT);
3370 assert_eq!(view_tex.width[0], WIDTH);
3371 assert_eq!(view_tex.nchannel[0], NCHANNEL);
3372
3373 assert_eq!(view_tex.data.as_ref().unwrap().len(), (WIDTH * HEIGHT * NCHANNEL) as usize);
3374 }
3375
3376 #[test]
3377 fn test_numeric_view() {
3378 const NUMERIC_NAME: &str = "gain_factor2";
3379 const SIZE: i32 = 3;
3380 const DATA: [f64; 3] = [1.25, 5.5, 10.0];
3381
3382 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
3383 let info_numeric = model.numeric(NUMERIC_NAME).unwrap();
3384 let view_numeric = info_numeric.view(&model);
3385
3386 assert_eq!(view_numeric.size[0], SIZE);
3387 assert_eq!(&view_numeric.data.as_ref().unwrap()[..SIZE as usize], DATA);
3388 }
3389
3390 #[test]
3391 fn test_material_view() {
3392 const MATERIAL_NAME: &str = "also_wood_material";
3393
3394 const TEXUNIFORM: bool = false;
3395 const TEXREPEAT: [f32; 2] = [2.0, 2.0];
3396 const EMISSION: f32 = 0.1;
3397 const SPECULAR: f32 = 0.5;
3398 const SHININESS: f32 = 0.7;
3399 const REFLECTANCE: f32 = 0.2;
3400 const METALLIC: f32 = 0.3;
3401 const ROUGHNESS: f32 = 0.5;
3402 const RGBA: [f32; 4] = [0.8, 0.5, 0.3, 1.0];
3403 const TEXID: i32 = -1;
3404
3405 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
3406 let info_material = model.material(MATERIAL_NAME).unwrap();
3407 let view_material = info_material.view(&model);
3408
3409 assert_eq!(view_material.texuniform[0], TEXUNIFORM);
3410 assert_eq!(view_material.texrepeat[..], TEXREPEAT);
3411 assert_eq!(view_material.emission[0], EMISSION);
3412 assert_eq!(view_material.specular[0], SPECULAR);
3413 assert_eq!(view_material.shininess[0], SHININESS);
3414 assert_eq!(view_material.reflectance[0], REFLECTANCE);
3415 assert_eq!(view_material.metallic[0], METALLIC);
3416 assert_eq!(view_material.roughness[0], ROUGHNESS);
3417 assert_eq!(view_material.rgba[..], RGBA);
3418 assert_eq!(view_material.texid[0], TEXID);
3419 }
3420
3421 #[test]
3422 fn test_light_view() {
3423 const LIGHT_NAME: &str = "lamp_light2";
3424 const MODE: MjtCamLight = MjtCamLight::mjCAMLIGHT_FIXED;
3425 const BODYID: usize = 0; const TYPE: MjtLightType = MjtLightType::mjLIGHT_SPOT; const CASTSHADOW: bool = true;
3428 const ACTIVE: bool = true;
3429
3430 const POS: [MjtNum; 3] = [0.0, 0.0, 0.0];
3431 const DIR: [MjtNum; 3] = [0.0, 0.0, -1.0];
3432 const POS0: [MjtNum; 3] = [0.0, 0.0, 0.0];
3433 const DIR0: [MjtNum; 3] = [0.0, 0.0, -1.0];
3434 const ATTENUATION: [f32; 3] = [0.1, 0.05, 0.01];
3435 const CUTOFF: f32 = 45.0;
3436 const EXPONENT: f32 = 2.0;
3437 const AMBIENT: [f32; 3] = [0.1, 0.1, 0.1];
3438 const DIFFUSE: [f32; 3] = [1.0, 1.0, 1.0];
3439 const SPECULAR: [f32; 3] = [1.0, 1.0, 1.0];
3440
3441 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
3442 let info_light = model.light(LIGHT_NAME).unwrap();
3443 let view_light = info_light.view(&model);
3444
3445 assert_eq!(view_light.mode[0], MODE);
3446 assert_eq!(view_light.bodyid[0] as usize, BODYID);
3447 assert_eq!(view_light.targetbodyid[0], -1);
3448 assert_eq!(view_light.r#type[0], TYPE);
3449 assert_eq!(view_light.castshadow[0], CASTSHADOW);
3450 assert_eq!(view_light.active[0], ACTIVE);
3451
3452 assert_eq!(view_light.pos[..], POS);
3453 assert_eq!(view_light.dir[..], DIR);
3454 assert_eq!(view_light.pos0[..], POS0);
3455 assert_eq!(view_light.dir0[..], DIR0);
3456 assert_eq!(view_light.attenuation[..], ATTENUATION);
3457 assert_eq!(view_light.cutoff[0], CUTOFF);
3458 assert_eq!(view_light.exponent[0], EXPONENT);
3459 assert_eq!(view_light.ambient[..], AMBIENT);
3460 assert_eq!(view_light.diffuse[..], DIFFUSE);
3461 assert_eq!(view_light.specular[..], SPECULAR);
3462 }
3463
3464 #[test]
3465 fn test_connect_eq_view() {
3466 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
3467
3468 let info_eq = model.equality("eq3").unwrap();
3470 let view_eq = info_eq.view(&model);
3471
3472 assert_eq!(view_eq.r#type[0], MjtEq::mjEQ_CONNECT);
3474
3475 assert_eq!(view_eq.obj1id[0], model.name_to_id(MjtObj::mjOBJ_BODY, "eq_body3").unwrap() as i32);
3477 assert_eq!(view_eq.obj2id[0], model.name_to_id(MjtObj::mjOBJ_BODY, "eq_body4").unwrap() as i32);
3478 assert_eq!(view_eq.objtype[0], MjtObj::mjOBJ_BODY);
3479
3480 assert!(view_eq.active0[0]);
3482
3483 let anchor = &view_eq.data[0..3];
3485 assert_eq!(anchor[0], 0.0);
3486 assert_eq!(anchor[1], 5.0);
3487 assert_eq!(anchor[2], 0.0);
3488 }
3489
3490 #[test]
3491 fn test_hfield_view() {
3492 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
3493
3494 let info_hf = model.hfield("hf2").unwrap();
3496 let view_hf = info_hf.view(&model);
3497
3498 let expected_size: [f64; 4] = [1.0, 1.0, 1.0, 5.25]; let expected_nrow = 5;
3501 let expected_ncol = 3;
3502 let expected_data: [f32; 15] = [0.0; 15];
3503
3504 assert_eq!(view_hf.size[..], expected_size);
3506 assert_eq!(view_hf.nrow[0], expected_nrow);
3507 assert_eq!(view_hf.ncol[0], expected_ncol);
3508
3509 assert_eq!(view_hf.data.as_ref().unwrap().len(), (expected_nrow * expected_ncol) as usize);
3511 assert_eq!(&view_hf.data.as_ref().unwrap()[..], &expected_data[..]);
3512
3513 assert_eq!(view_hf.pathadr[0], -1);
3515 }
3516
3517 #[test]
3519 fn test_state_extract() {
3520 use crate::wrappers::mj_data::MjtState;
3521
3522 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
3523 let data = MjData::new(&model);
3524
3525 let state_full_physics = data.state(MjtState::mjSTATE_FULLPHYSICS as u32);
3529 let state_physics = data.state(MjtState::mjSTATE_PHYSICS as u32);
3530
3531 let required_size = model.state_size(MjtState::mjSTATE_PHYSICS as u32);
3532 let mut dst_buffer = vec![0.0; required_size].into_boxed_slice();
3533 let _bytes_written = model.extract_state_into(
3534 &state_full_physics, MjtState::mjSTATE_FULLPHYSICS as u32,
3535 &mut dst_buffer, MjtState::mjSTATE_PHYSICS as u32
3536 );
3537
3538 assert_eq!(state_physics, dst_buffer);
3539
3540 let state_full_physics = data.state(MjtState::mjSTATE_FULLPHYSICS as u32);
3544 let state_physics = data.state(MjtState::mjSTATE_PHYSICS as u32);
3545
3546 let dst_buffer = model.extract_state(
3547 &state_full_physics, MjtState::mjSTATE_FULLPHYSICS as u32,
3548 MjtState::mjSTATE_PHYSICS as u32
3549 );
3550
3551 assert_eq!(state_physics, dst_buffer);
3552 }
3553
3554 #[test]
3558 fn test_state_extract_state_invalid_src() {
3559 use crate::wrappers::mj_data::MjtState;
3560
3561 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
3562 let data = MjData::new(&model);
3563
3564 let state_full_physics = data.state(MjtState::mjSTATE_PHYSICS as u32);
3565 let res = model.try_extract_state(
3566 &state_full_physics, MjtState::mjSTATE_FULLPHYSICS as u32,
3567 MjtState::mjSTATE_PHYSICS as u32
3568 );
3569
3570 let err = res.unwrap_err();
3571 assert!(matches!(err, MjModelError::StateSliceLengthMismatch { .. }));
3572 }
3573
3574 #[test]
3575 fn test_state_extract_state_into_invalid_src() {
3576 use crate::wrappers::mj_data::MjtState;
3577
3578 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
3579 let data = MjData::new(&model);
3580
3581 let required_size = model.state_size(MjtState::mjSTATE_PHYSICS as u32);
3582 let mut dst_buffer = vec![0.0; required_size].into_boxed_slice();
3583 let state_full_physics = data.state(MjtState::mjSTATE_PHYSICS as u32);
3584 let res = model.try_extract_state_into(
3585 &state_full_physics, MjtState::mjSTATE_FULLPHYSICS as u32,
3586 &mut dst_buffer, MjtState::mjSTATE_PHYSICS as u32
3587 );
3588
3589 let err = res.unwrap_err();
3590 assert!(matches!(err, MjModelError::StateSliceLengthMismatch { .. }));
3591 }
3592
3593 #[test]
3594 fn test_state_extract_dst_spec_not_subset() {
3595 use crate::wrappers::mj_data::MjtState;
3596
3597 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
3598 let data = MjData::new(&model);
3599
3600 let state_physics = data.state(MjtState::mjSTATE_PHYSICS as u32);
3601 let res = model.try_extract_state(
3602 &state_physics, MjtState::mjSTATE_PHYSICS as u32,
3603 MjtState::mjSTATE_FULLPHYSICS as u32
3604 );
3605
3606 let err = res.unwrap_err();
3607 assert!(matches!(err, MjModelError::SpecNotSubset));
3608 }
3609
3610 #[test]
3611 fn test_state_extract_into_dst_spec_not_subset() {
3612 use crate::wrappers::mj_data::MjtState;
3613
3614 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
3615 let data = MjData::new(&model);
3616
3617 let state_physics = data.state(MjtState::mjSTATE_PHYSICS as u32);
3618 let mut dst = vec![0.0; model.state_size(MjtState::mjSTATE_PHYSICS as u32)];
3619
3620 let res = model.try_extract_state_into(
3621 &state_physics, MjtState::mjSTATE_PHYSICS as u32,
3622 &mut dst, MjtState::mjSTATE_FULLPHYSICS as u32
3623 );
3624
3625 let err = res.unwrap_err();
3626 assert!(matches!(err, MjModelError::SpecNotSubset));
3627 }
3628
3629 #[test]
3630 fn test_state_extract_into_dst_buffer_too_small() {
3631 use crate::wrappers::mj_data::MjtState;
3632
3633 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
3634 let data = MjData::new(&model);
3635
3636 let state_full = data.state(MjtState::mjSTATE_FULLPHYSICS as u32);
3637 let required = model.state_size(MjtState::mjSTATE_PHYSICS as u32);
3638 let mut dst = vec![0.0; required.saturating_sub(1)];
3640
3641 let res = model.try_extract_state_into(
3642 &state_full, MjtState::mjSTATE_FULLPHYSICS as u32,
3643 &mut dst, MjtState::mjSTATE_PHYSICS as u32
3644 );
3645
3646 let err = res.unwrap_err();
3647 assert!(matches!(err, MjModelError::BufferTooSmall { .. }));
3648 }
3649
3650 #[test]
3651 fn test_state_extract_zero_spec() {
3652 use crate::wrappers::mj_data::MjtState;
3653
3654 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
3655 let data = MjData::new(&model);
3656
3657 let state_full = data.state(MjtState::mjSTATE_FULLPHYSICS as u32);
3658
3659 let dst = model.extract_state(&state_full, MjtState::mjSTATE_FULLPHYSICS as u32, 0u32);
3661 assert!(dst.is_empty());
3662
3663 let buf: &mut [f64] = &mut [];
3665 let written = model.extract_state_into(&state_full, MjtState::mjSTATE_FULLPHYSICS as u32, buf, 0u32);
3666 assert_eq!(written, 0);
3667 }
3668
3669 #[test]
3676 fn test_force_cast_body_model_arrays() {
3677 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
3678 let nbody = model.ffi().nbody as usize;
3679
3680 let body_pos = model.body_pos();
3681 let body_quat = model.body_quat();
3682 let body_inertia = model.body_inertia();
3683 let body_invweight0 = model.body_invweight0();
3684 let body_ipos = model.body_ipos();
3685 let body_iquat = model.body_iquat();
3686
3687 assert_eq!(body_pos.len(), nbody);
3688 assert_eq!(body_quat.len(), nbody);
3689 assert_eq!(body_inertia.len(), nbody);
3690 assert_eq!(body_invweight0.len(), nbody);
3691 assert_eq!(body_ipos.len(), nbody);
3692 assert_eq!(body_iquat.len(), nbody);
3693
3694 for i in 0..nbody {
3696 for j in 0..3 {
3697 assert_eq!(body_pos[i][j], unsafe { *model.ffi().body_pos.add(i * 3 + j) },
3698 "body_pos[{}][{}] mismatch", i, j);
3699 }
3700 for j in 0..4 {
3701 assert_eq!(body_quat[i][j], unsafe { *model.ffi().body_quat.add(i * 4 + j) },
3702 "body_quat[{}][{}] mismatch", i, j);
3703 }
3704 for j in 0..3 {
3705 assert_eq!(body_inertia[i][j], unsafe { *model.ffi().body_inertia.add(i * 3 + j) },
3706 "body_inertia[{}][{}] mismatch", i, j);
3707 }
3708 for j in 0..2 {
3709 assert_eq!(body_invweight0[i][j], unsafe { *model.ffi().body_invweight0.add(i * 2 + j) },
3710 "body_invweight0[{}][{}] mismatch", i, j);
3711 }
3712 }
3713 }
3714
3715 #[test]
3717 fn test_force_cast_jnt_type_enum() {
3718 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
3719 let njnt = model.ffi().njnt as usize;
3720 let jnt_type = model.jnt_type();
3721
3722 assert_eq!(jnt_type.len(), njnt);
3723
3724 for i in 0..njnt {
3726 let raw_i32 = unsafe { *model.ffi().jnt_type.add(i) };
3727 let expected: MjtJoint = unsafe { crate::util::force_cast(raw_i32) };
3728 assert_eq!(jnt_type[i], expected,
3729 "jnt_type[{}]: got {:?}, expected {:?} (raw={})", i, jnt_type[i], expected, raw_i32);
3730 }
3731
3732 let ball_jnt = model.joint("ball").unwrap();
3734 assert_eq!(jnt_type[ball_jnt.id], MjtJoint::mjJNT_FREE);
3735
3736 let rod_jnt = model.joint("rod").unwrap();
3737 assert_eq!(jnt_type[rod_jnt.id], MjtJoint::mjJNT_SLIDE);
3738 }
3739
3740 #[test]
3742 fn test_force_cast_jnt_limited_bool() {
3743 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
3744 let njnt = model.ffi().njnt as usize;
3745 let jnt_limited = model.jnt_limited();
3746
3747 assert_eq!(jnt_limited.len(), njnt);
3748
3749 for i in 0..njnt {
3751 let raw_bool = unsafe { *model.ffi().jnt_limited.add(i) };
3752 assert_eq!(jnt_limited[i], raw_bool,
3753 "jnt_limited[{}] mismatch: bool={}, raw={}", i, jnt_limited[i], raw_bool);
3754 }
3755
3756 let rod_jnt = model.joint("rod").unwrap();
3758 assert!(jnt_limited[rod_jnt.id]);
3759
3760 let ball_jnt = model.joint("ball").unwrap();
3761 assert!(!jnt_limited[ball_jnt.id]);
3762 }
3763
3764 #[test]
3766 fn test_force_cast_geom_type_enum() {
3767 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
3768 let ngeom = model.ffi().ngeom as usize;
3769 let geom_type = model.geom_type();
3770
3771 assert_eq!(geom_type.len(), ngeom);
3772
3773 for i in 0..ngeom {
3775 let raw_i32 = unsafe { *model.ffi().geom_type.add(i) };
3776 let expected: MjtGeom = unsafe { crate::util::force_cast(raw_i32) };
3777 assert_eq!(geom_type[i], expected);
3778 }
3779
3780 let sphere_geom = model.geom("green_sphere").unwrap();
3782 assert_eq!(geom_type[sphere_geom.id], MjtGeom::mjGEOM_SPHERE);
3783
3784 let floor_geom = model.geom("floor").unwrap();
3785 assert_eq!(geom_type[floor_geom.id], MjtGeom::mjGEOM_PLANE);
3786
3787 let rod_geom = model.geom("rod").unwrap();
3788 assert_eq!(geom_type[rod_geom.id], MjtGeom::mjGEOM_CYLINDER);
3789 }
3790
3791 #[test]
3795 fn test_force_cast_geom_model_arrays() {
3796 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
3797 let ngeom = model.ffi().ngeom as usize;
3798
3799 let geom_size = model.geom_size();
3800 let geom_pos = model.geom_pos();
3801 let geom_quat = model.geom_quat();
3802 let geom_rgba = model.geom_rgba();
3803 let geom_friction = model.geom_friction();
3804 let geom_aabb = model.geom_aabb();
3805
3806 assert_eq!(geom_size.len(), ngeom);
3807 assert_eq!(geom_pos.len(), ngeom);
3808 assert_eq!(geom_quat.len(), ngeom);
3809 assert_eq!(geom_rgba.len(), ngeom);
3810 assert_eq!(geom_friction.len(), ngeom);
3811 assert_eq!(geom_aabb.len(), ngeom);
3812
3813 for i in 0..ngeom {
3815 for j in 0..3 {
3816 assert_eq!(geom_size[i][j], unsafe { *model.ffi().geom_size.add(i * 3 + j) });
3817 assert_eq!(geom_pos[i][j], unsafe { *model.ffi().geom_pos.add(i * 3 + j) });
3818 assert_eq!(geom_friction[i][j], unsafe { *model.ffi().geom_friction.add(i * 3 + j) });
3819 }
3820 for j in 0..4 {
3821 assert_eq!(geom_quat[i][j], unsafe { *model.ffi().geom_quat.add(i * 4 + j) });
3822 assert_eq!(geom_rgba[i][j], unsafe { *model.ffi().geom_rgba.add(i * 4 + j) });
3823 }
3824 for j in 0..6 {
3825 assert_eq!(geom_aabb[i][j], unsafe { *model.ffi().geom_aabb.add(i * 6 + j) });
3826 }
3827 }
3828
3829 let gs = model.geom("green_sphere").unwrap();
3831 assert_eq!(geom_rgba[gs.id], [0.0f32, 1.0, 0.0, 1.0]);
3832 assert_relative_eq!(geom_size[gs.id][0], 0.1, epsilon = 1e-9);
3833 }
3834
3835 #[test]
3839 fn test_force_cast_camera_model_arrays() {
3840 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
3841 let ncam = model.ffi().ncam as usize;
3842
3843 if ncam == 0 {
3844 return;
3846 }
3847
3848 let cam_mode = model.cam_mode();
3849 let cam_projection = model.cam_projection();
3850 let cam_resolution = model.cam_resolution();
3851 let cam_sensorsize = model.cam_sensorsize();
3852 let cam_intrinsic = model.cam_intrinsic();
3853 let cam_mat0 = model.cam_mat0();
3854
3855 assert_eq!(cam_mode.len(), ncam);
3856 assert_eq!(cam_projection.len(), ncam);
3857 assert_eq!(cam_resolution.len(), ncam);
3858 assert_eq!(cam_sensorsize.len(), ncam);
3859 assert_eq!(cam_intrinsic.len(), ncam);
3860 assert_eq!(cam_mat0.len(), ncam);
3861
3862 for i in 0..ncam {
3864 let raw_mode = unsafe { *model.ffi().cam_mode.add(i) };
3865 let expected_mode: MjtCamLight = unsafe { crate::util::force_cast(raw_mode) };
3866 assert_eq!(cam_mode[i], expected_mode);
3867
3868 let raw_proj = unsafe { *model.ffi().cam_projection.add(i) };
3869 let expected_proj: MjtProjection = unsafe { crate::util::force_cast(raw_proj) };
3870 assert_eq!(cam_projection[i], expected_proj);
3871
3872 for j in 0..2 {
3873 assert_eq!(cam_resolution[i][j], unsafe { *model.ffi().cam_resolution.add(i * 2 + j) });
3874 assert_eq!(cam_sensorsize[i][j], unsafe { *model.ffi().cam_sensorsize.add(i * 2 + j) });
3875 }
3876 for j in 0..4 {
3877 assert_eq!(cam_intrinsic[i][j], unsafe { *model.ffi().cam_intrinsic.add(i * 4 + j) });
3878 }
3879 for j in 0..9 {
3880 assert_eq!(cam_mat0[i][j], unsafe { *model.ffi().cam_mat0.add(i * 9 + j) });
3881 }
3882 }
3883
3884 let cam1 = model.camera("cam1").unwrap();
3886 assert_eq!(cam_resolution[cam1.id], [100, 200]);
3887 }
3888
3889 #[test]
3891 fn test_force_cast_bvh_model_arrays() {
3892 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
3893 let nbvh = model.ffi().nbvh as usize;
3894
3895 let bvh_child = model.bvh_child();
3896 assert_eq!(bvh_child.len(), nbvh);
3897
3898 for i in 0..nbvh {
3899 for j in 0..2 {
3900 assert_eq!(bvh_child[i][j], unsafe { *model.ffi().bvh_child.add(i * 2 + j) });
3901 }
3902 }
3903
3904 let nbvhstatic = model.ffi().nbvhstatic as usize;
3905 let bvh_aabb = model.bvh_aabb();
3906 assert_eq!(bvh_aabb.len(), nbvhstatic);
3907
3908 for i in 0..nbvhstatic {
3909 for j in 0..6 {
3910 assert_eq!(bvh_aabb[i][j], unsafe { *model.ffi().bvh_aabb.add(i * 6 + j) });
3911 }
3912 }
3913 }
3914
3915 #[test]
3917 fn test_force_cast_sameframe_enum() {
3918 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
3919 let nbody = model.ffi().nbody as usize;
3920 let ngeom = model.ffi().ngeom as usize;
3921
3922 let body_sameframe = model.body_sameframe();
3923 let geom_sameframe = model.geom_sameframe();
3924
3925 assert_eq!(body_sameframe.len(), nbody);
3926 assert_eq!(geom_sameframe.len(), ngeom);
3927
3928 for i in 0..nbody {
3929 let raw = unsafe { *model.ffi().body_sameframe.add(i) };
3930 let expected: MjtSameFrame = unsafe { crate::util::force_cast(raw) };
3931 assert_eq!(body_sameframe[i], expected, "body_sameframe[{}] mismatch", i);
3932 }
3933
3934 for i in 0..ngeom {
3935 let raw = unsafe { *model.ffi().geom_sameframe.add(i) };
3936 let expected: MjtSameFrame = unsafe { crate::util::force_cast(raw) };
3937 assert_eq!(geom_sameframe[i], expected, "geom_sameframe[{}] mismatch", i);
3938 }
3939 }
3940
3941 #[test]
3944 fn test_force_cast_equality_model_arrays() {
3945 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
3946 let neq = model.ffi().neq as usize;
3947
3948 if neq == 0 {
3949 return;
3950 }
3951
3952 let eq_type = model.eq_type();
3953 let eq_objtype = model.eq_objtype();
3954 let eq_active0 = model.eq_active0();
3955
3956 assert_eq!(eq_type.len(), neq);
3957 assert_eq!(eq_objtype.len(), neq);
3958 assert_eq!(eq_active0.len(), neq);
3959
3960 for i in 0..neq {
3962 let raw_type = unsafe { *model.ffi().eq_type.add(i) };
3963 let expected_type: MjtEq = unsafe { crate::util::force_cast(raw_type) };
3964 assert_eq!(eq_type[i], expected_type);
3965
3966 let raw_objtype = unsafe { *model.ffi().eq_objtype.add(i) };
3967 let expected_objtype: MjtObj = unsafe { crate::util::force_cast(raw_objtype) };
3968 assert_eq!(eq_objtype[i], expected_objtype);
3969
3970 let raw_active = unsafe { *model.ffi().eq_active0.add(i) };
3971 assert_eq!(eq_active0[i], raw_active);
3972 }
3973
3974 let eq1 = model.equality("eq1").unwrap();
3976 assert_eq!(eq_type[eq1.id], MjtEq::mjEQ_CONNECT);
3977 assert_eq!(eq_objtype[eq1.id], MjtObj::mjOBJ_BODY);
3978 assert!(eq_active0[eq1.id]);
3979 }
3980
3981 #[test]
3984 fn test_force_cast_sensor_model_enums() {
3985 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
3986 let nsensor = model.ffi().nsensor as usize;
3987
3988 if nsensor == 0 {
3989 return;
3990 }
3991
3992 let sensor_type = model.sensor_type();
3993 let sensor_datatype = model.sensor_datatype();
3994 let sensor_needstage = model.sensor_needstage();
3995 let sensor_objtype = model.sensor_objtype();
3996 let sensor_reftype = model.sensor_reftype();
3997
3998 assert_eq!(sensor_type.len(), nsensor);
3999 assert_eq!(sensor_datatype.len(), nsensor);
4000 assert_eq!(sensor_needstage.len(), nsensor);
4001 assert_eq!(sensor_objtype.len(), nsensor);
4002 assert_eq!(sensor_reftype.len(), nsensor);
4003
4004 for i in 0..nsensor {
4005 let raw_type = unsafe { *model.ffi().sensor_type.add(i) };
4006 let raw_datatype = unsafe { *model.ffi().sensor_datatype.add(i) };
4007 let raw_needstage = unsafe { *model.ffi().sensor_needstage.add(i) };
4008 let raw_objtype = unsafe { *model.ffi().sensor_objtype.add(i) };
4009 let raw_reftype = unsafe { *model.ffi().sensor_reftype.add(i) };
4010
4011 assert_eq!(sensor_type[i], unsafe { crate::util::force_cast::<_, MjtSensor>(raw_type) });
4012 assert_eq!(sensor_datatype[i], unsafe { crate::util::force_cast::<_, MjtDataType>(raw_datatype) });
4013 assert_eq!(sensor_needstage[i], unsafe { crate::util::force_cast::<_, MjtStage>(raw_needstage) });
4014 assert_eq!(sensor_objtype[i], unsafe { crate::util::force_cast::<_, MjtObj>(raw_objtype) });
4015 assert_eq!(sensor_reftype[i], unsafe { crate::util::force_cast::<_, MjtObj>(raw_reftype) });
4016 }
4017
4018 let touch = model.sensor("touch").unwrap();
4020 assert_eq!(sensor_type[touch.id], MjtSensor::mjSENS_TOUCH);
4021 assert_eq!(sensor_objtype[touch.id], MjtObj::mjOBJ_SITE);
4022 }
4023
4024 #[test]
4027 fn test_force_cast_actuator_model_enums_and_bools() {
4028 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
4029 let nactuator = model.ffi().nactuator as usize;
4030 let nu = model.ffi().nu as usize;
4031
4032 if nactuator == 0 {
4033 return;
4034 }
4035
4036 let trntype = model.actuator_trntype();
4037 let dyntype = model.actuator_dyntype();
4038 let gaintype = model.actuator_gaintype();
4039 let biastype = model.actuator_biastype();
4040 let ctrllimited = model.actuator_ctrllimited();
4041 let forcelimited = model.actuator_forcelimited();
4042 let actlimited = model.actuator_actlimited();
4043 let actearly = model.actuator_actearly();
4044
4045 assert_eq!(trntype.len(), nactuator);
4046 assert_eq!(dyntype.len(), nactuator);
4047 assert_eq!(gaintype.len(), nactuator);
4048 assert_eq!(biastype.len(), nactuator);
4049 assert_eq!(ctrllimited.len(), nu);
4050 assert_eq!(forcelimited.len(), nactuator);
4051 assert_eq!(actlimited.len(), nactuator);
4052 assert_eq!(actearly.len(), nactuator);
4053
4054 for i in 0..nactuator {
4055 assert_eq!(trntype[i], unsafe { crate::util::force_cast::<_, MjtTrn>(*model.ffi().actuator_trntype.add(i)) });
4057 assert_eq!(dyntype[i], unsafe { crate::util::force_cast::<_, MjtDyn>(*model.ffi().actuator_dyntype.add(i)) });
4058 assert_eq!(gaintype[i], unsafe { crate::util::force_cast::<_, MjtGain>(*model.ffi().actuator_gaintype.add(i)) });
4059 assert_eq!(biastype[i], unsafe { crate::util::force_cast::<_, MjtBias>(*model.ffi().actuator_biastype.add(i)) });
4060
4061 let raw_ctrllimited = unsafe { *model.ffi().actuator_ctrllimited.add(i) };
4063 assert_eq!(ctrllimited[i], raw_ctrllimited);
4064 let raw_forcelimited = unsafe { *model.ffi().actuator_forcelimited.add(i) };
4065 assert_eq!(forcelimited[i], raw_forcelimited);
4066 let raw_actlimited = unsafe { *model.ffi().actuator_actlimited.add(i) };
4067 assert_eq!(actlimited[i], raw_actlimited);
4068 let raw_actearly = unsafe { *model.ffi().actuator_actearly.add(i) };
4069 assert_eq!(actearly[i], raw_actearly);
4070 }
4071
4072 let slider = model.actuator("slider").unwrap();
4074 assert_eq!(biastype[slider.id], MjtBias::mjBIAS_AFFINE);
4075 assert_eq!(gaintype[slider.id], MjtGain::mjGAIN_FIXED);
4076 assert!(ctrllimited[slider.id]);
4077 }
4078
4079 #[test]
4082 fn test_force_cast_actuator_param_arrays() {
4083 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
4084 let nactuator = model.ffi().nactuator as usize;
4085 let nout = model.ffi().nout as usize;
4086 let nu = model.ffi().nu as usize;
4087
4088 let dynprm = model.actuator_dynprm();
4089 let ctrlrange = model.actuator_ctrlrange();
4090 let gear = model.actuator_gear();
4091 let trnid = model.actuator_trnid();
4092
4093 assert_eq!(dynprm.len(), nactuator);
4094 assert_eq!(ctrlrange.len(), nu);
4095 assert_eq!(gear.len(), nout);
4096 assert_eq!(trnid.len(), nactuator);
4097
4098 let slider = model.actuator("slider").unwrap();
4100 assert_eq!(dynprm[slider.id][0..10], [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]);
4101 assert_eq!(ctrlrange[slider.id], [0.0, 1.0]);
4102
4103 let slider2 = model.actuator("slider2").unwrap();
4105 assert_eq!(dynprm[slider2.id][0..10], [10.0, 9.0, 8.0, 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0]);
4106
4107 for i in 0..nout {
4109 for j in 0..6 {
4110 assert_eq!(gear[i][j], unsafe { *model.ffi().actuator_gear.add(i * 6 + j) });
4111 }
4112 }
4113 for i in 0..nactuator {
4114 for j in 0..2 {
4115 assert_eq!(trnid[i][j], unsafe { *model.ffi().actuator_trnid.add(i * 2 + j) });
4116 }
4117 }
4118 for i in 0..nu {
4119 for j in 0..2 {
4120 assert_eq!(ctrlrange[i][j], unsafe { *model.ffi().actuator_ctrlrange.add(i * 2 + j) });
4121 }
4122 }
4123 }
4124
4125 #[test]
4128 fn test_force_cast_tendon_model_arrays() {
4129 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
4130 let ntendon = model.ffi().ntendon as usize;
4131
4132 if ntendon == 0 {
4133 return;
4134 }
4135
4136 let tendon_limited = model.tendon_limited();
4137 let tendon_range = model.tendon_range();
4138 let tendon_rgba = model.tendon_rgba();
4139 let tendon_treeid = model.tendon_treeid();
4140 let tendon_lengthspring = model.tendon_lengthspring();
4141
4142 assert_eq!(tendon_limited.len(), ntendon);
4143 assert_eq!(tendon_range.len(), ntendon);
4144 assert_eq!(tendon_rgba.len(), ntendon);
4145 assert_eq!(tendon_treeid.len(), ntendon);
4146 assert_eq!(tendon_lengthspring.len(), ntendon);
4147
4148 for i in 0..ntendon {
4149 let raw_limited = unsafe { *model.ffi().tendon_limited.add(i) };
4150 assert_eq!(tendon_limited[i], raw_limited);
4151
4152 for j in 0..2 {
4153 assert_eq!(tendon_range[i][j], unsafe { *model.ffi().tendon_range.add(i * 2 + j) });
4154 assert_eq!(tendon_treeid[i][j], unsafe { *model.ffi().tendon_treeid.add(i * 2 + j) });
4155 assert_eq!(tendon_lengthspring[i][j], unsafe { *model.ffi().tendon_lengthspring.add(i * 2 + j) });
4156 }
4157
4158 for j in 0..4 {
4159 assert_eq!(tendon_rgba[i][j], unsafe { *model.ffi().tendon_rgba.add(i * 4 + j) });
4160 }
4161 }
4162
4163 let ten2 = model.tendon("tendon2").unwrap();
4165 assert!(tendon_limited[ten2.id]);
4166 assert_eq!(tendon_range[ten2.id], [0.0, 1.0]);
4167 assert_relative_eq!(tendon_rgba[ten2.id][0], 0.0f32, epsilon = 1e-6);
4168 assert_relative_eq!(tendon_rgba[ten2.id][1], 0.1f32, epsilon = 1e-6);
4169 }
4170
4171 #[test]
4173 fn test_force_cast_texture_model_enums() {
4174 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
4175 let ntex = model.ffi().ntex as usize;
4176
4177 if ntex == 0 {
4178 return;
4179 }
4180
4181 let tex_type = model.tex_type();
4182 let tex_colorspace = model.tex_colorspace();
4183
4184 assert_eq!(tex_type.len(), ntex);
4185 assert_eq!(tex_colorspace.len(), ntex);
4186
4187 for i in 0..ntex {
4188 let raw_type = unsafe { *model.ffi().tex_type.add(i) };
4189 let expected_type: MjtTexture = unsafe { crate::util::force_cast(raw_type) };
4190 assert_eq!(tex_type[i], expected_type);
4191
4192 let raw_cs = unsafe { *model.ffi().tex_colorspace.add(i) };
4193 let expected_cs: MjtColorSpace = unsafe { crate::util::force_cast(raw_cs) };
4194 assert_eq!(tex_colorspace[i], expected_cs);
4195 }
4196
4197 let wall_tex = model.texture("wall_tex").unwrap();
4199 assert_eq!(tex_type[wall_tex.id], MjtTexture::mjTEXTURE_2D);
4200 assert_eq!(tex_colorspace[wall_tex.id], MjtColorSpace::mjCOLORSPACE_SRGB);
4201 }
4202
4203 #[test]
4206 fn test_force_cast_material_model_arrays() {
4207 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
4208 let nmat = model.ffi().nmat as usize;
4209
4210 if nmat == 0 {
4211 return;
4212 }
4213
4214 let mat_texuniform = model.mat_texuniform();
4215 let mat_texrepeat = model.mat_texrepeat();
4216 let mat_rgba = model.mat_rgba();
4217
4218 assert_eq!(mat_texuniform.len(), nmat);
4219 assert_eq!(mat_texrepeat.len(), nmat);
4220 assert_eq!(mat_rgba.len(), nmat);
4221
4222 for i in 0..nmat {
4223 let raw_uniform = unsafe { *model.ffi().mat_texuniform.add(i) };
4224 assert_eq!(mat_texuniform[i], raw_uniform);
4225
4226 for j in 0..2 {
4227 assert_eq!(mat_texrepeat[i][j], unsafe { *model.ffi().mat_texrepeat.add(i * 2 + j) });
4228 }
4229 for j in 0..4 {
4230 assert_eq!(mat_rgba[i][j], unsafe { *model.ffi().mat_rgba.add(i * 4 + j) });
4231 }
4232 }
4233
4234 let mat = model.material("also_wood_material").unwrap();
4236 assert!(!mat_texuniform[mat.id]);
4237 assert_eq!(mat_texrepeat[mat.id], [2.0f32, 2.0]);
4238 assert_eq!(mat_rgba[mat.id], [0.8f32, 0.5, 0.3, 1.0]);
4239 }
4240
4241 #[test]
4245 fn test_force_cast_light_model_arrays() {
4246 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
4247 let nlight = model.ffi().nlight as usize;
4248
4249 if nlight == 0 {
4250 return;
4251 }
4252
4253 let light_mode = model.light_mode();
4254 let light_type = model.light_type();
4255 let light_castshadow = model.light_castshadow();
4256 let light_active = model.light_active();
4257 let light_attenuation = model.light_attenuation();
4258 let light_ambient = model.light_ambient();
4259 let light_diffuse = model.light_diffuse();
4260 let light_specular = model.light_specular();
4261 let light_pos = model.light_pos();
4262 let light_dir = model.light_dir();
4263
4264 assert_eq!(light_mode.len(), nlight);
4265 assert_eq!(light_type.len(), nlight);
4266 assert_eq!(light_castshadow.len(), nlight);
4267 assert_eq!(light_active.len(), nlight);
4268
4269 for i in 0..nlight {
4270 assert_eq!(light_mode[i], unsafe { crate::util::force_cast::<_, MjtCamLight>(*model.ffi().light_mode.add(i)) });
4271 assert_eq!(light_type[i], unsafe { crate::util::force_cast::<_, MjtLightType>(*model.ffi().light_type.add(i)) });
4272
4273 let raw_shadow = unsafe { *model.ffi().light_castshadow.add(i) };
4274 assert_eq!(light_castshadow[i], raw_shadow);
4275 let raw_active = unsafe { *model.ffi().light_active.add(i) };
4276 assert_eq!(light_active[i], raw_active);
4277
4278 for j in 0..3 {
4279 assert_eq!(light_attenuation[i][j], unsafe { *model.ffi().light_attenuation.add(i * 3 + j) });
4280 assert_eq!(light_ambient[i][j], unsafe { *model.ffi().light_ambient.add(i * 3 + j) });
4281 assert_eq!(light_diffuse[i][j], unsafe { *model.ffi().light_diffuse.add(i * 3 + j) });
4282 assert_eq!(light_specular[i][j], unsafe { *model.ffi().light_specular.add(i * 3 + j) });
4283 assert_eq!(light_pos[i][j], unsafe { *model.ffi().light_pos.add(i * 3 + j) });
4284 assert_eq!(light_dir[i][j], unsafe { *model.ffi().light_dir.add(i * 3 + j) });
4285 }
4286 }
4287
4288 let l2 = model.light("lamp_light2").unwrap();
4290 assert_eq!(light_mode[l2.id], MjtCamLight::mjCAMLIGHT_FIXED);
4291 assert_eq!(light_type[l2.id], MjtLightType::mjLIGHT_SPOT);
4292 assert!(light_castshadow[l2.id]);
4293 }
4294
4295 #[test]
4299 fn test_force_cast_site_model_arrays() {
4300 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
4301 let nsite = model.ffi().nsite as usize;
4302
4303 let site_type = model.site_type();
4304 let site_sameframe = model.site_sameframe();
4305 let site_size = model.site_size();
4306 let site_pos = model.site_pos();
4307 let site_quat = model.site_quat();
4308 let site_rgba = model.site_rgba();
4309
4310 assert_eq!(site_type.len(), nsite);
4311 assert_eq!(site_sameframe.len(), nsite);
4312 assert_eq!(site_size.len(), nsite);
4313 assert_eq!(site_pos.len(), nsite);
4314 assert_eq!(site_quat.len(), nsite);
4315 assert_eq!(site_rgba.len(), nsite);
4316
4317 for i in 0..nsite {
4318 assert_eq!(site_type[i], unsafe { crate::util::force_cast::<_, MjtGeom>(*model.ffi().site_type.add(i)) });
4319 assert_eq!(site_sameframe[i], unsafe { crate::util::force_cast::<_, MjtSameFrame>(*model.ffi().site_sameframe.add(i)) });
4320
4321 for j in 0..3 {
4322 assert_eq!(site_size[i][j], unsafe { *model.ffi().site_size.add(i * 3 + j) });
4323 assert_eq!(site_pos[i][j], unsafe { *model.ffi().site_pos.add(i * 3 + j) });
4324 }
4325 for j in 0..4 {
4326 assert_eq!(site_quat[i][j], unsafe { *model.ffi().site_quat.add(i * 4 + j) });
4327 assert_eq!(site_rgba[i][j], unsafe { *model.ffi().site_rgba.add(i * 4 + j) });
4328 }
4329 }
4330 }
4331
4332 #[test]
4336 fn test_force_cast_joint_model_solver_arrays() {
4337 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
4338 let njnt = model.ffi().njnt as usize;
4339
4340 let jnt_solref = model.jnt_solref();
4341 let jnt_solimp = model.jnt_solimp();
4342 let jnt_pos = model.jnt_pos();
4343 let jnt_axis = model.jnt_axis();
4344 let jnt_range = model.jnt_range();
4345
4346 assert_eq!(jnt_solref.len(), njnt);
4347 assert_eq!(jnt_solimp.len(), njnt);
4348 assert_eq!(jnt_pos.len(), njnt);
4349 assert_eq!(jnt_axis.len(), njnt);
4350 assert_eq!(jnt_range.len(), njnt);
4351
4352 let nref = mjNREF as usize;
4353 let nimp = mjNIMP as usize;
4354
4355 for i in 0..njnt {
4356 for j in 0..nref {
4357 assert_eq!(jnt_solref[i][j], unsafe { *model.ffi().jnt_solref.add(i * nref + j) });
4358 }
4359 for j in 0..nimp {
4360 assert_eq!(jnt_solimp[i][j], unsafe { *model.ffi().jnt_solimp.add(i * nimp + j) });
4361 }
4362 for j in 0..3 {
4363 assert_eq!(jnt_pos[i][j], unsafe { *model.ffi().jnt_pos.add(i * 3 + j) });
4364 assert_eq!(jnt_axis[i][j], unsafe { *model.ffi().jnt_axis.add(i * 3 + j) });
4365 }
4366 for j in 0..2 {
4367 assert_eq!(jnt_range[i][j], unsafe { *model.ffi().jnt_range.add(i * 2 + j) });
4368 }
4369 }
4370
4371 let rod = model.joint("rod").unwrap();
4373 assert_eq!(&jnt_axis[rod.id][..], &[0.0, 1.0, 0.0]);
4374 assert_eq!(&jnt_range[rod.id][..], &[0.0, 1.0]);
4375 }
4376
4377 #[test]
4381 fn test_force_cast_view_enum_fields() {
4382 let mut model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
4383
4384 let rod_info = model.joint("rod").unwrap();
4386 let rod_view = rod_info.view(&model);
4387 assert_eq!(rod_view.r#type[0], MjtJoint::mjJNT_SLIDE);
4388 assert!(rod_view.limited[0]);
4389
4390 let ball2_info = model.geom("ball2").unwrap();
4392 let ball2_view = ball2_info.view(&model);
4393 assert_eq!(ball2_view.r#type[0], MjtGeom::mjGEOM_SPHERE);
4394
4395 let touch_info = model.sensor("touch").unwrap();
4397 let touch_view = touch_info.view(&model);
4398 assert_eq!(touch_view.r#type[0], MjtSensor::mjSENS_TOUCH);
4399 assert_eq!(touch_view.objtype[0], MjtObj::mjOBJ_SITE);
4400
4401 let slider_info = model.actuator("slider").unwrap();
4403 let slider_view = slider_info.view(&model);
4404 assert_eq!(slider_view.trntype[0], MjtTrn::mjTRN_JOINT);
4405 assert_eq!(slider_view.biastype[0], MjtBias::mjBIAS_AFFINE);
4406 assert_eq!(slider_view.gaintype[0], MjtGain::mjGAIN_FIXED);
4407 assert!(slider_view.ctrllimited[0]);
4408
4409 let mut slider_view_mut = slider_info.view_mut(&mut model);
4411 slider_view_mut.biastype[0] = MjtBias::mjBIAS_NONE;
4412 let slider_view2 = slider_info.view(&model);
4413 assert_eq!(slider_view2.biastype[0], MjtBias::mjBIAS_NONE);
4414 }
4415
4416 #[test]
4419 fn test_model_view_mut_roundtrip() {
4420 let mut model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
4421
4422 let ball2_info = model.body("ball2").unwrap();
4424 let ball2_id = ball2_info.id;
4425 let orig_pos = model.body_pos()[ball2_id];
4426 assert_eq!(orig_pos, [0.5, 0.0, 0.0]);
4427
4428 {
4429 let mut body_view = ball2_info.view_mut(&mut model);
4430 body_view.pos.copy_from_slice(&[99.0, 88.0, 77.0]);
4431 }
4432 assert_eq!(model.body_pos()[ball2_id], [99.0, 88.0, 77.0]);
4433
4434 for j in 0..3 {
4436 let ffi_val = unsafe { *model.ffi().body_pos.add(ball2_id * 3 + j) };
4437 assert_eq!(ffi_val, [99.0, 88.0, 77.0][j]);
4438 }
4439
4440 let gs_info = model.geom("green_sphere").unwrap();
4442 let gs_id = gs_info.id;
4443 {
4444 let mut geom_view = gs_info.view_mut(&mut model);
4445 geom_view.rgba.copy_from_slice(&[1.0f32, 0.0, 0.0, 0.5]);
4446 }
4447 assert_eq!(model.geom_rgba()[gs_id], [1.0f32, 0.0, 0.0, 0.5]);
4448 for j in 0..4 {
4449 assert_eq!(unsafe { *model.ffi().geom_rgba.add(gs_id * 4 + j) }, [1.0f32, 0.0, 0.0, 0.5][j]);
4450 }
4451 }
4452
4453 #[test]
4456 fn test_force_cast_sublen_dep_key_arrays() {
4457 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
4458 let nkey = model.ffi().nkey as usize;
4459 let nq = model.ffi().nq as usize;
4460 let nv = model.ffi().nv as usize;
4461 let na = model.ffi().na as usize;
4462 let nu = model.ffi().nu as usize;
4463
4464 assert!(nkey >= 2, "EXAMPLE_MODEL must have at least 2 keyframes");
4465
4466 let key_qpos = model.key_qpos();
4467 let key_qvel = model.key_qvel();
4468 let key_act = model.key_act();
4469 let key_ctrl = model.key_ctrl();
4470
4471 assert_eq!(key_qpos.len(), nkey * nq);
4473 assert_eq!(key_qvel.len(), nkey * nv);
4474 assert_eq!(key_act.len(), nkey * na);
4475 assert_eq!(key_ctrl.len(), nkey * nu);
4476
4477 for i in 0..(nkey * nq) {
4479 assert_eq!(key_qpos[i], unsafe { *model.ffi().key_qpos.add(i) });
4480 }
4481 for i in 0..(nkey * nv) {
4482 assert_eq!(key_qvel[i], unsafe { *model.ffi().key_qvel.add(i) });
4483 }
4484 for i in 0..(nkey * nu) {
4485 assert_eq!(key_ctrl[i], unsafe { *model.ffi().key_ctrl.add(i) });
4486 }
4487 }
4488
4489 #[test]
4492 fn test_force_cast_minimal_model_edge_case() {
4493 let xml = "<mujoco><worldbody><body><joint type='free'/><geom size='0.1'/></body></worldbody></mujoco>";
4494 let model = MjModel::from_xml_string(xml).unwrap();
4495
4496 assert_eq!(model.ffi().neq, 0);
4498 assert_eq!(model.ffi().ntendon, 0);
4499 assert_eq!(model.ffi().nu, 0);
4500 assert_eq!(model.ffi().nsensor, 0);
4501 assert_eq!(model.ffi().ncam, 0);
4502 assert_eq!(model.ffi().ntex, 0);
4503 assert_eq!(model.ffi().nmat, 0);
4504
4505 assert!(model.eq_type().is_empty());
4507 assert!(model.eq_active0().is_empty());
4508 assert!(model.tendon_limited().is_empty());
4509 assert!(model.tendon_rgba().is_empty());
4510 assert!(model.actuator_trntype().is_empty());
4511 assert!(model.actuator_ctrllimited().is_empty());
4512 assert!(model.sensor_type().is_empty());
4513 assert!(model.cam_mode().is_empty());
4514 assert!(model.cam_resolution().is_empty());
4515 assert!(model.tex_type().is_empty());
4516 assert!(model.mat_texuniform().is_empty());
4517 assert!(model.mat_rgba().is_empty());
4518
4519 let nbody = model.ffi().nbody as usize;
4521 assert!(nbody >= 2);
4522 assert_eq!(model.body_pos().len(), nbody);
4523 assert_eq!(model.body_sameframe().len(), nbody);
4524 assert_eq!(model.jnt_type().len(), model.ffi().njnt as usize);
4525 assert_eq!(model.geom_type().len(), model.ffi().ngeom as usize);
4526 }
4527
4528 #[test]
4531 fn test_force_cast_pair_model_arrays() {
4532 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
4533 let npair = model.ffi().npair as usize;
4534
4535 if npair == 0 {
4536 return;
4537 }
4538
4539 let pair_solref = model.pair_solref();
4540 let pair_friction = model.pair_friction();
4541 let pair_solimp = model.pair_solimp();
4542
4543 assert_eq!(pair_solref.len(), npair);
4544 assert_eq!(pair_friction.len(), npair);
4545 assert_eq!(pair_solimp.len(), npair);
4546
4547 let nref = mjNREF as usize;
4548 let nimp = mjNIMP as usize;
4549
4550 for i in 0..npair {
4551 for j in 0..nref {
4552 assert_eq!(pair_solref[i][j], unsafe { *model.ffi().pair_solref.add(i * nref + j) });
4553 }
4554 for j in 0..5 {
4555 assert_eq!(pair_friction[i][j], unsafe { *model.ffi().pair_friction.add(i * 5 + j) });
4556 }
4557 for j in 0..nimp {
4558 assert_eq!(pair_solimp[i][j], unsafe { *model.ffi().pair_solimp.add(i * nimp + j) });
4559 }
4560 }
4561 }
4562
4563 #[test]
4566 fn test_force_cast_model_non_aliasing() {
4567 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
4568 let njnt = model.ffi().njnt as usize;
4569
4570 if njnt < 2 {
4571 return;
4572 }
4573
4574 let jnt_solref = model.jnt_solref();
4575 let jnt_type = model.jnt_type();
4576
4577 assert_ne!(jnt_solref[0].as_ptr(), jnt_solref[1].as_ptr());
4579 assert_ne!(std::ptr::addr_of!(jnt_type[0]), std::ptr::addr_of!(jnt_type[1]));
4580
4581 let stride = jnt_solref.as_flattened().element_offset(&jnt_solref[1][0]).unwrap();
4583 assert_eq!(stride, mjNREF as usize,
4584 "jnt_solref stride must be mjNREF={}", mjNREF);
4585 }
4586
4587 const MOCAP_MODEL: &str = stringify!(
4590 <mujoco>
4591 <worldbody>
4592 <body name="mocap1" mocap="true" pos="0 0 0">
4593 <geom type="sphere" size="0.05" contype="0" conaffinity="0"/>
4594 </body>
4595 <body name="mocap2" mocap="true" pos="1 0 0">
4596 <geom type="sphere" size="0.05" contype="0" conaffinity="0"/>
4597 </body>
4598 <geom type="plane" size="5 5 0.1"/>
4599 </worldbody>
4600 <keyframe>
4601 <key name="k0"
4602 mpos="1.0 2.0 3.0 4.0 5.0 6.0"
4603 mquat="0.5 0.5 0.5 0.5 1.0 0.0 0.0 0.0"/>
4604 <key name="k1"
4605 mpos="10.0 20.0 30.0 40.0 50.0 60.0"
4606 mquat="0.0 0.0 0.0 1.0 0.0 1.0 0.0 0.0"/>
4607 </keyframe>
4608 </mujoco>
4609 );
4610
4611 #[test]
4614 fn test_key_mpos() {
4615 let model = MjModel::from_xml_string(MOCAP_MODEL).unwrap();
4616 let nkey = model.ffi().nkey as usize;
4617 let nmocap = model.ffi().nmocap as usize;
4618
4619 assert_eq!(nkey, 2, "expected 2 keyframes");
4620 assert_eq!(nmocap, 2, "expected 2 mocap bodies");
4621
4622 let mpos = model.key_mpos();
4623 assert_eq!(mpos.len(), nkey * nmocap * 3,
4624 "key_mpos length must be nkey * nmocap * 3 = {}", nkey * nmocap * 3);
4625
4626 let k0 = &mpos[..nmocap * 3];
4628 let expected_k0: &[f64] = &[1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
4629 assert_eq!(k0.len(), expected_k0.len());
4630 for (&a, &b) in k0.iter().zip(expected_k0.iter()) {
4631 assert_relative_eq!(a, b, epsilon = 1e-10);
4632 }
4633
4634 let k1 = &mpos[nmocap * 3..];
4636 let expected_k1: &[f64] = &[10.0, 20.0, 30.0, 40.0, 50.0, 60.0];
4637 assert_eq!(k1.len(), expected_k1.len());
4638 for (&a, &b) in k1.iter().zip(expected_k1.iter()) {
4639 assert_relative_eq!(a, b, epsilon = 1e-10);
4640 }
4641 }
4642
4643 #[test]
4646 fn test_key_mquat() {
4647 let model = MjModel::from_xml_string(MOCAP_MODEL).unwrap();
4648 let nkey = model.ffi().nkey as usize;
4649 let nmocap = model.ffi().nmocap as usize;
4650
4651 assert_eq!(nkey, 2, "expected 2 keyframes");
4652 assert_eq!(nmocap, 2, "expected 2 mocap bodies");
4653
4654 let mquat = model.key_mquat();
4655 assert_eq!(mquat.len(), nkey * nmocap * 4,
4656 "key_mquat length must be nkey * nmocap * 4 = {}", nkey * nmocap * 4);
4657
4658 let k0 = &mquat[..nmocap * 4];
4660 let expected_k0: &[f64] = &[0.5, 0.5, 0.5, 0.5, 1.0, 0.0, 0.0, 0.0];
4661 assert_eq!(k0.len(), expected_k0.len());
4662 for (&a, &b) in k0.iter().zip(expected_k0.iter()) {
4663 assert_relative_eq!(a, b, epsilon = 1e-10);
4664 }
4665
4666 let k1 = &mquat[nmocap * 4..];
4668 let expected_k1: &[f64] = &[0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0];
4669 assert_eq!(k1.len(), expected_k1.len());
4670 for (&a, &b) in k1.iter().zip(expected_k1.iter()) {
4671 assert_relative_eq!(a, b, epsilon = 1e-10);
4672 }
4673 }
4674
4675 #[test]
4678 fn test_key_mpos_view_consistency() {
4679 let model = MjModel::from_xml_string(MOCAP_MODEL).unwrap();
4680 let nmocap = model.ffi().nmocap as usize;
4681
4682 let info_k0 = model.key("k0").unwrap();
4683 let view_k0 = info_k0.view(&model);
4684
4685 let array_k0 = &model.key_mpos()[..nmocap * 3];
4687 assert_eq!(
4688 &view_k0.mpos[..nmocap * 3], array_k0,
4689 "key view mpos and key_mpos array accessor must return identical data"
4690 );
4691
4692 let info_k1 = model.key("k1").unwrap();
4693 let view_k1 = info_k1.view(&model);
4694 let array_k1 = &model.key_mpos()[nmocap * 3..];
4695 assert_eq!(
4696 &view_k1.mpos[..nmocap * 3], array_k1,
4697 "key view mpos and key_mpos array accessor must return identical data for key 1"
4698 );
4699 }
4700
4701 #[test]
4703 fn test_key_mpos_mquat_no_mocap() {
4704 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
4706 assert_eq!(model.ffi().nmocap, 0, "EXAMPLE_MODEL should have no mocap bodies");
4707 assert!(model.ffi().nkey > 0, "EXAMPLE_MODEL should have keyframes");
4708
4709 assert_eq!(model.key_mpos().len(), 0,
4710 "key_mpos must be empty when nmocap == 0");
4711 assert_eq!(model.key_mquat().len(), 0,
4712 "key_mquat must be empty when nmocap == 0");
4713 }
4714
4715 #[test]
4717 fn test_from_xml_string_invalid() {
4718 let result = MjModel::from_xml_string("<this is not valid mujoco xml>");
4719 assert!(result.is_err(), "loading invalid XML must return Err");
4720 let msg = result.unwrap_err().to_string();
4721 assert!(!msg.is_empty(), "error message must not be empty for invalid XML");
4722 }
4723
4724 #[test]
4729 fn test_mesh_view_new_fields() {
4730 const MESH_MODEL: &str = "<mujoco>\
4731 <asset>\
4732 <mesh name=\"cube\" vertex=\"-0.5 -0.5 -0.5 0.5 -0.5 -0.5 -0.5 0.5 -0.5 0.5 0.5 -0.5 \
4733 -0.5 -0.5 0.5 0.5 -0.5 0.5 -0.5 0.5 0.5 0.5 0.5 0.5\"/>\
4734 </asset>\
4735 <worldbody>\
4736 <geom type=\"mesh\" mesh=\"cube\"/>\
4737 </worldbody>\
4738 </mujoco>";
4739
4740 let mut model = MjModel::from_xml_string(MESH_MODEL).unwrap();
4741 let mesh_info = model.mesh("cube").unwrap();
4742
4743 let view = mesh_info.view(&model);
4744
4745 assert_eq!(view.scale.len(), 3);
4747 assert_eq!(view.pos.len(), 3);
4748 assert_eq!(view.quat.len(), 4);
4749
4750 assert_eq!(view.normaladr.len(), 1);
4752 assert_eq!(view.normalnum.len(), 1);
4753 assert_eq!(view.texcoordnum.len(), 1);
4754 assert_eq!(view.bvhadr.len(), 1);
4755 assert_eq!(view.bvhnum.len(), 1);
4756 assert_eq!(view.octadr.len(), 1);
4757 assert_eq!(view.octnum.len(), 1);
4758 assert_eq!(view.pathadr.len(), 1);
4759 assert_eq!(view.polynum.len(), 1);
4760 assert_eq!(view.polyadr.len(), 1);
4761
4762 let mut view_mut = mesh_info.view_mut(&mut model);
4764 view_mut.scale[0] = 2.0;
4765 view_mut.scale[1] = 3.0;
4766 view_mut.scale[2] = 4.0;
4767
4768 let view2 = mesh_info.view(&model);
4769 assert_eq!(view2.scale[0], 2.0);
4770 assert_eq!(view2.scale[1], 3.0);
4771 assert_eq!(view2.scale[2], 4.0);
4772 }
4773
4774 #[test]
4777 fn test_flex_array_slices_empty_for_non_flex_model() {
4778 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
4779 assert_eq!(model.ffi().nflex, 0);
4780 assert_eq!(model.flex_cellnum().len(), 0);
4781 assert_eq!(model.flex_stiffnessadr().len(), 0);
4782 assert_eq!(model.flex_bendingadr().len(), 0);
4783 }
4784
4785 #[test]
4787 fn test_max_contacts() {
4788 let model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
4789 let geom1 = model.name_to_id(MjtObj::mjOBJ_GEOM, "green_sphere").unwrap();
4790 let geom2 = model.name_to_id(MjtObj::mjOBJ_GEOM, "ball2").unwrap();
4791
4792 let mc = model.max_contacts(geom1, geom2, None); assert_eq!(mc, 1);
4794
4795 let mc = model.max_contacts(geom1, geom2, Some(false));
4796 assert_eq!(mc, 1);
4797
4798 let mc = model.max_contacts(geom1, geom2, Some(true));
4800 assert_eq!(mc, 1);
4801
4802 assert!( model.try_max_contacts(999, geom2, Some(true)).is_err());
4804 }
4805
4806 #[test]
4809 fn test_probe_dynamic_arrays_stays_in_bounds() {
4810 let mut model = MjModel::from_xml_string(EXAMPLE_MODEL).unwrap();
4811 assert!(model.ffi().nbody > 1, "the model must have bodies for the probe to mean anything");
4812 model.probe_dynamic_arrays();
4813
4814 unsafe { model.probe_dynamic_arrays_unsafe() };
4816 }
4817}