1use crate::{view_creator, info_method, info_with_view, array_slice_dyn};
3use crate::wrappers::mj_auxiliary::{MjVisual, MjStatistic};
4use crate::wrappers::mj_option::MjOption;
5use crate::{getter_setter, mujoco_c::*};
6use crate::error::MjDataError;
7
8use super::mj_statistic::{MjWarningStat, MjTimerStat, MjSolverStat};
9use super::mj_model::{MjModel, MjModelLayout, MjtSameFrame, MjtObj, MjtStage};
10use super::mj_model::traits::{ModelTypeMut, ModelType};
11use super::fun::utility::mju_norm_3;
12use super::mj_auxiliary::MjContact;
13use super::mj_primitive::*;
14
15use std::ptr::{self, NonNull};
16use std::sync::Arc;
17use std::ffi::CString;
18use std::borrow::Cow;
19use std::path::Path;
20use std::fmt::Debug;
21
22pub type MjtState = mjtState;
27
28pub type MjtConstraint = mjtConstraint;
31
32pub type MjtConstraintState = mjtConstraintState;
34
35pub type MjtWarning = mjtWarning;
38
39pub type MjtTimer = mjtTimer;
42
43pub type MjtSleepState = mjtSleepState;
45#[derive(Debug)]
55pub struct MjData<M: ModelType> {
56 data: NonNull<mjData>,
57 model: M
58}
59
60unsafe impl<M: ModelType + Send> Send for MjData<M> {}
65unsafe impl<M: ModelType + Sync> Sync for MjData<M> {}
66
67
68impl<M: ModelType> MjData<M> {
69 pub fn new(model: M) -> Self {
79 Self::try_new(model).expect("allocation of MjData failed")
80 }
81
82 pub fn try_new(model: M) -> Result<Self, MjDataError> {
90 let data_ptr = unsafe { mj_makeData(model.ffi()) };
93 NonNull::new(data_ptr)
94 .map(|data| Self { data, model })
95 .ok_or(MjDataError::AllocationFailed)
96 }
97
98 pub fn swap_model(&mut self, model: M) -> M {
131 self.try_swap_model(model).expect("swap_model failed: the model is not compatible")
132 }
133
134 pub fn try_swap_model(&mut self, model: M) -> Result<M, MjDataError> {
140 if !self.model.is_compatible_with_model(&model) {
141 return Err(MjDataError::IncompatibleModel {
142 source: model.signature(),
143 destination: self.model.signature(),
144 });
145 }
146
147 Ok(std::mem::replace(&mut self.model, model))
148 }
149
150 info_method! { Data, [model], body, [
151 xfrc_applied: 6, xpos: 3, xquat: 4, xmat: 9, xipos: 3, ximat: 9, subtree_com: 3, cinert: 10,
152 crb: 10, cvel: 6, subtree_linvel: 3, subtree_angmom: 3, cacc: 6, cfrc_int: 6, cfrc_ext: 6,
153 awake: 1
154 ], [], []}
155 info_method! { Data, [model], camera, [xpos: 3, xmat: 9], [], []}
156 info_method! { Data, [model], geom, [xpos: 3, xmat: 9], [], []}
157 info_method! { Data, [model], site, [xpos: 3, xmat: 9], [], []}
158 info_method! { Data, [model], light, [xpos: 3, xdir: 3], [], []}
159
160 info_method! { Data, [model], actuator,
161 [],
162 [],
163 [ctrl: nu, length: nout, velocity: nout, force: nout, act: na]
164 }
165
166 pub fn joint(&self, name: &str) -> Option<MjJointDataInfo> {
172 let model = self.model();
173 let id = model.name_to_id(MjtObj::mjOBJ_JOINT, name)?;
174 let nq_range = {
175 let slice = model.jnt_qposadr();
176 crate::util::optional_sparse_addr_range(slice, id, model.nq() as usize).unwrap_or((0, 0))
177 };
178 let nv_range = {
179 let slice = model.jnt_dofadr();
180 crate::util::optional_sparse_addr_range(slice, id, model.nv() as usize).unwrap_or((0, 0))
181 };
182
183 let qpos = nq_range;
184 let qvel = nv_range;
185 let qacc_warmstart = nv_range;
186 let qfrc_applied = nv_range;
187 let qacc = nv_range;
188 let xanchor = (id * 3, 3);
189 let xaxis = (id * 3, 3);
190 #[allow(non_snake_case)]
191 let qLDiagInv = nv_range;
192 let qfrc_bias = nv_range;
193 let qfrc_passive = nv_range;
194 let qfrc_actuator = nv_range;
195 let qfrc_smooth = nv_range;
196 let qacc_smooth = nv_range;
197 let qfrc_constraint = nv_range;
198 let qfrc_inverse = nv_range;
199
200 let qfrc_spring = nv_range;
201 let qfrc_damper = nv_range;
202 let qfrc_gravcomp = nv_range;
203 let qfrc_fluid = nv_range;
204 let qfrc_adhesion = nv_range;
205
206 let model_layout = self.model.layout().clone();
207 Some(MjJointDataInfo {name: name.to_string(), id, model_layout,
208 qpos, qvel, qacc_warmstart, qfrc_applied, qacc, xanchor, xaxis, qLDiagInv, qfrc_bias,
209 qfrc_spring, qfrc_damper, qfrc_gravcomp, qfrc_fluid, qfrc_adhesion, qfrc_passive,
210 qfrc_actuator, qfrc_smooth, qacc_smooth, qfrc_constraint, qfrc_inverse
211 })
212 }
213
214 info_method! { Data, [model], sensor, [], [], [data: nsensordata] }
215
216
217 info_method! { Data, [model], tendon,
218 [wrapadr: 1, wrapnum: 1, efcadr: 1, length: 1, velocity: 1],
219 [],
220 [J: nJten]
221 }
222
223 pub fn step(&mut self) {
225 unsafe {
226 mj_step(self.model.ffi(), self.ffi_mut());
227 }
228 }
229
230 pub fn step1(&mut self) {
233 unsafe {
234 mj_step1(self.model.ffi(), self.ffi_mut());
235 }
236 }
237
238 pub fn step2(&mut self) {
241 unsafe {
242 mj_step2(self.model.ffi(), self.ffi_mut());
243 }
244 }
245
246 pub fn forward(&mut self) {
248 unsafe {
249 mj_forward(self.model.ffi(), self.ffi_mut());
250 }
251 }
252
253 pub fn forward_skip(&mut self, skipstage: MjtStage, skipsensor: bool) {
255 unsafe {
256 mj_forwardSkip(self.model.ffi(), self.ffi_mut(), skipstage as i32, skipsensor as i32);
257 }
258 }
259
260 pub fn inverse(&mut self) {
262 unsafe {
263 mj_inverse(self.model.ffi(), self.ffi_mut());
264 }
265 }
266
267 pub fn inverse_skip(&mut self, skipstage: MjtStage, skipsensor: bool) {
269 unsafe {
270 mj_inverseSkip(self.model.ffi(), self.ffi_mut(), skipstage as i32, skipsensor as i32);
271 }
272 }
273
274 pub fn contact_force(&self, contact_id: usize) -> [MjtNum; 6] {
281 let mut force = [0.0; 6];
282 unsafe {
283 mj_contactForce(
284 self.model.ffi(), self.data.as_ptr(),
285 contact_id as i32, &mut force
286 );
287 }
288 force
289 }
290
291 pub fn reset(&mut self) {
299 unsafe { mj_resetData(self.model.ffi(), self.ffi_mut()) }
300 }
301
302 pub unsafe fn reset_debug(&mut self, debug_value: u8) {
316 unsafe { mj_resetDataDebug(self.model.ffi(), self.ffi_mut(), debug_value) }
317 }
318
319 pub fn reset_keyframe(&mut self, key: usize) -> Result<(), MjDataError> {
328 let nkey = self.model.ffi().nkey as usize;
329 if key >= nkey {
330 return Err(MjDataError::IndexOutOfBounds {
331 kind: "key",
332 id: key,
333 upper: nkey,
334 });
335 }
336 unsafe { mj_resetDataKeyframe(self.model.ffi(), self.ffi_mut(), key as i32) }
337 Ok(())
338 }
339
340 pub fn print_formatted<T: AsRef<Path>>(&self, filename: T, float_format: &str) -> Result<(), MjDataError> {
349 let path_str = filename.as_ref().to_str()
350 .ok_or(MjDataError::InvalidUtf8Path)?;
351 let c_filename = CString::new(path_str).unwrap();
352 let c_float_format = CString::new(float_format).unwrap();
353 unsafe { mj_printFormattedData(self.model.ffi(), self.ffi(), c_filename.as_ptr(), c_float_format.as_ptr()) }
354 Ok(())
355 }
356
357 pub fn print<T: AsRef<Path>>(&self, filename: T) -> Result<(), MjDataError> {
365 let path_str = filename.as_ref().to_str()
366 .ok_or(MjDataError::InvalidUtf8Path)?;
367 let c_filename = CString::new(path_str).unwrap();
368 unsafe { mj_printData(self.model.ffi(), self.ffi(), c_filename.as_ptr()) }
369 Ok(())
370 }
371
372 pub fn fwd_position(&mut self) {
374 unsafe { mj_fwdPosition(self.model.ffi(), self.ffi_mut()) }
375 }
376
377 pub fn fwd_velocity(&mut self) {
379 unsafe { mj_fwdVelocity(self.model.ffi(), self.ffi_mut()) }
380 }
381
382 pub fn fwd_actuation(&mut self) {
384 unsafe { mj_fwdActuation(self.model.ffi(), self.ffi_mut()) }
385 }
386
387 pub fn fwd_acceleration(&mut self) {
389 unsafe { mj_fwdAcceleration(self.model.ffi(), self.ffi_mut()) }
390 }
391
392 pub fn fwd_constraint(&mut self) {
394 unsafe { mj_fwdConstraint(self.model.ffi(), self.ffi_mut()) }
395 }
396
397 pub fn euler(&mut self) {
399 unsafe { mj_Euler(self.model.ffi(), self.ffi_mut()) }
400 }
401
402 pub fn runge_kutta(&mut self, n: u32) {
408 assert!(n == 4, "mj_RungeKutta only supports N=4, got {n}");
409 unsafe { mj_RungeKutta(self.model.ffi(), self.ffi_mut(), n as i32) }
410 }
411
412 pub fn implicit(&mut self) {
414 unsafe { mj_implicit(self.model.ffi(), self.ffi_mut()) }
415 }
416
417 pub fn inv_position(&mut self) {
419 unsafe { mj_invPosition(self.model.ffi(), self.ffi_mut()) }
420 }
421
422 pub fn inv_velocity(&mut self) {
424 unsafe { mj_invVelocity(self.model.ffi(), self.ffi_mut()) }
425 }
426
427 pub fn inv_constraint(&mut self) {
429 unsafe { mj_invConstraint(self.model.ffi(), self.ffi_mut()) }
430 }
431
432 pub fn compare_fwd_inv(&mut self) {
434 unsafe { mj_compareFwdInv(self.model.ffi(), self.ffi_mut()) }
435 }
436
437 pub fn sensor_pos(&mut self) {
439 unsafe { mj_sensorPos(self.model.ffi(), self.ffi_mut()) }
440 }
441
442 pub fn sensor_vel(&mut self) {
444 unsafe { mj_sensorVel(self.model.ffi(), self.ffi_mut()) }
445 }
446
447 pub fn sensor_acc(&mut self) {
449 unsafe { mj_sensorAcc(self.model.ffi(), self.ffi_mut()) }
450 }
451
452 pub fn energy_pos(&mut self) {
454 unsafe { mj_energyPos(self.model.ffi(), self.ffi_mut()) }
455 }
456
457 pub fn energy_vel(&mut self) {
459 unsafe { mj_energyVel(self.model.ffi(), self.ffi_mut()) }
460 }
461
462 pub fn check_pos(&mut self) {
464 unsafe { mj_checkPos(self.model.ffi(), self.ffi_mut()) }
465 }
466
467 pub fn check_vel(&mut self) {
469 unsafe { mj_checkVel(self.model.ffi(), self.ffi_mut()) }
470 }
471
472 pub fn check_acc(&mut self) {
474 unsafe { mj_checkAcc(self.model.ffi(), self.ffi_mut()) }
475 }
476
477 pub fn kinematics(&mut self) {
479 unsafe { mj_kinematics(self.model.ffi(), self.ffi_mut()) }
480 }
481
482 pub fn com_pos(&mut self) {
484 unsafe { mj_comPos(self.model.ffi(), self.ffi_mut()) }
485 }
486
487 pub fn camlight(&mut self) {
489 unsafe { mj_camlight(self.model.ffi(), self.ffi_mut()) }
490 }
491
492 pub fn flex_comp(&mut self) {
494 unsafe { mj_flex(self.model.ffi(), self.ffi_mut()) }
495 }
496
497 pub fn tendon_comp(&mut self) {
499 unsafe { mj_tendon(self.model.ffi(), self.ffi_mut()) }
500 }
501
502 pub fn transmission(&mut self) {
504 unsafe { mj_transmission(self.model.ffi(), self.ffi_mut()) }
505 }
506
507 pub fn crb_comp(&mut self) {
509 unsafe { mj_crb(self.model.ffi(), self.ffi_mut()) }
510 }
511
512 pub fn make_m(&mut self) {
514 unsafe { mj_makeM(self.model.ffi(), self.ffi_mut()) }
515 }
516
517 pub fn factor_m(&mut self) {
519 unsafe { mj_factorM(self.model.ffi(), self.ffi_mut()) }
520 }
521
522 pub fn com_vel(&mut self) {
524 unsafe { mj_comVel(self.model.ffi(), self.ffi_mut()) }
525 }
526
527 pub fn passive(&mut self) {
529 unsafe { mj_passive(self.model.ffi(), self.ffi_mut()) }
530 }
531
532 pub fn subtree_vel(&mut self) {
534 unsafe { mj_subtreeVel(self.model.ffi(), self.ffi_mut()) }
535 }
536
537 pub fn rne(&mut self, flg_acc: bool) -> Vec<MjtNum> {
540 let mut out = vec![0.0; self.model.ffi().nv as usize];
541 self.rne_into(flg_acc, &mut out);
542 out
543 }
544
545 pub fn rne_into(&mut self, flg_acc: bool, result: &mut [MjtNum]) {
552 self.try_rne_into(flg_acc, result).unwrap()
553 }
554
555 pub fn try_rne_into(&mut self, flg_acc: bool, result: &mut [MjtNum]) -> Result<(), MjDataError> {
560 let nv = self.model.ffi().nv as usize;
561 if result.len() < nv {
562 return Err(MjDataError::BufferTooSmall { name: "result", got: result.len(), needed: nv });
563 }
564 unsafe { mj_rne(self.model.ffi(), self.ffi_mut(), flg_acc as i32, result.as_mut_ptr()) };
566 Ok(())
567 }
568
569 pub fn rne_post_constraint(&mut self) {
572 unsafe { mj_rnePostConstraint(self.model.ffi(), self.ffi_mut()) }
573 }
574
575 pub fn collision(&mut self) {
577 unsafe { mj_collision(self.model.ffi(), self.ffi_mut()) }
578 }
579
580 pub fn make_constraint(&mut self) {
582 unsafe { mj_makeConstraint(self.model.ffi(), self.ffi_mut()) }
583 }
584
585 pub fn island(&mut self) {
587 unsafe { mj_island(self.model.ffi(), self.ffi_mut()) }
588 }
589
590 pub fn project_constraint(&mut self) {
592 unsafe { mj_projectConstraint(self.model.ffi(), self.ffi_mut()) }
593 }
594
595 pub fn reference_constraint(&mut self) {
597 unsafe { mj_referenceConstraint(self.model.ffi(), self.ffi_mut()) }
598 }
599
600 pub fn constraint_update(&mut self, jar: &[MjtNum], cost: Option<&mut MjtNum>, flg_cone_hessian: bool) -> Result<(), MjDataError> {
605 let nefc = self.ffi().nefc as usize;
606 if jar.len() < nefc {
607 return Err(MjDataError::BufferTooSmall { name: "jar", got: jar.len(), needed: nefc });
608 }
609
610 unsafe { mj_constraintUpdate(
611 self.model.ffi(), self.ffi_mut(),
612 jar.as_ptr(), cost.map_or(ptr::null_mut(), |x| x as *mut MjtNum),
613 flg_cone_hessian as i32
614 ) };
615
616 Ok(())
617 }
618
619 pub fn init_ctrl_history(&mut self, id: usize, times: Option<&[MjtNum]>, values: &[MjtNum]) -> Result<(), MjDataError> {
630 let nactuator = self.model.ffi().nactuator as usize;
631 if id >= nactuator {
632 return Err(MjDataError::IndexOutOfBounds { kind: "actuator_id", id, upper: nactuator });
633 }
634
635 let nsample = self.model.actuator_history()[id][0];
636 if nsample <= 0 {
637 return Err(MjDataError::NoHistoryBuffer { kind: "actuator", id });
638 }
639
640 let ns = nsample as usize;
641 if let Some(t) = times
642 && t.len() != ns
643 {
644 return Err(MjDataError::LengthMismatch { name: "times", expected: ns, got: t.len() });
645 }
646 if values.len() != ns {
647 return Err(MjDataError::LengthMismatch { name: "values", expected: ns, got: values.len() });
648 }
649
650 unsafe {
651 mj_initCtrlHistory(
652 self.model.ffi(), self.ffi_mut(), id as i32,
653 times.map_or(ptr::null(), |x| x.as_ptr()),
654 values.as_ptr()
655 );
656 }
657
658 Ok(())
659 }
660
661 pub fn init_sensor_history(&mut self, id: usize, times: Option<&[MjtNum]>, values: &[MjtNum], phase: MjtNum) -> Result<(), MjDataError> {
673 let nsensor = self.model.ffi().nsensor as usize;
674 if id >= nsensor {
675 return Err(MjDataError::IndexOutOfBounds { kind: "sensor_id", id, upper: nsensor });
676 }
677
678 let nsample = self.model.sensor_history()[id][0];
679 if nsample <= 0 {
680 return Err(MjDataError::NoHistoryBuffer { kind: "sensor", id });
681 }
682
683 let dim = self.model.sensor_dim()[id] as usize;
684 let required = (nsample as usize) * dim;
685
686 if let Some(t) = times
687 && t.len() != nsample as usize
688 {
689 return Err(MjDataError::LengthMismatch { name: "times", expected: nsample as usize, got: t.len() });
690 }
691 if values.len() != required {
692 return Err(MjDataError::LengthMismatch { name: "values", expected: required, got: values.len() });
693 }
694
695 unsafe {
696 mj_initSensorHistory(
697 self.model.ffi(), self.ffi_mut(), id as i32,
698 times.map_or(ptr::null(), |x| x.as_ptr()),
699 values.as_ptr(), phase
700 );
701 }
702
703 Ok(())
704 }
705
706 pub fn read_ctrl(&self, id: usize, time: MjtNum, interp: i32) -> MjtNum {
712 self.try_read_ctrl(id, time, interp).unwrap()
713 }
714
715 pub fn try_read_ctrl(&self, id: usize, time: MjtNum, interp: i32) -> Result<MjtNum, MjDataError> {
719 let nactuator = self.model.ffi().nactuator as usize;
720 if id >= nactuator {
721 return Err(MjDataError::IndexOutOfBounds { kind: "actuator_id", id, upper: nactuator });
722 }
723 let val = unsafe { mj_readCtrl(self.model.ffi(), self.ffi(), id as i32, time, interp) };
724 Ok(val)
725 }
726
727 pub fn read_sensor_into(&self, id: usize, time: MjtNum, interp: i32, dst: &mut [MjtNum]) -> Result<(), MjDataError> {
734 let nsensor = self.model.ffi().nsensor as usize;
735 if id >= nsensor {
736 return Err(MjDataError::IndexOutOfBounds { kind: "sensor_id", id, upper: nsensor });
737 }
738
739 let dim = self.model.sensor_dim()[id] as usize;
740 if dst.len() != dim {
741 return Err(MjDataError::LengthMismatch { name: "dst", expected: dim, got: dst.len() });
742 }
743 let ptr = unsafe { mj_readSensor(self.model.ffi(), self.ffi(), id as i32, time, dst.as_mut_ptr(), interp) };
744 if !ptr.is_null() {
745 dst.copy_from_slice(unsafe { std::slice::from_raw_parts(ptr, dim) });
747 }
748 Ok(())
749 }
750
751 pub fn read_sensor_fixed<const N: usize>(&self, id: usize, time: MjtNum, interp: i32) -> [MjtNum; N] {
758 self.try_read_sensor_fixed(id, time, interp).unwrap()
759 }
760
761 pub fn try_read_sensor_fixed<const N: usize>(&self, id: usize, time: MjtNum, interp: i32) -> Result<[MjtNum; N], MjDataError> {
766 let nsensor = self.model.ffi().nsensor as usize;
767 if id >= nsensor {
768 return Err(MjDataError::IndexOutOfBounds { kind: "sensor_id", id, upper: nsensor });
769 }
770
771 let dim = self.model.sensor_dim()[id] as usize;
772 if N != dim {
773 return Err(MjDataError::LengthMismatch { name: "N", expected: dim, got: N });
774 }
775 let mut out = [0.0 as MjtNum; N];
776 let ptr = unsafe { mj_readSensor(self.model.ffi(), self.ffi(), id as i32, time, out.as_mut_ptr(), interp) };
777 if !ptr.is_null() {
778 out.copy_from_slice(unsafe { std::slice::from_raw_parts(ptr, N) });
780 }
781 Ok(out)
782 }
783
784 pub fn read_sensor(&self, id: usize, time: MjtNum, interp: i32) -> Cow<'_, [MjtNum]> {
792 self.try_read_sensor(id, time, interp).unwrap()
793 }
794
795 pub fn try_read_sensor(&self, id: usize, time: MjtNum, interp: i32) -> Result<Cow<'_, [MjtNum]>, MjDataError> {
799 let nsensor = self.model.ffi().nsensor as usize;
800 if id >= nsensor {
801 return Err(MjDataError::IndexOutOfBounds { kind: "sensor_id", id, upper: nsensor });
802 }
803
804 let dim = self.model.sensor_dim()[id] as usize;
805 let mut out = vec![0.0 as MjtNum; dim];
806 let ptr = unsafe { mj_readSensor(self.model.ffi(), self.ffi(), id as i32, time, out.as_mut_ptr(), interp) };
807 if !ptr.is_null() {
808 Ok(Cow::Borrowed(unsafe { std::slice::from_raw_parts(ptr, dim) }))
810 } else {
811 Ok(Cow::Owned(out))
813 }
814 }
815
816 pub unsafe fn add_contact(&mut self, con: &MjContact) -> Result<(), MjDataError> {
833 match unsafe { mj_addContact(self.model.ffi(), self.ffi_mut(), con) } {
834 0 => Ok(()),
835 _ => Err(MjDataError::ContactBufferFull),
836 }
837 }
838
839 pub fn jac(&self, jacp: bool, jacr: bool, point: &[MjtNum; 3], body_id: usize) -> (Vec<MjtNum>, Vec<MjtNum>) {
846 self.try_jac(jacp, jacr, point, body_id).unwrap()
847 }
848
849 pub fn try_jac(&self, jacp: bool, jacr: bool, point: &[MjtNum; 3], body_id: usize) -> Result<(Vec<MjtNum>, Vec<MjtNum>), MjDataError> {
853 let nbody = self.model.ffi().nbody;
854 if body_id >= nbody as usize {
855 return Err(MjDataError::IndexOutOfBounds { kind: "body_id", id: body_id, upper: nbody as usize });
856 }
857 let required_len = 3 * self.model.ffi().nv as usize;
858 let mut jacp_vec = if jacp { vec![0 as MjtNum; required_len] } else { vec![] };
859 let mut jacr_vec = if jacr { vec![0 as MjtNum; required_len] } else { vec![] };
860 unsafe {
861 mj_jac(
862 self.model.ffi(), self.ffi(),
863 if jacp { jacp_vec.as_mut_ptr() } else { ptr::null_mut() },
864 if jacr { jacr_vec.as_mut_ptr() } else { ptr::null_mut() },
865 point, body_id as i32,
866 )
867 };
868 Ok((jacp_vec, jacr_vec))
869 }
870
871 pub fn jac_body(&self, jacp: bool, jacr: bool, body_id: usize) -> (Vec<MjtNum>, Vec<MjtNum>) {
877 self.try_jac_body(jacp, jacr, body_id).unwrap()
878 }
879
880 pub fn try_jac_body(&self, jacp: bool, jacr: bool, body_id: usize) -> Result<(Vec<MjtNum>, Vec<MjtNum>), MjDataError> {
884 let nbody = self.model.ffi().nbody;
885 if body_id >= nbody as usize {
886 return Err(MjDataError::IndexOutOfBounds { kind: "body_id", id: body_id, upper: nbody as usize });
887 }
888 let required_len = 3 * self.model.ffi().nv as usize;
889 let mut jacp_vec = if jacp { vec![0 as MjtNum; required_len] } else { vec![] };
890 let mut jacr_vec = if jacr { vec![0 as MjtNum; required_len] } else { vec![] };
891 unsafe {
892 mj_jacBody(
893 self.model.ffi(), self.ffi(),
894 if jacp { jacp_vec.as_mut_ptr() } else { ptr::null_mut() },
895 if jacr { jacr_vec.as_mut_ptr() } else { ptr::null_mut() },
896 body_id as i32,
897 )
898 };
899 Ok((jacp_vec, jacr_vec))
900 }
901
902 pub fn jac_body_com(&self, jacp: bool, jacr: bool, body_id: usize) -> (Vec<MjtNum>, Vec<MjtNum>) {
908 self.try_jac_body_com(jacp, jacr, body_id).unwrap()
909 }
910
911 pub fn try_jac_body_com(&self, jacp: bool, jacr: bool, body_id: usize) -> Result<(Vec<MjtNum>, Vec<MjtNum>), MjDataError> {
915 let nbody = self.model.ffi().nbody;
916 if body_id >= nbody as usize {
917 return Err(MjDataError::IndexOutOfBounds { kind: "body_id", id: body_id, upper: nbody as usize });
918 }
919 let required_len = 3 * self.model.ffi().nv as usize;
920 let mut jacp_vec = if jacp { vec![0 as MjtNum; required_len] } else { vec![] };
921 let mut jacr_vec = if jacr { vec![0 as MjtNum; required_len] } else { vec![] };
922 unsafe {
923 mj_jacBodyCom(
924 self.model.ffi(), self.ffi(),
925 if jacp { jacp_vec.as_mut_ptr() } else { ptr::null_mut() },
926 if jacr { jacr_vec.as_mut_ptr() } else { ptr::null_mut() },
927 body_id as i32,
928 )
929 };
930 Ok((jacp_vec, jacr_vec))
931 }
932
933 pub fn jac_subtree_com(&mut self, body_id: usize) -> Vec<MjtNum> {
938 self.try_jac_subtree_com(body_id).unwrap()
939 }
940
941 pub fn try_jac_subtree_com(&mut self, body_id: usize) -> Result<Vec<MjtNum>, MjDataError> {
945 let nbody = self.model.ffi().nbody;
946 if body_id >= nbody as usize {
947 return Err(MjDataError::IndexOutOfBounds { kind: "body_id", id: body_id, upper: nbody as usize });
948 }
949 let required_len = 3 * self.model.ffi().nv as usize;
950 let mut jacp_vec = vec![0 as MjtNum; required_len];
951 unsafe {
952 mj_jacSubtreeCom(
953 self.model.ffi(), self.ffi_mut(),
954 jacp_vec.as_mut_ptr(),
955 body_id as i32,
956 )
957 };
958 Ok(jacp_vec)
959 }
960
961 pub fn jac_geom(&self, jacp: bool, jacr: bool, geom_id: usize) -> (Vec<MjtNum>, Vec<MjtNum>) {
967 self.try_jac_geom(jacp, jacr, geom_id).unwrap()
968 }
969
970 pub fn try_jac_geom(&self, jacp: bool, jacr: bool, geom_id: usize) -> Result<(Vec<MjtNum>, Vec<MjtNum>), MjDataError> {
974 let ngeom = self.model.ffi().ngeom;
975 if geom_id >= ngeom as usize {
976 return Err(MjDataError::IndexOutOfBounds { kind: "geom_id", id: geom_id, upper: ngeom as usize });
977 }
978 let required_len = 3 * self.model.ffi().nv as usize;
979 let mut jacp_vec = if jacp { vec![0 as MjtNum; required_len] } else { vec![] };
980 let mut jacr_vec = if jacr { vec![0 as MjtNum; required_len] } else { vec![] };
981 unsafe {
982 mj_jacGeom(
983 self.model.ffi(), self.ffi(),
984 if jacp { jacp_vec.as_mut_ptr() } else { ptr::null_mut() },
985 if jacr { jacr_vec.as_mut_ptr() } else { ptr::null_mut() },
986 geom_id as i32,
987 )
988 };
989 Ok((jacp_vec, jacr_vec))
990 }
991
992 pub fn jac_site(&self, jacp: bool, jacr: bool, site_id: usize) -> (Vec<MjtNum>, Vec<MjtNum>) {
998 self.try_jac_site(jacp, jacr, site_id).unwrap()
999 }
1000
1001 pub fn try_jac_site(&self, jacp: bool, jacr: bool, site_id: usize) -> Result<(Vec<MjtNum>, Vec<MjtNum>), MjDataError> {
1005 let nsite = self.model.ffi().nsite;
1006 if site_id >= nsite as usize {
1007 return Err(MjDataError::IndexOutOfBounds { kind: "site_id", id: site_id, upper: nsite as usize });
1008 }
1009 let required_len = 3 * self.model.ffi().nv as usize;
1010 let mut jacp_vec = if jacp { vec![0 as MjtNum; required_len] } else { vec![] };
1011 let mut jacr_vec = if jacr { vec![0 as MjtNum; required_len] } else { vec![] };
1012 unsafe {
1013 mj_jacSite(
1014 self.model.ffi(), self.ffi(),
1015 if jacp { jacp_vec.as_mut_ptr() } else { ptr::null_mut() },
1016 if jacr { jacr_vec.as_mut_ptr() } else { ptr::null_mut() },
1017 site_id as i32,
1018 )
1019 };
1020 Ok((jacp_vec, jacr_vec))
1021 }
1022
1023 pub fn angmom_mat(&mut self, body_id: usize) -> Vec<MjtNum> {
1027 self.try_angmom_mat(body_id).unwrap()
1028 }
1029
1030 pub fn try_angmom_mat(&mut self, body_id: usize) -> Result<Vec<MjtNum>, MjDataError> {
1034 let nbody = self.model.ffi().nbody;
1035 if body_id >= nbody as usize {
1036 return Err(MjDataError::IndexOutOfBounds { kind: "body_id", id: body_id, upper: nbody as usize });
1037 }
1038 let mut mat = vec![0.0; 3 * self.model.ffi().nv as usize];
1039 unsafe { mj_angmomMat(self.model.ffi(), self.ffi_mut(), mat.as_mut_ptr(), body_id as i32) };
1040 Ok(mat)
1041 }
1042
1043 pub fn forward_kinematics(&mut self) {
1045 unsafe { mj_fwdKinematics(self.model.ffi(), self.ffi_mut()) }
1046 }
1047
1048 pub fn object_velocity(&self, obj_type: MjtObj, obj_id: usize, flg_local: bool) -> [MjtNum; 6] {
1053 self.try_object_velocity(obj_type, obj_id, flg_local).unwrap()
1054 }
1055
1056 pub fn try_object_velocity(&self, obj_type: MjtObj, obj_id: usize, flg_local: bool) -> Result<[MjtNum; 6], MjDataError> {
1063 let max_id = match obj_type {
1064 MjtObj::mjOBJ_BODY | MjtObj::mjOBJ_XBODY => self.model.ffi().nbody,
1065 MjtObj::mjOBJ_GEOM => self.model.ffi().ngeom,
1066 MjtObj::mjOBJ_SITE => self.model.ffi().nsite,
1067 MjtObj::mjOBJ_CAMERA => self.model.ffi().ncam,
1068 _ => return Err(MjDataError::UnsupportedObjectType(obj_type as i32)),
1069 };
1070 if obj_id >= max_id as usize {
1071 return Err(MjDataError::IndexOutOfBounds { kind: "obj_id", id: obj_id, upper: max_id as usize });
1072 }
1073 let mut result: [MjtNum; 6] = [0.0; 6];
1074 unsafe {
1075 mj_objectVelocity(self.model.ffi(), self.ffi(), obj_type as i32, obj_id as i32, &mut result, flg_local as i32)
1076 };
1077 Ok(result)
1078 }
1079
1080 pub fn object_acceleration(&self, obj_type: MjtObj, obj_id: usize, flg_local: bool) -> [MjtNum; 6] {
1085 self.try_object_acceleration(obj_type, obj_id, flg_local).unwrap()
1086 }
1087
1088 pub fn try_object_acceleration(&self, obj_type: MjtObj, obj_id: usize, flg_local: bool) -> Result<[MjtNum; 6], MjDataError> {
1094 let max_id = match obj_type {
1095 MjtObj::mjOBJ_BODY | MjtObj::mjOBJ_XBODY => self.model.ffi().nbody,
1096 MjtObj::mjOBJ_GEOM => self.model.ffi().ngeom,
1097 MjtObj::mjOBJ_SITE => self.model.ffi().nsite,
1098 MjtObj::mjOBJ_CAMERA => self.model.ffi().ncam,
1099 _ => return Err(MjDataError::UnsupportedObjectType(obj_type as i32)),
1100 };
1101 if obj_id >= max_id as usize {
1102 return Err(MjDataError::IndexOutOfBounds { kind: "obj_id", id: obj_id, upper: max_id as usize });
1103 }
1104 let mut result: [MjtNum; 6] = [0.0; 6];
1105 unsafe {
1106 mj_objectAcceleration(self.model.ffi(), self.ffi(), obj_type as i32, obj_id as i32, &mut result, flg_local as i32)
1107 };
1108 Ok(result)
1109 }
1110
1111 pub fn geom_distance(&mut self, geom1_id: usize, geom2_id: usize, dist_max: MjtNum, fromto: Option<&mut [MjtNum; 6]>) -> MjtNum {
1115 self.try_geom_distance(geom1_id, geom2_id, dist_max, fromto).unwrap()
1116 }
1117
1118 pub fn try_geom_distance(&mut self, geom1_id: usize, geom2_id: usize, dist_max: MjtNum, fromto: Option<&mut [MjtNum; 6]>) -> Result<MjtNum, MjDataError> {
1122 let ngeom = self.model.ffi().ngeom;
1123 if geom1_id >= ngeom as usize {
1124 return Err(MjDataError::IndexOutOfBounds { kind: "geom1_id", id: geom1_id, upper: ngeom as usize });
1125 }
1126 if geom2_id >= ngeom as usize {
1127 return Err(MjDataError::IndexOutOfBounds { kind: "geom2_id", id: geom2_id, upper: ngeom as usize });
1128 }
1129 Ok(unsafe {
1130 mj_geomDistance(
1131 self.model.ffi(), self.ffi_mut(),
1132 geom1_id as i32, geom2_id as i32, dist_max,
1133 fromto.map_or(ptr::null_mut(), |x| x),
1134 )
1135 })
1136 }
1137
1138 pub fn local_to_global(&mut self, pos: &[MjtNum; 3], quat: &[MjtNum; 4], body_id: usize, sameframe: MjtSameFrame) -> ([MjtNum; 3], [MjtNum; 9]) {
1143 self.try_local_to_global(pos, quat, body_id, sameframe).unwrap()
1144 }
1145
1146 pub fn try_local_to_global(&mut self, pos: &[MjtNum; 3], quat: &[MjtNum; 4], body_id: usize, sameframe: MjtSameFrame) -> Result<([MjtNum; 3], [MjtNum; 9]), MjDataError> {
1150 let nbody = self.model.ffi().nbody;
1151 if body_id >= nbody as usize {
1152 return Err(MjDataError::IndexOutOfBounds { kind: "body_id", id: body_id, upper: nbody as usize });
1153 }
1154 let mut xpos: [MjtNum; 3] = [0.0; 3];
1155 let mut xmat: [MjtNum; 9] = [0.0; 9];
1156 unsafe {
1157 mj_local2Global(self.ffi_mut(), &mut xpos, &mut xmat, pos, quat, body_id as i32, sameframe as MjtByte)
1158 };
1159 Ok((xpos, xmat))
1160 }
1161
1162 #[allow(clippy::too_many_arguments)]
1169 pub fn multi_ray(
1170 &mut self, pnt: &[MjtNum; 3], vec: &[[MjtNum; 3]], geomgroup: Option<&[MjtByte; mjNGROUP as usize]>,
1171 flg_static: MjtBool, bodyexclude: Option<usize>, cutoff: MjtNum, normals_out: Option<&mut [[MjtNum; 3]]>
1172 ) -> (Vec<Option<usize>>, Vec<MjtNum>) {
1173 self.try_multi_ray(pnt, vec, geomgroup, flg_static, bodyexclude, cutoff, normals_out).unwrap()
1174 }
1175
1176 #[allow(clippy::too_many_arguments)]
1180 pub fn try_multi_ray(
1181 &mut self, pnt: &[MjtNum; 3], vec: &[[MjtNum; 3]], geomgroup: Option<&[MjtByte; mjNGROUP as usize]>,
1182 flg_static: MjtBool, bodyexclude: Option<usize>, cutoff: MjtNum, normals_out: Option<&mut [[MjtNum; 3]]>
1183 ) -> Result<(Vec<Option<usize>>, Vec<MjtNum>), MjDataError> {
1184 let nray = vec.len();
1185 if let Some(buf) = &normals_out
1186 && buf.len() != nray
1187 {
1188 return Err(MjDataError::LengthMismatch { name: "normals_out", expected: nray, got: buf.len() });
1189 }
1190
1191 let mut geom_id_raw = vec![-1i32; nray];
1194 let mut distance = vec![0.0; nray];
1195
1196 unsafe { mj_multiRay(
1197 self.model.ffi(), self.ffi_mut(), pnt,
1198 bytemuck::cast_slice::<[MjtNum; 3], MjtNum>(vec).as_ptr(),
1199 geomgroup.map_or(ptr::null(), |x| x.as_ptr()),
1200 flg_static, bodyexclude.map_or(-1i32, |id| id as i32), geom_id_raw.as_mut_ptr(),
1201 distance.as_mut_ptr(),
1202 normals_out.map_or(ptr::null_mut(), |x| bytemuck::cast_slice_mut::<[MjtNum; 3], MjtNum>(x).as_mut_ptr()),
1203 nray as i32, cutoff
1204 ) };
1205
1206 let geom_id = geom_id_raw.into_iter().map(|id| if id == -1 { None } else { Some(id as usize) }).collect();
1207 Ok((geom_id, distance))
1208 }
1209
1210 pub fn ray(
1216 &mut self, pnt: &[MjtNum; 3], vec: &[MjtNum; 3],
1217 geomgroup: Option<&[MjtByte; mjNGROUP as usize]>, flg_static: MjtBool, bodyexclude: Option<usize>,
1218 normal_out: Option<&mut [MjtNum; 3]>
1219 ) -> (Option<usize>, MjtNum) {
1220 if mju_norm_3(vec) < mjMINVAL {
1223 if let Some(normal) = normal_out {
1224 *normal = [0.0; 3];
1225 }
1226 return (None, -1.0);
1227 }
1228
1229 let mut geom_id_raw = -1i32;
1231 let dist = unsafe { mj_ray(
1232 self.model.ffi(), self.ffi(),
1233 pnt, vec,
1234 geomgroup.map_or(ptr::null(), |x| x.as_ptr()),
1235 flg_static, bodyexclude.map_or(-1i32, |id| id as i32), &mut geom_id_raw,
1236 normal_out.map_or(ptr::null_mut(), |x| x)
1237 ) };
1238 let geom_id = if geom_id_raw == -1 { None } else { Some(geom_id_raw as usize) };
1239 (geom_id, dist)
1240 }
1241
1242 #[allow(clippy::too_many_arguments)]
1253 pub fn ray_flex(
1254 &self, flex_layer: i32, flg_vert: MjtBool, flg_edge: MjtBool, flg_face: MjtBool, flg_skin: MjtBool, flexid: usize,
1255 pnt: &[MjtNum; 3], vec: &[MjtNum; 3],
1256 vertid: Option<&mut i32>, normal_out: Option<&mut [MjtNum; 3]>
1257 ) -> MjtNum {
1258 self.try_ray_flex(flex_layer, flg_vert, flg_edge, flg_face, flg_skin, flexid, pnt, vec, vertid, normal_out).unwrap()
1259 }
1260
1261 #[allow(clippy::too_many_arguments)]
1268 pub fn try_ray_flex(
1269 &self, flex_layer: i32, flg_vert: MjtBool, flg_edge: MjtBool, flg_face: MjtBool, flg_skin: MjtBool, flexid: usize,
1270 pnt: &[MjtNum; 3], vec: &[MjtNum; 3],
1271 vertid: Option<&mut i32>, normal_out: Option<&mut [MjtNum; 3]>
1272 ) -> Result<MjtNum, MjDataError> {
1273 let nflex = self.model.ffi().nflex as usize;
1274 if flexid >= nflex {
1275 return Err(MjDataError::IndexOutOfBounds { kind: "flexid", id: flexid, upper: nflex });
1276 }
1277 Ok(unsafe { mj_rayFlex(
1278 self.model.ffi(), self.ffi(),
1279 flex_layer, flg_vert, flg_edge, flg_face, flg_skin, flexid as i32,
1280 pnt, vec,
1281 vertid.map_or(ptr::null_mut(), |x| x), normal_out.map_or(ptr::null_mut(), |x| x)
1282 ) })
1283 }
1284
1285 pub fn copy_state_from_data<N: ModelType>(&mut self, src: &MjData<N>, spec: u32) -> Result<(), MjDataError> {
1291 if !self.model.is_compatible_with_model(&src.model) {
1292 return Err(MjDataError::IncompatibleModel {
1293 source: src.model.signature(),
1294 destination: self.model.signature(),
1295 });
1296 }
1297 unsafe {
1298 mj_copyState(self.model.ffi(), src.ffi(), self.ffi_mut(), spec as i32);
1299 }
1300 Ok(())
1301 }
1302
1303 pub fn ray_hfield(
1314 &self, geom_id: usize, pnt: &[MjtNum; 3], vec: &[MjtNum; 3], normal_out: Option<&mut [MjtNum; 3]>
1315 ) -> MjtNum {
1316 self.try_ray_hfield(geom_id, pnt, vec, normal_out).unwrap()
1317 }
1318
1319 pub fn try_ray_hfield(
1329 &self, geom_id: usize, pnt: &[MjtNum; 3], vec: &[MjtNum; 3], normal_out: Option<&mut [MjtNum; 3]>
1330 ) -> Result<MjtNum, MjDataError> {
1331 let ngeom = self.model.ffi().ngeom as usize;
1332 if geom_id >= ngeom {
1333 return Err(MjDataError::IndexOutOfBounds { kind: "geom_id", id: geom_id, upper: ngeom });
1334 }
1335 Ok(unsafe {
1336 mj_rayHfield(self.model.ffi(), self.ffi(), geom_id as i32, pnt, vec, normal_out.map_or(ptr::null_mut(), |x| x))
1337 })
1338 }
1339
1340 pub fn ray_mesh(
1351 &mut self, geom_id: usize, pnt: &[MjtNum; 3], vec: &[MjtNum; 3], normal_out: Option<&mut [MjtNum; 3]>
1352 ) -> MjtNum {
1353 self.try_ray_mesh(geom_id, pnt, vec, normal_out).unwrap()
1354 }
1355
1356 pub fn try_ray_mesh(
1366 &mut self, geom_id: usize, pnt: &[MjtNum; 3], vec: &[MjtNum; 3], normal_out: Option<&mut [MjtNum; 3]>
1367 ) -> Result<MjtNum, MjDataError> {
1368 let ngeom = self.model.ffi().ngeom as usize;
1369 if geom_id >= ngeom {
1370 return Err(MjDataError::IndexOutOfBounds { kind: "geom_id", id: geom_id, upper: ngeom });
1371 }
1372 Ok(unsafe {
1373 mj_rayMesh(self.model.ffi(), self.ffi(), geom_id as i32, pnt, vec, normal_out.map_or(ptr::null_mut(), |x| x))
1374 })
1375 }
1376
1377 pub fn apply_ft(
1384 &mut self,
1385 force: &[MjtNum; 3],
1386 torque: &[MjtNum; 3],
1387 point: &[MjtNum; 3],
1388 body: usize,
1389 qfrc_target: &mut [MjtNum],
1390 ) -> Result<(), MjDataError> {
1391 let nbody = self.model.ffi().nbody;
1392 if body >= nbody as usize {
1393 return Err(MjDataError::IndexOutOfBounds {
1394 kind: "body",
1395 id: body,
1396 upper: nbody as usize,
1397 });
1398 }
1399 let nv = self.model.ffi().nv as usize;
1400 if qfrc_target.len() < nv {
1401 return Err(MjDataError::BufferTooSmall {
1402 name: "qfrc_target",
1403 got: qfrc_target.len(),
1404 needed: nv,
1405 });
1406 }
1407 unsafe {
1408 mj_applyFT(self.model.ffi(), self.ffi_mut(), force, torque, point, body as i32, qfrc_target.as_mut_ptr());
1409 }
1410 Ok(())
1411 }
1412
1413 pub fn read_state_into(&self, spec: u32, destination: &mut [MjtNum]) -> usize {
1436 self.try_read_state_into(spec, destination)
1437 .unwrap()
1438 }
1439
1440 pub fn try_read_state_into(&self, spec: u32, destination: &mut [MjtNum]) -> Result<usize, MjDataError> {
1448 let state_size = self.model.state_size(spec);
1449 if destination.len() < state_size {
1450 return Err(MjDataError::BufferTooSmall {
1451 name: "destination",
1452 got: destination.len(),
1453 needed: state_size,
1454 });
1455 }
1456 unsafe {
1457 mj_getState(self.model.ffi(), self.ffi(), destination.as_mut_ptr(), spec as i32);
1458 }
1459 Ok(state_size)
1460 }
1461
1462 pub fn state(&self, spec: u32) -> Box<[MjtNum]> {
1465 let mut destination = vec![0.0; self.model.state_size(spec)].into_boxed_slice();
1466 Self::read_state_into(self, spec, &mut destination);
1467 destination
1468 }
1469
1470 pub fn set_state(&mut self, state: &[MjtNum], spec: u32) -> Result<(), MjDataError> {
1485 let required_len = self.model.state_size(spec);
1486 if state.len() < required_len {
1487 return Err(MjDataError::BufferTooSmall {
1488 name: "state",
1489 got: state.len(),
1490 needed: required_len,
1491 });
1492 }
1493 let previous = (spec & MjtState::mjSTATE_HISTORY as u32 != 0).then(|| self.history().to_vec());
1497 unsafe {
1498 mj_setState(self.model.ffi(), self.ffi_mut(), state.as_ptr(), spec as i32);
1499 }
1500 if let Some(previous) = previous
1501 && let Some((kind, id, nsample)) = self.history_cursor_fault()
1502 {
1503 unsafe { self.history_mut() }.copy_from_slice(&previous);
1506 return Err(MjDataError::InvalidHistoryCursor { kind, id, nsample });
1507 }
1508 Ok(())
1509 }
1510
1511 fn history_cursor_fault(&self) -> Option<(&'static str, usize, usize)> {
1516 let history = self.history();
1517 let fault = |kind, spec: &[[i32; 2]], adr: &[i32]| {
1518 spec.iter().zip(adr).enumerate().find_map(|(id, (sample, &address))| {
1519 let nsample = sample[0];
1520 if nsample <= 0 || address < 0 {
1521 return None;
1522 }
1523 let cursor = *history.get(address as usize + 1)?;
1524 let valid = cursor.fract() == 0.0 && cursor >= 0.0 && cursor <= f64::from(nsample - 1);
1525 (!valid).then_some((kind, id, nsample as usize))
1526 })
1527 };
1528 fault("actuator", self.model.actuator_history(), self.model.actuator_historyadr())
1529 .or_else(|| fault("sensor", self.model.sensor_history(), self.model.sensor_historyadr()))
1530 }
1531
1532
1533 pub fn full_m(&self, dst: &mut [MjtNum]) -> Result<(), MjDataError> {
1538 let nv = self.model.ffi().nv as usize;
1539 let needed = nv * nv;
1540 if dst.len() < needed {
1541 return Err(MjDataError::BufferTooSmall { name: "dst", got: dst.len(), needed });
1542 }
1543 unsafe { mj_fullM(self.model.ffi(), self.ffi(), dst.as_mut_ptr()) };
1544 Ok(())
1545 }
1546
1547 pub fn set_threadpool(&mut self, nthread: i32) {
1549 unsafe { mju_threadpool(self.ffi_mut(), nthread) }
1550 }
1551
1552 pub fn copy_visual_to<N: ModelType>(&self, destination: &mut MjData<N>) -> Result<(), MjDataError> {
1564 if !self.model.is_compatible_with_model(&destination.model) {
1565 return Err(MjDataError::IncompatibleModel {
1566 source: self.model.signature(),
1567 destination: destination.model.signature(),
1568 });
1569 }
1570 unsafe {
1571 mjv_copyData(destination.ffi_mut(), self.model.ffi(), self.ffi());
1572 }
1573 Ok(())
1574 }
1575
1576 pub fn copy_to<N: ModelType>(&self, destination: &mut MjData<N>) -> Result<(), MjDataError> {
1586 if !self.model.is_compatible_with_model(&destination.model) {
1587 return Err(MjDataError::IncompatibleModel {
1588 source: self.model.signature(),
1589 destination: destination.model.signature(),
1590 });
1591 }
1592 unsafe {
1593 mj_copyData(destination.ffi_mut(), self.model.ffi(), self.ffi());
1594 }
1595 Ok(())
1596 }
1597
1598 #[cfg(feature = "cpp-viewer")]
1601 pub(crate) fn as_raw_ptr(&self) -> *mut mjData {
1602 self.data.as_ptr()
1603 }
1604}
1605
1606
1607impl<M: ModelType> MjData<M> {
1609 pub fn ffi(&self) -> &mjData {
1611 unsafe { self.data.as_ref() }
1614 }
1615
1616 pub unsafe fn ffi_mut(&mut self) -> &mut mjData {
1622 unsafe { self.data.as_mut() }
1623 }
1624
1625 pub fn model(&self) -> &MjModel {
1630 &self.model
1631 }
1632
1633 pub fn model_opt(&self) -> &MjOption {
1635 self.model.opt()
1636 }
1637
1638 pub fn model_vis(&self) -> &MjVisual {
1640 self.model.vis()
1641 }
1642
1643 pub fn model_stat(&self) -> &MjStatistic {
1645 self.model.stat()
1646 }
1647
1648 pub fn model_clone(&self) -> M where M: Clone {
1652 self.model.clone()
1653 }
1654
1655 getter_setter! {get, [
1656 [ffi] narena: MjtSize; "size of the arena in bytes (inclusive of the stack).";
1657 [ffi] nbuffer: MjtSize; "size of main buffer in bytes.";
1658 [ffi] nplugin: i32; "number of plugin instances.";
1659 [ffi] maxuse_stack: MjtSize; "maximum stack allocation in bytes (mutable).";
1660 [ffi] maxuse_arena: MjtSize; "maximum arena allocation in bytes.";
1661 [ffi] maxuse_con: i32; "maximum number of contacts.";
1662 [ffi] maxuse_efc: i32; "maximum number of scalar constraints.";
1663 [ffi] ncon: i32; "number of detected contacts.";
1664 [ffi] ne: i32; "number of equality constraints.";
1665 [ffi] nf: i32; "number of friction constraints.";
1666 [ffi] nl: i32; "number of limit constraints.";
1667 [ffi] nefc: i32; "number of constraints.";
1668 [ffi] nJ: i32; "number of non-zeros in constraint Jacobian.";
1669 [ffi] nefmK: i32; "number of non-zeros in effective-stiffness CSR.";
1670 [ffi] nefmdof: i32; "number of 3x3 blocks in the effective-metric preconditioner.";
1671 [ffi] nefmL: i32; "size of the effective-metric block storage (9*nefmdof).";
1672 [ffi] nY: i32; "number of non-zeros in constraint inverse inertia square root.";
1673 [ffi] nA: i32; "number of non-zeros in constraint inverse inertia matrix.";
1674 [ffi] nisland: i32; "number of detected constraint islands.";
1675 [ffi] nidof: i32; "number of dofs in all islands.";
1676 [ffi] ntree_awake: i32; "number of awake trees.";
1677 [ffi] nbody_awake: i32; "number of awake dynamic and static bodies.";
1678 [ffi] nparent_awake: i32; "number of bodies with awake parents.";
1679 [ffi] nv_awake: i32; "number of awake dofs.";
1680 [ffi] signature: u64; "compilation signature.";
1681 ]}
1682
1683 pub(crate) fn layout(&self) -> &Arc<MjModelLayout> {
1689 self.model.layout()
1690 }
1691
1692 getter_setter! {get, [
1693 [ffi] efm_active: bool; "whether the implicit effective metric M+K is active.";
1694 ]}
1695
1696 getter_setter! {get, [
1697 [ffi] flg_energypos: MjtBool; "has mj_energyPos been called.";
1698 [ffi] flg_energyvel: MjtBool; "has mj_energyVel been called.";
1699 [ffi] flg_subtreevel: MjtBool; "has mj_subtreeVel been called.";
1700 [ffi] flg_rnepost: MjtBool; "has mj_rnePostConstraint been called.";
1701 ]}
1702
1703 getter_setter! {with, get, set, [
1704 [ffi, ffi_mut] time: MjtNum; "simulation time.";
1705 [ffi, ffi_mut] threadlock: MjtBool; "disable stack freeing during threaded execution.";
1706 ]}
1707
1708 getter_setter! {with, get, [
1709 [ffi, ffi_mut] energy: &[MjtNum; 2]; "potential, kinetic energy.";
1710 ]}
1711
1712 getter_setter! {
1713 get, [
1714 [ffi, ffi_mut] solver: &[MjSolverStat; mjNISLAND as usize * mjNSOLVER as usize]; "solver statistics per island, per iteration.";
1715 [ffi, ffi_mut] solver_niter: &[i32; mjNISLAND as usize]; "number of solver iterations, per island.";
1716 [ffi, ffi_mut] solver_nnz: &[i32; mjNISLAND as usize]; "number of nonzeros in solver matrix, per island.";
1717 [ffi, ffi_mut] solver_fwdinv: &[MjtNum; 2]; "forward-inverse comparison: qfrc, efc.";
1718 [ffi, ffi_mut] warning: &[MjWarningStat; MjtWarning::mjNWARNING as usize]; "warning statistics (mutable).";
1719 [ffi, ffi_mut] timer: &[MjTimerStat; MjtTimer::mjNTIMER as usize]; "timer statistics.";
1720 ]
1721 }
1722}
1723
1724impl<M: ModelTypeMut> MjData<M> {
1725 pub unsafe fn model_mut(&mut self) -> &mut MjModel {
1757 &mut self.model
1758 }
1759
1760 pub fn model_opt_mut(&mut self) -> &mut MjOption {
1775 self.model.opt_mut()
1776 }
1777
1778 pub fn model_vis_mut(&mut self) -> &mut MjVisual {
1793 self.model.vis_mut()
1794 }
1795
1796 pub fn model_stat_mut(&mut self) -> &mut MjStatistic {
1810 self.model.stat_mut()
1811 }
1812}
1813
1814impl<M: ModelType> MjData<M> {
1816 array_slice_dyn! {
1817 probe = probe_dynamic_arrays;
1818 qpos: &[MjtNum; "position"; model.ffi().nq],
1819 qvel: &[MjtNum; "velocity"; model.ffi().nv],
1820 act: &[MjtNum; "actuator activation"; model.ffi().na],
1821 (mut = unsafe) history: &[MjtNum; "history buffer"; model.ffi().nhistory],
1822 qacc_warmstart: &[MjtNum; "acceleration used for warmstart"; model.ffi().nv],
1823 plugin_state: &[MjtNum; "plugin state"; model.ffi().npluginstate],
1824 ctrl: &[MjtNum; "control"; model.ffi().nu],
1825 qfrc_applied: &[MjtNum; "applied generalized force"; model.ffi().nv],
1826 xfrc_applied: &[[MjtNum; 6] [force]; "applied Cartesian force/torque"; model.ffi().nbody],
1827 eq_active: &[MjtBool; "enable/disable constraints"; model.ffi().neq],
1828 mocap_pos: &[[MjtNum; 3] [force]; "positions of mocap bodies"; model.ffi().nmocap],
1829 mocap_quat: &[[MjtNum; 4] [force]; "orientations of mocap bodies"; model.ffi().nmocap],
1830 qacc: &[MjtNum; "acceleration"; model.ffi().nv],
1831 act_dot: &[MjtNum; "time-derivative of actuator activation"; model.ffi().na],
1832 userdata: &[MjtNum; "user data, not touched by engine"; model.ffi().nuserdata],
1833 sensordata: &[MjtNum; "sensor data array"; model.ffi().nsensordata],
1834 (mut = unsafe) tree_asleep: &[i32; "<0: awake; >=0: index cycle of sleeping trees"; model.ffi().ntree],
1835 xpos: &[[MjtNum; 3] [force]; "Cartesian position of body frame"; model.ffi().nbody],
1836 xquat: &[[MjtNum; 4] [force]; "Cartesian orientation of body frame"; model.ffi().nbody],
1837 xmat: &[[MjtNum; 9] [force]; "Cartesian orientation of body frame"; model.ffi().nbody],
1838 xipos: &[[MjtNum; 3] [force]; "Cartesian position of body com"; model.ffi().nbody],
1839 ximat: &[[MjtNum; 9] [force]; "Cartesian orientation of body inertia"; model.ffi().nbody],
1840 xanchor: &[[MjtNum; 3] [force]; "Cartesian position of joint anchor"; model.ffi().njnt],
1841 xaxis: &[[MjtNum; 3] [force]; "Cartesian joint axis"; model.ffi().njnt],
1842 geom_xpos: &[[MjtNum; 3] [force]; "Cartesian geom position"; model.ffi().ngeom],
1843 geom_xmat: &[[MjtNum; 9] [force]; "Cartesian geom orientation"; model.ffi().ngeom],
1844 site_xpos: &[[MjtNum; 3] [force]; "Cartesian site position"; model.ffi().nsite],
1845 site_xmat: &[[MjtNum; 9] [force]; "Cartesian site orientation"; model.ffi().nsite],
1846 cam_xpos: &[[MjtNum; 3] [force]; "Cartesian camera position"; model.ffi().ncam],
1847 cam_xmat: &[[MjtNum; 9] [force]; "Cartesian camera orientation"; model.ffi().ncam],
1848 light_xpos: &[[MjtNum; 3] [force]; "Cartesian light position"; model.ffi().nlight],
1849 light_xdir: &[[MjtNum; 3] [force]; "Cartesian light direction"; model.ffi().nlight],
1850 subtree_com: &[[MjtNum; 3] [force]; "center of mass of each subtree"; model.ffi().nbody],
1851 cdof: &[[MjtNum; 6] [force]; "com-based motion axis of each dof (rot:lin)"; model.ffi().nv],
1852 cinert: &[[MjtNum; 10] [force]; "com-based body inertia and mass"; model.ffi().nbody],
1853 flexvert_xpos: &[[MjtNum; 3] [force]; "Cartesian flex vertex positions"; model.ffi().nflexvert],
1854 flexelem_aabb: &[[MjtNum; 6] [force]; "flex element bounding boxes (center, size)"; model.ffi().nflexelem],
1855 flexelem_krot: &[MjtNum; "corotated element stiffness (implicit only)"; model.ffi().nflexstiffness],
1856 flexedge_J: &[MjtNum; "flex edge Jacobian"; model.ffi().nJfe],
1857 flexedge_length: &[MjtNum; "flex edge lengths"; model.ffi().nflexedge],
1858 flexvert_J: &[[MjtNum; 2] [force]; "flex vertex Jacobian"; model.ffi().nJfv],
1859 flexvert_length: &[[MjtNum; 2] [force]; "flex vertex lengths"; model.ffi().nflexvert],
1860 bvh_aabb_dyn: &[[MjtNum; 6] [force]; "global bounding box (center, size)"; model.ffi().nbvhdynamic],
1861 (mut = unsafe) ten_wrapadr: &[i32; "start address of tendon's path"; model.ffi().ntendon],
1862 (mut = unsafe) ten_wrapnum: &[i32; "number of wrap points in path"; model.ffi().ntendon],
1863 ten_J: &[MjtNum; "tendon Jacobian"; model.ffi().nJten],
1864 ten_length: &[MjtNum; "tendon lengths"; model.ffi().ntendon],
1865 (mut = unsafe) wrap_obj: &[[i32; 2] [force]; "geom id; -1: site; -2: pulley"; model.ffi().nwrap],
1866 wrap_xpos: &[[MjtNum; 6] [force]; "Cartesian 3D points in all paths"; model.ffi().nwrap],
1867 actuator_length: &[MjtNum; "actuator lengths, one per force output"; model.ffi().nout],
1868 (mut = unsafe) moment_rownnz: &[i32; "number of non-zeros in actuator_moment row"; model.ffi().nout],
1869 (mut = unsafe) moment_rowadr: &[i32; "row start address in colind array"; model.ffi().nout],
1870 (mut = unsafe) moment_colind: &[i32; "column indices in sparse Jacobian"; model.ffi().nJmom],
1871 actuator_moment: &[MjtNum; "actuator moments"; model.ffi().nJmom],
1872 crb: &[[MjtNum; 10] [force]; "com-based composite inertia and mass"; model.ffi().nbody],
1873 M: &[MjtNum; "inertia (compressed sparse row)"; model.ffi().nC],
1874 qLD: &[MjtNum; "L'*D*L factorization of M (sparse)"; model.ffi().nC],
1875 qLDiagInv: &[MjtNum; "1/diag(D)"; model.ffi().nv],
1876 bvh_active: &[MjtBool; "was bounding volume checked for collision"; model.ffi().nbvh],
1877 tree_awake: &[i32; "is tree awake; 0: asleep; 1: awake"; model.ffi().ntree],
1878 body_awake: &[MjtSleepState [force]; "body sleep state"; model.ffi().nbody],
1879 (mut = unsafe) body_awake_ind: &[i32; "indices of awake and static bodies"; model.ffi().nbody],
1880 (mut = unsafe) parent_awake_ind: &[i32; "indices of bodies with awake or static parents"; model.ffi().nbody],
1881 (mut = unsafe) dof_awake_ind: &[i32; "indices of awake dofs"; model.ffi().nv],
1882 flexedge_velocity: &[MjtNum; "flex edge velocities"; model.ffi().nflexedge],
1883 ten_velocity: &[MjtNum; "tendon velocities"; model.ffi().ntendon],
1884 actuator_velocity: &[MjtNum; "actuator velocities, one per force output"; model.ffi().nout],
1885 cvel: &[[MjtNum; 6] [force]; "com-based velocity (rot:lin)"; model.ffi().nbody],
1886 cdof_dot: &[[MjtNum; 6] [force]; "time-derivative of cdof (rot:lin)"; model.ffi().nv],
1887 qfrc_bias: &[MjtNum; "C(qpos,qvel)"; model.ffi().nv],
1888 qfrc_spring: &[MjtNum; "passive spring force"; model.ffi().nv],
1889 qfrc_damper: &[MjtNum; "passive damper force"; model.ffi().nv],
1890 qfrc_gravcomp: &[MjtNum; "passive gravity compensation force"; model.ffi().nv],
1891 qfrc_fluid: &[MjtNum; "passive fluid force"; model.ffi().nv],
1892 qfrc_adhesion: &[MjtNum; "passive contact adhesion force"; model.ffi().nv],
1893 qfrc_passive: &[MjtNum; "total passive force"; model.ffi().nv],
1894 subtree_linvel: &[[MjtNum; 3] [force]; "linear velocity of subtree com"; model.ffi().nbody],
1895 subtree_angmom: &[[MjtNum; 3] [force]; "angular momentum about subtree com"; model.ffi().nbody],
1896 qH: &[MjtNum; "L'*D*L factorization of modified M"; model.ffi().nC],
1897 qHDiagInv: &[MjtNum; "1/diag(D) of modified M"; model.ffi().nv],
1898 qDeriv: &[MjtNum; "d (passive + actuator - bias) / d qvel"; model.ffi().nD],
1899 qLU: &[MjtNum; "sparse LU of (M - dt*qDeriv)"; model.ffi().nD],
1900 actuator_force: &[MjtNum; "actuator force in actuation space"; model.ffi().nout],
1901 qfrc_actuator: &[MjtNum; "actuator force in joint space"; model.ffi().nv],
1902 qfrc_smooth: &[MjtNum; "net unconstrained force"; model.ffi().nv],
1903 qacc_smooth: &[MjtNum; "unconstrained acceleration"; model.ffi().nv],
1904 qfrc_constraint: &[MjtNum; "constraint force"; model.ffi().nv],
1905 qfrc_inverse: &[MjtNum; "net external force; should equal qfrc_applied + J'*xfrc_applied + qfrc_actuator"; model.ffi().nv],
1906 cacc: &[[MjtNum; 6] [force]; "com-based acceleration"; model.ffi().nbody],
1907 cfrc_int: &[[MjtNum; 6] [force]; "com-based interaction force with parent"; model.ffi().nbody],
1908 cfrc_ext: &[[MjtNum; 6] [force]; "com-based external force on body"; model.ffi().nbody],
1909 (mut = unsafe) contact: &[MjContact; "array of all detected contacts"; ffi().ncon],
1910 (mut = unsafe) efc_type: &[MjtConstraint [force]; "constraint type"; ffi().nefc],
1911 (mut = unsafe) efc_id: &[i32; "id of object of specified type"; ffi().nefc],
1912 (read = unsafe) efc_J_rownnz: &[i32; "number of non-zeros in constraint Jacobian row"; ffi().nefc],
1913 (read = unsafe) efc_J_rowadr: &[i32; "row start address in colind array"; ffi().nefc],
1914 (read = unsafe) efc_J_rowsuper: &[i32; "number of subsequent rows in supernode"; ffi().nefc],
1915 (read = unsafe) efc_J_colind: &[i32; "column indices in constraint Jacobian"; ffi().nJ],
1916 efc_J: &[MjtNum; "constraint Jacobian"; ffi().nJ],
1917 efc_pos: &[MjtNum; "constraint position (equality, contact)"; ffi().nefc],
1918 efc_margin: &[MjtNum; "inclusion margin (contact)"; ffi().nefc],
1919 efc_frictionloss: &[MjtNum; "frictionloss (friction)"; ffi().nefc],
1920 efc_diagA: &[MjtNum; "diagonal of A matrix, approximate or exact"; ffi().nefc],
1921 efc_KBIP: &[[MjtNum; 4] [force]; "stiffness, damping, impedance, imp'"; ffi().nefc],
1922 efc_D: &[MjtNum; "constraint mass"; ffi().nefc],
1923 efc_R: &[MjtNum; "inverse constraint mass"; ffi().nefc],
1924 (mut = unsafe) tendon_efcadr: &[i32; "first efc address involving tendon; -1: none"; model.ffi().ntendon],
1925 (mut = unsafe) tree_island: &[i32; "island id of this tree; -1: none"; model.ffi().ntree],
1926 (mut = unsafe) island_ntree: &[i32; "number of trees in this island"; ffi().nisland],
1927 (mut = unsafe) island_itreeadr: &[i32; "island start address in itree vector"; ffi().nisland],
1928 (mut = unsafe) map_itree2tree: &[i32; "map from itree to tree"; model.ffi().ntree],
1929 (mut = unsafe) dof_island: &[i32; "island id of this dof; -1: none"; model.ffi().nv],
1930 (mut = unsafe) island_nv: &[i32; "number of dofs in this island"; ffi().nisland],
1931 (mut = unsafe) island_idofadr: &[i32; "island start address in idof vector"; ffi().nisland],
1932 (mut = unsafe) island_dofadr: &[i32; "island start address in dof vector"; ffi().nisland],
1933 (mut = unsafe) map_dof2idof: &[i32; "map from dof to idof"; model.ffi().nv],
1934 (mut = unsafe) map_idof2dof: &[i32; "map from idof to dof; >= nidof: unconstrained"; model.ffi().nv],
1935 (read = unsafe) ifrc_smooth: &[MjtNum; "net unconstrained force"; ffi().nidof],
1936 (read = unsafe) iacc_smooth: &[MjtNum; "unconstrained acceleration"; ffi().nidof],
1937 (read = unsafe) iacc: &[MjtNum; "acceleration"; ffi().nidof],
1938 (mut = unsafe) efc_island: &[i32; "island id of this constraint"; ffi().nefc],
1939 (mut = unsafe) island_ne: &[i32; "number of equality constraints in island"; ffi().nisland],
1940 (mut = unsafe) island_nf: &[i32; "number of friction constraints in island"; ffi().nisland],
1941 (mut = unsafe) island_nefc: &[i32; "number of constraints in island"; ffi().nisland],
1942 (mut = unsafe) island_iefcadr: &[i32; "start address in iefc vector"; ffi().nisland],
1943 (mut = unsafe) map_efc2iefc: &[i32; "map from efc to iefc"; ffi().nefc],
1944 (mut = unsafe) map_iefc2efc: &[i32; "map from iefc to efc"; ffi().nefc],
1945 (mut = unsafe) iefc_type: &[MjtConstraint [force]; "constraint type"; ffi().nefc],
1946 (mut = unsafe) iefc_id: &[i32; "id of object of specified type"; ffi().nefc],
1947 iefc_frictionloss: &[MjtNum; "frictionloss (friction)"; ffi().nefc],
1948 iefc_D: &[MjtNum; "constraint mass"; ffi().nefc],
1949 iefc_R: &[MjtNum; "inverse constraint mass"; ffi().nefc],
1950 (mut = unsafe) efc_Y_rownnz: &[i32; "number of non-zeros in Y row"; ffi().nefc],
1951 (mut = unsafe) efc_Y_rowadr: &[i32; "row start address in Y colind array"; ffi().nefc],
1952 (mut = unsafe) efc_Y_colind: &[i32; "column indices in sparse Y"; ffi().nY],
1953 efc_Y: &[MjtNum; "whitened Jacobian Y = J*M^(-1/2)"; ffi().nY],
1954 (mut = unsafe) efc_AR_rownnz: &[i32; "number of non-zeros in AR"; ffi().nefc],
1955 (mut = unsafe) efc_AR_rowadr: &[i32; "row start address in colind array"; ffi().nefc],
1956 (mut = unsafe) efc_AR_colind: &[i32; "column indices in sparse AR"; ffi().nA],
1957 efc_AR: &[MjtNum; "J*inv(M)*J' + R"; ffi().nA],
1958 (read = unsafe) efc_vel: &[MjtNum; "velocity in constraint space: J*qvel"; ffi().nefc],
1959 (read = unsafe) efc_aref: &[MjtNum; "reference pseudo-acceleration"; ffi().nefc],
1960 efm_c: &[MjtNum; "smooth-force shift h*K*qvel"; model.ffi().nv],
1961 (mut = unsafe) efm_K_rownnz: &[i32; "effective-stiffness CSR row nonzeros"; model.ffi().nv],
1962 (mut = unsafe) efm_K_rowadr: &[i32; "effective-stiffness CSR row addresses"; model.ffi().nv],
1963 (mut = unsafe) efm_K_colind: &[i32; "effective-stiffness CSR column indices"; ffi().nefmK],
1964 efm_K_val: &[MjtNum; "effective-stiffness CSR values"; ffi().nefmK],
1965 (mut = unsafe) efm_dofid: &[i32; "block k -> dof address of its vertex triple"; ffi().nefmdof],
1966 efm_L: &[MjtNum; "factored 3x3 diagonal blocks of M+K"; ffi().nefmL],
1967 (read = unsafe) efc_b: &[MjtNum; "linear cost term: J*qacc_smooth - aref"; ffi().nefc],
1968 (read = unsafe) iefc_aref: &[MjtNum; "reference pseudo-acceleration"; ffi().nefc],
1969 (read = unsafe) iefc_state: &[MjtConstraintState [force]; "constraint state"; ffi().nefc],
1970 (read = unsafe) iefc_force: &[MjtNum; "constraint force in constraint space"; ffi().nefc],
1971 (read = unsafe) efc_state: &[MjtConstraintState [force]; "constraint state"; ffi().nefc],
1972 (read = unsafe) efc_force: &[MjtNum; "constraint force in constraint space"; ffi().nefc],
1973 (read = unsafe) ifrc_constraint: &[MjtNum; "constraint force"; ffi().nidof]
1974 }
1975}
1976
1977impl<M: ModelType> Drop for MjData<M> {
1978 fn drop(&mut self) {
1979 unsafe {
1981 mj_deleteData(self.data.as_ptr());
1982 }
1983 }
1984}
1985
1986impl<M: ModelType + Clone> Clone for MjData<M> {
1987 #[expect(deprecated, reason = "try_clone keeps the implementation until it is removed")]
1990 fn clone(&self) -> Self {
1991 self.try_clone().expect("not enough space to clone data")
1992 }
1993}
1994
1995impl<M: ModelType + Clone> MjData<M> {
1996 #[deprecated(
2005 since = "6.0.0",
2006 note = "always returns Ok; use `clone`"
2007 )]
2008 pub fn try_clone(&self) -> Result<Self, MjDataError> {
2009 let raw = unsafe { mj_copyData(ptr::null_mut(), self.model.ffi(), self.ffi()) };
2010 NonNull::new(raw)
2011 .map(|data| Self { data, model: self.model.clone() })
2012 .ok_or(MjDataError::AllocationFailed)
2013 }
2014}
2015
2016info_with_view!(Data, actuator,
2017 [ctrl: MjtNum,
2018 [actuator_] length: MjtNum,
2019 [actuator_] velocity: MjtNum,
2020 [actuator_] force: MjtNum],
2021 [],
2022 [act: MjtNum], M: ModelType);
2023
2024info_with_view!(Data, body,
2025 [xfrc_applied: MjtNum,
2026 xpos: MjtNum,
2027 xquat: MjtNum,
2028 xmat: MjtNum,
2029 xipos: MjtNum,
2030 ximat: MjtNum,
2031 subtree_com: MjtNum,
2032 cinert: MjtNum,
2033 crb: MjtNum,
2034 cvel: MjtNum,
2035 subtree_linvel: MjtNum,
2036 subtree_angmom: MjtNum,
2037 cacc: MjtNum,
2038 cfrc_int: MjtNum,
2039 cfrc_ext: MjtNum,
2040 [body_] awake: MjtSleepState [force]],
2041 [],
2042 [], M: ModelType);
2043
2044info_with_view!(Data, camera,
2045 [[cam_] xpos: MjtNum,
2046 [cam_] xmat: MjtNum],
2047 [],
2048 [], M: ModelType);
2049
2050info_with_view!(Data, geom,
2051 [[geom_] xpos: MjtNum,
2052 [geom_] xmat: MjtNum],
2053 [],
2054 [], M: ModelType);
2055
2056info_with_view!(Data, joint,
2057 [qpos: MjtNum,
2058 qvel: MjtNum,
2059 qacc_warmstart: MjtNum,
2060 qfrc_applied: MjtNum,
2061 qacc: MjtNum,
2062 xanchor: MjtNum,
2063 xaxis: MjtNum,
2064 qLDiagInv: MjtNum,
2065 qfrc_bias: MjtNum,
2066 qfrc_spring: MjtNum,
2067 qfrc_damper: MjtNum,
2068 qfrc_gravcomp: MjtNum,
2069 qfrc_fluid: MjtNum,
2070 qfrc_adhesion: MjtNum,
2071 qfrc_passive: MjtNum,
2072 qfrc_actuator: MjtNum,
2073 qfrc_smooth: MjtNum,
2074 qacc_smooth: MjtNum,
2075 qfrc_constraint: MjtNum,
2076 qfrc_inverse: MjtNum],
2077 [],
2078 [], M: ModelType);
2079
2080info_with_view!(Data, light,
2081 [[light_] xpos: MjtNum,
2082 [light_] xdir: MjtNum],
2083 [],
2084 [], M: ModelType);
2085
2086info_with_view!(Data, sensor,
2087 [[sensor] data: MjtNum],
2088 [],
2089 [], M: ModelType);
2090
2091info_with_view!(Data, site,
2092 [[site_] xpos: MjtNum,
2093 [site_] xmat: MjtNum],
2094 [],
2095 [], M: ModelType);
2096
2097info_with_view!(Data, tendon,
2098 [[ten_] J: MjtNum,
2099 [ten_] length: MjtNum,
2100 [ten_] velocity: MjtNum],
2101 [[ten_] wrapadr: i32,
2102 [ten_] wrapnum: i32,
2103 [tendon_] efcadr: i32],
2104 [], M: ModelType);
2105
2106#[cfg(test)]
2111#[allow(clippy::needless_range_loop)]
2113mod test {
2114 use crate::assert_relative_eq;
2115 use crate::prelude::*;
2116 use super::*;
2117
2118 const MODEL: &str = "
2119<mujoco>
2120 <asset>
2121 <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 -0.5 -0.5 0.5 0.5 -0.5 0.5 -0.5 0.5 0.5 0.5 0.5 0.5\"/>
2122 <hfield name=\"terrain\" nrow=\"10\" ncol=\"10\" size=\"10 10 .1 .1\"/>
2123 </asset>
2124 <worldbody>
2125 <light ambient=\"0.2 0.2 0.2\"/>
2126 <body name=\"ball\" pos=\".2 .2 .1\">
2127 <geom name=\"green_sphere\" size=\".1\" rgba=\"0 1 0 1\" solref=\"0.004 1.0\"/>
2128 <joint name=\"ball\" type=\"free\"/>
2129 </body>
2130
2131 <body name=\"ball2\" pos=\".7 .2 .1\">
2132 <geom name=\"green_sphere2\" size=\".1\" rgba=\"0 1 0 1\" solref=\"0.004 1.0\"/>
2133 <joint name=\"ball2\" type=\"free\"/>
2134 </body>
2135
2136 <geom name=\"floor1\" type=\"plane\" size=\"10 10 1\" solref=\"0.004 1.0\"/>
2137 <geom name=\"mesh_cube\" type=\"mesh\" mesh=\"cube\" pos=\"2 2 0.5\"/>
2138 <geom name=\"hfield_terrain\" type=\"hfield\" hfield=\"terrain\" pos=\"-2 -2 0\"/>
2139 </worldbody>
2140 <actuator>
2141 <motor name=\"motor_ball\" joint=\"ball\"/>
2142 </actuator>
2143</mujoco>";
2144
2145
2146 #[test]
2147 fn test_joint_view() {
2148 let model = MjModel::from_xml_string(MODEL).unwrap();
2149 let mut data = model.make_data();
2150 let joint_info = data.joint("ball").unwrap();
2151 let body_info = data.body("ball").unwrap();
2152
2153 for _ in 0..10 {
2154 data.step();
2155 }
2156
2157 let mut joint_view = joint_info.view(&data);
2159 assert_relative_eq!(joint_view.qvel[0], 0.0, epsilon=1e-9); assert_relative_eq!(joint_view.qvel[1], 0.0, epsilon=1e-9); assert_relative_eq!(joint_view.qvel[3], 0.0, epsilon=1e-9); assert_relative_eq!(joint_view.qvel[4], 0.0, epsilon=1e-9); assert_relative_eq!(joint_view.qvel[5], 0.0, epsilon=1e-9); let mut joint_view_mut = joint_info.view_mut(&mut data);
2168 joint_view_mut.qvel[0] = 0.5; joint_view_mut.qvel[4] = 0.5 / 0.1; let initial_qpos: [MjtNum; 3] = joint_view_mut.qpos[..3].try_into().unwrap(); data.step();
2173
2174 joint_view = joint_info.view(&data);
2176 assert_eq!(joint_view.qfrc_spring.len(), joint_view.qvel.len());
2177 assert_eq!(joint_view.qfrc_damper.len(), joint_view.qvel.len());
2178 assert_eq!(joint_view.qfrc_gravcomp.len(), joint_view.qvel.len());
2179 assert_eq!(joint_view.qfrc_fluid.len(), joint_view.qvel.len());
2180 joint_view = joint_info.view(&data);
2181 assert_relative_eq!(joint_view.qvel[0], 0.5, epsilon=1e-3); assert_relative_eq!(joint_view.qvel[4], 0.5 / 0.1, epsilon=1e-3); let timestep = model.opt().timestep;
2186 assert_relative_eq!(joint_view.qpos[0], initial_qpos[0] + timestep * joint_view.qvel[0], epsilon=1e-9); assert_relative_eq!(joint_view.qpos[1], initial_qpos[1] + timestep * joint_view.qvel[1], epsilon=1e-9);
2188 assert_relative_eq!(joint_view.qpos[2], initial_qpos[2] + timestep * joint_view.qvel[2], epsilon=1e-9);
2189
2190 data.step1(); joint_view = joint_info.view(&data);
2194 let body_view = body_info.view(&data);
2195 assert_relative_eq!(joint_view.qpos[0], body_view.xpos[0], epsilon=1e-9); assert_relative_eq!(joint_view.qpos[1], body_view.xpos[1], epsilon=1e-9);
2198 assert_relative_eq!(joint_view.qpos[2], body_view.xpos[2], epsilon=1e-9);
2199
2200 assert_relative_eq!(joint_view.qpos[3], body_view.xquat[0], epsilon=1e-9); assert_relative_eq!(joint_view.qpos[4], body_view.xquat[1], epsilon=1e-9);
2202 assert_relative_eq!(joint_view.qpos[5], body_view.xquat[2], epsilon=1e-9);
2203 assert_relative_eq!(joint_view.qpos[6], body_view.xquat[3], epsilon=1e-9);
2204
2205 assert_relative_eq!(joint_view.qvel[0], body_view.cvel[3], epsilon=1e-9); assert_relative_eq!(joint_view.qvel[1], body_view.cvel[4], epsilon=1e-9);
2208 assert_relative_eq!(joint_view.qvel[2], body_view.cvel[5], epsilon=1e-9);
2209
2210 assert_relative_eq!(joint_view.qvel[3], body_view.cvel[0], epsilon=1e-9); assert_relative_eq!(joint_view.qvel[4], body_view.cvel[1], epsilon=1e-9);
2212 assert_relative_eq!(joint_view.qvel[5], body_view.cvel[2], epsilon=1e-9);
2213 }
2214
2215 #[test]
2216 fn test_actuator_view() {
2217 let model = MjModel::from_xml_string(MODEL).unwrap();
2218 let data = model.make_data();
2219 let actuator_info = data.actuator("motor_ball").unwrap();
2220
2221 let actuator_view = actuator_info.view(&data);
2222 assert_eq!(actuator_view.length.len(), 1);
2223 assert_eq!(actuator_view.velocity.len(), 1);
2224 assert_eq!(actuator_view.force.len(), 1);
2225 assert!(actuator_view.act.is_none());
2226
2227 let outadr = model.actuator_outadr()[actuator_info.id] as usize;
2229 unsafe {
2230 assert_relative_eq!(actuator_view.length[0], *data.ffi().actuator_length.add(outadr), epsilon=1e-9);
2231 assert_relative_eq!(actuator_view.velocity[0], *data.ffi().actuator_velocity.add(outadr), epsilon=1e-9);
2232 assert_relative_eq!(actuator_view.force[0], *data.ffi().actuator_force.add(outadr), epsilon=1e-9);
2233 }
2234 }
2235
2236 #[test]
2237 fn test_body_view() {
2238 let model = MjModel::from_xml_string(MODEL).unwrap();
2239 let mut data = model.make_data();
2240 let body_info = data.body("ball2").unwrap();
2241 let mut cvel;
2242
2243 data.step1();
2244
2245 for _ in 0..10 {
2246 data.step2();
2247 data.step1(); }
2249
2250 cvel = body_info.view(&data).cvel;
2254 assert_relative_eq!(cvel[0], 0.0, epsilon=1e-5);
2255 assert_relative_eq!(cvel[1], 0.0, epsilon=1e-5);
2256 assert_relative_eq!(cvel[2], 0.0, epsilon=1e-5);
2257 assert_relative_eq!(cvel[3], 0.0, epsilon=1e-5);
2258 assert_relative_eq!(cvel[4], 0.0, epsilon=1e-5);
2259 body_info.view_mut(&mut data).xfrc_applied[0] = 5.0;
2263 data.step2();
2264 data.step1();
2265
2266 let view = body_info.view(&data);
2267 cvel = view.cvel;
2268 println!("{:?}", cvel);
2269 assert_relative_eq!(cvel[0], 0.0, epsilon=1e-9);
2270 assert!(cvel[1] > 0.0); assert_relative_eq!(cvel[2], 0.0, epsilon=1e-9);
2272 assert!(cvel[3] > 0.0); assert_relative_eq!(view.xfrc_applied[0], 5.0, epsilon=1e-9); data.step2();
2278 data.step1();
2279 }
2280
2281 #[test]
2282 fn test_copy_reset_variants() {
2283 let model = MjModel::from_xml_string(MODEL).unwrap();
2284 let mut data = model.make_data();
2285
2286 data.reset();
2288 unsafe { data.reset_debug(7) };
2291 assert!(data.reset_keyframe(0).is_err());
2293 }
2294
2295 #[test]
2296 fn test_dynamics_and_sensors() {
2297 let model = MjModel::from_xml_string(MODEL).unwrap();
2298 let mut data = model.make_data();
2299
2300 data.fwd_position();
2302 data.fwd_velocity();
2303 data.fwd_actuation();
2304 data.fwd_acceleration();
2305 data.fwd_constraint();
2306
2307 data.euler();
2308 data.runge_kutta(4);
2309 data.inv_position();
2312 data.inv_velocity();
2313 data.inv_constraint();
2314 data.compare_fwd_inv();
2315
2316 data.sensor_pos();
2318 data.sensor_vel();
2319 data.sensor_acc();
2320
2321 data.energy_pos();
2322 data.energy_vel();
2323
2324 data.check_pos();
2325 data.check_vel();
2326 data.check_acc();
2327
2328 data.kinematics();
2329 data.com_pos();
2330 data.camlight();
2331 data.flex_comp();
2332 data.tendon_comp();
2333 data.transmission();
2334 data.crb_comp();
2335 data.make_m();
2336 data.factor_m();
2337 data.com_vel();
2338 data.passive();
2339 data.subtree_vel();
2340 }
2341
2342
2343 #[test]
2344 fn test_rne_and_collision_pipeline() {
2345 let model = MjModel::from_xml_string(MODEL).unwrap();
2346 let mut data = model.make_data();
2347 for _ in 0..5 {
2348 data.step();
2349 }
2350
2351 data.rne(true);
2353
2354 data.rne_post_constraint();
2355
2356 data.collision();
2358 data.make_constraint();
2359 data.island();
2360 data.project_constraint();
2361 data.reference_constraint();
2362
2363 let nefc = data.nefc() as usize;
2364 assert!(nefc > 0, "expected at least one effective constraint after stepping");
2365 let jar = vec![0.0; nefc];
2366 let mut cost = 0.0;
2367 data.constraint_update(&jar, None, false).unwrap();
2368 data.constraint_update(&jar, Some(&mut cost), true).unwrap();
2369 }
2370
2371 #[test]
2372 fn test_add_contact() {
2373 let model = MjModel::from_xml_string(MODEL).unwrap();
2374 let mut data = model.make_data();
2375
2376 let dummy_contact: MjContact = bytemuck::Zeroable::zeroed();
2379 unsafe { data.add_contact(&dummy_contact).unwrap(); }
2380 assert_eq!(data.ncon(), 1, "contact count should reflect the added contact");
2381 }
2382
2383 #[test]
2384 fn test_jacobian() {
2385 let model = MjModel::from_xml_string(MODEL).unwrap();
2386 let mut data = model.make_data();
2387
2388 let nv = data.model.ffi().nv as usize;
2389 let expected_len = 3 * nv;
2390
2391 let point = [0.1, 0.0, 0.0];
2393
2394 let ball_body_id = model.body("ball").unwrap().id;
2395
2396 let (jacp, jacr) = data.jac(true, true, &point, ball_body_id);
2398 assert_eq!(jacp.len(), expected_len);
2399 assert_eq!(jacr.len(), expected_len);
2400
2401 let (jacp_body, jacr_body) = data.jac_body(true, true, ball_body_id);
2403 assert_eq!(jacp_body.len(), expected_len);
2404 assert_eq!(jacr_body.len(), expected_len);
2405
2406 let (jacp_com, jacr_com) = data.jac_body_com(true, true, ball_body_id);
2408 assert_eq!(jacp_com.len(), expected_len);
2409 assert_eq!(jacr_com.len(), expected_len);
2410
2411 let jac_subtree = data.jac_subtree_com(0);
2413 assert_eq!(jac_subtree.len(), expected_len);
2414
2415 let green_geom_id = model.geom("green_sphere").unwrap().id;
2417 let (jacp_geom, jacr_geom) = data.jac_geom(true, true, green_geom_id);
2418 assert_eq!(jacp_geom.len(), expected_len);
2419 assert_eq!(jacr_geom.len(), expected_len);
2420
2421 if model.ffi().nsite > 0 {
2423 let site_id = 0usize;
2424 let (jacp_site, jacr_site) = data.jac_site(true, true, site_id);
2425 assert_eq!(jacp_site.len(), expected_len);
2426 assert_eq!(jacr_site.len(), expected_len);
2427 }
2428
2429 let (jacp_none, jacr_none) = data.jac(false, false, &[0.0; 3], ball_body_id);
2431 assert!(jacp_none.is_empty());
2432 assert!(jacr_none.is_empty());
2433 }
2434
2435 #[test]
2436 fn test_angmom_and_object_dynamics() {
2437 let model = MjModel::from_xml_string(MODEL).unwrap();
2438 let mut data = model.make_data();
2439
2440 let mat = data.angmom_mat(0);
2441 assert_eq!(mat.len(), (3 * data.model.ffi().nv as usize));
2442
2443 let vel = data.object_velocity(MjtObj::mjOBJ_BODY, 0, true);
2444 assert_eq!(vel.len(), 6);
2445
2446 let acc = data.object_acceleration(MjtObj::mjOBJ_BODY, 0, false);
2447 assert_eq!(acc.len(), 6);
2448 }
2449
2450 #[test]
2451 fn test_geom_distance_and_transforms() {
2452 let model = MjModel::from_xml_string(MODEL).unwrap();
2453 let mut data = model.make_data();
2454 data.step();
2455
2456 let geom0_id = model.name_to_id(MjtObj::mjOBJ_GEOM, "green_sphere").unwrap();
2460 let geom1_id = model.name_to_id(MjtObj::mjOBJ_GEOM, "green_sphere2").unwrap();
2461
2462 let mut ft = [0.0; 6];
2463 let dist = data.geom_distance(geom0_id, geom1_id, 1.0, Some(&mut ft));
2464 assert!(dist > 0.0, "distance between separate geoms should be positive, got {dist}");
2465 assert!(dist < 1.0, "distance should be less than distmax, got {dist}");
2466 assert_relative_eq!(dist, 0.3, epsilon=1e-3);
2467 let ft_norm = ft.iter().map(|x| x * x).sum::<MjtNum>().sqrt();
2469 assert!(ft_norm > 0.0, "fromto should be non-zero for non-overlapping geoms");
2470
2471 let pos = [0.0; 3];
2472 let quat = [1.0, 0.0, 0.0, 0.0];
2473 let (xpos, xmat) = data.local_to_global(&pos, &quat, 0, MjtSameFrame::mjSAMEFRAME_NONE);
2474 assert_eq!(xpos.len(), 3);
2475 assert_eq!(xmat.len(), 9);
2476
2477 let ray_vecs = [[1.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 0.0, 0.0]];
2478 let rays = data.multi_ray(&pos, &ray_vecs, None, false, None, 10.0, None);
2479 assert_eq!(rays.0.len(), 3);
2480 assert_eq!(rays.1.len(), 3);
2481
2482 let (_geomid, dist) = data.ray(&pos, &[1.0, 0.0, 0.0], None, true, None, None);
2483 assert!(dist.is_finite());
2484
2485 let mut normal = [0.0; 3];
2487 let (_geomid2, dist2) = data.ray(&pos, &[1.0, 0.0, 0.0], None, true, None, Some(&mut normal));
2488 assert!(dist2.is_finite());
2489 let norm_len = (normal[0]*normal[0] + normal[1]*normal[1] + normal[2]*normal[2]).sqrt();
2490 if dist2 >= 0.0 {
2491 assert!(norm_len > 0.0);
2493 } else {
2494 assert_eq!(normal, [0.0; 3]);
2496 }
2497
2498 let mut normals_buf = vec![[0.0; 3]; ray_vecs.len()];
2499 let (gids, dists) = data.multi_ray(&pos, &ray_vecs, None, false, None, 10.0, Some(&mut normals_buf));
2500 assert_eq!(gids.len(), normals_buf.len());
2501 assert_eq!(dists.len(), normals_buf.len());
2502 for (d, n) in dists.iter().zip(normals_buf.iter()) {
2503 let l = (n[0]*n[0] + n[1]*n[1] + n[2]*n[2]).sqrt();
2504 if *d >= 0.0 {
2505 assert!(l > 0.0);
2506 } else {
2507 assert_eq!(*n, [0.0; 3]);
2508 }
2509 }
2510 }
2511
2512 #[test]
2513 fn test_constraint_update_checks_jar_length() {
2514 let model = MjModel::from_xml_string(MODEL).unwrap();
2515 let mut data = model.make_data();
2516
2517 for _ in 0..5 {
2520 data.step();
2521 }
2522
2523 let nefc = data.ffi().nefc as usize;
2525 assert!(nefc > 0, "Expected nefc > 0 after stepping with contact model, got {}", nefc);
2526
2527 let jar = vec![0.0; nefc];
2529 data.constraint_update(&jar, None, false).unwrap();
2530
2531 let bad_jar = vec![0.0; 1];
2533 let result = data.constraint_update(&bad_jar, None, false);
2534 assert!(result.is_err());
2535 }
2536
2537 #[test]
2538 fn test_init_history_ctrl_and_sensor() {
2539 const HIST_MODEL: &str = r#"
2540<mujoco>
2541 <option timestep="0.01"/>
2542 <worldbody>
2543 <body name="body">
2544 <joint name="slide" type="slide"/>
2545 <geom size="0.1"/>
2546 </body>
2547 </worldbody>
2548 <actuator>
2549 <motor name="motor0" joint="slide" delay="0.05" nsample="6"/>
2550 </actuator>
2551 <sensor>
2552 <jointpos name="jointpos0" joint="slide" delay="0.025" interp="linear" nsample="4"/>
2553 </sensor>
2554</mujoco>
2555"#;
2556
2557 let model = MjModel::from_xml_string(HIST_MODEL).unwrap();
2558 let mut data = model.make_data();
2559
2560 let times_ctrl: Vec<MjtNum> = (0..6).map(|i| i as MjtNum * 0.01).collect();
2561 let values_ctrl = vec![1.2345; 6];
2562 data.init_ctrl_history(0, Some(×_ctrl), &values_ctrl).unwrap();
2563
2564 let val = data.read_ctrl(0, times_ctrl[2], 0);
2566 assert_relative_eq!(val, values_ctrl[2], epsilon=1e-12);
2567
2568 let times_sens: Vec<MjtNum> = (0..4).map(|i| i as MjtNum * 0.01).collect();
2570 let values_sens = vec![std::f64::consts::E; 4];
2572 data.init_sensor_history(0, Some(×_sens), &values_sens, 0.0).unwrap();
2573
2574 let got = data.read_sensor_fixed::<1>(0, times_sens[1], 0);
2575 assert_eq!(got.len(), 1);
2576 assert_relative_eq!(got[0], values_sens[1], epsilon=1e-12);
2577
2578 let interp_res = data.read_sensor_fixed::<1>(0, 0.005, 1);
2580 assert_eq!(interp_res.len(), 1);
2581 assert_relative_eq!(interp_res[0], values_sens[0], epsilon=1e-12);
2583 }
2584
2585 #[test]
2586 fn test_fwd_kinematics_and_mul_wrappers() {
2587 let model = MjModel::from_xml_string(MODEL).unwrap();
2588 let mut data = model.make_data();
2589
2590 let joint_info = data.joint("ball").unwrap();
2591 {
2592 let mut jv = joint_info.view_mut(&mut data);
2593 jv.qpos[0] = 0.42; jv.qpos[1] = 0.43; jv.qpos[2] = 0.44; jv.qpos[3] = 1.0; jv.qpos[4] = 0.0;
2598 jv.qpos[5] = 0.0;
2599 jv.qpos[6] = 0.0;
2600 }
2601 data.forward_kinematics();
2602 let body_view = data.body("ball").unwrap().view(&data);
2603 assert_relative_eq!(body_view.xpos[0], 0.42, epsilon=1e-9);
2604 assert_relative_eq!(body_view.xpos[1], 0.43, epsilon=1e-9);
2605 assert_relative_eq!(body_view.xpos[2], 0.44, epsilon=1e-9);
2606 }
2607
2608 #[test]
2609 fn test_qpos_view() {
2610 const JOINT_BALL_DOF: usize = 7;
2611 const BALL_INDEX: usize = 1;
2612 const DOF_TO_MODIFY: usize = 2;
2613 const MODIFIED_VALUE: f64 = 15.0;
2614
2615 let model = MjModel::from_xml_string(MODEL).unwrap();
2616 let name = model.id_to_name(MjtObj::mjOBJ_JOINT, BALL_INDEX).unwrap();
2617
2618 let mut data = MjData::new(&model);
2619 let ball2_joint_info = data.joint(name).unwrap();
2620 ball2_joint_info.view_mut(&mut data).qpos[DOF_TO_MODIFY] = MODIFIED_VALUE;
2621
2622 assert_eq!(data.qpos()[JOINT_BALL_DOF * BALL_INDEX + DOF_TO_MODIFY], MODIFIED_VALUE);
2623 }
2624
2625 #[test]
2626 fn test_contacts() {
2627 let model = MjModel::from_xml_string(MODEL).unwrap();
2628 let mut data = MjData::new(&model);
2629 let ptr = unsafe { data.ffi_mut().contact };
2630 assert!(ptr.is_aligned());
2631 assert!(!ptr.is_null());
2632 assert!(data.contact().is_empty());
2633 data.step();
2634
2635 assert!(!data.contact().is_empty());
2636 }
2637
2638 #[test]
2642 fn test_set_state_rejects_out_of_range_history_cursor() {
2643 const HIST_MODEL: &str = r#"
2644<mujoco>
2645 <worldbody>
2646 <body name="body">
2647 <joint name="slide" type="slide"/>
2648 <geom size="0.1"/>
2649 </body>
2650 </worldbody>
2651 <actuator>
2652 <motor name="motor0" joint="slide" delay="0.05" nsample="6"/>
2653 </actuator>
2654</mujoco>
2655"#;
2656 let model = MjModel::from_xml_string(HIST_MODEL).unwrap();
2657 let mut data = model.make_data();
2658
2659 let spec = MjtState::mjSTATE_HISTORY as u32;
2660 let mut state = data.state(spec).to_vec();
2661 let before = data.history().to_vec();
2662 assert!(!before.is_empty(), "the fixture must have a history buffer");
2663
2664 let address = model.actuator_historyadr()[0] as usize;
2666 let nsample = model.actuator_history()[0][0];
2667
2668 assert!(data.set_state(&state, spec).is_ok());
2670
2671 for poison in [-1.0, f64::from(nsample), 1e18] {
2672 state[address + 1] = poison;
2673 let err = data.set_state(&state, spec).unwrap_err();
2674 assert_eq!(
2675 err,
2676 MjDataError::InvalidHistoryCursor { kind: "actuator", id: 0, nsample: nsample as usize },
2677 "cursor {poison} must be rejected",
2678 );
2679 assert_eq!(data.history(), before.as_slice(), "a rejected write must not change history");
2680 }
2681
2682 state[address + 1] = 1.5;
2684 assert!(data.set_state(&state, spec).is_err());
2685
2686 for cursor in 0..nsample {
2688 state[address + 1] = f64::from(cursor);
2689 assert!(data.set_state(&state, spec).is_ok(), "cursor {cursor} must be accepted");
2690 }
2691 }
2692
2693 #[test]
2694 fn test_init_ctrl_history_all_combinations() {
2695 const HIST_MODEL: &str = r#"
2696<mujoco>
2697 <worldbody>
2698 <body name="body">
2699 <joint name="slide" type="slide"/>
2700 <geom size="0.1"/>
2701 </body>
2702 </worldbody>
2703 <actuator>
2704 <motor name="motor0" joint="slide" delay="0.05" nsample="6"/>
2705 </actuator>
2706</mujoco>
2707"#;
2708
2709 let model = MjModel::from_xml_string(HIST_MODEL).unwrap();
2710 let mut data = model.make_data();
2711
2712 let times: Vec<MjtNum> = (0..6).map(|i| i as MjtNum * 0.01).collect();
2713 let values = vec![1.2345; 6];
2714
2715 assert!(data.init_ctrl_history(0, Some(×), &values).is_ok());
2717 assert!(data.init_ctrl_history(0, None, &values).is_ok());
2718
2719 let bad_times = vec![0.0f64];
2721 let err = data.init_ctrl_history(0, Some(&bad_times), &values).unwrap_err();
2722 assert!(matches!(err, MjDataError::LengthMismatch { name: "times", .. }));
2723
2724 let bad_values = vec![1.0; 5];
2726 let err = data.init_ctrl_history(0, Some(×), &bad_values).unwrap_err();
2727 assert!(matches!(err, MjDataError::LengthMismatch { name: "values", .. }));
2728
2729 let err = data.init_ctrl_history(99, Some(×), &values).unwrap_err();
2731 assert!(matches!(err, MjDataError::IndexOutOfBounds { kind: "actuator_id", .. }));
2732
2733 let err = data.try_read_ctrl(99, times[0], 0).unwrap_err();
2735 assert!(matches!(err, MjDataError::IndexOutOfBounds { kind: "actuator_id", .. }));
2736
2737 const NO_HIST_ACT_MODEL: &str = r#"
2739<mujoco>
2740 <worldbody>
2741 <body>
2742 <joint name="slide" type="slide"/>
2743 <geom size="0.1"/>
2744 </body>
2745 </worldbody>
2746 <actuator>
2747 <motor name="motor0" joint="slide"/>
2748 </actuator>
2749</mujoco>
2750"#;
2751 let model2 = MjModel::from_xml_string(NO_HIST_ACT_MODEL).unwrap();
2752 let mut data2 = model2.make_data();
2753 let err = data2.init_ctrl_history(0, Some(×), &values).unwrap_err();
2754 assert!(matches!(err, MjDataError::NoHistoryBuffer { kind: "actuator", id: 0 }));
2755 }
2756
2757 #[test]
2758 fn test_init_sensor_history_all_combinations() {
2759 const HIST_SENSOR_MODEL: &str = r#"
2760<mujoco>
2761 <worldbody>
2762 <body name="body">
2763 <joint name="slide" type="slide"/>
2764 <geom size="0.1"/>
2765 </body>
2766 </worldbody>
2767 <sensor>
2768 <jointpos name="jointpos0" joint="slide" delay="0.025" interp="linear" nsample="4"/>
2769 </sensor>
2770</mujoco>
2771"#;
2772
2773 let model = MjModel::from_xml_string(HIST_SENSOR_MODEL).unwrap();
2774 let mut data = model.make_data();
2775
2776 let times_sens: Vec<MjtNum> = (0..4).map(|i| i as MjtNum * 0.01).collect();
2777 let values_sens = vec![std::f64::consts::E; 4]; assert!(data.init_sensor_history(0, Some(×_sens), &values_sens, 0.0).is_ok());
2781 assert!(data.init_sensor_history(0, None, &values_sens, 0.0).is_ok());
2782
2783 let bad_times = vec![0.0f64];
2785 let err = data.init_sensor_history(0, Some(&bad_times), &values_sens, 0.0).unwrap_err();
2786 assert!(matches!(err, MjDataError::LengthMismatch { name: "times", .. }));
2787
2788 let bad_values = vec![std::f64::consts::PI; 3];
2790 let err = data.init_sensor_history(0, Some(×_sens), &bad_values, 0.0).unwrap_err();
2791 assert!(matches!(err, MjDataError::LengthMismatch { name: "values", .. }));
2792
2793 let err = data.init_sensor_history(99, Some(×_sens), &values_sens, 0.0).unwrap_err();
2795 assert!(matches!(err, MjDataError::IndexOutOfBounds { kind: "sensor_id", .. }));
2796
2797 let err: Result<[MjtNum; 1], MjDataError> = data.try_read_sensor_fixed::<1>(99, times_sens[0], 0);
2799 assert!(matches!(err.unwrap_err(), MjDataError::IndexOutOfBounds { kind: "sensor_id", .. }));
2800
2801 const NO_HIST_SENS_MODEL: &str = r#"
2803<mujoco>
2804 <worldbody>
2805 <body>
2806 <joint name="slide" type="slide"/>
2807 <geom size="0.1"/>
2808 </body>
2809 </worldbody>
2810 <sensor>
2811 <jointpos name="jointpos0" joint="slide"/>
2812 </sensor>
2813</mujoco>
2814"#;
2815 let model2 = MjModel::from_xml_string(NO_HIST_SENS_MODEL).unwrap();
2816 let mut data2 = model2.make_data();
2817 let err = data2.init_sensor_history(0, Some(×_sens), &values_sens, 0.0).unwrap_err();
2818 assert!(matches!(err, MjDataError::NoHistoryBuffer { kind: "sensor", id: 0 }));
2819 }
2820
2821 #[test]
2822 fn test_read_sensor_variants() {
2823 const HIST_MODEL: &str = r#"
2826<mujoco>
2827 <option timestep="0.01"/>
2828 <worldbody>
2829 <body>
2830 <joint name="slide" type="slide"/>
2831 <geom size="0.1"/>
2832 </body>
2833 </worldbody>
2834 <sensor>
2835 <jointpos name="jp" joint="slide" delay="0.03" interp="linear" nsample="4"/>
2836 </sensor>
2837</mujoco>
2838"#;
2839 let model = MjModel::from_xml_string(HIST_MODEL).unwrap();
2840 let mut data = model.make_data();
2841 let delay = 0.03;
2842
2843 let hist_times: Vec<_> = (0..4).map(|i| i as MjtNum * 0.01).collect();
2845 let values = vec![10.0, 20.0, 30.0, 40.0]; data.init_sensor_history(0, Some(&hist_times), &values, 0.0).unwrap();
2847
2848 let query_time = hist_times[2] + delay; let cow = data.read_sensor(0, query_time, 0);
2854 assert_eq!(cow.len(), 1);
2855 assert_relative_eq!(cow[0], 30.0, epsilon = 1e-12);
2856 assert!(matches!(cow, std::borrow::Cow::Borrowed(_)),
2857 "exact match should yield Cow::Borrowed");
2858
2859 let interp_query = (hist_times[1] + hist_times[2]) / 2.0 + delay; let cow_interp = data.read_sensor(0, interp_query, 1);
2863 assert_eq!(cow_interp.len(), 1);
2864 assert_relative_eq!(cow_interp[0], 25.0, epsilon = 1e-6); assert!(matches!(cow_interp, std::borrow::Cow::Owned(_)),
2866 "interpolation should yield Cow::Owned");
2867
2868 let arr_exact: [f64; 1] = data.read_sensor_fixed(0, query_time, 0);
2870 assert_relative_eq!(arr_exact[0], 30.0, epsilon = 1e-12);
2871
2872 let arr_interp: [f64; 1] = data.read_sensor_fixed(0, interp_query, 1);
2874 assert_relative_eq!(arr_interp[0], 25.0, epsilon = 1e-6);
2875
2876 let err: Result<[MjtNum; 3], MjDataError> = data.try_read_sensor_fixed::<3>(0, query_time, 0);
2878 assert!(matches!(err.unwrap_err(), MjDataError::LengthMismatch { name: "N", .. }));
2879
2880 let mut buf = [0.0; 1];
2882 data.read_sensor_into(0, hist_times[0] + delay, 0, &mut buf).unwrap();
2883 assert_relative_eq!(buf[0], 10.0, epsilon = 1e-12);
2884
2885 let mut buf2 = [0.0; 1];
2887 data.read_sensor_into(0, interp_query, 1, &mut buf2).unwrap();
2888 assert_relative_eq!(buf2[0], 25.0, epsilon = 1e-6);
2889
2890 let mut tiny: [MjtNum; 0] = [];
2892 let err = data.read_sensor_into(0, hist_times[0] + delay, 0, &mut tiny).unwrap_err();
2893 assert!(matches!(err, MjDataError::LengthMismatch { name: "dst", .. }));
2894
2895 let mut big = [0.0; 4];
2897 let err = data.read_sensor_into(0, hist_times[3] + delay, 0, &mut big).unwrap_err();
2898 assert!(matches!(err, MjDataError::LengthMismatch { name: "dst", .. }));
2899
2900 let err: Result<[MjtNum; 1], MjDataError> = data.try_read_sensor_fixed::<1>(99, 0.0, 0);
2902 assert!(matches!(err.unwrap_err(), MjDataError::IndexOutOfBounds { kind: "sensor_id", .. }));
2903 let err = data.try_read_sensor(99, 0.0, 0).unwrap_err();
2904 assert!(matches!(err, MjDataError::IndexOutOfBounds { kind: "sensor_id", .. }));
2905 let err = data.read_sensor_into(99, 0.0, 0, &mut buf).unwrap_err();
2906 assert!(matches!(err, MjDataError::IndexOutOfBounds { kind: "sensor_id", .. }));
2907
2908 for &t in &hist_times {
2910 let query = t + delay;
2911 let arr = data.read_sensor_fixed::<1>(0, query, 0);
2912 let cow_val = data.read_sensor(0, query, 0);
2913 let mut into_val = [0.0; 1];
2914 data.read_sensor_into(0, query, 0, &mut into_val).unwrap();
2915 assert_relative_eq!(arr[0], cow_val[0], epsilon = 1e-12);
2916 assert_relative_eq!(arr[0], into_val[0], epsilon = 1e-12);
2917 }
2918 }
2919
2920 #[test]
2921 fn test_multi_ray_zero_rays() {
2922 let model = MjModel::from_xml_string(MODEL).unwrap();
2923 let mut data = model.make_data();
2924 let pos = [0.0; 3];
2925 let ray_vecs: Vec<[MjtNum; 3]> = Vec::new();
2926
2927 let (gids, dists) = data.multi_ray(&pos, &ray_vecs, None, false, None, 10.0, None);
2929 assert!(gids.is_empty());
2930 assert!(dists.is_empty());
2931 }
2932
2933 #[test]
2934 fn test_multi_ray_normals_length_mismatch() {
2935 let model = MjModel::from_xml_string(MODEL).unwrap();
2936 let mut data = model.make_data();
2937 let pos = [0.0; 3];
2938 let ray_vecs = [[1.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 0.0, 0.0]];
2939 let mut bad_normals = vec![[0.0; 3]; 2]; let res = data.try_multi_ray(&pos, &ray_vecs, None, false, None, 10.0, Some(&mut bad_normals));
2942 assert!(res.is_err());
2943 assert!(matches!(res.unwrap_err(), MjDataError::LengthMismatch { name: "normals_out", .. }));
2944 }
2945
2946 #[test]
2947 fn test_copy_state_from_data() {
2948 let model = MjModel::from_xml_string(MODEL).unwrap();
2949 let mut data1 = model.make_data();
2950 let mut data2 = model.make_data();
2951
2952 data1.set_time(1.23);
2953 data2.copy_state_from_data(&data1, MjtState::mjSTATE_TIME as u32).unwrap();
2954
2955 assert_eq!(data2.time(), 1.23);
2956 }
2957
2958 #[test]
2959 #[should_panic]
2960 fn test_ray_flex() {
2961 let model = MjModel::from_xml_string(MODEL).unwrap();
2962 let data = model.make_data();
2963 let pos = [0.0; 3];
2964 let vec = [1.0, 0.0, 0.0];
2965
2966 data.ray_flex(0, false, false, false, false, 0, &pos, &vec, None, None);
2968 }
2969
2970 #[test]
2971 fn test_ray_mesh_hfield() {
2972 let model = MjModel::from_xml_string(MODEL).unwrap();
2973 let mut data = model.make_data();
2974
2975 let mesh_id = model.geom("mesh_cube").unwrap().id;
2976 let hfield_id = model.geom("hfield_terrain").unwrap().id;
2977
2978 data.forward_kinematics();
2979
2980 let mesh_dist = data.ray_mesh(mesh_id, &[2.0, 2.0, 5.0], &[0.0, 0.0, -1.0], None);
2985 assert_relative_eq!(mesh_dist, 4.0, epsilon=1e-5);
2986
2987 let hfield_dist = data.ray_hfield(hfield_id, &[-2.0, -2.0, 5.0], &[0.0, 0.0, -1.0], None);
2991 assert_relative_eq!(hfield_dist, 5.0, epsilon=1e-5);
2992 }
2993
2994 #[test]
2995 fn test_apply_ft() {
2996 let model = MjModel::from_xml_string(MODEL).unwrap();
2997 let mut data = model.make_data();
2998 let nv = model.nv() as usize;
2999 let body_id = model.body("ball").unwrap().id;
3000 let mut qfrc = vec![0.0; nv];
3001
3002 data.forward();
3003
3004 let force = [1.5, 2.5, 3.5];
3006 let torque = [0.1, 0.2, 0.3];
3007 let point = [0.2, 0.2, 0.1];
3008
3009 data.apply_ft(&force, &torque, &point, body_id, &mut qfrc).unwrap();
3010
3011 let dof_adr = model.body_dofadr()[body_id] as usize;
3016 assert!((qfrc[dof_adr] - 1.5).abs() < 1e-5);
3017 assert!((qfrc[dof_adr + 1] - 2.5).abs() < 1e-5);
3018 assert!((qfrc[dof_adr + 2] - 3.5).abs() < 1e-5);
3019 assert!((qfrc[dof_adr + 3] - 0.1).abs() < 1e-5);
3020 assert!((qfrc[dof_adr + 4] - 0.2).abs() < 1e-5);
3021 assert!((qfrc[dof_adr + 5] - 0.3).abs() < 1e-5);
3022 }
3023
3024 #[test]
3025 #[should_panic(expected = "the model is not compatible")]
3026 fn test_signature_mismatch_panics() {
3027 let model1 = MjModel::from_xml_string("<mujoco><worldbody><body name='b1'><joint name='j1' type='free'/><geom size='0.1' mass='1'/></body></worldbody></mujoco>").unwrap();
3028 let model2 = MjModel::from_xml_string("<mujoco><worldbody><body name='b1'><joint name='j1' type='free'/><geom size='0.1' mass='1'/></body><body name='extra'/></worldbody></mujoco>").unwrap();
3029
3030 let data1 = model1.make_data();
3031 let joint_info1 = data1.joint("j1").unwrap();
3032
3033 let data2 = model2.make_data();
3035 let _view = joint_info1.view(&data2);
3036 }
3037
3038 #[test]
3039 #[should_panic(expected = "the model is not compatible")]
3040 fn test_signature_mismatch_reversed_joints() {
3041 let model1 = MjModel::from_xml_string("<mujoco><worldbody><body name='b1'><joint name='j1' type='free'/><geom size='0.1' mass='1'/></body><body name='b2'><joint name='j2' type='ball'/><geom size='0.1' mass='1'/></body></worldbody></mujoco>").unwrap();
3042 let model2 = MjModel::from_xml_string("<mujoco><worldbody><body name='b1'><joint name='j2' type='ball'/><geom size='0.1' mass='1'/></body><body name='b2'><joint name='j1' type='free'/><geom size='0.1' mass='1'/></body></worldbody></mujoco>").unwrap();
3043
3044 let data1 = model1.make_data();
3045 let joint_info1 = data1.joint("j1").unwrap();
3046
3047 let data2 = model2.make_data();
3049 let _view = joint_info1.view(&data2);
3050 }
3051
3052 #[test]
3053 #[should_panic(expected = "the model is not compatible")]
3054 fn test_signature_mismatch_view_mut_panics() {
3055 let model1 = MjModel::from_xml_string("<mujoco><worldbody><body name='b1'><joint name='j1' type='free'/><geom size='0.1' mass='1'/></body></worldbody></mujoco>").unwrap();
3056 let model2 = MjModel::from_xml_string("<mujoco><worldbody><body name='b1'><joint name='j1' type='free'/><geom size='0.1' mass='1'/></body><body name='extra'/></worldbody></mujoco>").unwrap();
3057
3058 let data1 = model1.make_data();
3059 let joint_info1 = data1.joint("j1").unwrap();
3060
3061 let mut data2 = model2.make_data();
3062 let _view = joint_info1.view_mut(&mut data2);
3063 }
3064
3065
3066 #[test]
3069 fn test_try_swap_model_rejects_unpinned_size() {
3070 const BODY: &str = "<worldbody><body name='b1'><joint type='free'/><geom size='0.1'/></body></worldbody>";
3071 let plain = Box::new(MjModel::from_xml_string(&format!("<mujoco>{BODY}</mujoco>")).unwrap());
3072 let userdata = Box::new(
3073 MjModel::from_xml_string(&format!("<mujoco><size nuserdata='2000'/>{BODY}</mujoco>")).unwrap()
3074 );
3075 assert_eq!(plain.signature(), userdata.signature(), "the pair must share a signature");
3076
3077 let mut data = MjData::new(plain);
3078 let buffer_len = data.userdata().len();
3079 let err = data.try_swap_model(userdata).unwrap_err();
3080 match err {
3081 MjDataError::IncompatibleModel { source, destination } => assert_eq!(source, destination),
3082 other => panic!("expected IncompatibleModel, got {other:?}"),
3083 }
3084 assert_eq!(data.userdata().len(), buffer_len);
3085 }
3086
3087 #[test]
3088 fn test_try_view_signature_mismatch() {
3089 let model1 = MjModel::from_xml_string("<mujoco><worldbody><body name='b1'><joint name='j1' type='free'/><geom size='0.1' mass='1'/></body></worldbody></mujoco>").unwrap();
3090 let model2 = MjModel::from_xml_string("<mujoco><worldbody><body name='b1'><joint name='j1' type='free'/><geom size='0.1' mass='1'/></body><body name='extra'/></worldbody></mujoco>").unwrap();
3091
3092 let data1 = model1.make_data();
3093 let joint_info1 = data1.joint("j1").unwrap();
3094 let mut data2 = model2.make_data();
3095
3096 let err = joint_info1.try_view(&data2).unwrap_err();
3097 match err {
3098 MjDataError::IncompatibleModel { source, destination } => {
3099 assert_eq!(source, data1.signature());
3100 assert_eq!(destination, data2.signature());
3101 }
3102 other => panic!("expected IncompatibleModel, got {other:?}"),
3103 }
3104
3105 let err = joint_info1.try_view_mut(&mut data2).unwrap_err();
3106 match err {
3107 MjDataError::IncompatibleModel { source, destination } => {
3108 assert_eq!(source, data1.signature());
3109 assert_eq!(destination, data2.signature());
3110 }
3111 other => panic!("expected IncompatibleModel, got {other:?}"),
3112 }
3113 }
3114
3115 #[test]
3116 fn test_try_view_rejects_a_different_sensor_split() {
3117 let sensor_model = |first: u32, second: u32| MjModel::from_xml_string(&format!(
3118 "<mujoco><worldbody><body name='b1'><joint name='j' type='hinge'/><geom size='0.1'/>\
3119 </body></worldbody><sensor><user name='u1' dim='{first}' objtype='body' objname='b1'/>\
3120 <user name='u2' dim='{second}' objtype='body' objname='b1'/></sensor></mujoco>"
3121 )).unwrap();
3122
3123 let model = sensor_model(3, 1);
3124 let swapped = sensor_model(1, 3);
3125 assert_eq!(model.signature(), swapped.signature(), "the pair must share a signature");
3126 assert_eq!(model.nsensordata(), swapped.nsensordata(), "the data totals must agree");
3127
3128 let data = model.make_data();
3129 let info = data.sensor("u1").unwrap();
3130 assert_eq!(info.view(&data).data.len(), 3);
3131
3132 let mut other = swapped.make_data();
3134 assert!(info.try_view(&other).is_err());
3135 assert!(info.try_view_mut(&mut other).is_err());
3136 }
3137
3138 #[test]
3142 fn test_info_update_layout_accepts_only_a_compatible_model() {
3143 let sensor_model = |first: u32, second: u32| MjModel::from_xml_string(&format!(
3144 "<mujoco><worldbody><body name='b1'><joint name='j' type='hinge'/><geom size='0.1'/>\
3145 </body></worldbody><sensor><user name='u1' dim='{first}' objtype='body' objname='b1'/>\
3146 <user name='u2' dim='{second}' objtype='body' objname='b1'/></sensor></mujoco>"
3147 )).unwrap();
3148
3149 let model = sensor_model(3, 1);
3150 let data = model.make_data();
3151 let mut info = data.sensor("u1").unwrap();
3152
3153 let twin = sensor_model(3, 1);
3155 let twin_data = twin.make_data();
3156 assert!(info.try_view(&twin_data).is_ok());
3157 info.update_layout(&twin_data).unwrap();
3158 assert_eq!(info.view(&twin_data).data.len(), 3, "the cached range must survive");
3159
3160 let swapped = sensor_model(1, 3);
3162 let swapped_data = swapped.make_data();
3163 assert!(info.update_layout(&swapped_data).is_err());
3164 assert_eq!(info.view(&data).data.len(), 3, "a refused re-point must not disturb the Info");
3165 }
3166
3167 #[test]
3168 fn test_ray_zero_direction_reports_no_intersection() {
3169 let model = MjModel::from_xml_string(MODEL).unwrap();
3170 let mut data = model.make_data();
3171
3172 let mut normal = [1.0; 3];
3173 let (geom_id, distance) = data.ray(&[0.0; 3], &[0.0; 3], None, false, None, Some(&mut normal));
3174 assert_eq!(geom_id, None);
3175 assert_eq!(distance, -1.0);
3176 assert_eq!(normal, [0.0; 3]);
3177
3178 let (geom_ids, distances) = data.multi_ray(&[0.0; 3], &[[0.0; 3]], None, false, None, 10.0, None);
3179 assert_eq!(geom_ids, vec![None]);
3180 assert_eq!(distances, vec![-1.0]);
3181 }
3182
3183 #[test]
3184 fn test_signature_match_physics_param_change() {
3185 let model1 = MjModel::from_xml_string("<mujoco><worldbody><body name='b1'><joint name='j1' type='free'/><geom size='0.1' mass='1'/></body></worldbody></mujoco>").unwrap();
3186 let model2 = MjModel::from_xml_string("<mujoco><worldbody><body name='b1'><joint name='j1' type='free'/><geom size='0.1' mass='2'/></body></worldbody></mujoco>").unwrap();
3187
3188 let data1 = model1.make_data();
3189 let joint_info1 = data1.joint("j1").unwrap();
3190
3191 let data2 = model2.make_data();
3193 let _view = joint_info1.view(&data2);
3194 }
3195
3196 #[test]
3197 fn test_act_mixed_stateful_stateless() {
3198 let xml = "<mujoco><option timestep=\"0.002\"/>\
3202<worldbody><body name=\"b\"><joint name=\"j1\" type=\"slide\" range=\"-1 1\" limited=\"true\"/>\
3203<joint name=\"j2\" type=\"slide\"/><geom size=\"0.1\" mass=\"1\"/></body></worldbody>\
3204<actuator><muscle name=\"m1\" joint=\"j1\" lengthrange=\"0 1\"/><motor name=\"m2\" joint=\"j2\"/></actuator></mujoco>";
3205 let model = MjModel::from_xml_string(xml).unwrap();
3206 let data = model.make_data();
3207 let actadr = model.actuator_actadr();
3208 let actnum = model.actuator_actnum();
3209 eprintln!("actadr[0]={} actadr[1]={}", actadr[0], actadr[1]);
3210 eprintln!("actnum[0]={} actnum[1]={}", actnum[0], actnum[1]);
3211 let info_m1 = data.actuator("m1").unwrap();
3213 let view_m1 = info_m1.view(&data);
3214 let act_m1 = view_m1.act.as_ref().expect("muscle must have an act view (bug: overflow sets it to None/garbage)");
3215 assert_eq!(act_m1.len(), actnum[0] as usize,
3216 "muscle act len wrong: expected {} got {} (overflow bug?)", actnum[0], act_m1.len());
3217 let info_m2 = data.actuator("m2").unwrap();
3219 let view_m2 = info_m2.view(&data);
3220 assert!(view_m2.act.is_none(), "motor must have no act view");
3221 }
3222
3223 #[test]
3226 fn test_view_indices_mixed_joint_types() {
3227 const MIXED_MODEL: &str = "
3228<mujoco>
3229 <worldbody>
3230 <body name='b_free'>
3231 <joint name='j_free' type='free'/>
3232 <geom size='0.1' mass='1'/>
3233 </body>
3234 <body name='b_ball'>
3235 <joint name='j_ball' type='ball'/>
3236 <geom size='0.1' mass='1'/>
3237 </body>
3238 <body name='b_slide'>
3239 <joint name='j_slide' type='slide'/>
3240 <geom size='0.1' mass='1'/>
3241 </body>
3242 </worldbody>
3243</mujoco>";
3244
3245 let model = MjModel::from_xml_string(MIXED_MODEL).unwrap();
3246 let data = model.make_data();
3247
3248 let jfree = data.joint("j_free").unwrap();
3250 let jball = data.joint("j_ball").unwrap();
3251 let jslide = data.joint("j_slide").unwrap();
3252
3253 let vfree = jfree.view(&data);
3254 let vball = jball.view(&data);
3255 let vslide = jslide.view(&data);
3256
3257 assert_eq!(vfree.qpos.len(), 7);
3258 assert_eq!(vfree.qvel.len(), 6);
3259 assert_eq!(vball.qpos.len(), 4);
3260 assert_eq!(vball.qvel.len(), 3);
3261 assert_eq!(vslide.qpos.len(), 1);
3262 assert_eq!(vslide.qvel.len(), 1);
3263
3264 assert_eq!(model.ffi().nq as usize, 7 + 4 + 1);
3266 assert_eq!(model.ffi().nv as usize, 6 + 3 + 1);
3267 }
3268
3269 #[test]
3272 fn test_body_data_view_stride_lengths() {
3273 let model = MjModel::from_xml_string(MODEL).unwrap();
3274 let data = model.make_data();
3275
3276 let ball = data.body("ball").unwrap();
3277 let ball2 = data.body("ball2").unwrap();
3278
3279 let v1 = ball.view(&data);
3280 let v2 = ball2.view(&data);
3281
3282 assert_eq!(v1.xpos.len(), 3);
3284 assert_eq!(v1.xmat.len(), 9);
3285 assert_eq!(v1.xquat.len(), 4);
3286 assert_eq!(v1.cinert.len(), 10);
3287 assert_eq!(v1.cvel.len(), 6);
3288
3289 assert_ne!(v1.xpos.as_ptr(), v2.xpos.as_ptr());
3291 assert_ne!(v1.cvel.as_ptr(), v2.cvel.as_ptr());
3292 }
3293
3294 #[test]
3296 fn test_time_getter_setter_roundtrip() {
3297 let model = MjModel::from_xml_string(MODEL).unwrap();
3298 let mut data = model.make_data();
3299
3300 assert_relative_eq!(data.time(), 0.0, epsilon = 1e-15);
3301
3302 data.set_time(std::f64::consts::PI);
3303 assert_relative_eq!(data.time(), std::f64::consts::PI, epsilon = 1e-15);
3304
3305 let data2 = model.make_data().with_time(std::f64::consts::E);
3306 assert_relative_eq!(data2.time(), std::f64::consts::E, epsilon = 1e-15);
3307 }
3308
3309 #[test]
3311 fn test_jac_invalid_body_id() {
3312 let model = MjModel::from_xml_string(MODEL).unwrap();
3313 let data = model.make_data();
3314 let point = [0.0; 3];
3315
3316 let err = data.try_jac(true, true, &point, 9999).unwrap_err();
3318 assert!(matches!(err, MjDataError::IndexOutOfBounds { kind: "body_id", .. }));
3319 }
3320
3321 #[test]
3324 fn test_object_velocity_error_paths() {
3325 let model = MjModel::from_xml_string(MODEL).unwrap();
3326 let data = model.make_data();
3327
3328 let err = data.try_object_velocity(MjtObj::mjOBJ_JOINT, 0, false).unwrap_err();
3330 assert!(matches!(err, MjDataError::UnsupportedObjectType(_)));
3331
3332 let err = data.try_object_velocity(MjtObj::mjOBJ_BODY, 9999, false).unwrap_err();
3334 assert!(matches!(err, MjDataError::IndexOutOfBounds { kind: "obj_id", .. }));
3335 }
3336
3337 #[test]
3339 fn test_state_spec_flags_select_subsets() {
3340 let model = MjModel::from_xml_string(MODEL).unwrap();
3341 let mut data = model.make_data();
3342
3343 let jinfo = data.joint("ball").unwrap();
3345 jinfo.view_mut(&mut data).qvel[0] = 42.0;
3346 let original_qvel0 = jinfo.view(&data).qvel[0];
3347
3348 let fresh = model.make_data();
3350 data.copy_state_from_data(&fresh, MjtState::mjSTATE_QPOS as u32).unwrap();
3351
3352 assert_relative_eq!(jinfo.view(&data).qvel[0], original_qvel0, epsilon = 1e-15);
3354 }
3355
3356 #[test]
3358 fn test_copy_state_signature_mismatch() {
3359 let model1 = MjModel::from_xml_string("<mujoco><worldbody><body><joint type='free'/><geom size='0.1'/></body></worldbody></mujoco>").unwrap();
3360 let model2 = MjModel::from_xml_string("<mujoco><worldbody><body><joint type='slide'/><geom size='0.1'/></body></worldbody></mujoco>").unwrap();
3361
3362 let data1 = model1.make_data();
3363 let mut data2 = model2.make_data();
3364
3365 let err = data2.copy_state_from_data(&data1, MjtState::mjSTATE_FULLPHYSICS as u32).unwrap_err();
3366 match err {
3367 MjDataError::IncompatibleModel { source, destination } => {
3368 assert_ne!(source, destination);
3369 }
3370 other => panic!("expected IncompatibleModel, got {:?}", other),
3371 }
3372 }
3373
3374 #[test]
3376 fn test_copy_state_full_physics() {
3377 let model = MjModel::from_xml_string(MODEL).unwrap();
3378 let mut data1 = model.make_data();
3379 let mut data2 = model.make_data();
3380
3381 data1.set_time(1.0);
3383 data1.joint("ball").unwrap().view_mut(&mut data1).qpos[0] = 5.0;
3384 data1.joint("ball").unwrap().view_mut(&mut data1).qvel[0] = 3.0;
3385
3386 data2.copy_state_from_data(&data1, MjtState::mjSTATE_FULLPHYSICS as u32).unwrap();
3387
3388 assert_relative_eq!(data2.time(), 1.0, epsilon = 1e-15);
3389 assert_relative_eq!(data2.qpos()[0], 5.0, epsilon = 1e-15);
3390 assert_relative_eq!(data2.qvel()[0], 3.0, epsilon = 1e-15);
3391 }
3392
3393 const FORCE_MODEL: &str = "
3400<mujoco>
3401 <worldbody>
3402 <body name='b_free' pos='1 2 3'>
3403 <joint name='j_free' type='free'/>
3404 <geom name='g_sphere' type='sphere' size='0.1' mass='1'/>
3405 </body>
3406
3407 <body name='b_slide' pos='0 0 5'>
3408 <joint name='j_slide' type='slide' axis='0 0 1' range='-1 1' limited='true'/>
3409 <geom name='g_box' type='box' size='0.1 0.2 0.3' mass='1'/>
3410 <site name='s1' pos='0 0 0' size='0.05'/>
3411 </body>
3412
3413 <body name='b_hinge' pos='0 5 0'>
3414 <joint name='j_hinge' type='hinge' axis='0 1 0'/>
3415 <geom name='g_capsule' type='capsule' size='0.1 0.5' mass='1'/>
3416 <site name='s2' pos='0 0 0' size='0.05'/>
3417 </body>
3418
3419 <body name='mocap_body' mocap='true' pos='10 10 10'>
3420 <geom name='g_mocap' type='sphere' size='0.01' contype='0' conaffinity='0'/>
3421 </body>
3422
3423 <geom name='floor' type='plane' size='50 50 1'/>
3424 </worldbody>
3425
3426 <equality>
3427 <connect name='eq1' body1='b_slide' body2='b_hinge' anchor='0 0 0'/>
3428 <connect name='eq2' body1='b_hinge' body2='b_slide' anchor='1 2 3'/>
3429 </equality>
3430
3431 <tendon>
3432 <spatial name='ten1'>
3433 <site site='s1'/>
3434 <site site='s2'/>
3435 </spatial>
3436 </tendon>
3437
3438 <actuator>
3439 <motor name='motor_slide' joint='j_slide'/>
3440 </actuator>
3441
3442 <sensor>
3443 <touch name='touch_sensor' site='s1'/>
3444 </sensor>
3445</mujoco>";
3446
3447 #[test]
3450 fn test_force_cast_xpos_array_grouping() {
3451 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
3452 let mut data = model.make_data();
3453 data.forward();
3454
3455 let nbody = model.ffi().nbody as usize;
3456 let xpos = data.xpos();
3457
3458 assert_eq!(xpos.len(), nbody, "xpos slice len must equal nbody");
3461
3462 for i in 0..nbody {
3464 for j in 0..3 {
3465 let ffi_val = unsafe { *data.ffi().xpos.add(i * 3 + j) };
3466 assert_eq!(xpos[i][j], ffi_val,
3467 "xpos[{}][{}] mismatch: slice={} ffi={}", i, j, xpos[i][j], ffi_val);
3468 }
3469 }
3470 }
3471
3472 #[test]
3474 fn test_force_cast_xmat_xquat_grouping() {
3475 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
3476 let mut data = model.make_data();
3477 data.forward();
3478
3479 let nbody = model.ffi().nbody as usize;
3480 let xmat = data.xmat();
3481 let xquat = data.xquat();
3482
3483 assert_eq!(xmat.len(), nbody);
3484 assert_eq!(xquat.len(), nbody);
3485
3486 for i in 0..nbody {
3488 for j in 0..9 {
3489 let ffi_val = unsafe { *data.ffi().xmat.add(i * 9 + j) };
3490 assert_eq!(xmat[i][j], ffi_val,
3491 "xmat[{}][{}] mismatch", i, j);
3492 }
3493 }
3494
3495 for i in 0..nbody {
3497 for j in 0..4 {
3498 let ffi_val = unsafe { *data.ffi().xquat.add(i * 4 + j) };
3499 assert_eq!(xquat[i][j], ffi_val,
3500 "xquat[{}][{}] mismatch", i, j);
3501 }
3502 }
3503 }
3504
3505 #[test]
3507 fn test_force_cast_cinert_10_element_grouping() {
3508 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
3509 let mut data = model.make_data();
3510 data.forward();
3511
3512 let nbody = model.ffi().nbody as usize;
3513 let cinert = data.cinert();
3514 assert_eq!(cinert.len(), nbody);
3515
3516 for i in 0..nbody {
3517 for j in 0..10 {
3518 let ffi_val = unsafe { *data.ffi().cinert.add(i * 10 + j) };
3519 assert_eq!(cinert[i][j], ffi_val,
3520 "cinert[{}][{}] mismatch", i, j);
3521 }
3522 }
3523 }
3524
3525 #[test]
3527 fn test_force_cast_6_element_groupings() {
3528 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
3529 let mut data = model.make_data();
3530 data.forward();
3531
3532 let nbody = model.ffi().nbody as usize;
3533
3534 let cvel = data.cvel();
3536 assert_eq!(cvel.len(), nbody);
3537 for i in 0..nbody {
3538 for j in 0..6 {
3539 let ffi_val = unsafe { *data.ffi().cvel.add(i * 6 + j) };
3540 assert_eq!(cvel[i][j], ffi_val, "cvel[{}][{}] mismatch", i, j);
3541 }
3542 }
3543
3544 let xfrc = data.xfrc_applied();
3546 assert_eq!(xfrc.len(), nbody);
3547 for i in 0..nbody {
3548 for j in 0..6 {
3549 let ffi_val = unsafe { *data.ffi().xfrc_applied.add(i * 6 + j) };
3550 assert_eq!(xfrc[i][j], ffi_val, "xfrc_applied[{}][{}] mismatch", i, j);
3551 }
3552 }
3553 }
3554
3555 #[test]
3557 fn test_force_cast_mocap_arrays() {
3558 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
3559 let data = model.make_data();
3560
3561 let nmocap = model.ffi().nmocap as usize;
3562 assert!(nmocap > 0, "test model must have at least one mocap body");
3563
3564 let mocap_pos = data.mocap_pos();
3565 let mocap_quat = data.mocap_quat();
3566
3567 assert_eq!(mocap_pos.len(), nmocap);
3568 assert_eq!(mocap_quat.len(), nmocap);
3569
3570 assert_relative_eq!(mocap_pos[0][0], 10.0, epsilon = 1e-9);
3572 assert_relative_eq!(mocap_pos[0][1], 10.0, epsilon = 1e-9);
3573 assert_relative_eq!(mocap_pos[0][2], 10.0, epsilon = 1e-9);
3574
3575 assert_relative_eq!(mocap_quat[0][0], 1.0, epsilon = 1e-9);
3577 assert_relative_eq!(mocap_quat[0][1], 0.0, epsilon = 1e-9);
3578 assert_relative_eq!(mocap_quat[0][2], 0.0, epsilon = 1e-9);
3579 assert_relative_eq!(mocap_quat[0][3], 0.0, epsilon = 1e-9);
3580
3581 for i in 0..nmocap {
3583 for j in 0..3 {
3584 assert_eq!(mocap_pos[i][j], unsafe { *data.ffi().mocap_pos.add(i * 3 + j) });
3585 }
3586 for j in 0..4 {
3587 assert_eq!(mocap_quat[i][j], unsafe { *data.ffi().mocap_quat.add(i * 4 + j) });
3588 }
3589 }
3590 }
3591
3592 #[test]
3594 fn test_force_cast_eq_active_bool() {
3595 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
3596 let mut data = model.make_data();
3597
3598 let neq = model.ffi().neq as usize;
3599 assert_eq!(neq, 2, "test model must have exactly 2 equality constraints");
3600
3601 let eq_active = data.eq_active();
3603 assert_eq!(eq_active.len(), neq);
3604 assert!(eq_active[0]);
3605 assert!(eq_active[1]);
3606
3607 for i in 0..neq {
3609 let raw_val = unsafe { *data.ffi().eq_active.add(i) };
3610 assert_eq!(eq_active[i], raw_val,
3611 "eq_active[{}]: bool={} raw={}", i, eq_active[i], raw_val);
3612 }
3613
3614 data.eq_active_mut()[0] = false;
3616 assert!(!data.eq_active()[0]);
3617 assert!(data.eq_active()[1]);
3618
3619 let raw_val = unsafe { *data.ffi().eq_active.add(0) };
3621 assert!(!raw_val, "disabling eq_active[0] must write false to FFI");
3622 }
3623
3624 #[test]
3626 fn test_force_cast_mutable_roundtrip() {
3627 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
3628 let mut data = model.make_data();
3629
3630 let nbody = model.ffi().nbody as usize;
3631 assert!(nbody > 1);
3632
3633 let body_idx = 1; data.xfrc_applied_mut()[body_idx] = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
3636
3637 let xfrc = data.xfrc_applied();
3639 assert_eq!(xfrc[body_idx], [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
3640
3641 for j in 0..6 {
3643 let ffi_val = unsafe { *data.ffi().xfrc_applied.add(body_idx * 6 + j) };
3644 assert_eq!(ffi_val, (j + 1) as f64,
3645 "xfrc_applied FFI[{}] mismatch", j);
3646 }
3647
3648 let nmocap = model.ffi().nmocap as usize;
3650 if nmocap > 0 {
3651 data.mocap_pos_mut()[0] = [99.0, 88.0, 77.0];
3652 assert_eq!(data.mocap_pos()[0], [99.0, 88.0, 77.0]);
3653 for j in 0..3 {
3654 let ffi_val = unsafe { *data.ffi().mocap_pos.add(j) };
3655 assert_eq!(ffi_val, [99.0, 88.0, 77.0][j]);
3656 }
3657 }
3658 }
3659
3660 #[test]
3663 fn test_force_cast_body_view_strides_and_values() {
3664 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
3665 let mut data = model.make_data();
3666 data.forward();
3667
3668 let body_info = data.body("b_free").unwrap();
3669 let view = body_info.view(&data);
3670
3671 assert_eq!(view.xpos.len(), 3);
3673 assert_eq!(view.xquat.len(), 4);
3674 assert_eq!(view.xmat.len(), 9);
3675 assert_eq!(view.xipos.len(), 3);
3676 assert_eq!(view.ximat.len(), 9);
3677 assert_eq!(view.cinert.len(), 10);
3678 assert_eq!(view.cvel.len(), 6);
3679 assert_eq!(view.xfrc_applied.len(), 6);
3680 assert_eq!(view.crb.len(), 10);
3681 assert_eq!(view.subtree_com.len(), 3);
3682 assert_eq!(view.subtree_linvel.len(), 3);
3683 assert_eq!(view.subtree_angmom.len(), 3);
3684 assert_eq!(view.cacc.len(), 6);
3685 assert_eq!(view.cfrc_int.len(), 6);
3686 assert_eq!(view.cfrc_ext.len(), 6);
3687
3688 let body_id = body_info.id;
3691 for j in 0..3 {
3692 let ffi_val = unsafe { *data.ffi().xpos.add(body_id * 3 + j) };
3693 assert_eq!(view.xpos[j], ffi_val,
3694 "body view xpos[{}] must match FFI xpos", j);
3695 }
3696 }
3697
3698 #[test]
3701 fn test_force_cast_world_body_view() {
3702 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
3703 let mut data = model.make_data();
3704 data.forward();
3705
3706 let world_info = data.body("world").unwrap();
3708 assert_eq!(world_info.id, 0);
3709 let view = world_info.view(&data);
3710
3711 assert_eq!(view.xpos[..], [0.0, 0.0, 0.0]);
3713 assert_relative_eq!(view.xquat[0], 1.0, epsilon = 1e-9);
3715 assert_relative_eq!(view.xquat[1], 0.0, epsilon = 1e-9);
3716 assert_relative_eq!(view.xquat[2], 0.0, epsilon = 1e-9);
3717 assert_relative_eq!(view.xquat[3], 0.0, epsilon = 1e-9);
3718 assert_eq!(view.xmat.len(), 9);
3720 assert_eq!(view.cinert.len(), 10);
3721 }
3722
3723 #[test]
3725 fn test_force_cast_body_view_mut_roundtrip() {
3726 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
3727 let mut data = model.make_data();
3728
3729 let body_info = data.body("b_free").unwrap();
3730 let body_id = body_info.id;
3731
3732 body_info.view_mut(&mut data).xfrc_applied.copy_from_slice(&[10.0, 20.0, 30.0, 40.0, 50.0, 60.0]);
3734
3735 let view = body_info.view(&data);
3737 assert_eq!(&view.xfrc_applied[..], &[10.0, 20.0, 30.0, 40.0, 50.0, 60.0]);
3738
3739 assert_eq!(data.xfrc_applied()[body_id], [10.0, 20.0, 30.0, 40.0, 50.0, 60.0]);
3741
3742 for j in 0..6 {
3744 let ffi_val = unsafe { *data.ffi().xfrc_applied.add(body_id * 6 + j) };
3745 assert_eq!(ffi_val, ((j + 1) * 10) as f64);
3746 }
3747 }
3748
3749 #[test]
3751 fn test_force_cast_geom_site_cam_light_data() {
3752 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
3753 let mut data = model.make_data();
3754 data.forward();
3755
3756 let ngeom = model.ffi().ngeom as usize;
3758 assert_eq!(data.geom_xpos().len(), ngeom);
3759 assert_eq!(data.geom_xmat().len(), ngeom);
3760 for i in 0..ngeom {
3761 for j in 0..3 {
3762 assert_eq!(data.geom_xpos()[i][j],
3763 unsafe { *data.ffi().geom_xpos.add(i * 3 + j) });
3764 }
3765 for j in 0..9 {
3766 assert_eq!(data.geom_xmat()[i][j],
3767 unsafe { *data.ffi().geom_xmat.add(i * 9 + j) });
3768 }
3769 }
3770
3771 let nsite = model.ffi().nsite as usize;
3773 assert_eq!(data.site_xpos().len(), nsite);
3774 assert_eq!(data.site_xmat().len(), nsite);
3775 for i in 0..nsite {
3776 for j in 0..3 {
3777 assert_eq!(data.site_xpos()[i][j],
3778 unsafe { *data.ffi().site_xpos.add(i * 3 + j) });
3779 }
3780 }
3781
3782 let ncam = model.ffi().ncam as usize;
3784 assert_eq!(data.cam_xpos().len(), ncam);
3785 assert_eq!(data.cam_xmat().len(), ncam);
3786
3787 let nlight = model.ffi().nlight as usize;
3789 assert_eq!(data.light_xpos().len(), nlight);
3790 assert_eq!(data.light_xdir().len(), nlight);
3791 }
3792
3793 #[test]
3795 fn test_force_cast_joint_anchor_axis() {
3796 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
3797 let mut data = model.make_data();
3798 data.forward();
3799
3800 let njnt = model.ffi().njnt as usize;
3801 let xanchor = data.xanchor();
3802 let xaxis = data.xaxis();
3803
3804 assert_eq!(xanchor.len(), njnt);
3805 assert_eq!(xaxis.len(), njnt);
3806
3807 for i in 0..njnt {
3808 for j in 0..3 {
3809 assert_eq!(xanchor[i][j], unsafe { *data.ffi().xanchor.add(i * 3 + j) });
3810 assert_eq!(xaxis[i][j], unsafe { *data.ffi().xaxis.add(i * 3 + j) });
3811 }
3812 }
3813 }
3814
3815 #[test]
3817 fn test_force_cast_cdof_6_element() {
3818 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
3819 let mut data = model.make_data();
3820 data.forward();
3821
3822 let nv = model.ffi().nv as usize;
3823 let cdof = data.cdof();
3824 let cdof_dot = data.cdof_dot();
3825
3826 assert_eq!(cdof.len(), nv);
3827 assert_eq!(cdof_dot.len(), nv);
3828
3829 for i in 0..nv {
3830 for j in 0..6 {
3831 assert_eq!(cdof[i][j], unsafe { *data.ffi().cdof.add(i * 6 + j) });
3832 assert_eq!(cdof_dot[i][j], unsafe { *data.ffi().cdof_dot.add(i * 6 + j) });
3833 }
3834 }
3835 }
3836
3837 #[test]
3840 fn test_force_cast_efc_kbip_4_element() {
3841 let model = MjModel::from_xml_string(MODEL).unwrap();
3843 let mut data = model.make_data();
3844
3845 for _ in 0..10 {
3847 data.step();
3848 }
3849
3850 let nefc = data.ffi().nefc as usize;
3851 if nefc > 0 {
3852 let efc_kbip = data.efc_kbip();
3853 assert_eq!(efc_kbip.len(), nefc);
3854 for i in 0..nefc {
3855 for j in 0..4 {
3856 assert_eq!(efc_kbip[i][j], unsafe { *data.ffi().efc_KBIP.add(i * 4 + j) });
3857 }
3858 }
3859 }
3860 }
3861
3862 #[test]
3864 fn test_force_cast_efc_type_enum() {
3865 let model = MjModel::from_xml_string(MODEL).unwrap();
3866 let mut data = model.make_data();
3867
3868 for _ in 0..10 {
3869 data.step();
3870 }
3871
3872 let nefc = data.ffi().nefc as usize;
3873 if nefc > 0 {
3874 let efc_type = data.efc_type();
3875 assert_eq!(efc_type.len(), nefc);
3876
3877 for i in 0..nefc {
3878 let raw_i32 = unsafe { *data.ffi().efc_type.add(i) };
3879 let expected: MjtConstraint = unsafe { crate::util::force_cast(raw_i32) };
3880 assert_eq!(efc_type[i], expected,
3881 "efc_type[{}]: got {:?}, expected {:?} (raw={})", i, efc_type[i], expected, raw_i32);
3882 }
3883 }
3884 }
3885
3886 #[test]
3888 fn test_force_cast_body_awake_enum() {
3889 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
3890 let mut data = model.make_data();
3891 data.forward();
3892
3893 let nbody = model.ffi().nbody as usize;
3894 let body_awake = data.body_awake();
3895 assert_eq!(body_awake.len(), nbody);
3896
3897 for i in 0..nbody {
3898 let raw_i32 = unsafe { *data.ffi().body_awake.add(i) };
3899 let expected: MjtSleepState = unsafe { crate::util::force_cast(raw_i32) };
3900 assert_eq!(body_awake[i], expected,
3901 "body_awake[{}] mismatch", i);
3902 }
3903 }
3904
3905 #[test]
3907 fn test_force_cast_bvh_active_bool() {
3908 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
3909 let mut data = model.make_data();
3910
3911 data.step();
3913
3914 let nbvh = model.ffi().nbvh as usize;
3915 let bvh_active = data.bvh_active();
3916 assert_eq!(bvh_active.len(), nbvh);
3917
3918 for i in 0..nbvh {
3919 let raw_bool = unsafe { *data.ffi().bvh_active.add(i) };
3920 assert_eq!(bvh_active[i], raw_bool);
3921 }
3922 }
3923
3924 #[test]
3926 fn test_force_cast_wrap_arrays() {
3927 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
3928 let mut data = model.make_data();
3929 data.forward();
3930
3931 let nwrap = model.ffi().nwrap as usize;
3932 let wrap_obj = data.wrap_obj();
3933 let wrap_xpos = data.wrap_xpos();
3934
3935 assert_eq!(wrap_obj.len(), nwrap);
3936 assert_eq!(wrap_xpos.len(), nwrap);
3937
3938 for i in 0..nwrap {
3939 for j in 0..2 {
3940 assert_eq!(wrap_obj[i][j], unsafe { *data.ffi().wrap_obj.add(i * 2 + j) });
3941 }
3942 for j in 0..6 {
3943 assert_eq!(wrap_xpos[i][j], unsafe { *data.ffi().wrap_xpos.add(i * 6 + j) });
3944 }
3945 }
3946 }
3947
3948 #[test]
3951 fn test_force_cast_flex_empty_slices() {
3952 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
3953 let data = model.make_data();
3954
3955 assert!(data.flexvert_xpos().is_empty(), "no flex -> empty flexvert_xpos");
3957 assert!(data.flexelem_aabb().is_empty(), "no flex -> empty flexelem_aabb");
3958 assert!(data.flexvert_j().is_empty(), "no flex -> empty flexvert_J");
3959 assert!(data.flexvert_length().is_empty(), "no flex -> empty flexvert_length");
3960 assert!(data.flexedge_j().is_empty(), "no flex -> empty flexedge_J");
3961 assert!(data.flexedge_length().is_empty(), "no flex -> empty flexedge_length");
3962 }
3963
3964 #[test]
3966 fn test_force_cast_bvh_aabb_dyn() {
3967 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
3968 let mut data = model.make_data();
3969 data.forward();
3970
3971 let nbvhdyn = model.ffi().nbvhdynamic as usize;
3972 let aabb_dyn = data.bvh_aabb_dyn();
3973 assert_eq!(aabb_dyn.len(), nbvhdyn);
3974
3975 for i in 0..nbvhdyn {
3976 for j in 0..6 {
3977 assert_eq!(aabb_dyn[i][j], unsafe { *data.ffi().bvh_aabb_dyn.add(i * 6 + j) });
3978 }
3979 }
3980 }
3981
3982 #[test]
3985 fn test_force_cast_subtree_3_element() {
3986 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
3987 let mut data = model.make_data();
3988 data.forward();
3989
3990 let nbody = model.ffi().nbody as usize;
3991
3992 let subtree_com = data.subtree_com();
3993 let subtree_linvel = data.subtree_linvel();
3994 let subtree_angmom = data.subtree_angmom();
3995
3996 assert_eq!(subtree_com.len(), nbody);
3997 assert_eq!(subtree_linvel.len(), nbody);
3998 assert_eq!(subtree_angmom.len(), nbody);
3999
4000 for i in 0..nbody {
4001 for j in 0..3 {
4002 assert_eq!(subtree_com[i][j], unsafe { *data.ffi().subtree_com.add(i * 3 + j) });
4003 }
4004 }
4005 }
4006
4007 #[test]
4010 fn test_force_cast_view_vs_flat_slice_consistency() {
4011 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
4012 let mut data = model.make_data();
4013 data.forward();
4014
4015 let body_names = ["world", "b_free", "b_slide", "b_hinge", "mocap_body"];
4017 for name in &body_names {
4018 let info = data.body(name).unwrap();
4019 let id = info.id;
4020 let view = info.view(&data);
4021 let flat = data.xpos();
4022
4023 for j in 0..3 {
4024 assert_eq!(view.xpos[j], flat[id][j],
4025 "body {} xpos[{}]: view={} flat={}", id, j, view.xpos[j], flat[id][j]);
4026 }
4027
4028 for j in 0..4 {
4029 assert_eq!(view.xquat[j], data.xquat()[id][j],
4030 "body {} xquat[{}] mismatch", id, j);
4031 }
4032
4033 for j in 0..10 {
4034 assert_eq!(view.cinert[j], data.cinert()[id][j],
4035 "body {} cinert[{}] mismatch", id, j);
4036 }
4037 }
4038 }
4039
4040 #[test]
4042 fn test_force_cast_body_cfrc_arrays() {
4043 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
4044 let mut data = model.make_data();
4045 data.forward();
4046
4047 let nbody = model.ffi().nbody as usize;
4048 let cacc = data.cacc();
4049 let cfrc_int = data.cfrc_int();
4050 let cfrc_ext = data.cfrc_ext();
4051
4052 assert_eq!(cacc.len(), nbody);
4053 assert_eq!(cfrc_int.len(), nbody);
4054 assert_eq!(cfrc_ext.len(), nbody);
4055
4056 for i in 0..nbody {
4057 for j in 0..6 {
4058 assert_eq!(cacc[i][j], unsafe { *data.ffi().cacc.add(i * 6 + j) });
4059 assert_eq!(cfrc_int[i][j], unsafe { *data.ffi().cfrc_int.add(i * 6 + j) });
4060 assert_eq!(cfrc_ext[i][j], unsafe { *data.ffi().cfrc_ext.add(i * 6 + j) });
4061 }
4062 }
4063 }
4064
4065 #[test]
4067 fn test_force_cast_crb_10_element() {
4068 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
4069 let mut data = model.make_data();
4070 data.forward();
4071
4072 let nbody = model.ffi().nbody as usize;
4073 let crb = data.crb();
4074 assert_eq!(crb.len(), nbody);
4075
4076 for i in 0..nbody {
4077 for j in 0..10 {
4078 assert_eq!(crb[i][j], unsafe { *data.ffi().crb.add(i * 10 + j) });
4079 }
4080 }
4081 }
4082
4083 #[test]
4086 fn test_force_cast_empty_model_edge_case() {
4087 let xml = "<mujoco><worldbody><body><joint type='free'/><geom size='0.1'/></body></worldbody></mujoco>";
4088 let model = MjModel::from_xml_string(xml).unwrap();
4089 let data = model.make_data();
4090
4091 assert_eq!(model.ffi().neq, 0);
4092 assert_eq!(model.ffi().nmocap, 0);
4093 assert_eq!(model.ffi().ntendon, 0);
4094
4095 assert!(data.eq_active().is_empty());
4097 assert!(data.mocap_pos().is_empty());
4098 assert!(data.mocap_quat().is_empty());
4099 assert!(data.wrap_obj().is_empty());
4100 assert!(data.wrap_xpos().is_empty());
4101 assert!(data.ten_j().is_empty());
4102 assert!(model.ten_j_colind().is_empty());
4103 assert!(model.ten_j_rownnz().is_empty());
4104 assert!(model.ten_j_rowadr().is_empty());
4105 assert_eq!(model.ffi().nJten, 0);
4106
4107 let nbody = model.ffi().nbody as usize;
4109 assert!(nbody >= 2); assert_eq!(data.xpos().len(), nbody);
4111 assert_eq!(data.xquat().len(), nbody);
4112 assert_eq!(data.cinert().len(), nbody);
4113 }
4114
4115 #[test]
4117 fn test_sparse_ten_j() {
4118 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
4119 let mut data = model.make_data();
4120 data.forward();
4121
4122 let ntendon = model.ffi().ntendon as usize;
4123 let njten = model.ffi().nJten as usize;
4124 assert!(ntendon > 0);
4125 assert!(njten > 0);
4126
4127 let ten_j = data.ten_j();
4128 let ten_j_colind = model.ten_j_colind();
4129 assert_eq!(ten_j.len(), njten);
4130 assert_eq!(ten_j_colind.len(), njten);
4131 assert_eq!(model.ten_j_rownnz().len(), ntendon);
4132 assert_eq!(model.ten_j_rowadr().len(), ntendon);
4133
4134 for i in 0..njten {
4135 assert_eq!(ten_j[i], unsafe { *data.ffi().ten_J.add(i) });
4136 assert_eq!(ten_j_colind[i], unsafe { *model.ffi().ten_J_colind.add(i) });
4137 }
4138 }
4139
4140 #[test]
4143 fn test_ten_j_vs_jac_site() {
4144 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
4145 let mut data = model.make_data();
4146 let nv = model.nv() as usize;
4147
4148 let s1_id = model.name_to_id(MjtObj::mjOBJ_SITE, "s1").unwrap();
4149 let s2_id = model.name_to_id(MjtObj::mjOBJ_SITE, "s2").unwrap();
4150
4151 for config in 0..2 {
4152 if config == 0 {
4153 data.forward();
4154 } else {
4155 for _ in 0..50 { data.step(); }
4156 }
4157
4158 let ten_j = data.ten_j();
4159 let rownnz = model.ten_j_rownnz();
4160 let rowadr = model.ten_j_rowadr();
4161 let colind = model.ten_j_colind();
4162
4163 let (jacp_s1, _) = data.jac_site(true, false, s1_id);
4164 let (jacp_s2, _) = data.jac_site(true, false, s2_id);
4165
4166 let p1 = data.site_xpos()[s1_id];
4167 let p2 = data.site_xpos()[s2_id];
4168 let diff = [p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2]];
4169 let length = (diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2]).sqrt();
4170 assert!(length > 1e-10);
4171 let dir = [diff[0] / length, diff[1] / length, diff[2] / length];
4172
4173 let mut expected = vec![0.0 as MjtNum; nv];
4175 for j in 0..nv {
4176 for k in 0..3 {
4177 expected[j] += dir[k] * (jacp_s2[k * nv + j] - jacp_s1[k * nv + j]);
4178 }
4179 }
4180
4181 let nnz = rownnz[0] as usize;
4183 let adr = rowadr[0] as usize;
4184 assert!(nnz > 0);
4185 let mut actual = vec![0.0 as MjtNum; nv];
4186 for k in 0..nnz {
4187 actual[colind[adr + k] as usize] = ten_j[adr + k];
4188 }
4189
4190 let max_abs = actual.iter().map(|v| v.abs()).fold(0.0f64, f64::max);
4191 assert!(max_abs > 0.1, "config {config}: max |J| = {max_abs}");
4192 for j in 0..nv {
4193 assert_relative_eq!(actual[j], expected[j], epsilon = 1e-10);
4194 }
4195
4196 let ten_view = data.tendon("ten1").unwrap().view(&data);
4198 let model_view = model.tendon("ten1").unwrap().view(&model);
4199 let view_nnz = model_view.J_rownnz[0] as usize;
4200 let mut view_dense = vec![0.0 as MjtNum; nv];
4201 for k in 0..view_nnz {
4202 view_dense[model_view.J_colind[k] as usize] = ten_view.J[k];
4203 }
4204 for j in 0..nv {
4205 assert_relative_eq!(view_dense[j], expected[j], epsilon = 1e-10);
4206 }
4207
4208 let ten_info = data.tendon("ten1").unwrap();
4210 assert_relative_eq!(ten_view.length[0], data.ten_length()[ten_info.id], epsilon = 1e-15);
4211 assert_relative_eq!(ten_view.length[0], length, epsilon = 1e-10);
4212 }
4213 }
4214
4215 #[test]
4218 fn test_tendon_view_j_fields() {
4219 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
4220 let mut data = model.make_data();
4221 data.forward();
4222
4223 let flat_j = data.ten_j();
4224 let flat_rownnz = model.ten_j_rownnz();
4225 let flat_rowadr = model.ten_j_rowadr();
4226 let flat_colind = model.ten_j_colind();
4227
4228 let ten_info = data.tendon("ten1").unwrap();
4229 let ten_view = ten_info.view(&data);
4230 let nnz = flat_rownnz[ten_info.id] as usize;
4231 let adr = flat_rowadr[ten_info.id] as usize;
4232 assert_eq!(ten_view.J.len(), nnz);
4233
4234 let mut any_nonzero = false;
4235 for k in 0..nnz {
4236 assert_eq!(ten_view.J[k], flat_j[adr + k]);
4237 any_nonzero |= ten_view.J[k].abs() > 1e-12;
4238 }
4239 assert!(any_nonzero);
4240
4241 let model_info = model.tendon("ten1").unwrap();
4242 let model_view = model_info.view(&model);
4243 assert_eq!(model_view.J_rownnz[0], flat_rownnz[model_info.id]);
4244 assert_eq!(model_view.J_rowadr[0], flat_rowadr[model_info.id]);
4245 assert_eq!(model_view.J_colind.len(), nnz);
4246 for k in 0..nnz {
4247 assert_eq!(model_view.J_colind[k], flat_colind[adr + k]);
4248 }
4249 }
4250
4251 #[test]
4253 fn test_tendon_data_view_ro_field_unsafe_mutation_api() {
4254 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
4255 let mut data = model.make_data();
4256 data.forward();
4257
4258 let tendon_info = data.tendon("ten1").unwrap();
4259 let tendon_id = tendon_info.id;
4260 let original;
4261
4262 {
4263 let tendon_view = tendon_info.view(&data);
4264 assert!(!tendon_view.wrapadr.is_empty(), "expected non-empty tendon wrapadr for FORCE_MODEL::ten1");
4265 original = tendon_view.wrapadr[0];
4266 }
4267
4268 let temporary = if original == i32::MAX { i32::MIN } else { original + 1 };
4269 assert_ne!(temporary, original);
4270
4271 {
4274 let mut tendon_view_mut = tendon_info.view_mut(&mut data);
4275 unsafe {
4276 tendon_view_mut.wrapadr.as_mut_slice()[0] = temporary;
4277 }
4278 }
4279 assert_eq!(data.ten_wrapadr()[tendon_id], temporary);
4280
4281 {
4283 let mut tendon_view_mut = tendon_info.view_mut(&mut data);
4284 unsafe {
4285 tendon_view_mut.wrapadr.as_mut_slice()[0] = original;
4286 }
4287 }
4288
4289 assert_eq!(data.ten_wrapadr()[tendon_id], original);
4290 }
4291
4292 #[test]
4295 fn test_ten_j_velocity_transform() {
4296 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
4297 let mut data = model.make_data();
4298 let ntendon = model.ntendon() as usize;
4299 assert!(ntendon > 0);
4300
4301 let qvel_patterns: &[&[MjtNum]] = &[
4303 &[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.5, -0.7],
4304 &[0.1, -0.2, 0.3, 0.5, -0.1, 0.4, 0.0, 0.0],
4305 &[0.5, 0.0, -0.3, 0.0, 0.0, 0.0, 2.0, 1.0],
4306 ];
4307
4308 for (round, &qv) in qvel_patterns.iter().enumerate() {
4309 if round == 0 {
4310 data.forward();
4311 } else {
4312 for _ in 0..20 { data.step(); }
4313 }
4314
4315 data.qvel_mut().copy_from_slice(qv);
4316 data.forward();
4317
4318 let ten_j = data.ten_j();
4319 let rownnz = model.ten_j_rownnz();
4320 let rowadr = model.ten_j_rowadr();
4321 let colind = model.ten_j_colind();
4322 let qvel = data.qvel();
4323 let ten_vel = data.ten_velocity();
4324
4325 for t in 0..ntendon {
4326 let nnz = rownnz[t] as usize;
4327 let adr = rowadr[t] as usize;
4328 let dot: MjtNum = (0..nnz)
4329 .map(|k| ten_j[adr + k] * qvel[colind[adr + k] as usize])
4330 .sum();
4331 assert_relative_eq!(dot, ten_vel[t], epsilon = 1e-10);
4332 }
4333
4334 let ten_view = data.tendon("ten1").unwrap().view(&data);
4336 let model_view = model.tendon("ten1").unwrap().view(&model);
4337 let view_nnz = model_view.J_rownnz[0] as usize;
4338 let view_dot: MjtNum = (0..view_nnz)
4339 .map(|k| ten_view.J[k] * qvel[model_view.J_colind[k] as usize])
4340 .sum();
4341 assert_relative_eq!(view_dot, ten_view.velocity[0], epsilon = 1e-10);
4342
4343 let ten_info = data.tendon("ten1").unwrap();
4344 assert_relative_eq!(ten_view.velocity[0], ten_vel[ten_info.id], epsilon = 1e-10);
4345 }
4346
4347 let max_vel = data.ten_velocity().iter().map(|v| v.abs()).fold(0.0f64, f64::max);
4348 assert!(max_vel > 0.1, "max |ten_velocity| = {max_vel}");
4349 }
4350
4351 const TENDON_JAC_MODEL: &str = "
4354<mujoco>
4355 <worldbody>
4356 <body name='b1'>
4357 <joint name='j_slide' type='slide' axis='1 0 0'/>
4358 <geom type='sphere' size='0.1' mass='1'/>
4359 <site name='s1' pos='0 0 0'/>
4360 </body>
4361 <body name='b2' pos='0 0 3'>
4362 <joint name='j_hinge' type='hinge' axis='0 1 0'/>
4363 <geom type='sphere' size='0.1' mass='1'/>
4364 <site name='s2' pos='1 0 0'/>
4365 </body>
4366 </worldbody>
4367 <tendon>
4368 <spatial name='ten1'>
4369 <site site='s1'/>
4370 <site site='s2'/>
4371 </spatial>
4372 </tendon>
4373</mujoco>";
4374
4375 #[test]
4378 fn test_ten_j_numerical_correctness() {
4379 let model = MjModel::from_xml_string(TENDON_JAC_MODEL).unwrap();
4380 let mut data = model.make_data();
4381 let nv = model.nv() as usize;
4382 assert_eq!(nv, 2);
4383
4384 let s1_id = model.name_to_id(MjtObj::mjOBJ_SITE, "s1").unwrap();
4385 let s2_id = model.name_to_id(MjtObj::mjOBJ_SITE, "s2").unwrap();
4386
4387 let to_dense = |data: &MjData<_>, model: &MjModel| -> Vec<MjtNum> {
4388 let ten_j = data.ten_j();
4389 let nnz = model.ten_j_rownnz()[0] as usize;
4390 let adr = model.ten_j_rowadr()[0] as usize;
4391 let colind = model.ten_j_colind();
4392 let mut dense = vec![0.0 as MjtNum; nv];
4393 for k in 0..nnz {
4394 dense[colind[adr + k] as usize] = ten_j[adr + k];
4395 }
4396 dense
4397 };
4398
4399 let to_dense_view = |data: &MjData<_>, model: &MjModel| -> Vec<MjtNum> {
4400 let ten_view = data.tendon("ten1").unwrap().view(data);
4401 let model_view = model.tendon("ten1").unwrap().view(model);
4402 let view_nnz = model_view.J_rownnz[0] as usize;
4403 let mut dense = vec![0.0 as MjtNum; nv];
4404 for k in 0..view_nnz {
4405 dense[model_view.J_colind[k] as usize] = ten_view.J[k];
4406 }
4407 dense
4408 };
4409
4410 let from_jac_site = |data: &MjData<_>| -> Vec<MjtNum> {
4412 let p1 = data.site_xpos()[s1_id];
4413 let p2 = data.site_xpos()[s2_id];
4414 let d = [p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2]];
4415 let len = (d[0] * d[0] + d[1] * d[1] + d[2] * d[2]).sqrt();
4416 let dir = [d[0] / len, d[1] / len, d[2] / len];
4417 let (jp1, _) = data.jac_site(true, false, s1_id);
4418 let (jp2, _) = data.jac_site(true, false, s2_id);
4419 let mut expected = vec![0.0 as MjtNum; nv];
4420 for j in 0..nv {
4421 let mut v: MjtNum = 0.0;
4422 for k in 0..3 {
4423 v += dir[k] * (jp2[k * nv + j] - jp1[k * nv + j]);
4424 }
4425 expected[j] = v;
4426 }
4427 expected
4428 };
4429
4430 let check_j = |data: &MjData<_>, expected: &[MjtNum; 2]| {
4431 let dense = to_dense(data, &model);
4432 let dense_v = to_dense_view(data, &model);
4433 let from_jac = from_jac_site(data);
4434 for j in 0..nv {
4435 assert_relative_eq!(dense[j], expected[j], epsilon = 1e-10);
4436 assert_relative_eq!(dense_v[j], expected[j], epsilon = 1e-10);
4437 assert_relative_eq!(from_jac[j], expected[j], epsilon = 1e-10);
4438 }
4439 };
4440
4441 let check_vel = |data: &MjData<_>, expected: MjtNum| {
4442 assert_relative_eq!(data.ten_velocity()[0], expected, epsilon = 1e-10);
4443 let v = data.tendon("ten1").unwrap().view(data);
4444 assert_relative_eq!(v.velocity[0], expected, epsilon = 1e-10);
4445 };
4446
4447 data.forward();
4451 let sqrt10 = 10.0f64.sqrt();
4452 check_j(&data, &[-1.0 / sqrt10, -3.0 / sqrt10]);
4453
4454 data.qvel_mut().copy_from_slice(&[1.0, 0.0]);
4455 data.forward();
4456 check_vel(&data, -1.0 / sqrt10);
4457
4458 data.qvel_mut().copy_from_slice(&[0.0, 1.0]);
4459 data.forward();
4460 check_vel(&data, -3.0 / sqrt10);
4461
4462 data.qvel_mut().copy_from_slice(&[2.0, -0.5]);
4464 data.forward();
4465 check_vel(&data, -0.5 / sqrt10);
4466
4467 let a = std::f64::consts::FRAC_PI_4;
4470 let (c, s) = (a.cos(), a.sin());
4471 data.qpos_mut()[1] = a;
4472 data.qvel_mut().copy_from_slice(&[0.0; 2]);
4473 data.forward();
4474
4475 let len2 = (c * c + (3.0 - s) * (3.0 - s)).sqrt();
4476 let dir2 = [c / len2, 0.0, (3.0 - s) / len2];
4477 let j_slide = -dir2[0];
4478 let j_hinge = dir2[0] * (-s) + dir2[2] * (-c);
4479 check_j(&data, &[j_slide, j_hinge]);
4480 assert!(j_slide.abs() > 0.05);
4481 assert!(j_hinge.abs() > 0.05);
4482
4483 data.qvel_mut().copy_from_slice(&[1.0, 0.0]);
4484 data.forward();
4485 check_vel(&data, j_slide);
4486
4487 data.qvel_mut().copy_from_slice(&[0.0, 1.0]);
4488 data.forward();
4489 check_vel(&data, j_hinge);
4490
4491 data.qvel_mut().copy_from_slice(&[-1.0, 3.0]);
4492 data.forward();
4493 check_vel(&data, -j_slide + 3.0 * j_hinge);
4494
4495 let a3 = -std::f64::consts::FRAC_PI_3;
4498 let (c3, s3) = (a3.cos(), a3.sin());
4499 data.qpos_mut().copy_from_slice(&[0.5, a3]);
4500 data.qvel_mut().copy_from_slice(&[0.0; 2]);
4501 data.forward();
4502
4503 let dx3 = c3 - 0.5;
4504 let dz3 = 3.0 - s3;
4505 let len3 = (dx3 * dx3 + dz3 * dz3).sqrt();
4506 let dir3 = [dx3 / len3, 0.0, dz3 / len3];
4507 let j_slide3 = -dir3[0];
4508 let j_hinge3 = dir3[0] * (-s3) + dir3[2] * (-c3);
4509 check_j(&data, &[j_slide3, j_hinge3]);
4510
4511 data.qvel_mut().copy_from_slice(&[1.7, -2.3]);
4512 data.forward();
4513 check_vel(&data, j_slide3 * 1.7 + j_hinge3 * (-2.3));
4514 }
4515
4516 #[test]
4518 fn test_force_cast_island_efc_enums() {
4519 let model = MjModel::from_xml_string(MODEL).unwrap();
4520 let mut data = model.make_data();
4521
4522 for _ in 0..10 {
4523 data.step();
4524 }
4525
4526 let nefc = data.ffi().nefc as usize;
4527 if nefc > 0 {
4528 let iefc_type = data.iefc_type();
4529 let iefc_state = unsafe { data.iefc_state() };
4531
4532 assert_eq!(iefc_type.len(), nefc);
4533 assert_eq!(iefc_state.len(), nefc);
4534
4535 for i in 0..nefc {
4536 let raw_type = unsafe { *data.ffi().iefc_type.add(i) };
4537 let raw_state = unsafe { *data.ffi().iefc_state.add(i) };
4538 let expected_type: MjtConstraint = unsafe { crate::util::force_cast(raw_type) };
4539 let expected_state: MjtConstraintState = unsafe { crate::util::force_cast(raw_state) };
4540 assert_eq!(iefc_type[i], expected_type);
4541 assert_eq!(iefc_state[i], expected_state);
4542 }
4543 }
4544 }
4545
4546 #[test]
4548 fn test_force_cast_non_aliasing_adjacent_bodies() {
4549 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
4550 let mut data = model.make_data();
4551 data.forward();
4552
4553 let b_free = data.body("b_free").unwrap();
4554 let b_slide = data.body("b_slide").unwrap();
4555
4556 let v_free = b_free.view(&data);
4557 let v_slide = b_slide.view(&data);
4558
4559 assert_ne!(v_free.xpos.as_ptr(), v_slide.xpos.as_ptr(),
4561 "adjacent bodies must have non-overlapping xpos");
4562 assert_ne!(v_free.cinert.as_ptr(), v_slide.cinert.as_ptr(),
4563 "adjacent bodies must have non-overlapping cinert");
4564 assert_ne!(v_free.cvel.as_ptr(), v_slide.cvel.as_ptr(),
4565 "adjacent bodies must have non-overlapping cvel");
4566
4567 let xpos_diff = unsafe { v_slide.xpos.as_ptr().offset_from(v_free.xpos.as_ptr()) };
4569 let id_diff = b_slide.id as isize - b_free.id as isize;
4570 assert_eq!(xpos_diff, id_diff * 3, "xpos pointer gap must be stride*id_diff = 3*{}", id_diff);
4571
4572 let cinert_diff = unsafe { v_slide.cinert.as_ptr().offset_from(v_free.cinert.as_ptr()) };
4573 assert_eq!(cinert_diff, id_diff * 10, "cinert pointer gap must be 10*{}", id_diff);
4574 }
4575
4576 #[test]
4584 fn test_force_cast_multi_step_xpos_qpos_diverge() {
4585 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
4586 let mut data = model.make_data();
4587 data.forward();
4588
4589 let nbody = model.ffi().nbody as usize;
4590 let nq = model.ffi().nq as usize;
4591 let nv = model.ffi().nv as usize;
4592
4593 let init_xpos: Vec<[MjtNum; 3]> = data.xpos().to_vec();
4595 let init_qpos: Vec<MjtNum> = data.qpos().to_vec();
4596 let init_qvel: Vec<MjtNum> = data.qvel().to_vec();
4597
4598 assert_eq!(init_xpos.len(), nbody);
4599 assert_eq!(init_qpos.len(), nq);
4600 assert_eq!(init_qvel.len(), nv);
4601
4602 for _ in 0..100 {
4604 data.step();
4605 }
4606
4607 let post_xpos = data.xpos();
4608 let post_qpos = data.qpos();
4609 let post_qvel = data.qvel();
4610
4611 assert_eq!(post_xpos.len(), nbody);
4612 assert_eq!(post_qpos.len(), nq);
4613 assert_eq!(post_qvel.len(), nv);
4614
4615 let b_free = data.body("b_free").unwrap();
4617 assert!(post_xpos[b_free.id][2] < init_xpos[b_free.id][2],
4618 "free body should have fallen: init_z={}, post_z={}",
4619 init_xpos[b_free.id][2], post_xpos[b_free.id][2]);
4620
4621 assert_ne!(post_qpos, &init_qpos[..], "qpos must change after 100 steps");
4623
4624 let any_nonzero_vel = post_qvel.iter().any(|v| v.abs() > 1e-12);
4626 assert!(any_nonzero_vel, "qvel must have nonzero entries after gravity steps");
4627
4628 for i in 0..nbody {
4630 for j in 0..3 {
4631 assert_eq!(post_xpos[i][j], unsafe { *data.ffi().xpos.add(i * 3 + j) },
4632 "xpos[{}][{}] FFI mismatch after stepping", i, j);
4633 }
4634 }
4635
4636 for i in 0..nv {
4638 assert_eq!(post_qvel[i], unsafe { *data.ffi().qvel.add(i) },
4639 "qvel[{}] FFI mismatch after stepping", i);
4640 }
4641 }
4642
4643 #[test]
4646 fn test_force_cast_multi_step_actuator_ctrl() {
4647 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
4648 let mut data = model.make_data();
4649
4650 data.ctrl_mut()[0] = 5.0;
4652
4653 for _ in 0..20 {
4655 data.step();
4656 }
4657
4658 let nu = model.ffi().nu as usize;
4659 let ctrl = data.ctrl();
4660 assert_eq!(ctrl.len(), nu);
4661 assert_eq!(ctrl[0], 5.0);
4662
4663 let nout = model.ffi().nout as usize;
4665 let act_force = data.actuator_force();
4666 assert_eq!(act_force.len(), nout);
4667 assert!(act_force[0].abs() > 1e-12,
4668 "actuator_force should be nonzero with ctrl=5.0, got {}", act_force[0]);
4669
4670 assert_eq!(act_force[0], unsafe { *data.ffi().actuator_force });
4672
4673 let nv = model.ffi().nv as usize;
4675 let qfrc = data.qfrc_actuator();
4676 assert_eq!(qfrc.len(), nv);
4677 let any_nonzero = qfrc.iter().any(|v| v.abs() > 1e-12);
4678 assert!(any_nonzero, "qfrc_actuator must reflect the actuator force");
4679 }
4680
4681 #[test]
4685 fn test_force_cast_multi_step_constraints_evolve() {
4686 let model = MjModel::from_xml_string(MODEL).unwrap();
4688 let mut data = model.make_data();
4689
4690 for _ in 0..50 {
4692 data.step();
4693 }
4694
4695 let nefc = data.ffi().nefc as usize;
4696 let ncon = data.ffi().ncon as usize;
4697
4698 if ncon > 0 {
4701 let contacts = data.contact();
4703 assert_eq!(contacts.len(), ncon);
4704 for c in contacts {
4705 let pos_nonzero = c.pos.iter().any(|v| v.abs() > 1e-12);
4707 assert!(pos_nonzero, "contact pos should be nonzero for an active contact");
4708 }
4709 }
4710
4711 if nefc > 0 {
4712 let efc_type = data.efc_type();
4713 let efc_state = unsafe { data.efc_state() };
4715 assert_eq!(efc_type.len(), nefc);
4716 assert_eq!(efc_state.len(), nefc);
4717
4718 for i in 0..nefc {
4720 let raw_type = unsafe { *data.ffi().efc_type.add(i) };
4721 let raw_state = unsafe { *data.ffi().efc_state.add(i) };
4722 assert_eq!(efc_type[i], unsafe { crate::util::force_cast::<_, MjtConstraint>(raw_type) });
4723 assert_eq!(efc_state[i], unsafe { crate::util::force_cast::<_, MjtConstraintState>(raw_state) });
4724 }
4725
4726 let kbip = data.efc_kbip();
4728 assert_eq!(kbip.len(), nefc);
4729 for i in 0..nefc {
4730 for j in 0..4 {
4731 assert_eq!(kbip[i][j], unsafe { *data.ffi().efc_KBIP.add(i * 4 + j) });
4732 }
4733 }
4734 }
4735 }
4736
4737 #[test]
4741 fn test_force_cast_view_consistency_across_steps() {
4742 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
4743 let mut data = model.make_data();
4744
4745 let body_names = ["world", "b_free", "b_slide", "b_hinge", "mocap_body"];
4746
4747 for step_idx in 0..30 {
4748 data.step();
4749
4750 let xpos_flat = data.xpos();
4751 let xquat_flat = data.xquat();
4752 let cvel_flat = data.cvel();
4753 let cinert_flat = data.cinert();
4754
4755 for name in &body_names {
4756 let info = data.body(name).unwrap();
4757 let view = info.view(&data);
4758 let id = info.id;
4759
4760 assert_eq!(&view.xpos[..], &xpos_flat[id][..],
4762 "xpos mismatch at step {} body '{}'", step_idx, name);
4763
4764 assert_eq!(&view.xquat[..], &xquat_flat[id][..],
4766 "xquat mismatch at step {} body '{}'", step_idx, name);
4767
4768 assert_eq!(&view.cvel[..], &cvel_flat[id][..],
4770 "cvel mismatch at step {} body '{}'", step_idx, name);
4771
4772 assert_eq!(&view.cinert[..], &cinert_flat[id][..],
4774 "cinert mismatch at step {} body '{}'", step_idx, name);
4775 }
4776 }
4777 }
4778
4779 #[test]
4782 fn test_force_cast_multi_step_xfrc_applied_effect() {
4783 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
4784 let mut data = model.make_data();
4785
4786 let b_free = data.body("b_free").unwrap();
4787 let b_free_id = b_free.id;
4788
4789 data.forward();
4791 let baseline_xpos = data.xpos()[b_free_id];
4792
4793 data.reset();
4795 data.xfrc_applied_mut()[b_free_id] = [0.0, 0.0, 100.0, 0.0, 0.0, 0.0];
4797
4798 for _ in 0..50 {
4799 data.step();
4800 }
4801
4802 let forced_xpos = data.xpos()[b_free_id];
4803
4804 assert!(forced_xpos[2] > baseline_xpos[2],
4806 "Upward force should raise body: baseline_z={}, forced_z={}",
4807 baseline_xpos[2], forced_xpos[2]);
4808
4809 for j in 0..3 {
4811 assert_eq!(forced_xpos[j], unsafe { *data.ffi().xpos.add(b_free_id * 3 + j) });
4812 }
4813
4814 assert_eq!(data.xfrc_applied()[b_free_id], [0.0, 0.0, 100.0, 0.0, 0.0, 0.0]);
4816 }
4817
4818 #[test]
4821 fn test_force_cast_multi_step_mocap_mutation() {
4822 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
4823 let mut data = model.make_data();
4824 data.forward();
4825
4826 let nmocap = model.ffi().nmocap as usize;
4827 assert!(nmocap > 0, "model should have at least one mocap body");
4828
4829 let init_pos = data.mocap_pos()[0];
4831 assert_eq!(init_pos, [10.0, 10.0, 10.0]);
4832
4833 for _ in 0..10 {
4835 data.step();
4836 }
4837
4838 data.mocap_pos_mut()[0] = [20.0, 30.0, 40.0];
4840 data.forward();
4841
4842 assert_eq!(data.mocap_pos()[0], [20.0, 30.0, 40.0]);
4843 for j in 0..3 {
4845 assert_eq!(unsafe { *data.ffi().mocap_pos.add(j) }, [20.0, 30.0, 40.0][j]);
4846 }
4847
4848 data.mocap_pos_mut()[0] = [-5.0, -5.0, -5.0];
4850 for _ in 0..10 {
4851 data.step();
4852 }
4853 assert_eq!(data.mocap_pos()[0], [-5.0, -5.0, -5.0]);
4854 }
4855
4856 #[test]
4859 fn test_force_cast_multi_step_sensor_data_evolves() {
4860 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
4861 let mut data = model.make_data();
4862
4863 data.forward();
4864 let init_qpos: Vec<MjtNum> = data.qpos().to_vec();
4865
4866 let b_slide = data.body("b_slide").unwrap();
4868 data.xfrc_applied_mut()[b_slide.id] = [0.0, 0.0, -50.0, 0.0, 0.0, 0.0];
4869
4870 for _ in 0..100 {
4871 data.step();
4872 }
4873
4874 let post_qpos = data.qpos();
4876 assert_ne!(post_qpos, init_qpos.as_slice(),
4877 "qpos did not evolve after 100 steps with applied force");
4878
4879 let nsensordata = model.ffi().nsensordata as usize;
4881 let post_sensor = data.sensordata();
4882 assert_eq!(post_sensor.len(), nsensordata);
4883 for i in 0..nsensordata {
4884 assert_eq!(post_sensor[i], unsafe { *data.ffi().sensordata.add(i) },
4885 "sensordata[{}] FFI mismatch", i);
4886 }
4887 }
4888
4889 #[test]
4892 fn test_force_cast_multi_step_eq_active_toggle() {
4893 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
4894 let mut data = model.make_data();
4895
4896 for _ in 0..20 {
4898 data.step();
4899 }
4900 let pos_with_eq = data.xpos().to_vec();
4901
4902 data.reset();
4904 data.eq_active_mut()[0] = false;
4905 data.eq_active_mut()[1] = false;
4906
4907 for _ in 0..20 {
4908 data.step();
4909 }
4910 let pos_without_eq = data.xpos().to_vec();
4911
4912 let b_slide = data.body("b_slide").unwrap();
4914 let diff: MjtNum = (0..3)
4915 .map(|j| (pos_with_eq[b_slide.id][j] - pos_without_eq[b_slide.id][j]).abs())
4916 .sum();
4917
4918 assert!(diff > 1e-15 || {
4921 let b_hinge = data.body("b_hinge").unwrap();
4923 (0..3)
4924 .map(|j| (pos_with_eq[b_hinge.id][j] - pos_without_eq[b_hinge.id][j]).abs())
4925 .sum::<MjtNum>() > 1e-15
4926 }, "disabling equality constraints should change positions");
4927
4928 assert!(!data.eq_active()[0]);
4930 assert!(!data.eq_active()[1]);
4931 assert!(!unsafe { *data.ffi().eq_active });
4932 assert!(!unsafe { *data.ffi().eq_active.add(1) });
4933 }
4934
4935 #[test]
4938 fn test_force_cast_split_step_consistency() {
4939 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
4940 let mut data = model.make_data();
4941
4942 let nbody = model.ffi().nbody as usize;
4943
4944 for _ in 0..15 {
4945 data.step1();
4946
4947 let mid_xpos = data.xpos();
4949 assert_eq!(mid_xpos.len(), nbody);
4950 for i in 0..nbody {
4951 for j in 0..3 {
4952 assert_eq!(mid_xpos[i][j], unsafe { *data.ffi().xpos.add(i * 3 + j) },
4953 "xpos[{}][{}] FFI mismatch after step1", i, j);
4954 }
4955 }
4956
4957 data.step2();
4958
4959 let post_xpos = data.xpos();
4961 for i in 0..nbody {
4962 for j in 0..3 {
4963 assert_eq!(post_xpos[i][j], unsafe { *data.ffi().xpos.add(i * 3 + j) },
4964 "xpos[{}][{}] FFI mismatch after step2", i, j);
4965 }
4966 }
4967 }
4968 }
4969
4970 #[test]
4973 fn test_force_cast_multi_step_state_save_restore() {
4974 use crate::wrappers::mj_data::MjtState;
4975
4976 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
4977 let mut data = model.make_data();
4978
4979 for _ in 0..30 {
4981 data.step();
4982 }
4983 data.forward();
4985
4986 let saved_state = data.state(MjtState::mjSTATE_FULLPHYSICS as u32);
4988 let saved_xpos: Vec<[MjtNum; 3]> = data.xpos().to_vec();
4989 let saved_qpos: Vec<MjtNum> = data.qpos().to_vec();
4990
4991 for _ in 0..30 {
4993 data.step();
4994 }
4995 let diverged_xpos: Vec<[MjtNum; 3]> = data.xpos().to_vec();
4996 assert_ne!(diverged_xpos, saved_xpos, "state should diverge after more steps");
4997
4998 data.set_state(&saved_state, MjtState::mjSTATE_FULLPHYSICS as u32).unwrap();
5000 data.forward();
5001
5002 let restored_qpos = data.qpos();
5004 for i in 0..saved_qpos.len() {
5005 assert_eq!(restored_qpos[i], saved_qpos[i],
5006 "qpos[{}] should match saved state after restore", i);
5007 }
5008
5009 let restored_xpos = data.xpos();
5012 for i in 0..saved_xpos.len() {
5013 for j in 0..3 {
5014 assert!(
5015 (restored_xpos[i][j] - saved_xpos[i][j]).abs() < 1e-10,
5016 "xpos[{}][{}] should approximately match saved state: got {} vs {}",
5017 i, j, restored_xpos[i][j], saved_xpos[i][j]
5018 );
5019 }
5020 }
5021 }
5022
5023 #[test]
5026 fn test_force_cast_multi_step_kinematics_evolve() {
5027 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
5028 let mut data = model.make_data();
5029
5030 let b_free = data.body("b_free").unwrap();
5032 data.xfrc_applied_mut()[b_free.id] = [1.0, 0.0, 0.0, 0.0, 0.5, 0.0];
5033 data.forward();
5034
5035 let nbody = model.ffi().nbody as usize;
5036 let init_xmat: Vec<[MjtNum; 9]> = data.xmat().to_vec();
5037 let init_xipos: Vec<[MjtNum; 3]> = data.xipos().to_vec();
5038
5039 for _ in 0..50 {
5041 data.step();
5042 }
5043
5044 let post_xmat = data.xmat();
5045 let post_xipos = data.xipos();
5046
5047 assert_eq!(post_xmat.len(), nbody);
5048 assert_eq!(post_xipos.len(), nbody);
5049
5050 let pos_changed = (0..3).any(|j| (post_xipos[b_free.id][j] - init_xipos[b_free.id][j]).abs() > 1e-6);
5052 assert!(pos_changed, "free body xipos should change as it moves");
5053
5054 let mat_changed = (0..9).any(|j| (post_xmat[b_free.id][j] - init_xmat[b_free.id][j]).abs() > 1e-12);
5056 assert!(mat_changed, "free body xmat should change with off-center force");
5057
5058 for i in 0..nbody {
5060 for j in 0..9 {
5061 assert_eq!(post_xmat[i][j], unsafe { *data.ffi().xmat.add(i * 9 + j) });
5062 }
5063 for j in 0..3 {
5064 assert_eq!(post_xipos[i][j], unsafe { *data.ffi().xipos.add(i * 3 + j) });
5065 }
5066 }
5067 }
5068
5069 #[test]
5072 fn test_force_cast_multi_step_subtree_dynamics() {
5073 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
5074 let mut data = model.make_data();
5075 data.forward();
5076
5077 let nbody = model.ffi().nbody as usize;
5078 let init_subtree_com: Vec<[MjtNum; 3]> = data.subtree_com().to_vec();
5079
5080 for _ in 0..60 {
5081 data.step();
5082 }
5083
5084 let post_subtree_com = data.subtree_com();
5085 let post_subtree_linvel = data.subtree_linvel();
5086 let post_subtree_angmom = data.subtree_angmom();
5087
5088 assert_eq!(post_subtree_com.len(), nbody);
5089 assert_eq!(post_subtree_linvel.len(), nbody);
5090 assert_eq!(post_subtree_angmom.len(), nbody);
5091
5092 let world_com_diff: MjtNum = (0..3)
5094 .map(|j| (post_subtree_com[0][j] - init_subtree_com[0][j]).abs())
5095 .sum();
5096 assert!(world_com_diff > 1e-6,
5097 "world subtree_com should shift as bodies fall");
5098
5099 for i in 0..nbody {
5101 for j in 0..3 {
5102 assert_eq!(post_subtree_com[i][j], unsafe { *data.ffi().subtree_com.add(i * 3 + j) });
5103 assert_eq!(post_subtree_linvel[i][j], unsafe { *data.ffi().subtree_linvel.add(i * 3 + j) });
5104 assert_eq!(post_subtree_angmom[i][j], unsafe { *data.ffi().subtree_angmom.add(i * 3 + j) });
5105 }
5106 }
5107 }
5108
5109 #[test]
5111 fn test_model_swap() {
5112 const OLD_TIMESTEP: f64 = 0.002;
5113 const NEW_TIMESTEP: f64 = 0.1;
5114
5115 let mut model_template = Box::new(MjSpec::new().compile().unwrap());
5116 model_template.opt_mut().timestep = OLD_TIMESTEP;
5117
5118 let model = model_template.clone();
5119 let mut data = MjData::new(model);
5120
5121 model_template.opt_mut().timestep = NEW_TIMESTEP;
5122 model_template = data.swap_model(model_template);
5123 assert_eq!(model_template.opt().timestep, OLD_TIMESTEP);
5124 assert_eq!(data.model().opt().timestep, NEW_TIMESTEP);
5125 }
5126
5127 #[test]
5131 fn test_sensor_info_nsensordata_arm() {
5132 const SENSOR_MODEL: &str = r#"<mujoco>
5133 <worldbody>
5134 <body>
5135 <joint name="hinge" type="hinge"/>
5136 <geom size="0.1"/>
5137 </body>
5138 </worldbody>
5139 <sensor>
5140 <jointpos name="jp" joint="hinge"/>
5141 </sensor>
5142</mujoco>"#;
5143 let model = MjModel::from_xml_string(SENSOR_MODEL).expect("model load failed");
5144 let data = model.make_data();
5145 let info = data.sensor("jp").expect("sensor 'jp' not found");
5146 let view = info.view(&data);
5147 assert_eq!(view.data.len(), 1, "jointpos sensor must produce 1 sensordata element");
5149 }
5150
5151 #[test]
5154 fn test_energy_ref_getter_arms() {
5155 let model = MjModel::from_xml_string(MODEL).expect("model load failed");
5156 let mut data = model.make_data();
5157 data.forward();
5158 data.energy_pos();
5159 data.energy_vel();
5160
5161 let energy: &[MjtNum; 2] = data.energy();
5165 assert_ne!(energy[0], 0.0, "potential energy at rest above the floor");
5166 assert_eq!(energy[1], 0.0, "kinetic energy at rest");
5167
5168 let nv = model.nv() as usize;
5170 data.qvel_mut()[0] = 0.5;
5171 data.qvel_mut()[7] = -0.25;
5172 data.forward();
5173 data.energy_vel();
5174 let mut mass = vec![0.0; nv * nv];
5175 data.full_m(&mut mass).unwrap();
5176 let qvel = data.qvel();
5177 let expected = 0.5 * (0..nv)
5178 .map(|i| qvel[i] * (0..nv).map(|j| mass[i * nv + j] * qvel[j]).sum::<MjtNum>())
5179 .sum::<MjtNum>();
5180 assert!(expected > 0.0, "the derived kinetic energy must be positive");
5183 assert!(
5184 (data.energy()[1] - expected).abs() < 1e-12,
5185 "kinetic energy {} does not match 0.5 * qvel' M qvel = {expected}", data.energy()[1]
5186 );
5187 }
5188
5189 #[test]
5193 fn test_energy_mut_eval_or_expand_true() {
5194 let model = MjModel::from_xml_string(MODEL).expect("model load failed");
5195 let mut data = model.make_data();
5196 data.energy_pos();
5197 let energy_mut: &mut [MjtNum; 2] = data.energy_mut();
5198 energy_mut[0] = 1.23;
5199 assert!(
5200 (data.energy()[0] - 1.23).abs() < 1e-12,
5201 "written energy value must be readable back"
5202 );
5203 }
5204
5205 #[test]
5208 fn test_body_view_awake_field() {
5209 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
5210 let mut data = model.make_data();
5211 data.forward();
5212
5213 let body_info = data.body("b_free").unwrap();
5214 let view = body_info.view(&data);
5215
5216 assert_eq!(view.awake.len(), 1);
5218
5219 assert_eq!(view.awake[0], data.body_awake()[body_info.id]);
5221 }
5222
5223 #[test]
5226 fn test_tendon_view_efcadr_field() {
5227 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
5228 let mut data = model.make_data();
5229 data.forward();
5230
5231 let tendon_info = data.tendon("ten1").unwrap();
5232 let view = tendon_info.view(&data);
5233
5234 assert_eq!(view.efcadr.len(), 1);
5236
5237 assert_eq!(view.efcadr[0], data.tendon_efcadr()[tendon_info.id]);
5239 }
5240
5241 #[test]
5244 fn test_dof_island_map_dof2idof_dof_awake_ind_lengths() {
5245 let model = MjModel::from_xml_string(FORCE_MODEL).unwrap();
5246 let mut data = model.make_data();
5247 data.forward();
5248
5249 let nv = model.ffi().nv as usize;
5250
5251 assert_eq!(data.dof_island().len(), nv);
5252 assert_eq!(data.map_dof2idof().len(), nv);
5253 assert_eq!(data.dof_awake_ind().len(), nv);
5254
5255 for i in 0..nv {
5256 assert_eq!(data.dof_island()[i], unsafe { *data.ffi().dof_island.add(i) });
5257 assert_eq!(data.map_dof2idof()[i], unsafe { *data.ffi().map_dof2idof.add(i) });
5258 assert_eq!(data.dof_awake_ind()[i], unsafe { *data.ffi().dof_awake_ind.add(i) });
5259 }
5260 }
5261
5262 #[test]
5270 fn test_probe_dynamic_arrays_stays_in_bounds() {
5271 let model = MjModel::from_xml_string(MODEL).unwrap();
5272 let mut data = model.make_data();
5273 data.probe_dynamic_arrays();
5274
5275 for _ in 0..5 {
5277 data.step();
5278 }
5279 assert!(data.ffi().nefc > 0, "the model must build constraints for the probe to mean anything");
5280 data.probe_dynamic_arrays();
5281
5282 unsafe { data.probe_dynamic_arrays_unsafe() };
5284 }
5285
5286}