mujoco_rs/wrappers/
mj_data.rs

1use super::mj_model::{MjModel, MjtSameFrame, MjtObj, MjtStage};
2use super::mj_statistic::{MjWarningStat, MjTimerStat};
3use super::mj_auxiliary::MjContact;
4use super::mj_primitive::*;
5use crate::mujoco_c::*;
6
7use std::io::{self, Error, ErrorKind};
8use std::ffi::CString;
9use std::ptr;
10
11use crate::{mj_view_indices, mj_model_nx_to_mapping, mj_model_nx_to_nitem};
12use crate::{view_creator, info_method, info_with_view};
13
14/*******************************************/
15// Types
16/// State component elements as integer bitflags and several convenient combinations of these flags. Used by
17/// `mj_getState`, `mj_setState` and `mj_stateSize`.
18pub type MjtState = mjtState;
19
20/// Constraint types. These values are not used in mjModel, but are used in the mjData field ``d->efc_type`` when the list
21/// of active constraints is constructed at each simulation time step.
22pub type MjtConstraint = mjtConstraint;
23
24/// These values are used by the solver internally to keep track of the constraint states.
25pub type MjtConstraintState = mjtConstraintState;
26
27/// Warning types. The number of warning types is given by `mjNWARNING`
28/// which is also the length of the array `mjData.warning`.
29pub type MjtWarning = mjtWarning;
30
31/// Timer types. The number of timer types is given by `mjNTIMER`
32/// which is also the length of the array `mjData.timer`, as well as the length of
33/// the string array `mjTIMERSTRING` with timer names.
34pub type MjtTimer = mjtTimer;
35/*******************************************/
36
37
38/**************************************************************************************************/
39// MjData
40/**************************************************************************************************/
41
42/// Wrapper around the ``mjData`` struct.
43/// Provides lifetime guarantees as well as automatic cleanup.
44pub struct MjData<'a> {
45    data: *mut mjData,
46    model: &'a MjModel
47}
48
49// Allow usage in threaded contexts as the data won't be shared anywhere outside Rust,
50// except in the C++ code.
51unsafe impl Send for MjData<'_> {}
52unsafe impl Sync for MjData<'_> {}
53
54
55impl<'a> MjData<'a> {
56    /// Constructor for a new MjData. This should is called from MjModel.
57    pub fn new(model: &'a MjModel) -> Self {
58        unsafe {
59            Self {
60                data: mj_makeData(model.ffi()),
61                model: model,
62            }
63        }
64    }
65
66    /// Reference to the wrapped FFI struct.
67    pub fn ffi(&self) -> &mjData {
68        unsafe { self.data.as_ref().unwrap() }
69    }
70
71    /// Mutable reference to the wrapped FFI struct.
72    pub unsafe fn ffi_mut(&mut self) -> &mut mjData {
73        unsafe { self.data.as_mut().unwrap() }
74    }
75
76    /// Returns a reference to data's [`MjModel`].
77    /// This is used for checking whether the correct
78    /// data is used in `update_x(data)` methods.
79    pub(crate) fn model(&self) -> &MjModel {
80        self.model
81    }
82
83    /// Returns a slice of detected contacts.
84    /// To obtain the contact force, call [`MjData::contact_force`].
85    pub fn contacts(&self) -> &[MjContact] {
86        unsafe {
87            std::slice::from_raw_parts((*self.data).contact, (*self.data).ncon as usize)
88        }
89    }
90
91    info_method! { Data, model.ffi(), body, [
92        xfrc_applied: 6, xpos: 3, xquat: 4, xmat: 9, xipos: 3, ximat: 9, subtree_com: 3, cinert: 10,
93        crb: 10, cvel: 6, subtree_linvel: 3, subtree_angmom: 3, cacc: 6, cfrc_int: 6, cfrc_ext: 6
94    ], [], []}
95    info_method! { Data, model.ffi(), camera, [xpos: 3, xmat: 9], [], []}
96    info_method! { Data, model.ffi(), geom, [xpos: 3, xmat: 9], [], []}
97    info_method! { Data, model.ffi(), site, [xpos: 3, xmat: 9], [], []}
98    info_method! { Data, model.ffi(), light, [xpos: 3, xdir: 3], [], []}
99
100    /// Obtains a [`MjActuatorDataInfo`] struct containing information about the name, id, and
101    /// indices required for obtaining a slice view to the correct locations in [`MjData`].
102    /// The actual view can be obtained via [`MjActuatorDataInfo::view`].
103    /// # Panics
104    /// When the `name` contains '\0' characters, a panic occurs.
105    pub fn actuator(&self, name: &str) -> Option<MjActuatorDataInfo> {
106        let c_name = CString::new(name).unwrap();
107        let id = unsafe { mj_name2id(self.model.ffi(), MjtObj::mjOBJ_ACTUATOR as i32, c_name.as_ptr())};
108        if id == -1 {  // not found
109            return None;
110        }
111
112        let ctrl;
113        let act;
114        let model_ffi = self.model.ffi();
115        unsafe {
116            ctrl = (id as usize, 1);
117            act = mj_view_indices!(id as usize, model_ffi.actuator_actadr, model_ffi.nu as usize, model_ffi.na as usize);
118        }
119
120        Some(MjActuatorDataInfo { name: name.to_string(), id: id as usize, ctrl, act})
121    }
122
123    /// Obtains a [`MjJointDataInfo`] struct containing information about the name, id, and
124    /// indices required for obtaining a slice view to the correct locations in [`MjData`].
125    /// The actual view can be obtained via [`MjJointDataInfo::view`].
126    /// # Panics
127    /// When the `name` contains \0' characters, a panic occurs.
128    pub fn joint(&self, name: &str) -> Option<MjJointDataInfo> {
129        let c_name = CString::new(name).unwrap();
130        let id = unsafe { mj_name2id(self.model.ffi(), MjtObj::mjOBJ_JOINT as i32, c_name.as_ptr())};
131        if id == -1 {  // not found
132            return None;
133        }
134        let model_ffi = self.model.ffi();
135        let id = id as usize;
136        unsafe {
137            let nq_range = mj_view_indices!(id, mj_model_nx_to_mapping!(model_ffi, nq), mj_model_nx_to_nitem!(model_ffi, nq), model_ffi.nq);
138            let nv_range = mj_view_indices!(id, mj_model_nx_to_mapping!(model_ffi, nv), mj_model_nx_to_nitem!(model_ffi, nv), model_ffi.nv);
139
140            // $id:expr, $addr_map:expr, $njnt:expr, $max_n:expr
141            let qpos = nq_range;
142            let qvel = nv_range;
143            let qacc_warmstart = nv_range;
144            let qfrc_applied = nv_range;
145            let qacc = nv_range;
146            let xanchor = (id * 3, 3);
147            let xaxis = (id * 3, 3);
148            #[allow(non_snake_case)]
149            let qLDiagInv = nv_range;
150            let qfrc_bias = nv_range;
151            let qfrc_passive = nv_range;
152            let qfrc_actuator = nv_range;
153            let qfrc_smooth = nv_range;
154            let qacc_smooth = nv_range;
155            let qfrc_constraint = nv_range;
156            let qfrc_inverse = nv_range;
157            
158            let qfrc_spring = nv_range;
159            let qfrc_damper = nv_range;
160            let qfrc_gravcomp = nv_range;
161            let qfrc_fluid = nv_range;
162            /* Special case attributes, used for some internal calculation */
163            // cdof
164            // cdof_dot
165
166            Some(MjJointDataInfo {name: name.to_string(), id: id as usize,
167                qpos, qvel, qacc_warmstart, qfrc_applied, qacc, xanchor, xaxis, qLDiagInv, qfrc_bias,
168                qfrc_spring, qfrc_damper, qfrc_gravcomp, qfrc_fluid, qfrc_passive,
169                qfrc_actuator, qfrc_smooth, qacc_smooth, qfrc_constraint, qfrc_inverse
170            })
171        }
172    }
173
174    /// Obtains a [`MjSensorDataInfo`] struct containing information about the name, id, and
175    /// indices required for obtaining a slice view to the correct locations in [`MjData`].
176    /// The actual view can be obtained via [`MjSensorDataInfo::view`].
177    /// # Panics
178    /// When the `name` contains '\0' characters, a panic occurs.
179    pub fn sensor(&self, name: &str) -> Option<MjSensorDataInfo> {
180        let c_name = CString::new(name).unwrap();
181        let id = unsafe { mj_name2id(self.model.ffi(), MjtObj::mjOBJ_SENSOR as i32, c_name.as_ptr())};
182        if id == -1 {  // not found
183            return None;
184        }
185        let model_ffi = self.model.ffi();
186        let id = id as usize;
187
188        unsafe {
189            let data = mj_view_indices!(id, mj_model_nx_to_mapping!(model_ffi, nsensordata), mj_model_nx_to_nitem!(model_ffi, nsensordata), model_ffi.nsensordata);
190            Some(MjSensorDataInfo { id, name: name.to_string(), data })
191        }
192    }
193
194
195    /// Obtains a [`MjTendonDataInfo`] struct containing information about the name, id, and
196    /// indices required for obtaining a slice view to the correct locations in [`MjData`].
197    /// The actual view can be obtained via [`MjTendonDataInfo::view`].
198    /// # Panics
199    /// When the `name` contains '\0' characters, a panic occurs.
200    #[allow(non_snake_case)]
201    pub fn tendon(&self, name: &str) -> Option<MjTendonDataInfo> {
202        let c_name = CString::new(name).unwrap();
203        let id = unsafe { mj_name2id(self.model.ffi(), MjtObj::mjOBJ_TENDON as i32, c_name.as_ptr())};
204        if id == -1 {  // not found
205            return None;
206        }
207
208        let model_ffi = self.model.ffi();
209        let id = id as usize;
210        let nv = model_ffi.nv as usize;
211        let wrapadr = (id, 1);
212        let wrapnum = (id, 1);
213        let J_rownnz = (id, 1);
214        let J_rowadr = (id, 1);
215        let J_colind = (id * nv, nv);
216        let length = (id, 1);
217        let J = (id * nv, nv);
218        let velocity = (id, 1);
219
220        Some(MjTendonDataInfo { id, name: name.to_string(), wrapadr, wrapnum, J_rownnz, J_rowadr, J_colind, length, J, velocity })
221    }
222
223    /// Steps the MuJoCo simulation.
224    pub fn step(&mut self) {
225        unsafe {
226            mj_step(self.model.ffi(), self.ffi_mut());
227        }
228    }
229
230    /// Calculates new dynamics. This is a wrapper around `mj_step1`.
231    pub fn step1(&mut self) {
232        unsafe {
233            mj_step1(self.model.ffi(), self.ffi_mut());
234        }
235    }
236
237    /// Calculates the rest after dynamics and integrates in time.
238    /// This is a wrapper around `mj_step2`.
239    pub fn step2(&mut self) {
240        unsafe {
241            mj_step2(self.model.ffi(), self.ffi_mut());
242        }
243    }
244
245    /// Forward dynamics: same as mj_step but do not integrate in time.
246    /// This is a wrapper around `mj_forward`.
247    pub fn forward(&mut self) {
248        unsafe { 
249            mj_forward(self.model.ffi(), self.ffi_mut());
250        }
251    }
252
253    /// [`MjData::forward`] dynamics with skip.
254    /// This is a wrapper around `mj_forwardSkip`.
255    pub fn forward_skip(&mut self, skipstage: MjtStage, skipsensor: bool) {
256        unsafe { 
257            mj_forwardSkip(self.model.ffi(), self.ffi_mut(), skipstage as i32, skipsensor as i32);
258        }
259    }
260
261    /// Inverse dynamics: qacc must be set before calling ([`MjData::forward`]).
262    /// This is a wrapper around `mj_inverse`.
263    pub fn inverse(&mut self) {
264        unsafe {
265            mj_inverse(self.model.ffi(), self.ffi_mut());
266        }
267    }
268
269    /// [`MjData::inverse`] dynamics with skip; skipstage is [`MjtStage`].
270    /// This is a wrapper around `mj_inverseSkip`.
271    pub fn inverse_skip(&mut self, skipstage: MjtStage, skipsensor: bool) {
272        unsafe {
273            mj_inverseSkip(self.model.ffi(), self.ffi_mut(), skipstage as i32, skipsensor as i32);
274        }
275    }
276
277    /// Calculates the contact force for the given `contact_id`.
278    /// The `contact_id` matches the index of the contact when iterating
279    /// via [`MjData::contacts`].
280    /// Calls `mj_contactForce` internally.
281    pub fn contact_force(&self, contact_id: usize) -> [MjtNum; 6] {
282        let mut force = [0.0; 6];
283        unsafe {
284            mj_contactForce(
285                self.model.ffi(), self.data,
286                contact_id as i32, force.as_mut_ptr()
287            );
288        }
289        force
290    }
291
292    /* Partially auto-generated */
293
294    /// Reset data to defaults.
295    pub fn reset(&mut self) {
296        unsafe { mj_resetData(self.model.ffi(), self.ffi_mut()) }
297    }
298
299    /// Reset data to defaults, fill everything else with debug_value.
300    pub fn reset_debug(&mut self, debug_value: u8) {
301        unsafe { mj_resetDataDebug(self.model.ffi(), self.ffi_mut(), debug_value) }
302    }
303
304    /// Reset data. If 0 <= key < nkey, set fields from specified keyframe.
305    pub fn reset_keyframe(&mut self, key: i32) {
306        unsafe { mj_resetDataKeyframe(self.model.ffi(), self.ffi_mut(), key) }
307    }
308
309    /// Print mjData to text file, specifying format.
310    /// float_format must be a valid printf-style format string for a single float value
311    /// # Panics
312    /// When the `filename` or `float_format` contain '\0' characters, a panic occurs.
313    pub fn print_formatted(&self, filename: &str, float_format: &str) {
314        let c_filename = CString::new(filename).unwrap();
315        let c_float_format = CString::new(float_format).unwrap();
316        unsafe { mj_printFormattedData(self.model.ffi(), self.ffi(), c_filename.as_ptr(), c_float_format.as_ptr()) }
317    }
318
319    /// Print data to text file.
320    /// # Panics
321    /// When the `filename` contains '\0' characters, a panic occurs.
322    pub fn print(&self, filename: &str) {
323        let c_filename = CString::new(filename).unwrap();
324        unsafe { mj_printData(self.model.ffi(), self.ffi(), c_filename.as_ptr()) }
325    }
326
327    /// Run position-dependent computations.
328    pub fn fwd_position(&mut self) {
329        unsafe { mj_fwdPosition(self.model.ffi(), self.ffi_mut()) }
330    }
331
332    /// Run velocity-dependent computations.
333    pub fn fwd_velocity(&mut self) {
334        unsafe { mj_fwdVelocity(self.model.ffi(), self.ffi_mut()) }
335    }
336
337    /// Compute actuator force qfrc_actuator.
338    pub fn fwd_actuation(&mut self) {
339        unsafe { mj_fwdActuation(self.model.ffi(), self.ffi_mut()) }
340    }
341
342    /// Add up all non-constraint forces, compute qacc_smooth.
343    pub fn fwd_acceleration(&mut self) {
344        unsafe { mj_fwdAcceleration(self.model.ffi(), self.ffi_mut()) }
345    }
346
347    /// Run selected constraint solver.
348    pub fn fwd_constraint(&mut self) {
349        unsafe { mj_fwdConstraint(self.model.ffi(), self.ffi_mut()) }
350    }
351
352    /// Euler integrator, semi-implicit in velocity.
353    pub fn euler(&mut self) {
354        unsafe { mj_Euler(self.model.ffi(), self.ffi_mut()) }
355    }
356
357    /// Runge-Kutta explicit order-N integrator.
358    pub fn runge_kutta(&mut self, n: i32) {
359        unsafe { mj_RungeKutta(self.model.ffi(), self.ffi_mut(), n) }
360    }
361
362    /// Implicit-in-velocity integrators.
363    pub fn implicit(&mut self) {
364        unsafe { mj_implicit(self.model.ffi(), self.ffi_mut()) }
365    }
366
367    /// Run position-dependent computations in inverse dynamics.
368    pub fn inv_position(&mut self) {
369        unsafe { mj_invPosition(self.model.ffi(), self.ffi_mut()) }
370    }
371
372    /// Run velocity-dependent computations in inverse dynamics.
373    pub fn inv_velocity(&mut self) {
374        unsafe { mj_invVelocity(self.model.ffi(), self.ffi_mut()) }
375    }
376
377    /// Apply the analytical formula for inverse constraint dynamics.
378    pub fn inv_constraint(&mut self) {
379        unsafe { mj_invConstraint(self.model.ffi(), self.ffi_mut()) }
380    }
381
382    /// Compare forward and inverse dynamics, save results in fwdinv.
383    pub fn compare_fwd_inv(&mut self) {
384        unsafe { mj_compareFwdInv(self.model.ffi(), self.ffi_mut()) }
385    }
386
387    /// Evaluate position-dependent sensors.
388    pub fn sensor_pos(&mut self) {
389        unsafe { mj_sensorPos(self.model.ffi(), self.ffi_mut()) }
390    }
391
392    /// Evaluate velocity-dependent sensors.
393    pub fn sensor_vel(&mut self) {
394        unsafe { mj_sensorVel(self.model.ffi(), self.ffi_mut()) }
395    }
396
397    /// Evaluate acceleration and force-dependent sensors.
398    pub fn sensor_acc(&mut self) {
399        unsafe { mj_sensorAcc(self.model.ffi(), self.ffi_mut()) }
400    }
401
402    /// Evaluate position-dependent energy (potential).
403    pub fn energy_pos(&mut self) {
404        unsafe { mj_energyPos(self.model.ffi(), self.ffi_mut()) }
405    }
406
407    /// Evaluate velocity-dependent energy (kinetic).
408    pub fn energy_vel(&mut self) {
409        unsafe { mj_energyVel(self.model.ffi(), self.ffi_mut()) }
410    }
411
412    /// Check qpos, reset if any element is too big or nan.
413    pub fn check_pos(&mut self) {
414        unsafe { mj_checkPos(self.model.ffi(), self.ffi_mut()) }
415    }
416
417    /// Check qvel, reset if any element is too big or nan.
418    pub fn check_vel(&mut self) {
419        unsafe { mj_checkVel(self.model.ffi(), self.ffi_mut()) }
420    }
421
422    /// Check qacc, reset if any element is too big or nan.
423    pub fn check_acc(&mut self) {
424        unsafe { mj_checkAcc(self.model.ffi(), self.ffi_mut()) }
425    }
426
427    /// Run forward kinematics.
428    pub fn kinematics(&mut self) {
429        unsafe { mj_kinematics(self.model.ffi(), self.ffi_mut()) }
430    }
431
432    /// Map inertias and motion dofs to global frame centered at CoM.
433    pub fn com_pos(&mut self) {
434        unsafe { mj_comPos(self.model.ffi(), self.ffi_mut()) }
435    }
436
437    /// Compute camera and light positions and orientations.
438    pub fn camlight(&mut self) {
439        unsafe { mj_camlight(self.model.ffi(), self.ffi_mut()) }
440    }
441
442    /// Compute flex-related quantities.
443    pub fn flex_comp(&mut self) {
444        unsafe { mj_flex(self.model.ffi(), self.ffi_mut()) }
445    }
446
447    /// Compute tendon lengths, velocities and moment arms.
448    pub fn tendon_comp(&mut self) {
449        unsafe { mj_tendon(self.model.ffi(), self.ffi_mut()) }
450    }
451
452    /// Compute actuator transmission lengths and moments.
453    pub fn transmission(&mut self) {
454        unsafe { mj_transmission(self.model.ffi(), self.ffi_mut()) }
455    }
456
457    /// Run composite rigid body inertia algorithm (CRB).
458    pub fn crb(&mut self) {
459        unsafe { mj_crb(self.model.ffi(), self.ffi_mut()) }
460    }
461
462    /// Make inertia matrix.
463    pub fn make_m(&mut self) {
464        unsafe { mj_makeM(self.model.ffi(), self.ffi_mut()) }
465    }
466
467    /// Compute sparse L'*D*L factorizaton of inertia matrix.
468    pub fn factor_m(&mut self) {
469        unsafe { mj_factorM(self.model.ffi(), self.ffi_mut()) }
470    }
471
472    /// Compute cvel, cdof_dot.
473    pub fn com_vel(&mut self) {
474        unsafe { mj_comVel(self.model.ffi(), self.ffi_mut()) }
475    }
476
477    /// Compute qfrc_passive from spring-dampers, gravity compensation and fluid forces.
478    pub fn passive(&mut self) {
479        unsafe { mj_passive(self.model.ffi(), self.ffi_mut()) }
480    }
481
482    /// Sub-tree linear velocity and angular momentum: compute subtree_linvel, subtree_angmom.
483    pub fn subtree_vel(&mut self) {
484        unsafe { mj_subtreeVel(self.model.ffi(), self.ffi_mut()) }
485    }
486
487    /// RNE: compute M(qpos)*qacc + C(qpos,qvel); flg_acc=false removes inertial term.
488    pub fn rne(&mut self, flg_acc: bool) -> Vec<MjtNum> {
489        let mut out = vec![0.0; self.model.ffi().nv as usize];
490        unsafe { mj_rne(self.model.ffi(), self.ffi_mut(), flg_acc as i32, out.as_mut_ptr()) };
491        out
492    }
493
494    /// RNE with complete data: compute cacc, cfrc_ext, cfrc_int.
495    pub fn rne_post_constraint(&mut self) {
496        unsafe { mj_rnePostConstraint(self.model.ffi(), self.ffi_mut()) }
497    }
498
499    /// Run collision detection.
500    pub fn collision(&mut self) {
501        unsafe { mj_collision(self.model.ffi(), self.ffi_mut()) }
502    }
503
504    /// Construct constraints.
505    pub fn make_constraint(&mut self) {
506        unsafe { mj_makeConstraint(self.model.ffi(), self.ffi_mut()) }
507    }
508
509    /// Find constraint islands.
510    pub fn island(&mut self) {
511        unsafe { mj_island(self.model.ffi(), self.ffi_mut()) }
512    }
513
514    /// Compute inverse constraint inertia efc_AR.
515    pub fn project_constraint(&mut self) {
516        unsafe { mj_projectConstraint(self.model.ffi(), self.ffi_mut()) }
517    }
518
519    /// Compute efc_vel, efc_aref.
520    pub fn reference_constraint(&mut self) {
521        unsafe { mj_referenceConstraint(self.model.ffi(), self.ffi_mut()) }
522    }
523
524    /// Compute efc_state, efc_force, qfrc_constraint, and (optionally) cone Hessians.
525    /// If cost is not NULL, set *cost = s(jar) where jar = Jac*qacc-aref.
526    /// Nullable: cost
527    pub fn constraint_update(&mut self, jar: &[MjtNum], cost: Option<&mut MjtNum>, flg_cone_hessian: bool) {
528        unsafe { mj_constraintUpdate(
529            self.model.ffi(), self.ffi_mut(),
530            jar.as_ptr(), cost.map_or(ptr::null_mut(), |x| x as *mut MjtNum),
531            flg_cone_hessian as i32
532        ) }
533    }
534
535    /// Add contact to d->contact list; return 0 if success; 1 if buffer full.
536    pub fn add_contact(&mut self, con: &MjContact) -> io::Result<()> {
537        match unsafe { mj_addContact(self.model.ffi(), self.ffi_mut(), con) } {
538            0 => Ok(()),
539            1 => Err(Error::new(ErrorKind::StorageFull, "buffer full")),
540            _ => Err(Error::new(ErrorKind::Other, "unknown error"))
541        }
542    }
543
544    /// Compute 3/6-by-nv end-effector Jacobian of a global point attached to the given body.
545    /// Set `jacp` to `true` to calculate the translational Jacobian and `jacr` to `true` for
546    /// the rotational Jacobian. Returns a `(Vec, Vec)` for translation and rotation. Empty `Vec`s
547    /// indicate that the corresponding Jacobian was not computed.
548    pub fn jac(&self, jacp: bool, jacr: bool, point: &[MjtNum; 3], body_id: i32) -> (Vec<MjtNum>, Vec<MjtNum>) {
549        let required_len = 3 * self.model.ffi().nv as usize;
550        let mut jacp_vec = if jacp { vec![0 as MjtNum; required_len] } else { vec![] };
551        let mut jacr_vec = if jacr { vec![0 as MjtNum; required_len] } else { vec![] };
552
553        unsafe {
554            mj_jac(
555                self.model.ffi(),
556                self.ffi(),
557                if jacp { jacp_vec.as_mut_ptr() } else { ptr::null_mut() },
558                if jacr { jacr_vec.as_mut_ptr() } else { ptr::null_mut() },
559                point.as_ptr(),
560                body_id,
561            )
562        };
563
564        (jacp_vec, jacr_vec)
565    }
566
567    /// Compute body frame end-effector Jacobian.
568    /// Set `jacp`/`jacr` to `true` to calculate translational/rotational components.
569    /// Returns `(Vec, Vec)` for translation and rotation. Empty `Vec`s indicate not computed.
570    pub fn jac_body(&self, jacp: bool, jacr: bool, body_id: i32) -> (Vec<MjtNum>, Vec<MjtNum>) {
571        let required_len = 3 * self.model.ffi().nv as usize;
572        let mut jacp_vec = if jacp { vec![0 as MjtNum; required_len] } else { vec![] };
573        let mut jacr_vec = if jacr { vec![0 as MjtNum; required_len] } else { vec![] };
574
575        unsafe {
576            mj_jacBody(
577                self.model.ffi(),
578                self.ffi(),
579                if jacp { jacp_vec.as_mut_ptr() } else { ptr::null_mut() },
580                if jacr { jacr_vec.as_mut_ptr() } else { ptr::null_mut() },
581                body_id,
582            )
583        };
584
585        (jacp_vec, jacr_vec)
586    }
587
588    /// Compute body center-of-mass end-effector Jacobian.
589    /// Set `jacp`/`jacr` to `true` to calculate translational/rotational components.
590    /// Returns `(Vec, Vec)` for translation and rotation. Empty `Vec`s indicate not computed.
591    pub fn jac_body_com(&self, jacp: bool, jacr: bool, body_id: i32) -> (Vec<MjtNum>, Vec<MjtNum>) {
592        let required_len = 3 * self.model.ffi().nv as usize;
593        let mut jacp_vec = if jacp { vec![0 as MjtNum; required_len] } else { vec![] };
594        let mut jacr_vec = if jacr { vec![0 as MjtNum; required_len] } else { vec![] };
595
596        unsafe {
597            mj_jacBodyCom(
598                self.model.ffi(),
599                self.ffi(),
600                if jacp { jacp_vec.as_mut_ptr() } else { ptr::null_mut() },
601                if jacr { jacr_vec.as_mut_ptr() } else { ptr::null_mut() },
602                body_id,
603            )
604        };
605
606        (jacp_vec, jacr_vec)
607    }
608
609    /// Compute subtree center-of-mass end-effector Jacobian (translational only).
610    /// Set `jacp` to `true` to calculate the translational component. Returns a `Vec`.
611    /// Empty `Vec` indicates that the Jacobian was not computed.
612    pub fn jac_subtree_com(&mut self, jacp: bool, body_id: i32) -> Vec<MjtNum> {
613        let required_len = 3 * self.model.ffi().nv as usize;
614        let mut jacp_vec = if jacp { vec![0 as MjtNum; required_len] } else { vec![] };
615
616        unsafe {
617            mj_jacSubtreeCom(
618                self.model.ffi(),
619                self.ffi_mut(),
620                if jacp { jacp_vec.as_mut_ptr() } else { ptr::null_mut() },
621                body_id,
622            )
623        };
624
625        jacp_vec
626    }
627
628    /// Compute geom end-effector Jacobian.
629    /// Set `jacp`/`jacr` to `true` to calculate translational/rotational components.
630    /// Returns `(Vec, Vec)` for translation and rotation. Empty `Vec`s indicate not computed.
631    pub fn jac_geom(&self, jacp: bool, jacr: bool, geom_id: i32) -> (Vec<MjtNum>, Vec<MjtNum>) {
632        let required_len = 3 * self.model.ffi().nv as usize;
633        let mut jacp_vec = if jacp { vec![0 as MjtNum; required_len] } else { vec![] };
634        let mut jacr_vec = if jacr { vec![0 as MjtNum; required_len] } else { vec![] };
635
636        unsafe {
637            mj_jacGeom(
638                self.model.ffi(),
639                self.ffi(),
640                if jacp { jacp_vec.as_mut_ptr() } else { ptr::null_mut() },
641                if jacr { jacr_vec.as_mut_ptr() } else { ptr::null_mut() },
642                geom_id,
643            )
644        };
645
646        (jacp_vec, jacr_vec)
647    }
648
649    /// Compute site end-effector Jacobian.
650    /// Set `jacp`/`jacr` to `true` to calculate translational/rotational components.
651    /// Returns `(Vec, Vec)` for translation and rotation. Empty `Vec`s indicate not computed.
652    pub fn jac_site(&self, jacp: bool, jacr: bool, site_id: i32) -> (Vec<MjtNum>, Vec<MjtNum>) {
653        let required_len = 3 * self.model.ffi().nv as usize;
654        let mut jacp_vec = if jacp { vec![0 as MjtNum; required_len] } else { vec![] };
655        let mut jacr_vec = if jacr { vec![0 as MjtNum; required_len] } else { vec![] };
656
657        unsafe {
658            mj_jacSite(
659                self.model.ffi(),
660                self.ffi(),
661                if jacp { jacp_vec.as_mut_ptr() } else { ptr::null_mut() },
662                if jacr { jacr_vec.as_mut_ptr() } else { ptr::null_mut() },
663                site_id,
664            )
665        };
666
667        (jacp_vec, jacr_vec)
668    }
669
670    /// Compute subtree angular momentum matrix.
671    pub fn angmom_mat(&mut self, body_id: i32) -> Vec<MjtNum> {
672        let mut mat = vec![0.0; 3 * self.model.ffi().nv as usize];
673        unsafe { mj_angmomMat(self.model.ffi(), self.ffi_mut(), mat.as_mut_ptr(), body_id) };
674        mat
675    }
676
677    /// Compute object 6D velocity (rot:lin) in object-centered frame, world/local orientation.
678    pub fn object_velocity(&self, obj_type: MjtObj, obj_id: i32, flg_local: bool) -> [MjtNum; 6] {
679        let mut result: [MjtNum; 6] = [0.0; 6];
680        unsafe { mj_objectVelocity(
681            self.model.ffi(), self.ffi(),
682            obj_type as i32, obj_id,
683            result.as_mut_ptr(), flg_local as i32
684        ) };
685        result
686    }
687
688    /// Compute object 6D acceleration (rot:lin) in object-centered frame, world/local orientation.
689    pub fn object_acceleration(&self, obj_type: MjtObj, obj_id: i32, flg_local: bool) -> [MjtNum; 6] {
690        let mut result: [MjtNum; 6] = [0.0; 6];
691        unsafe { mj_objectAcceleration(
692            self.model.ffi(), self.ffi(),
693            obj_type as i32, obj_id,
694            result.as_mut_ptr(), flg_local as i32
695        ) };
696        result
697    }
698
699    /// Returns smallest signed distance between two geoms and optionally segment from geom1 to geom2.
700    pub fn geom_distance(&self, geom1_id: i32, geom2_id: i32, dist_max: MjtNum, fromto: Option<&mut [MjtNum; 6]>) -> MjtNum {
701        unsafe { mj_geomDistance(
702            self.model.ffi(), self.ffi(),
703            geom1_id, geom2_id, dist_max,
704            fromto.map_or(ptr::null_mut(), |x| x.as_mut_ptr())
705        ) }
706    }
707
708    /// Map from body local to global Cartesian coordinates, sameframe takes values from [`MjtSameFrame`].
709    /// Returns (global position, global orientation matrix).
710    /// Wraps ``mj_local2Global``.
711    pub fn local_to_global(&mut self, pos: &[MjtNum; 3], quat: &[MjtNum; 4], body_id: i32, sameframe: MjtSameFrame) -> ([MjtNum; 3], [MjtNum; 9]) {
712        /* Create uninitialized because this gets filled by the function. */
713        let mut xpos: [MjtNum; 3] =  [0.0; 3];
714        let mut xmat: [MjtNum; 9] = [0.0; 9];
715        unsafe { mj_local2Global(self.ffi_mut(), xpos.as_mut_ptr(), xmat.as_mut_ptr(), pos.as_ptr(), quat.as_ptr(), body_id, sameframe as MjtByte) };
716        (xpos, xmat)
717    }
718
719    /// Intersect multiple rays emanating from a single point.
720    /// Similar semantics to mj_ray, but vec is an array of (nray x 3) directions.
721    /// Returns (geomids, distances).
722    pub fn multi_ray(
723        &mut self, pnt: &[MjtNum; 3], vec: &[[MjtNum; 3]], geomgroup: Option<&[MjtByte; mjNGROUP as usize]>,
724        flg_static: bool, bodyexclude: i32, cutoff: MjtNum
725    ) -> (Vec<i32>, Vec<MjtNum>) {
726        let nray = vec.len();
727        let mut geom_id = vec![0; nray];
728        let mut distance = vec![0.0; nray];
729
730        unsafe { mj_multiRay(
731            self.model.ffi(), self.ffi_mut(), pnt.as_ptr(),
732            vec.as_ptr() as *const MjtNum, geomgroup.map_or(ptr::null(), |x| x.as_ptr()),
733            flg_static as u8, bodyexclude, geom_id.as_mut_ptr(),
734            distance.as_mut_ptr(), nray as i32, cutoff
735        ) };
736
737        (geom_id, distance)
738    }
739
740    /// Intersect ray (pnt+x*vec, x>=0) with visible geoms, except geoms in bodyexclude.
741    /// Return distance (x) to nearest surface, or -1 if no intersection and output geomid.
742    /// geomgroup, flg_static are as in mjvOption; geomgroup==NULL skips group exclusion.
743    pub fn ray(
744        &self, pnt: &[MjtNum; 3], vec: &[MjtNum; 3],
745        geomgroup: Option<&[MjtByte; mjNGROUP as usize]>, flg_static: bool, bodyexclude: i32
746    ) -> (i32, MjtNum) {
747        let mut geom_id = -1;
748        let dist = unsafe { mj_ray(
749            self.model.ffi(), self.ffi(),
750            pnt.as_ptr(), vec.as_ptr(),
751            geomgroup.map_or(ptr::null(), |x| x.as_ptr()),
752            flg_static as MjtByte, bodyexclude, &mut geom_id
753        ) };
754        (geom_id, dist)
755    }
756
757    /// Returns a direct pointer to the underlying model.
758    /// THIS IS NOT TO BE USED.
759    /// It is only meant for the viewer code, which currently still depends
760    /// on mutable pointers to model and data. This will be removed in the future.
761    pub(crate) unsafe fn __raw(&self) -> *mut mjData {
762        self.data
763    }
764
765}
766
767
768/// Some public attribute methods.
769impl MjData<'_> {
770    /// Maximum stack allocation in bytes.
771    pub fn maxuse_stack(&self) -> MjtSize {
772        self.ffi().maxuse_stack
773    }
774
775    /// Maximum stack allocation per thread in bytes.
776    pub fn maxuse_threadstack(&self) -> &[MjtSize] {
777        &self.ffi().maxuse_threadstack
778    }
779
780    /// Warning statistics.
781    pub fn warning_stats(&self) -> &[MjWarningStat] {
782        &self.ffi().warning
783    }
784
785    /// Timer statistics.
786    pub fn timer_stats(&self) -> &[MjTimerStat] {
787        &self.ffi().timer
788    }
789
790    /// Simulation time
791    pub fn time(&self) -> MjtNum {
792        self.ffi().time
793    }
794
795    /// Potential, kinetic energy.
796    pub fn energy(&self) -> &[MjtNum] {
797        &self.ffi().energy
798    }
799}
800
801impl Drop for MjData<'_> {
802    fn drop(&mut self) {
803        unsafe {
804            mj_deleteData(self.data);
805        }
806    }
807}
808
809
810/**************************************************************************************************/
811// Joint view
812/**************************************************************************************************/
813info_with_view!(
814    Data,
815    joint,
816    [
817        qpos: MjtNum, qvel: MjtNum, qacc_warmstart: MjtNum, qfrc_applied: MjtNum, qacc: MjtNum, xanchor: MjtNum, xaxis: MjtNum, qLDiagInv: MjtNum,
818        qfrc_bias: MjtNum, qfrc_spring: MjtNum, qfrc_damper: MjtNum, qfrc_gravcomp: MjtNum, qfrc_fluid: MjtNum, qfrc_passive: MjtNum,
819        qfrc_actuator: MjtNum, qfrc_smooth: MjtNum, qacc_smooth: MjtNum, qfrc_constraint: MjtNum, qfrc_inverse: MjtNum
820    ],
821    []
822);
823
824/// Deprecated name for [`MjJointDataInfo`].
825#[deprecated]
826pub type MjJointInfo = MjJointDataInfo;
827
828
829/* Backward compatibility */
830impl MjJointDataViewMut<'_> {
831    /// Deprecated. Use [`MjJointDataViewMut::zero`] instead.
832    #[deprecated]
833    pub fn reset(&mut self) {
834        self.zero();
835    }
836}
837
838/// Deprecated name for [`MjJointDataView`].
839#[deprecated]
840pub type MjJointView<'d> = MjJointDataView<'d>;
841
842/// Deprecated name for [`MjJointDataViewMut`].
843#[deprecated]
844pub type MjJointViewMut<'d> = MjJointDataViewMut<'d>;
845
846/**************************************************************************************************/
847// Sensor view
848/**************************************************************************************************/
849info_with_view!(Data, sensor, sensor, [data: MjtNum], []);
850
851/**************************************************************************************************/
852// Geom view
853/**************************************************************************************************/
854info_with_view!(Data, geom, geom_, [xpos: MjtNum, xmat: MjtNum], []);
855
856/// Deprecated name for [`MjGeomDataInfo`].
857#[deprecated]
858pub type MjGeomInfo = MjGeomDataInfo;
859
860/// Deprecated name for [`MjGeomDataView`].
861#[deprecated]
862pub type MjGeomView<'d> = MjGeomDataView<'d>;
863
864/// Deprecated name for [`MjGeomDataViewMut`].
865#[deprecated]
866pub type MjGeomViewMut<'d> = MjGeomDataViewMut<'d>;
867
868/**************************************************************************************************/
869// Actuator view
870/**************************************************************************************************/
871info_with_view!(Data, actuator, [ctrl: MjtNum], [act: MjtNum]);
872
873/// Deprecated name for [`MjActuatorDataInfo`].
874#[deprecated]
875pub type MjActuatorInfo = MjActuatorDataInfo;
876
877/// Deprecated name for [`MjActuatorDataView`].
878#[deprecated]
879pub type MjActuatorView<'d> = MjActuatorDataView<'d>;
880
881/// Deprecated name for [`MjActuatorDataViewMut`].
882#[deprecated]
883pub type MjActuatorViewMut<'d> = MjActuatorDataViewMut<'d>;
884
885/**************************************************************************************************/
886// Body view
887/**************************************************************************************************/
888info_with_view!(
889    Data, body, [
890        xfrc_applied: MjtNum, xpos: MjtNum, xquat: MjtNum, xmat: MjtNum, xipos: MjtNum, ximat: MjtNum,
891        subtree_com: MjtNum, cinert: MjtNum, crb: MjtNum, cvel: MjtNum, subtree_linvel: MjtNum,
892        subtree_angmom: MjtNum, cacc: MjtNum, cfrc_int: MjtNum, cfrc_ext: MjtNum
893    ], []
894);
895
896/**************************************************************************************************/
897// Camera view
898/**************************************************************************************************/
899info_with_view!(Data, camera, cam_, [xpos: MjtNum, xmat: MjtNum], []);
900
901/**************************************************************************************************/
902// Site view
903/**************************************************************************************************/
904info_with_view!(Data, site, site_, [xpos: MjtNum, xmat: MjtNum], []);
905
906/**************************************************************************************************/
907// Tendon view
908/**************************************************************************************************/
909info_with_view!(Data, tendon, ten_, [wrapadr: i32, wrapnum: i32, J_rownnz: i32, J_rowadr: i32, J_colind: i32, length: MjtNum, J: MjtNum, velocity: MjtNum], []);
910
911/**************************************************************************************************/
912// Light view
913/**************************************************************************************************/
914info_with_view!(Data, light, light_, [xpos: MjtNum, xdir: MjtNum], []);
915
916/**************************************************************************************************/
917// Unit tests
918/**************************************************************************************************/
919
920#[cfg(test)]
921mod test {
922    use crate::assert_relative_eq;
923    use crate::prelude::*;
924    use super::*;
925
926    const MODEL: &str = "
927<mujoco>
928  <worldbody>
929    <light ambient=\"0.2 0.2 0.2\"/>
930    <body name=\"ball\" pos=\".2 .2 .1\">
931        <geom name=\"green_sphere\" size=\".1\" rgba=\"0 1 0 1\" solref=\"0.004 1.0\"/>
932        <joint name=\"ball\" type=\"free\"/>
933    </body>
934
935    <body name=\"ball2\" pos=\".7 .2 .1\">
936        <geom name=\"green_sphere2\" size=\".1\" rgba=\"0 1 0 1\" solref=\"0.004 1.0\"/>
937        <joint name=\"ball2\" type=\"free\"/>
938    </body>
939
940    <geom name=\"floor1\" type=\"plane\" size=\"10 10 1\" solref=\"0.004 1.0\"/>
941  </worldbody>
942</mujoco>";
943
944
945    #[test]
946    fn test_joint_view() {
947        let model = MjModel::from_xml_string(MODEL).unwrap();
948        let mut data = model.make_data();
949        let joint_info = data.joint("ball").unwrap();
950        let body_info = data.body("ball").unwrap();
951
952        for _ in 0..10 {
953            data.step();
954        }
955
956        /* The ball should start in a still position */
957        let mut joint_view = joint_info.view(&data);
958        assert_relative_eq!(joint_view.qvel[0], 0.0, epsilon=1e-9);  // vx
959        assert_relative_eq!(joint_view.qvel[1], 0.0, epsilon=1e-9);  // vy
960        // assert_relative_eq!(view.qvel[2], 0.0);  // vz Ignore due to slight instability of the model.
961        assert_relative_eq!(joint_view.qvel[3], 0.0, epsilon=1e-9);  // wx
962        assert_relative_eq!(joint_view.qvel[4], 0.0, epsilon=1e-9);  // wy
963        assert_relative_eq!(joint_view.qvel[5], 0.0, epsilon=1e-9);  // wz
964
965        /* Give the ball some velocity */
966        let mut joint_view_mut = joint_info.view_mut(&mut data);
967        joint_view_mut.qvel[0] = 0.5;  // vx = 0.5 m/s
968        joint_view_mut.qvel[4] = 0.5 / 0.1;  // wy = 0.5 m/s / 0.1 m
969
970        let initial_qpos: [MjtNum; 3] = joint_view_mut.qpos[..3].try_into().unwrap();  // initial x, y and z.
971        data.step();
972
973        /* Test if the ball is moving in the x direction and rotating around y. */
974        joint_view = joint_info.view(&data);
975        assert_relative_eq!(joint_view.qvel[0], 0.5, epsilon=1e-3);  // vx
976        assert_relative_eq!(joint_view.qvel[4], 0.5 / 0.1, epsilon=1e-3);  // wy
977
978        /* Test correct placement */
979        let timestep = model.opt().timestep;
980        assert_relative_eq!(joint_view.qpos[0], initial_qpos[0] + timestep * joint_view.qvel[0], epsilon=1e-9);  // p = p + dp/dt * dt
981        assert_relative_eq!(joint_view.qpos[1], initial_qpos[1] + timestep * joint_view.qvel[1], epsilon=1e-9);
982        assert_relative_eq!(joint_view.qpos[2], initial_qpos[2] + timestep * joint_view.qvel[2], epsilon=1e-9);
983
984        /* Test consistency with the body */
985        data.step1();  // update derived variables.
986
987        
988        joint_view = joint_info.view(&data);
989        let body_view = body_info.view(&data);
990        /* Consistency in position */
991        assert_relative_eq!(joint_view.qpos[0], body_view.xpos[0], epsilon=1e-9);  // same position.
992        assert_relative_eq!(joint_view.qpos[1], body_view.xpos[1], epsilon=1e-9);
993        assert_relative_eq!(joint_view.qpos[2], body_view.xpos[2], epsilon=1e-9);
994
995        assert_relative_eq!(joint_view.qpos[3], body_view.xquat[0], epsilon=1e-9);  // same orientation.
996        assert_relative_eq!(joint_view.qpos[4], body_view.xquat[1], epsilon=1e-9);
997        assert_relative_eq!(joint_view.qpos[5], body_view.xquat[2], epsilon=1e-9);
998        assert_relative_eq!(joint_view.qpos[6], body_view.xquat[3], epsilon=1e-9);
999
1000        /* Consistency in velocity */
1001        assert_relative_eq!(joint_view.qvel[0], body_view.cvel[3], epsilon=1e-9);  // same position velocity.
1002        assert_relative_eq!(joint_view.qvel[1], body_view.cvel[4], epsilon=1e-9);
1003        assert_relative_eq!(joint_view.qvel[2], body_view.cvel[5], epsilon=1e-9);
1004
1005        assert_relative_eq!(joint_view.qvel[3], body_view.cvel[0], epsilon=1e-9);  // same rotational velocity.
1006        assert_relative_eq!(joint_view.qvel[4], body_view.cvel[1], epsilon=1e-9);
1007        assert_relative_eq!(joint_view.qvel[5], body_view.cvel[2], epsilon=1e-9);
1008    }
1009
1010    #[test]
1011    fn test_body_view() {
1012        let model = MjModel::from_xml_string(MODEL).unwrap();
1013        let mut data = model.make_data();
1014        let body_info = data.body("ball2").unwrap();
1015        let mut cvel;
1016
1017        data.step1();
1018
1019        for _ in 0..10 {
1020            data.step2();
1021            data.step1();  // step() and step2() update before integration, thus we need to manually update non-state variables.
1022        }
1023
1024        // The ball should start in a still position
1025        cvel = body_info.view(&data).cvel;
1026        assert_relative_eq!(cvel[0], 0.0, epsilon=1e-9);
1027        assert_relative_eq!(cvel[1], 0.0, epsilon=1e-9);
1028        assert_relative_eq!(cvel[2], 0.0, epsilon=1e-9);
1029        assert_relative_eq!(cvel[3], 0.0, epsilon=1e-9);
1030        assert_relative_eq!(cvel[4], 0.0, epsilon=1e-9);
1031        // assert_relative_eq!(cvel[5], 0.0);  // Ignore due to slight instability of the model.
1032
1033        // Give the ball some velocity
1034        body_info.view_mut(&mut data).xfrc_applied[0] = 5.0;
1035        data.step2();
1036        data.step1();
1037
1038        let view = body_info.view(&data);
1039        cvel = view.cvel;
1040        println!("{:?}", cvel);
1041        assert_relative_eq!(cvel[0], 0.0, epsilon=1e-9);
1042        assert!(cvel[1] > 0.0);  // wy should be positive when rolling with positive vx.
1043        assert_relative_eq!(cvel[2], 0.0, epsilon=1e-9);
1044        assert!(cvel[3] > 0.0);  // vx should point in the direction of the applied force.
1045        assert_relative_eq!(cvel[4], 0.0, epsilon=1e-9);  // vy should be 0.
1046        // assert_relative_eq!(cvel[5], 0.0);  // vz should be 0, but we don't test it due to jumpiness (instability) of the ball.
1047
1048        assert_relative_eq!(view.xfrc_applied[0], 5.0, epsilon=1e-9); // the original force should stay applied.
1049
1050        data.step2();
1051        data.step1();
1052    }
1053
1054    #[test]
1055    fn test_copy_reset_variants() {
1056        let model = MjModel::from_xml_string(MODEL).unwrap();
1057        let mut data = model.make_data();
1058
1059        // Test reset variants
1060        data.reset();
1061        data.reset_debug(7);
1062        data.reset_keyframe(0);
1063    }
1064
1065    #[test]
1066    fn test_dynamics_and_sensors() {
1067        let model = MjModel::from_xml_string(MODEL).unwrap();
1068        let mut data = model.make_data();
1069
1070        // Simulation pipeline components
1071        data.fwd_position();
1072        data.fwd_velocity();
1073        data.fwd_actuation();
1074        data.fwd_acceleration();
1075        data.fwd_constraint();
1076
1077        data.euler();
1078        data.runge_kutta(4);
1079        // data.implicit();  // integrator isn't implicit in the model => skip this check
1080
1081        data.inv_position();
1082        data.inv_velocity();
1083        data.inv_constraint();
1084        data.compare_fwd_inv();
1085
1086        // Sensors
1087        data.sensor_pos();
1088        data.sensor_vel();
1089        data.sensor_acc();
1090
1091        data.energy_pos();
1092        data.energy_vel();
1093
1094        data.check_pos();
1095        data.check_vel();
1096        data.check_acc();
1097
1098        data.kinematics();
1099        data.com_pos();
1100        data.camlight();
1101        data.flex_comp();
1102        data.tendon_comp();
1103        data.transmission();
1104        data.crb();
1105        data.make_m();
1106        data.factor_m();
1107        data.com_vel();
1108        data.passive();
1109        data.subtree_vel();
1110    }
1111
1112
1113    #[test]
1114    fn test_rne_and_collision_pipeline() {
1115        let model = MjModel::from_xml_string(MODEL).unwrap();
1116        let mut data = model.make_data();
1117        data.step();
1118
1119        // mj_rne returns a scalar as result
1120        data.rne(true);
1121
1122        data.rne_post_constraint();
1123
1124        // // Collision and constraint pipeline
1125        data.collision();
1126        data.make_constraint();
1127        data.island();
1128        data.project_constraint();
1129        data.reference_constraint();
1130
1131        let jar = vec![0.0; (data.model.ffi().nv) as usize];
1132        let mut cost = 0.0;
1133        data.constraint_update(&jar, None, false);
1134        data.constraint_update(&jar, Some(&mut cost), true);
1135    }
1136
1137    #[test]
1138    fn test_add_contact() {
1139        let model = MjModel::from_xml_string(MODEL).unwrap();
1140        let mut data = model.make_data();
1141
1142        // Add a dummy contact
1143        let dummy_contact = unsafe { std::mem::zeroed() };
1144        data.add_contact(&dummy_contact).unwrap();
1145    }
1146
1147    #[test]
1148    fn test_jacobian() {
1149        let model = MjModel::from_xml_string(MODEL).unwrap();
1150        let mut data = model.make_data();
1151
1152        let nv = data.model.ffi().nv as usize;
1153        let expected_len = 3 * nv;
1154
1155        // Use a small offset point relative to the joint origin
1156        let point = [0.1, 0.0, 0.0];
1157
1158        let ball_body_id = model.body("ball").unwrap().id as i32;
1159
1160        // Test global point Jacobian
1161        let (jacp, jacr) = data.jac(true, true, &point, ball_body_id);
1162        assert_eq!(jacp.len(), expected_len);
1163        assert_eq!(jacr.len(), expected_len);
1164
1165        // Test body frame Jacobian
1166        let (jacp_body, jacr_body) = data.jac_body(true, true, ball_body_id);
1167        assert_eq!(jacp_body.len(), expected_len);
1168        assert_eq!(jacr_body.len(), expected_len);
1169
1170        // Test body COM Jacobian
1171        let (jacp_com, jacr_com) = data.jac_body_com(true, true, ball_body_id);
1172        assert_eq!(jacp_com.len(), expected_len);
1173        assert_eq!(jacr_com.len(), expected_len);
1174
1175        // Test subtree COM Jacobian (translational only)
1176        let jac_subtree = data.jac_subtree_com(true, 0);
1177        assert_eq!(jac_subtree.len(), expected_len);
1178
1179        // Test geom Jacobian
1180        let (jacp_geom, jacr_geom) = data.jac_geom(true, true, ball_body_id);
1181        assert_eq!(jacp_geom.len(), expected_len);
1182        assert_eq!(jacr_geom.len(), expected_len);
1183
1184        // Test site Jacobian
1185        let (jacp_site, jacr_site) = data.jac_site(true, true, ball_body_id);
1186        assert_eq!(jacp_site.len(), expected_len);
1187        assert_eq!(jacr_site.len(), expected_len);
1188
1189        // Test flags set to false produce empty Vec
1190        let (jacp_none, jacr_none) = data.jac(false, false, &[0.0; 3], ball_body_id);
1191        assert!(jacp_none.is_empty());
1192        assert!(jacr_none.is_empty());
1193    }
1194
1195    #[test]
1196    fn test_angmom_and_object_dynamics() {
1197        let model = MjModel::from_xml_string(MODEL).unwrap();
1198        let mut data = model.make_data();
1199
1200        let mat = data.angmom_mat(0);
1201        assert_eq!(mat.len(), (3 * data.model.ffi().nv as usize));
1202
1203        let vel = data.object_velocity(MjtObj::mjOBJ_BODY, 0, true);
1204        assert_eq!(vel, vel); // just ensure it returns 6-length
1205
1206        let acc = data.object_acceleration(MjtObj::mjOBJ_BODY, 0, false);
1207        assert_eq!(acc, acc);
1208    }
1209
1210    #[test]
1211    fn test_geom_distance_and_transforms() {
1212        let model = MjModel::from_xml_string(MODEL).unwrap();
1213        let mut data = model.make_data();
1214        data.step();
1215
1216        let mut ft = [0.0; 6];
1217        let dist = data.geom_distance(0, 0, 1.0, Some(&mut ft));
1218        assert_eq!(ft, [0.0; 6]);
1219        assert_eq!(dist, 1.0);
1220
1221        let pos = [0.0; 3];
1222        let quat = [1.0, 0.0, 0.0, 0.0];
1223        let (xpos, xmat) = data.local_to_global(&pos, &quat, 0, MjtSameFrame::mjSAMEFRAME_NONE);
1224        assert_eq!(xpos.len(), 3);
1225        assert_eq!(xmat.len(), 9);
1226
1227        let ray_vecs = [[1.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 0.0, 0.0]];
1228        let rays = data.multi_ray(&pos, &ray_vecs, None, false, -1, 10.0);
1229        assert_eq!(rays.0.len(), 3);
1230        assert_eq!(rays.1.len(), 3);
1231
1232        let (geomid, dist) = data.ray(&pos, &[1.0, 0.0, 0.0], None, true, -1);
1233        assert!(dist.is_finite());
1234        assert!(geomid >= -1);
1235    }
1236}