Changelog

Versioning

This project uses semantic versioning. This means that any incompatible changes increase the major version (X.y.z). This also includes breaking changes that MuJoCo itself introduced, thus even an update of MuJoCo alone can increase the major version.

5.0.0 (MuJoCo 3.9.0)

Breaking changes

MuJoCo FFI upgraded to 3.9.0

(Potentially breaking) Changed raw pointer types of trait methods

  • Changed raw pointer types of trait methods. For example, element_pointer(&self) now returns *const mjsElement. This is a change that should not affect most users, unless they explicitly opted in to using the C model-editing API of MuJoCo directly, or implemented/depended on the old trait method signature in advanced extension code.

MjrContext::upload_texture parameter type changed

MjrContext::upload_texture is now fallible

  • MjrContext::upload_texture now returns Result<(), MjrContextError> instead of () and no longer panics. Pass an out-of-range ID to receive MjrContextError::IndexOutOfBounds { id, len } instead of a panic.

MjrContext::add_aux / set_aux error type changed

  • MjrContext::add_aux and MjrContext::set_aux previously returned Result<(), MjSceneError> (with MjSceneError::InvalidAuxBufferIndex); they now return Result<(), MjrContextError> (with MjrContextError::IndexOutOfBounds).

MjSceneError::InvalidAuxBufferIndex / InvalidViewport / BufferTooSmall removed

  • MjSceneError::InvalidAuxBufferIndex, MjSceneError::InvalidViewport, and MjSceneError::BufferTooSmall have been removed from MjSceneError. Code matching on these variants will fail to compile. Replace them with the corresponding MjrContextError variants (see migration guide).

MjrContext::read_pixels error type changed

  • MjrContext::read_pixels previously returned Result<…, MjSceneError> (with MjSceneError::InvalidViewport and MjSceneError::BufferTooSmall); it now returns Result<…, MjrContextError> (with MjrContextError::InvalidViewport and MjrContextError::BufferTooSmall).

MjModelError::InvalidIndex removed

  • MjModelError::InvalidIndex(usize, usize) has been removed from MjModelError. Code matching on this variant will fail to compile. Replace it with IndexOutOfBounds (see migration guide).

MjsTuple::set_objtype now takes ``&[MjtObj]`` and is fallible

  • MjsTuple::set_objtype now accepts &[MjtObj] instead of &[i32] and is a safe fn returning Result<(), MjEditError> instead of (). Out-of-range object types are rejected with MjEditError::InvalidParameter. See the migration guide.

MjsSensor::set_objtype / set_reftype are now fallible

  • MjsSensor::set_objtype and MjsSensor::set_reftype now return Result<(), MjEditError> instead of (), rejecting out-of-range object types with MjEditError::InvalidParameter. The builder counterparts with_objtype / with_reftype keep their signature but panic on a rejected value. See the migration guide.

MjsNumeric::set_size is now fallible

  • MjsNumeric::set_size now returns Result<(), MjEditError> instead of (), rejecting a negative size with MjEditError::InvalidParameter (a negative size triggers an out-of-bounds write in the model compiler). See the migration guide.

Some index/size vector setters are now ``unsafe``

  • MjsFlex::set_elemtexcoord, MjsSkin::set_face, MjsMesh::set_userfacetexcoord and MjsMesh::set_userfacenormal are now unsafe fn: each writes a value the model compiler or renderer later trusts as an unchecked array index, count, or memcpy length, and the correct constraint is cross-field (it cannot be checked from the setter alone). Each carries a # Safety section describing the caller’s obligation. See the migration guide.

MjData::history_mut is now an ``unsafe fn``

  • MjData::history_mut is now unsafe. The signature is otherwise unchanged; existing calls only need an unsafe block. The immutable history accessor remains safe. See the migration guide.

Mjs element handles and MjsDefault are no longer Send/Sync

  • The Mjs* element handles (MjsBody, MjsGeom, …) and MjsDefault are now !Send + !Sync; they previously carried a blanket unsafe impl Send/Sync. Each handle is only a raw pointer into one shared mjSpec/mjCModel arena, so moving or sharing one across threads was unsound (see Bug fixes). Code that did so will no longer compile — move the owning MjSpec instead, which remains Send (it is no longer Sync; see below).

MjData::add_contact is now an ``unsafe fn``

  • MjData::add_contact is now unsafe. It stores the caller-supplied MjContact verbatim, and MuJoCo later uses it without validation, so a malformed contact can cause undefined behavior (see Bug fixes). The signature is otherwise unchanged; existing calls only need an unsafe block.

MjData::reset_debug is now an ``unsafe fn``

  • MjData::reset_debug is now unsafe. The debug fill writes raw bytes into arrays whose accessors expose types with validity invariants (bool, fieldless enums), so reading them afterwards can be undefined behavior (see Bug fixes). The signature is otherwise unchanged; existing calls only need an unsafe block.

MjData::ray / ray_mesh now take &mut self

  • MjData::ray, MjData::ray_mesh, and MjData::try_ray_mesh now take &mut self (previously &self). Their bounding-volume traversal mutates data.bvh_active (see Bug fixes), so callers holding a shared &MjData need an exclusive borrow instead. ray_flex / ray_hfield are unaffected and remain &self; multi_ray already took &mut self.

MjvScene::find_selection now takes &mut MjData

  • MjvScene::find_selection now takes data: &mut MjData<M> (previously &MjData<M>). It calls mjv_select -> mj_ray, whose bounding-volume traversal mutates data.bvh_active (see Bug fixes), so an exclusive borrow is required. This mirrors MjvScene’s update (already &mut MjData) and the ray / ray_mesh change above.

MjSpec is no longer Sync

  • MjSpec is now Send but no longer Sync (see Bug fixes). Several &self methods – notably clone / try_clone – drive C-side writes to the spec, so sharing a &MjSpec across threads is unsound. Code that required MjSpec: Sync (for example placing a &MjSpec in a type that demands Sync) will no longer compile; transfer ownership instead, as MjSpec remains Send.

MjsTendon::wrap / wrap_mut no longer return Option

  • MjsTendon’s wrap and wrap_mut now return &MjsWrap / &mut MjsWrap (previously Option<&MjsWrap> / Option<&mut MjsWrap>) and panic if the index is out of bounds. The old signature documented None on an out-of-range index, but the underlying C mjs_getWrap aborts the process for such an index and never returns null, so the None arm was unreachable. Drop the .unwrap() on existing calls and ensure the index is in range (< wrap_num()), or use the new fallible try_wrap / try_wrap_mut.

Deprecations

  • Deprecated SpecItem::delete due to it relying on undefined behavior. Users are expected to use MjSpec::delete_element instead.

  • Deprecated MjsTendon’s try_wrap_site, try_wrap_geom, try_wrap_joint, and try_wrap_pulley — their MjEditError::AllocationFailed arm is unreachable and an allocation failure cannot be recovered soundly (MuJoCo aborts the process, or writes through the null pointer before returning). Use the panicking wrap_site / wrap_geom / wrap_joint / wrap_pulley instead. These methods may be undeprecated in the future if MuJoCo’s upstream C++ code is changed to return null recoverably.

Error handling

New error types:

  • MjrContextError: new error enum for MjrContext operations, with variants:

    • IndexOutOfBounds { id, len } — an index passed to a rendering-context operation is out of range (covers GPU asset uploads and aux buffer index checks).

    • InvalidViewport { width, height } — the viewport dimensions are invalid for pixel read-back.

    • BufferTooSmall { name, got, needed } — the caller-supplied buffer is too small to hold the read-back data.

New error variants in pre-existing enums:

  • MjViewerError: SignatureMismatch (model structure does not match the viewer’s passive model), IndexOutOfBounds { id, len } (asset ID out of range), ContextError(MjrContextError) (wraps a rendering-context error, e.g. from pixel read-back during screenshot capture).

  • RendererError: SignatureMismatch (model structure does not match the renderer’s current scene), ContextError(MjrContextError) (wraps a rendering-context error, e.g. an out-of-range asset ID).

  • MjEditError: InvalidParameter(String) (a value was rejected; the string describes the rejection). Returned by the new set_to_* actuator helpers (see New features and improvements) and by the now-validating setters MjsSensor::set_objtype / set_reftype, MjsTuple::set_objtype and MjsNumeric::set_size (see Breaking changes).

New features and improvements

Fallible tendon wrap accessors

  • Added MjsTendon::try_wrap and MjsTendon::try_wrap_mut, the fallible counterparts of wrap / wrap_mut. They return Result<&MjsWrap, MjEditError> and yield MjEditError::IndexOutOfBounds for an out-of-range index instead of panicking. A new MjEditError IndexOutOfBounds variant carries the offending index and length.

MjrContext GPU re-upload methods

Viewer GPU asset re-upload

The Rust viewer now supports re-uploading textures, meshes, and heightfields to the GPU at runtime, mirroring the behaviour of MuJoCo’s C++ Simulate viewer.

Renderer GPU asset re-upload

The Rust renderer now supports re-uploading textures, meshes, and heightfields to the GPU at runtime.

Model-editing and utility API additions

  • Added mju_sym2dense as a wrapper for MuJoCo’s sparse-to-dense conversion utility. It is an unsafe fn: the C routine indexes mat / colind / res from the caller-supplied sparse structure without bounds checks, and the only way to validate that structure would be to iterate the index arrays, so the consistency of rownnz / rowadr / colind is documented as a # Safety precondition instead.

  • Added model-editing convenience APIs:

    • timer for CTIMER values.

    • child and child_mut for named child lookup.

    • id for retrieving optional element IDs.

    • delete_element for deleting a spec element by its FFI pointer — the sound replacement for the deprecated SpecItem::delete.

  • Added the missing frame-finding methods to MjSpec: frame and frame_mut.

  • MjsActuator now exposes the full family of actuator-configuration helpers covering MuJoCo’s mjs_setToX C API: set_to_motor, set_to_position, set_to_int_velocity, set_to_velocity, set_to_damper, set_to_cylinder, set_to_muscle, set_to_adhesion, and set_to_dc_motor. Helpers whose C function takes nullable parameters take a dedicated Default-able config struct (PositionConfig, IntVelocityConfig, DcMotorConfig) in which the nullable parameters are Option fields. Each config can be built either with struct-update syntax or with chainable with_* builder methods (which take the inner value and wrap optionals in Some), so callers specify only the fields they need and unset optionals default to None. For example: actuator.set_to_dc_motor(DcMotorConfig::default().with_motorconst([1.0, 1.0]).with_resistance(1.0))?. Helpers whose parameters are all mandatory keep simple positional arguments (e.g. set_to_velocity(kv), set_to_cylinder(timeconst, bias, area, diameter), set_to_muscle(...)). Helpers that MuJoCo can reject return Result<(), MjEditError> (e.g. set_to_damper, set_to_muscle, set_to_dc_motor); those that cannot fail (set_to_motor, set_to_velocity, set_to_cylinder) return ().

New examples

  • Asset re-upload — demonstrates animated heightfield, texture, and mesh GPU re-uploads with active physics: a ball falls onto a rippling terrain and bounces within a smooth-step wall. Physics and rendering run on separate threads.

  • Renderer asset re-upload — demonstrates the same animated assets using the offscreen renderer: heightfield, texture, and mesh are mutated each frame and immediately re-uploaded before rendering to PNG frames.

Bug fixes

  • Fixed MjsTendon’s wrap / wrap_mut, which documented returning None for an out-of-bounds index but could never do so: the underlying C mjs_getWrap aborts the process for such an index and never returns null, so the None case was unreachable. The accessors now reject an out-of-range index in Rust — panicking, or returning MjEditError::IndexOutOfBounds via the new try_wrap / try_wrap_mut (see Breaking changes).

  • Fixed a model-editing potential undefined behavior in MjsBody, which occurred when yielded references of body’s items were not destroyed before the next entry to the iterator. This was problematic if individual references of yielded items were stored and later used — technically a undefined behavior due to reuse of the MjsBody inside the iterator. The MjsBody parent is now stored as a raw FFI pointer to prevent such aliasing.

  • Fixed an out-of-bounds read reachable from safe code in MjvCamera::frame: a fixed camera whose fixedcamid was outside the model’s camera range caused MuJoCo’s mjv_cameraFrame to read cam_xpos / cam_xmat out of bounds. frame now validates the id against the model and panics on an out-of-range fixed camera id instead.

  • Extended that same fixed-camera range check to MjvScene::update: an out-of-range fixedcamid no longer reaches mjv_cameraFrame (whose mjCAMERA_FIXED branch reads cam_xpos / cam_xmat before MuJoCo’s own range check). This also closes the same out-of-bounds read on the offscreen renderer and the viewer, which both update through this method.

  • Fixed an out-of-bounds read reachable from safe code in MjvScene::update (and update_with_catmask) when an active perturbation carried an out-of-range select body id. MuJoCo’s mjv_updateScene only guards select <= 0 (it has no upper-bound check) before indexing xpos / xmat / body_bvhnum by select, so a too-large id read out of bounds. update now validates select against the model’s nbody and panics on an out-of-range id.

  • Fixed an out-of-bounds read reachable from safe code in MjvPerturb::move_ when the public select field held an out-of-range body id. move_ now validates select against the model’s nbody and panics on an out-of-range id.

  • Closed an unsoundness in MjData::add_contact, which was safe but copied a caller-built MjContact into the data arena without validation. MuJoCo later uses the stored contact’s fields without bound checks (when building constraints and when reading forces), so a malformed contact could cause out-of-bounds access from safe code. add_contact is now an unsafe fn: the caller guarantees the contact is valid for the model. See the Breaking changes section and the migration guide.

  • Closed an unsoundness in MjData::reset_debug, which was safe but filled mjData arrays with raw debug_value bytes that safe accessors could then read back as invalid values for types with validity invariants (e.g. bool, fieldless enums) — undefined behavior for any debug_value other than 0 or 1. reset_debug is now an unsafe fn: the caller guarantees such accessors are not read before the next reset. See the Breaking changes section and the migration guide.

  • Fixed a thread-safety hole reachable from safe code in the model-editing handles. Every Mjs* element handle and MjsDefault carried a blanket unsafe impl Send/Sync, although each is only a raw pointer into one shared mjSpec/mjCModel arena. Together with the lending mutable iterators (which hand out several simultaneously-live &mut handles), safe code could move handles to different threads and call mutators such as SpecItem::set_name concurrently; mjs_setName reaches model-global state (mjCModel::CheckRepeat and the shared error buffer), so the calls raced on the one arena – undefined behavior. The handles (and MjsDefault) are now !Send + !Sync, so such a mutation can no longer cross a thread boundary; the owning MjSpec stays Send (it is now !Sync; see below).

  • Closed an unsoundness reachable from safe code in MjData::ray and MjData::ray_mesh, which took &self although their bounding-volume traversal (mju_rayTree) writes data.bvh_active when the model’s bvactive visualization flag is set. The MuJoCo C functions declare const mjData* yet mutate through it, so the &self signature let safe code drive shared-state mutation: with MjData<M>: Sync two threads could race on bvh_active, and a &[bool] borrowed from the safe bvh_active accessor could be mutated while still live. ray, ray_mesh, and try_ray_mesh now take &mut self. See the Breaking changes section and the migration guide.

  • Closed an unsoundness reachable from safe code in MjvScene::find_selection, which took a shared &MjData although it calls mjv_select -> mj_ray, whose bounding-volume traversal writes data.bvh_active (the same const mjData*-but-mutating path as ray above). The shared borrow let safe code drive that mutation: with MjData<M>: Sync two threads could race on bvh_active, and a &[bool] from the safe bvh_active accessor could be mutated while still live. find_selection now takes &mut MjData. See the Breaking changes section and the migration guide.

  • Closed an unsoundness reachable from safe code in MjSpec, which carried an unconditional unsafe impl Sync. clone / try_clone produce a faithful, independent copy, but the C++ copy constructor behind mj_copySpec is not strictly const on the source: for each actuator it clears two std::map keyframe-resolution caches (ForgetKeyframes). Those maps are empty for a normally-built spec, yet std::map::clear rewrites the tree header unconditionally, so two threads sharing a &MjSpec and cloning concurrently raced on those writes (no spec data is lost). MjSpec is now !Sync (it stays Send). See the Breaking changes section and the migration guide.

  • Closed an out-of-bounds write reachable from safe code in utility::mju_normalize when called with an empty slice. The wrapper now panics on an empty slice.

  • Closed an unsoundness reachable from safe code in MjsMesh::set_userfacenormal, which stored unvalidated face-normal indices the renderer later dereferences without a bound check. It is now an unsafe fn with a # Safety contract. See the Breaking changes section and the migration guide.

  • Closed a heap overflow reachable from safe code when compiling an MjSpec whose texture has a builtin pattern and nchannel < 3 (MuJoCo’s builtin generators write more bytes than such a buffer holds). MjSpec::compile now rejects this configuration with MjEditError::CompileFailed.

  • Closed an out-of-bounds read reachable from safe code in MjData::history_mut, which exposed a cursor the sensor/control read path trusts as an array index. It is now an unsafe fn; the immutable history accessor stays safe. See the Breaking changes section and the migration guide.

Other changes

  • Updated bool-typed wrappers to use MjtBool where MuJoCo uses mjtBool. This is not a Rust API breaking change because MjtBool is a type alias to mjtBool, and mjtBool is bool by default. Related struct fields and function parameters now avoid force-casting and use MuJoCo’s bool mapping directly. Fields that are MjtByte in MuJoCo remain byte-backed and are not part of the MjtBool migration.

  • Added missing mjt* type aliases for recently surfaced MuJoCo enums: MjtMeshBuiltin, MjtCTimer.

  • Added auxiliary exposed aliases: MjPreContact, MjfCollision, MjpResourceProvider.

  • SpecItem::element_pointer and SpecItem::element_mut_pointer are no longer marked unsafe.

  • MjData::set_state is no longer marked unsafe. MuJoCo 3.9.0 changed eq_active to mjtBool (C bool), so writes through mjSTATE_EQ_ACTIVE are booleanized on assignment and reading eq_active() afterwards can no longer cause undefined behavior. Existing unsafe { data.set_state(...) } call sites keep compiling and only produce an unused_unsafe warning.

4.0.1 (MuJoCo 3.8.0)

Bug fixes

  • When using the auto-download-mujoco feature, the build script now checks for existence of the library before downloading it. When the library exists, its download and extraction are skipped.

4.0.0 (MuJoCo 3.8.0)

Breaking changes

MuJoCo upgraded to 3.8.0

  • MuJoCo-rs 4.0.0 is based on MuJoCo 3.8.0, upgraded from 3.6.0. MuJoCo 3.8.0 introduces breaking changes to its C API (renamed and removed constants, new struct fields). Code relying on specific MuJoCo enum values or internal constants may need to be updated.

MjData::geom_distance now requires mutable access

Model-editing wrappers now expose polynomial stiffness and damping coefficients

  • MjsJoint: stiffness and damping now expose coefficient arrays instead of scalar f64 values.

  • MjsTendon: stiffness and damping now expose coefficient arrays instead of scalar f64 values.

MjsFlex::vertcollide removed

  • MjsFlex::vertcollide is no longer available. MuJoCo 3.7.0 removed the upstream mjsFlex::vertcollide field, so the wrapper no longer exposes it.

MjData::model_mut is now unsafe

  • Because the MjData::model_mut allows full model replacement in unsafe ways, it is now marked unsafe.

MjViewer::add_ui_callback closure now receives Box<MjModel>

  • The passive model stored inside the viewer changed from Arc<MjModel> to Box<MjModel>. The closure passed to MjViewer::add_ui_callback now receives &mut MjData<Box<MjModel>>.

Error handling

New features and improvements

  • Updated FFI bindings and wrappers to match MuJoCo 3.8.0 headers.

  • MjModel now exposes the new joint, dof, tendon, and actuator coefficient arrays and actuator-linkage fields added in newer MuJoCo releases. MjsActuator now exposes its damping coefficient array and armature field.

  • MjtLRMode is now exported for typed access to MjLROpt.mode.

  • In light of MjData::model_mut becoming unsafe, six new methods are created to allow safe access to model fields:

    Immutable getters:

    Mutable accessors:

MjViewer model parameter synchronization

Viewer camera tracking modal

  • The viewer UI now features an interactive camera tracking modal for selecting bodies to track. Select “Track” in the camera panel to open a modal with a scrollable grid of body buttons.

Other changes

  • Removed the stl_mesh example. It existed in MuJoCo-rs 3.0.1 due to MuJoCo 3.6.0 requiring a plugin to load STL meshes. MuJoCo 3.7.0+ fixes this, thus the example is no longer required.

  • Reduced default width of the viewer’s UI side panel to 200.0.

  • Physics options (integrator, cone, jacobian, solver) in the viewer UI display a yellow warning when model parameters (opt/vis/stat) are out of sync with their initial values, indicating that parameters have been modified in the viewer.

3.0.1 (MuJoCo 3.6.0)

New features and improvements

3.0.0 (MuJoCo 3.6.0)

Breaking changes

Methods now return Result instead of panicking

  • The following methods previously returned () and panicked on error. They now return Result directly:

    • MjData: reset_keyframe, set_state, copy_visual_to, copy_to

    • MjrContext: read_pixels

    • MjvFigure: push, set_at

    • MjRenderer: set_font_scale now returns Result<(), RendererError>; with_font_scale now returns Result<Self, RendererError>.

    Callers must now handle the Result (e.g. append .unwrap() or ?).

Default features changed

  • viewer, viewer-ui, renderer, and renderer-winit-fallback are no longer enabled by default. Enable them explicitly when needed (e.g. cargo add mujoco-rs --features "viewer-ui renderer-winit-fallback").

MjvScene is no longer generic

  • MjvScene is no longer generic over M. It is now a plain struct MjvScene without a type parameter.

    • MjvScene::new now takes model: M where M: Deref<Target = MjModel> (accepts Arc<MjModel>, &MjModel, etc.).

    • MjvScene::update, MjvScene::update_with_catmask, and MjvScene::find_selection now take a generic <M> parameter on the method rather than on the struct. They panic if data was created from a different model (signature mismatch).

    • MjvScene exposes a new signature() -> u64 method for the model signature the scene was built for.

    • MjvPerturb::start / move_ and MjvCamera::move_ now take &MjvScene without a type parameter.

    • MjvPerturb::start and MjvPerturb::apply no longer take a model: &MjModel parameter (the model is now obtained internally via data.model()).

    • vis_common::sync_geoms is now non-generic.

    • MjvScene now derives Send + Sync unconditionally.

MjRenderer is no longer generic

MjViewer, MjViewerBuilder, ViewerSharedState are no longer generic

  • MjViewer, MjViewerBuilder, and ViewerSharedState are no longer generic over M. Remove the <M> type parameter from all usage sites.

    • The viewer stores a passive Arc<MjModel> internally (analogous to the existing passive MjData copy). sync_data, sync_data_full, and build_passive retain <M: Deref<Target = MjModel>> as method-level generics, so the call sites are unchanged.

    • MjViewerBuilder::build_passive now accepts any M: Deref<Target = MjModel> (e.g. &MjModel, Arc<MjModel>, Rc<MjModel>).

    • The closure passed to MjViewer::add_ui_callback now receives &mut MjData<Arc<MjModel>> instead of &mut MjData<M>. Update callback signatures accordingly.

Viewer and renderer sync model automatically

Separate renderer sync and render

MuJoCo upgrade

  • Updated MuJoCo from 3.3.7 to 3.6.0. C FFI bindings and all generated wrappers have been regenerated. See Installation for the matching release.

Error handling: io::Error replaced with typed enums

All methods that previously returned io::Error or bare types now return typed Result variants. Six new error enums have been added to the error module (all #[non_exhaustive], re-exported from the prelude): MjDataError, MjSceneError, MjEditError, MjModelError, MjVfsError, GlInitError. Pre-existing RendererError and MjViewerError gained new variants. See Error handling below for the full method list.

Type and signature changes

  • get_mujoco_version has been renamed to ~~mujoco_rs::mujoco_version. The old name is deprecated.

  • MjData::get_state has been renamed to MjData::state. The old name is deprecated.

  • MjvFigure::new has been renamed to MjvFigure::new_boxed. The old name is deprecated.

  • MjData::copy_to and MjData::copy_visual_to now accept a destination of a different model type: destination: &mut MjData<N> where N: Deref<Target = MjModel>. Previously the source and destination had to share the same M.

  • set_name now returns Result<(), MjEditError> instead of (). Append ? to call sites. with_name still returns &mut Self but now panics on duplicate names.

  • SpecItem is now a sealed trait. External implementations are no longer permitted.

  • MjsOrientation::switch_quat no longer has a type parameter. Replace switch_quat::<[f64; 4]>() with switch_quat().

  • MjModel now implements the standard Clone trait. The old clone() returning Option<MjModel> has been removed; use model.clone() (panics on failure) or the new try_clone() for a fallible version.

  • MjModel::save(filename, buffer) removed. Use the new save_to_file and save_to_buffer methods.

  • MjModel::size returns usize (was i32).

  • MjModel::save_last_xml now accepts AsRef<Path> (was &str) and returns Err(MjModelError::InvalidUtf8Path) for non-UTF-8 paths (previously impossible to pass since &str is always UTF-8).

  • MjViewerCpp<M> (cpp-viewer feature) is no longer generic over M. Remove the type parameter from all usage sites. The launch_passive associated function retains <M: Deref<Target = MjModel>> as a method-level generic.

  • MjModel::state_size returns usize (was i32).

  • MjModel::name_to_id returns Option<usize> (was i32; -1 is now None).

  • MjModel::totalmass renamed from get_totalmass.

  • MjrContext::set_buffer renamed from mjr_set_buffer.

  • MjData:

  • MjModel: MjModel::id_to_name: id takes usize (was i32).

  • mju_ray_geom gained normal_out: Option<&mut [MjtNum; 3]>.

  • MjvPerturb::update_local_pos takes selection_xyz by &[MjtNum; 3] (was by value).

  • MjvPerturb::move_ no longer takes a separate model parameter; data changed from &mut MjData<M> to &MjData<M>.

  • MjvScene::find_selection returns SceneSelection (was a 5-tuple (i32, i32, i32, i32, [MjtNum; 3])). SceneSelection fields (body_id, geom_id, flex_id, skin_id) are Option<usize> (None = no selection). Use if let Some(id) = sel.body_id instead of if sel.body_id >= 0.

  • MjvCamera: new_fixed, new_tracking, track, fix now take usize (was u32). Remove as u32 casts at call sites.

  • TryFrom<i32> for MjtCamera now uses MjSceneError as its error type (was ()). Replace Err(()) matches with Err(MjSceneError::InvalidCameraType(_)).

  • MjvScene::update: the pertub parameter has been renamed to perturb (typo fix).

  • MjvGeom::set_label now returns Result<(), MjSceneError>.

  • MjViewer::render now returns Result<(), MjViewerError>.

  • MjModel: print / print_formatted now accept AsRef<Path> and return Result<(), MjModelError>.

  • MjModel: tuple_objtype accessor now returns &[MjtObj] instead of &[i32].

  • MjCameraModelView / MjCameraModelViewMut: camera field projection (type MjtProjection) replaces the old boolean orthographic field, matching MuJoCo’s cam_projection rename.

  • MjsTendon: limited and actfrclimited are now MjtLimited (tri-state: FALSE / TRUE / AUTO) instead of bool, matching the C mjtLimited semantics.

  • MjsTexture::set_data now requires T: bytemuck::NoUninit.

New unsafe requirements

  • MjrContext::new is now unsafe fn. A valid OpenGL context must be current on the calling thread.

  • MjData::set_state is now unsafe fn and returns Result<(), MjDataError>. When spec includes mjSTATE_EQ_ACTIVE, MuJoCo writes raw f64 bytes into the eq_active array without booleanization; calling eq_active() afterwards is UB until the state is re-validated (e.g. via mj_forward / mj_step).

  • MjViewerCpp::render is now unsafe fn, must be called from the main thread, no longer accepts update_timer, and returns Result<(), &'static str>.

  • MjViewerCpp::launch_passive is now unsafe fn. Model and data must remain alive and at a stable address for the lifetime of the viewer.

Thread-safety bound tightening

  • MjData<M>: Send / Sync now require M: Send / M: Sync. Previously the bounds were unconditional, making MjData<Rc<MjModel>> incorrectly Send + Sync. (MjvScene is now non-generic and unconditionally Send + Sync.)

  • MjViewerCpp::launch_passive now requires M: Send + Sync (previously only Deref<Target = MjModel> + Clone).

Mutable accessor restrictions

*_mut() methods on MjModel, MjData, and MjvScene array fields are now unsafe fn where unrestricted writes can corrupt MuJoCo’s internal state. Two categories of fields are affected:

  • Structural invariants — topology, address, and engine-computed arrays that must not be changed at runtime: 199 fields on MjModel, 43 on MjData, 12 on MjvScene.

  • Companion-index fields — type/mode fields whose values control which array a companion index (*id, *adr) indexes into; writing inconsistent values causes out-of-bounds access: 17 fields on MjModel (jnt_type, actuator_trntype, actuator_dyntype, eq_type, eq_objtype, wrap_type, wrap_prm, sensor_type, sensor_objtype, sensor_reftype, skin_matid, tendon_matid, tendon_treeid, body_plugin, actuator_plugin, geom_plugin, sensor_plugin), 4 on MjData (efc_type, iefc_type, tree_asleep, wrap_obj).

  • Null-terminated string buffers — concatenated c_char arrays where each entry is null-terminated; removing a '\\0' byte allows MuJoCo’s C string functions (and CStr::from_ptr) to read past the buffer boundary: 4 fields on MjModel (names, plugin_attr, text_data, paths).

info_with_view! generated ViewMut types expose the companion-index fields as PointerViewUnsafeMut; mutation requires as_mut_slice inside an unsafe block. See migration for full field lists and before/after examples.

Removed APIs

  • MjViewerCpp::__raw() removed (exposed internal C++ binding pointers).

  • MjvFigure: figure renamed to draw.

  • MjViewer::sync removed (deprecated in 2.2.0; use sync_data + render).

  • MjViewer::user_scn / user_scn_mut removed (deprecated in 1.3.0).

  • MjViewer::user_scene / user_scene_mut removed (deprecated in 2.2.0; access via ViewerSharedState::user_scene / user_scene_mut through viewer.state()).

  • Removed deprecated methods:

    Type

    Removed

    Replacement

    MjData

    warning_stats

    MjData::warning

    MjData

    timer_stats

    MjData::timer

    MjData

    Type aliases MjJointInfo, MjJointView, MjJointViewMut, MjGeomInfo, MjGeomView, MjGeomViewMut, MjActuatorInfo, MjActuatorView, MjActuatorViewMut

    Use the current MjJointData*, MjGeomData*, MjActuatorData* types.

    MjJointDataViewMut

    reset

    MjJointDataViewMut::zero

    MjModel

    name2id

    MjModel::name_to_id

    MjvCamera

    new

    new_free, new_fixed, new_tracking, or new_user

MjTendonDataInfo field removal

  • MjTendonDataInfo no longer exposes J_rownnz, J_rowadr, or J_colind; these fields moved upstream to mjModel in MuJoCo 3.6.0 and are now accessible via MjTendonModelInfo (i.e. model.tendon()).

Error handling

New error types in error (all #[non_exhaustive], re-exported from the prelude):

Methods returning Result types

Type

Methods

Error type

MjModel

from_xml, from_xml_vfs, from_xml_string, from_buffer, save_last_xml, save_to_file new, save_to_buffer new, print, print_formatted, try_extract_state new, try_extract_state_into new

MjModelError

MjData

add_contact, constraint_update, try_jac new, try_jac_body new, try_jac_body_com new, try_jac_subtree_com new, try_jac_geom new, try_jac_site new, try_angmom_mat new, try_object_velocity new, try_object_acceleration new, try_geom_distance new, try_local_to_global new, try_multi_ray new, print, print_formatted, init_ctrl_history new, init_sensor_history new, try_read_ctrl new, read_sensor_into new, try_read_sensor_fixed new, try_read_sensor new, try_swap_model new, reset_keyframe, copy_state_from_data new, apply_ft new, set_state, copy_visual_to, copy_to

MjDataError

MjVfs

add_file new, add_file_from new, add_from_file deprecated, add_from_buffer, delete_file

MjVfsError

MjSpec

from_xml, from_xml_vfs, from_xml_string, compile, save_xml, save_xml_string, try_add_default new, from_parse new, from_parse_vfs new

MjEditError

MjvScene / MjvGeom / MjvFigure / MjrContext

try_create_geom new, set_label, add_aux, set_aux, push, set_at, read_pixels

MjSceneError

MjRenderer

try_rgb new, try_depth new, save_rgb, save_depth, save_depth_raw

RendererError

MjViewer

render

MjViewerError

Methods marked new are new in 3.0.0; the rest existed in 2.x and previously returned bare types or io::Error.

New error variants in pre-existing enums:

  • RendererError: RgbDisabled, DepthDisabled, DimensionMismatch, ZeroDimension, IoError, SceneError, GlInitFailed.

  • MjViewerError: PainterInitError, GlInitFailed, SceneError.

The six new enums (all #[non_exhaustive]) have the following variants:

  • MjModelError: InvalidUtf8Path, LoadFailed, SaveFailed, AllocationFailed, StateSliceLengthMismatch, SpecNotSubset, BufferTooSmall, SignatureMismatch, VfsError.

  • MjDataError: IndexOutOfBounds, UnsupportedObjectType, AllocationFailed, BufferTooSmall, LengthMismatch, SignatureMismatch, NoHistoryBuffer, ContactBufferFull, InvalidUtf8Path.

  • MjVfsError: AlreadyExists, LoadFailed, NotFound, InvalidUtf8Path, BufferTooLarge, Unknown.

  • MjEditError: AllocationFailed, InvalidUtf8Path, ParseFailed, CompileFailed, SaveFailed, NotFound, AlreadyExists, UnsupportedOperation, DeleteFailed, XmlBufferTooSmall — returned by save_xml_string when the supplied buffer is too small; the required_size field carries the snprintf-style byte count (excluding NUL), so retry with required_size + 1.

  • MjSceneError: SceneFull, LabelTooLong, InvalidAuxBufferIndex, InvalidViewport, BufferTooSmall, FigureBufferFull, FigureIndexOutOfBounds, InvalidPlotIndex, NonAsciiLabel, InvalidCameraType.

  • GlInitError (requires viewer or renderer-winit-fallback feature): DisplayBuild, NoWindow, WindowHandle, ContextCreation, SurfaceAttributes, SurfaceCreation, MakeCurrent.

  • New try_ methods paired with existing infallible counterparts (infallible variants now delegate to try_.``expect()``):

    • MjData: try_new, try_clone, try_read_state_into, try_swap_model, try_read_ctrl, try_read_sensor_fixed, try_read_sensor, try_jac, try_jac_body, try_jac_body_com, try_jac_subtree_com, try_jac_geom, try_jac_site, try_angmom_mat, try_object_velocity, try_object_acceleration, try_geom_distance, try_local_to_global, try_multi_ray

    • MjModel: try_clone, try_make_data, try_extract_state, try_extract_state_into

    • MjSpec / MjsTendon / MjsBody: try_new, try_clone, try_add_frame, try_wrap_site, try_wrap_geom, try_wrap_joint, try_wrap_pulley, try_add_default, macro-generated try_add_*

    • MjRenderer: try_rgb, try_depth

    • MjvScene: try_create_geom

    • MjData: try_ray_flex new, try_ray_hfield new, try_ray_mesh new

    • MjvFigure: try_full new, try_empty new, try_pop_front new, try_pop_back new, cut_front new, cut_end new

  • New MjData methods that return Result directly (no separate try_ variant): copy_state_from_data, apply_ft.

New features and improvements

  • MjRenderer: PNG compression is now configurable via MjRendererBuilder::png_compression (builder) and MjRenderer::set_png_compression (runtime setter). Defaults to png::Compression::NoCompression. Affects MjRenderer::save_rgb and MjRenderer::save_depth. The png crate is re-exported as mujoco_rs::renderer::png for naming png::Compression.

  • MjModel: extract_state / extract_state_into, try_make_data.

  • MjData: swap_model new (validates model signature), model_mut new (-> &mut MjModel, available when M: DerefMut), forward_kinematics, apply_ft, copy_state_from_data, ray_flex / ray_mesh / ray_hfield, init_ctrl_history / init_sensor_history, read_ctrl / read_sensor / read_sensor_into / read_sensor_fixed, model_clone new (-> M, available when M: Clone).

  • MjSpec: from_parse / from_parse_vfs. from_parse, from_parse_vfs, and save_xml now accept AsRef<Path> (was &str); returns Err(MjEditError::InvalidUtf8Path) for non-UTF-8 paths. MjSpec and SpecItem are also re-exported from mujoco_rs::wrappers.

  • MjsTendon: wrap / wrap_mut / wrap_num.

  • MjsWrap: coef, divisor, side_site, side_site_mut.

  • MjVfs: add_file / add_file_from replace add_from_file (which is now deprecated). add_from_buffer and delete_file now accept AsRef<Path>; returns Err(MjVfsError::InvalidUtf8Path) for non-UTF-8 paths.

  • MjvScene: update_with_catmask (exposes catmask filter parameter).

  • MjvCamera: frame, frustum.

  • New SceneSelection struct (returned by find_selection); implements Default.

  • The vis_common module is now public (requires viewer or renderer feature), exposing sync_geoms, write_png, flip_image_vertically.

  • MjModel and MjData views expose additional fields from MuJoCo 3.6.0. New MjModel view types added for exclude, mesh, and skin. dof_bodyid and dof_treeid are now exposed in per-dof joint model view types (the fields existed in MuJoCo 3.3.7 but were not previously accessible via per-object view types).

  • MjsMaterial: MjsMaterial::set_texture / MjsMaterial::with_texture set a texture name by MjtTextureRole, correctly targeting the pre-sized slot the MuJoCo renderer reads (e.g. mjTEXROLE_RGB). The existing set_textures / append_textures methods are retained but should generally be avoided for role-indexed vectors.

  • MjsTexture: MjsTexture::set_cubefile / MjsTexture::with_cubefile set a cube-map face file by MjtCubeFace. The existing set_cubefiles / append_cubefiles methods are retained.

  • info_with_view! structs now have try_view / try_view_mut methods.

  • Trait additions: Clone for MjSpec; Default for MjVfs, MjSpec, MjOption, MjRendererBuilder, MjViewerBuilder; Send + Sync for MjVfs; FusedIterator for all model-editing iterators; Eq for PointerView / PointerViewMut; Drop for MjRenderer.

  • PointerView::new, PointerViewMut::new, MjrRectangle::new are now const fn.

  • MjModel and MjData now use NonNull<mjModel> / NonNull<mjData> internally, encoding the non-null invariant at the type level and enabling niche optimisation for Option<MjModel> / Option<MjData>.

  • MjData: contacts() is deprecated; use contact() instead.

MjViewer

  • Added support for the Depth rendering flag (mjRND_DEPTH).

  • Added a “Print camera” button that prints the camera as an MJCF element.

  • Added a “Screenshot” panel with: a Screenshot button (timestamped PNG), a Viewport only checkbox (captures before UI is drawn), and a Depth checkbox (16-bit grayscale depth PNG).

Bug fixes

  • MjvCamera new_fixed / new_tracking and MjvScene new: debug_assert! guards on user-supplied camera_id, tracking_id, and max_geom (checking they fit in i32) were silently skipped in release builds. Changed to assert! so the precondition is always enforced. The # Panics documentation now correctly states panics occur in all builds, not only debug builds.

  • MjModel: MjModel::save_last_xml: a _ => unreachable!() arm would panic if MuJoCo ever returned a value other than 0 or 1; changed to _ => Err(MjModelError::SaveFailed(...)) so any unexpected return code is surfaced as an error instead of aborting the process.

  • mju_is_bad: now tests != 0 instead of == 1.

  • MjRenderer:

    • save_depth: guards against division by zero when the depth range is zero.

    • rgb, rgb_flat, save_rgb, save_depth, save_depth_raw: images were output vertically flipped (OpenGL reads bottom-up); now corrected.

    • MjRendererBuilder: font_scale setting was silently ignored during build(); now applied.

  • PointerView and PointerViewMut PartialEq now compares both pointer and length (previously pointer only).

  • MjModel: key_mpos and key_mquat array accessors were missing; added.

  • MjViewer: mouse normalization now uses inner_size() instead of outer_size(), fixing cursor mapping with window decorations.

  • MjViewer now implements Drop to make the GL context current before cleanup (prevents resource leaks on some platforms).

  • MjViewerCpp::sync:

    • FFI declaration used bool for state_only (should be c_int); fixed.

    • Could be called after the viewer window was closed, causing UB; now returns early if not running.

  • MjViewerCpp::launch_passive: asserts the C++ simulate handle is non-null after allocation.

  • MjvPerturb::update_local_pos: now uses bounds-checked slice indexing instead of raw pointer arithmetic.

  • MjrContext: set_aux now has a mjNAUX-based index bounds check.

  • Fixed potential UB when parsing null string pointers from MuJoCo C++ wrappers.

  • Added strict runtime signature check to view creation (panics with model signature mismatch on mismatch).

  • ViewerSharedState: on model change, ViewerSharedState::sync_data / ViewerSharedState::sync_data_full no longer spuriously write back the new model’s default pose (qpos0) to the incoming data. Previously, models with free joints, ball joints, or joints with a non-zero ref attribute would have their simulation state silently reset to the default pose on the first sync after a model switch. Active perturbations are also cleared on model change to prevent stale body-index references from the previous model being applied.

Other changes

2.3.5 (MuJoCo 3.3.7)

  • Fixed #161: actuator views could crash when a non-muscle actuator followed a muscle actuator.

  • Internal: minor performance improvement in sync_geoms (early exit when no geoms are added).

2.3.4 (MuJoCo 3.3.7)

  • 3D viewer bug fixes and usability improvements:

    • Keyboard events are now ignored whenever an egui input widget has focus.

    • Fixed a logic error where pressing an unhandled mouse button would drop subsequent events.

    • Ctrl+C no longer toggles camera visualization (reserved for copy).

2.3.3 (MuJoCo 3.3.7)

  • Fixed #144: the viewer caused high stack usage, potentially problematic on Windows (1M stack limit).

2.3.2 (MuJoCo 3.3.7)

  • Small performance improvement by removing mutex contention during scene render.

2.3.1 (MuJoCo 3.3.7)

  • Bug fixes:

    • Added a NULL pointer check to MjData; allocation failure now panics instead of accepting a NULL pointer.

    • Fixed #139: MjrContext::read_pixels no longer allows buffers smaller than the viewport.

    • Fixed potential viewer crashes when the display is disconnected or reconfigured.

2.3.0 (MuJoCo 3.3.7)

  • 3D viewer:

    • Added add_ui_callback_detached for passive UI widgets.

    • Added with_ui_egui_ctx for scoped egui context access.

    • Replaced panicking mutex handling with automatic unpoison logic.

    • Performance optimizations.

  • MjModel and MjSpec: version check against the MuJoCo shared library on instantiation; panics on mismatch.

2.2.2 (MuJoCo 3.3.7)

2.2.1 (MuJoCo 3.3.7)

  • Fixed #119: borrow tracker tracked the wrong lifetime.

  • Reset more OpenGL state after egui draw (figures now render correctly).

2.2.0 (MuJoCo 3.3.7)

  • 3D renderer now uses EGL on Linux by default (true offscreen rendering). New feature flag renderer-winit-fallback for compatibility (enabled by default).

  • 3D viewer:

    • add_ui_callback for custom egui UI widgets.

    • Info menu (F2): smoothed FPS, simulation time, used memory.

    • Realtime warning (F4): shows realtime factor when sync time deviates ≥ 2%.

    • Separate sync and render: sync_data (deprecates sync) + render.

    • Added ViewerSharedState for multi-threaded viewer/simulation sync.

  • New MjData methods: copy_visual_to, copy_to, set_state, get_state, read_state_into.

  • New example: rust_viewer_threaded.

  • Deprecated: user_scene() / user_scene_mut() (replaced by ViewerSharedState::user_scene / user_scene_mut).

2.1.0 / 2.1.1 (MuJoCo 3.3.7)

  • Option to automatically download MuJoCo (auto-download-mujoco feature).

  • pkg-config support for Linux and macOS.

  • Optional egui UI for MjViewer (enabled by default).

2.0.1 (MuJoCo 3.3.7)

  • Fixed the renderer feature not enabling all needed crates.

2.0.0 (MuJoCo 3.3.7)

  • Breaking changes:

    • Updated MuJoCo to 3.3.7.

    • Model editing: items no longer wrapped (just aliased types); attributes made private; immutable iterators added; _mut() methods added.

    • Viewer / renderer: switched from GLFW to Winit + Glutin.

    • Wrapper of MuJoCo’s C++ 3D viewer: moved to mujoco_rs::cpp_viewer; build target changed to glfw libmujoco_simulate.

    • MjData and related types now generic over model handle (Deref<Target = MjModel>).

    • Various c_inti32 / bool type replacements.

    • Removed mujoco_rs::wrappers::mj_interface.

  • Other: additional getters / setters / array slice methods for MjData, MjModel, MjvScene.

1.5.0 (MuJoCo 3.3.5)

1.4.2 (MuJoCo 3.3.5)

  • Fixed segfault when model specification is invalid (#65).

1.4.1 (MuJoCo 3.3.5)

  • Added missing MjSpec named accessors: geom, site, camera, light.

1.4.0 (MuJoCo 3.3.5)

  • MjModel: added views for key, tuple, texture, site, pair, numeric, material, light, hfield, equality.

  • Model editing support added (mujoco_rs::wrappers::mj_editing module).

  • MjRenderer / MjRendererBuilder improvements.

  • MjViewer: new key bindings and visualization toggles; visual-geom headroom increased from 100 to 2000.

  • fix added.

1.3.0 (MuJoCo 3.3.5)

  • Added mujoco_rs::renderer module with MjRenderer.

  • Deprecated MjvCamera::new(); replaced by new_free, new_fixed, new_tracking, new_user.

  • Added MjData::maxuse_stack, maxuse_threadstack, warning_stats, timer_stats, time, energy.

  • Added MjModel::signature, opt / opt_mut, vis / vis_mut, stat / stat_mut.

  • Added joint view attributes: qfrc_spring, qfrc_damper, qfrc_gravcomp, qfrc_fluid.

1.2.0 (MuJoCo 3.3.5)

  • Function wrappers for utility and derivative functions (mujoco_rs::wrappers::fun).

  • Completed virtual file system wrapper: add_from_file, delete_file, from_xml_vfs.

1.1.0 (MuJoCo 3.3.5)

1.0.1 (MuJoCo 3.3.5)

  • Bug fixes in Drop implementations.

1.0.0 (MuJoCo 3.3.5)

  • Breaking: all ffi_mut() methods now require unsafe.

  • Viewer: help overlay (F1), user scene, mouse perturbation.

0.4.3 (MuJoCo 3.3.5)

  • Removed unnecessary header files (smaller crate size).

0.4.2 (MuJoCo 3.3.5)

  • Renamed env vars: MUJOCO_DYNAMIC_LINK_LIBMUJOCO_DYNAMIC_LINK_DIR, MUJOCO_STATIC_LINK_LIBMUJOCO_STATIC_LINK_DIR.

0.4.1 (MuJoCo 3.3.5)

  • Fixed event handling.

0.4.0 (MuJoCo 3.3.5)

  • Renamed package to mujoco-rs.

0.3.0 (MuJoCo 3.3.5)

  • Initial public release.