Merge branch 'google-deepmind:main' into main

This commit is contained in:
Abhishek Joshi
2024-05-01 16:27:16 -05:00
committed by GitHub
143 changed files with 16311 additions and 6848 deletions
+1 -1
View File
@@ -250,7 +250,7 @@ jobs:
shell: bash
run:
source ${{ matrix.tmpdir }}/venv/bin/activate &&
pytest -n auto -v --pyargs mujoco.mjx
pytest -n auto -v -k 'not IntegrationTest' --pyargs mujoco.mjx
- name: Notify team chat
shell: bash
env:
+1 -1
View File
@@ -28,7 +28,7 @@ set(MSVC_INCREMENTAL_DEFAULT ON)
project(
mujoco
VERSION 3.1.4
VERSION 3.1.5
DESCRIPTION "MuJoCo Physics Simulator"
HOMEPAGE_URL "https://mujoco.org"
)
+4 -4
View File
@@ -1,6 +1,6 @@
1 VERSIONINFO
FILEVERSION 3,1,4,0
PRODUCTVERSION 3,1,4,0
FILEVERSION 3,1,5,0
PRODUCTVERSION 3,1,5,0
FILEOS 0x4
FILETYPE 0x1
{
@@ -9,9 +9,9 @@ FILETYPE 0x1
BLOCK "040904b0"
{
VALUE "ProductName", "MuJoCo"
VALUE "ProductVersion", "3.1.4"
VALUE "ProductVersion", "3.1.5"
VALUE "FileDescription", "MuJoCo"
VALUE "FileVersion", "3.1.4"
VALUE "FileVersion", "3.1.5"
VALUE "InternalName", "mujoco.dll"
VALUE "OriginalFilename", "mujoco.dll"
VALUE "CompanyName", "Google DeepMind"
+4 -4
View File
@@ -1,8 +1,8 @@
MUJOCO ICON "mujoco.ico"
1 VERSIONINFO
FILEVERSION 3,1,4,0
PRODUCTVERSION 3,1,4,0
FILEVERSION 3,1,5,0
PRODUCTVERSION 3,1,5,0
FILEOS 0x4
FILETYPE 0x1
{
@@ -11,9 +11,9 @@ FILETYPE 0x1
BLOCK "040904b0"
{
VALUE "ProductName", "MuJoCo"
VALUE "ProductVersion", "3.1.4"
VALUE "ProductVersion", "3.1.5"
VALUE "FileDescription", "MuJoCo"
VALUE "FileVersion", "3.1.4"
VALUE "FileVersion", "3.1.5"
VALUE "InternalName", "simulate.exe"
VALUE "OriginalFilename", "simulate.exe"
VALUE "CompanyName", "Google DeepMind"
+1 -1
View File
@@ -522,7 +522,7 @@ shown in the table below. Their names are in the format ``mjKEY_XXX``. They corr
- Maximum number of UI rectangles.
Defined in `mjui.h <https://github.com/google-deepmind/mujoco/blob/main/include/mujoco/mjui.h>`_.
* - ``mjVERSION_HEADER``
- 314
- 315
- The version of the MuJoCo headers; changes with every release. This is an integer equal to 100x the software
version, so 210 corresponds to version 2.1. Defined in mujoco.h. The API function :ref:`mj_version` returns a
number with the same meaning but for the compiled library.
+10
View File
@@ -2840,6 +2840,16 @@ mju_quatZ2Vec
Construct quaternion performing rotation from z-axis to given vector.
.. _mju_euler2Quat:
mju_euler2Quat
~~~~~~~~~~~~~~
.. mujoco-include:: mju_euler2Quat
Convert sequence of Euler angles (radians) to quaternion.
seq[0,1,2] must be in 'xyzXYZ', lower/upper-case mean intrinsic/extrinsic rotations.
.. _Poses:
Poses
+977 -876
View File
File diff suppressed because it is too large Load Diff
+125 -121
View File
@@ -7,6 +7,42 @@
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| mujoco |br| |L| | | .. table:: |
| :ref:`option | \* | :class: mjcf-attributes |
| <option>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`timestep<option-timestep>` | :ref:`apirate<option-apirate>` | :ref:`impratio<option-impratio>` | :ref:`tolerance<option-tolerance>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`ls_tolerance<option-ls_tolerance>` | :ref:`noslip_tolerance<option-noslip_tolerance>` | :ref:`mpr_tolerance<option-mpr_tolerance>` | :ref:`gravity<option-gravity>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`wind<option-wind>` | :ref:`magnetic<option-magnetic>` | :ref:`density<option-density>` | :ref:`viscosity<option-viscosity>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`o_margin<option-o_margin>` | :ref:`o_solref<option-o_solref>` | :ref:`o_solimp<option-o_solimp>` | :ref:`o_friction<option-o_friction>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`integrator<option-integrator>` | :ref:`cone<option-cone>` | :ref:`jacobian<option-jacobian>` | :ref:`solver<option-solver>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`iterations<option-iterations>` | :ref:`ls_iterations<option-ls_iterations>` | :ref:`noslip_iterations<option-noslip_iterations>` | :ref:`mpr_iterations<option-mpr_iterations>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`sdf_iterations<option-sdf_iterations>` | :ref:`sdf_initpoints<option-sdf_initpoints>` | :ref:`actuatorgroupdisable<option-actuatorgroupdisable>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| option |br| |_| |L| | | .. table:: |
| :ref:`flag | ? | :class: mjcf-attributes |
| <option-flag>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`constraint<option-flag-constraint>` | :ref:`equality<option-flag-equality>` | :ref:`frictionloss<option-flag-frictionloss>` | :ref:`limit<option-flag-limit>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`contact<option-flag-contact>` | :ref:`passive<option-flag-passive>` | :ref:`gravity<option-flag-gravity>` | :ref:`clampctrl<option-flag-clampctrl>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`warmstart<option-flag-warmstart>` | :ref:`filterparent<option-flag-filterparent>` | :ref:`actuation<option-flag-actuation>` | :ref:`refsafe<option-flag-refsafe>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`sensor<option-flag-sensor>` | :ref:`midphase<option-flag-midphase>` | :ref:`eulerdamp<option-flag-eulerdamp>` | :ref:`override<option-flag-override>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`energy<option-flag-energy>` | :ref:`fwdinv<option-flag-fwdinv>` | :ref:`invdiscrete<option-flag-invdiscrete>` | :ref:`multiccd<option-flag-multiccd>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`island<option-flag-island>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| mujoco |br| |L| | | .. table:: |
| :ref:`compiler | \* | :class: mjcf-attributes |
| <compiler>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
@@ -55,83 +91,6 @@
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| mujoco |br| |L| | | *no attributes* |
| :ref:`visual<visual>` | | |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| visual |br| |_| |L| | | .. table:: |
| :ref:`global | ? | :class: mjcf-attributes |
| <visual-global>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`fovy<visual-global-fovy>` | :ref:`ipd<visual-global-ipd>` | :ref:`azimuth<visual-global-azimuth>` | :ref:`elevation<visual-global-elevation>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`linewidth<visual-global-linewidth>` | :ref:`glow<visual-global-glow>` | :ref:`offwidth<visual-global-offwidth>` | :ref:`offheight<visual-global-offheight>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`realtime<visual-global-realtime>` | :ref:`ellipsoidinertia<visual-global-ellipsoidinertia>` | :ref:`bvactive<visual-global-bvactive>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| visual |br| |_| |L| | | .. table:: |
| :ref:`quality | ? | :class: mjcf-attributes |
| <visual-quality>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`shadowsize<visual-quality-shadowsize>` | :ref:`offsamples<visual-quality-offsamples>` | :ref:`numslices<visual-quality-numslices>` | :ref:`numstacks<visual-quality-numstacks>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`numquads<visual-quality-numquads>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| visual |br| |_| |L| | | .. table:: |
| :ref:`headlight | ? | :class: mjcf-attributes |
| <visual-headlight>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`ambient<visual-headlight-ambient>` | :ref:`diffuse<visual-headlight-diffuse>` | :ref:`specular<visual-headlight-specular>` | :ref:`active<visual-headlight-active>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| visual |br| |_| |L| | | .. table:: |
| :ref:`map | ? | :class: mjcf-attributes |
| <visual-map>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`stiffness<visual-map-stiffness>` | :ref:`stiffnessrot<visual-map-stiffnessrot>` | :ref:`force<visual-map-force>` | :ref:`torque<visual-map-torque>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`alpha<visual-map-alpha>` | :ref:`fogstart<visual-map-fogstart>` | :ref:`fogend<visual-map-fogend>` | :ref:`znear<visual-map-znear>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`zfar<visual-map-zfar>` | :ref:`haze<visual-map-haze>` | :ref:`shadowclip<visual-map-shadowclip>` | :ref:`shadowscale<visual-map-shadowscale>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`actuatortendon<visual-map-actuatortendon>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| visual |br| |_| |L| | | .. table:: |
| :ref:`scale | ? | :class: mjcf-attributes |
| <visual-scale>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`forcewidth<visual-scale-forcewidth>` | :ref:`contactwidth<visual-scale-contactwidth>` | :ref:`contactheight<visual-scale-contactheight>` | :ref:`connect<visual-scale-connect>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`com<visual-scale-com>` | :ref:`camera<visual-scale-camera>` | :ref:`light<visual-scale-light>` | :ref:`selectpoint<visual-scale-selectpoint>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`jointlength<visual-scale-jointlength>` | :ref:`jointwidth<visual-scale-jointwidth>` | :ref:`actuatorlength<visual-scale-actuatorlength>` | :ref:`actuatorwidth<visual-scale-actuatorwidth>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`framelength<visual-scale-framelength>` | :ref:`framewidth<visual-scale-framewidth>` | :ref:`constraint<visual-scale-constraint>` | :ref:`slidercrank<visual-scale-slidercrank>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`frustum<visual-scale-frustum>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| visual |br| |_| |L| | | .. table:: |
| :ref:`rgba | ? | :class: mjcf-attributes |
| <visual-rgba>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`fog<visual-rgba-fog>` | :ref:`haze<visual-rgba-haze>` | :ref:`force<visual-rgba-force>` | :ref:`inertia<visual-rgba-inertia>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`joint<visual-rgba-joint>` | :ref:`actuator<visual-rgba-actuator>` | :ref:`actuatornegative<visual-rgba-actuatornegative>` | :ref:`actuatorpositive<visual-rgba-actuatorpositive>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`com<visual-rgba-com>` | :ref:`camera<visual-rgba-camera>` | :ref:`light<visual-rgba-light>` | :ref:`selectpoint<visual-rgba-selectpoint>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`connect<visual-rgba-connect>` | :ref:`contactpoint<visual-rgba-contactpoint>` | :ref:`contactforce<visual-rgba-contactforce>` | :ref:`contactfriction<visual-rgba-contactfriction>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`contacttorque<visual-rgba-contacttorque>` | :ref:`contactgap<visual-rgba-contactgap>` | :ref:`rangefinder<visual-rgba-rangefinder>` | :ref:`constraint<visual-rgba-constraint>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`slidercrank<visual-rgba-slidercrank>` | :ref:`crankbroken<visual-rgba-crankbroken>` | :ref:`frustum<visual-rgba-frustum>` | :ref:`bv<visual-rgba-bv>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`bvactive<visual-rgba-bvactive>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| mujoco |br| |L| | | *no attributes* |
| :ref:`asset<asset>` | | |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| asset |br| |_| |L| | | .. table:: |
@@ -213,43 +172,7 @@
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`texuniform<asset-material-texuniform>` | :ref:`emission<asset-material-emission>` | :ref:`specular<asset-material-specular>` | :ref:`shininess<asset-material-shininess>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`reflectance<asset-material-reflectance>` | :ref:`rgba<asset-material-rgba>` | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| mujoco |br| |L| | | .. table:: |
| :ref:`option | \* | :class: mjcf-attributes |
| <option>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`timestep<option-timestep>` | :ref:`apirate<option-apirate>` | :ref:`impratio<option-impratio>` | :ref:`tolerance<option-tolerance>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`ls_tolerance<option-ls_tolerance>` | :ref:`noslip_tolerance<option-noslip_tolerance>` | :ref:`mpr_tolerance<option-mpr_tolerance>` | :ref:`gravity<option-gravity>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`wind<option-wind>` | :ref:`magnetic<option-magnetic>` | :ref:`density<option-density>` | :ref:`viscosity<option-viscosity>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`o_margin<option-o_margin>` | :ref:`o_solref<option-o_solref>` | :ref:`o_solimp<option-o_solimp>` | :ref:`o_friction<option-o_friction>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`integrator<option-integrator>` | :ref:`cone<option-cone>` | :ref:`jacobian<option-jacobian>` | :ref:`solver<option-solver>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`iterations<option-iterations>` | :ref:`ls_iterations<option-ls_iterations>` | :ref:`noslip_iterations<option-noslip_iterations>` | :ref:`mpr_iterations<option-mpr_iterations>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`sdf_iterations<option-sdf_iterations>` | :ref:`sdf_initpoints<option-sdf_initpoints>` | :ref:`actuatorgroupdisable<option-actuatorgroupdisable>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| option |br| |_| |L| | | .. table:: |
| :ref:`flag | ? | :class: mjcf-attributes |
| <option-flag>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`constraint<option-flag-constraint>` | :ref:`equality<option-flag-equality>` | :ref:`frictionloss<option-flag-frictionloss>` | :ref:`limit<option-flag-limit>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`contact<option-flag-contact>` | :ref:`passive<option-flag-passive>` | :ref:`gravity<option-flag-gravity>` | :ref:`clampctrl<option-flag-clampctrl>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`warmstart<option-flag-warmstart>` | :ref:`filterparent<option-flag-filterparent>` | :ref:`actuation<option-flag-actuation>` | :ref:`refsafe<option-flag-refsafe>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`sensor<option-flag-sensor>` | :ref:`midphase<option-flag-midphase>` | :ref:`eulerdamp<option-flag-eulerdamp>` | :ref:`override<option-flag-override>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`energy<option-flag-energy>` | :ref:`fwdinv<option-flag-fwdinv>` | :ref:`invdiscrete<option-flag-invdiscrete>` | :ref:`multiccd<option-flag-multiccd>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`island<option-flag-island>` | | | | |
| | | | :ref:`reflectance<asset-material-reflectance>` | :ref:`metallic<asset-material-metallic>` | :ref:`roughness<asset-material-roughness>` | :ref:`rgba<asset-material-rgba>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| mujoco |br| |L| | | .. table:: |
@@ -369,11 +292,11 @@
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`name<body-light-name>` | :ref:`class<body-light-class>` | :ref:`directional<body-light-directional>` | :ref:`castshadow<body-light-castshadow>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`active<body-light-active>` | :ref:`pos<body-light-pos>` | :ref:`dir<body-light-dir>` | :ref:`attenuation<body-light-attenuation>` | |
| | | | :ref:`active<body-light-active>` | :ref:`pos<body-light-pos>` | :ref:`dir<body-light-dir>` | :ref:`bulbradius<body-light-bulbradius>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`cutoff<body-light-cutoff>` | :ref:`exponent<body-light-exponent>` | :ref:`ambient<body-light-ambient>` | :ref:`diffuse<body-light-diffuse>` | |
| | | | :ref:`attenuation<body-light-attenuation>` | :ref:`cutoff<body-light-cutoff>` | :ref:`exponent<body-light-exponent>` | :ref:`ambient<body-light-ambient>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`specular<body-light-specular>` | :ref:`mode<body-light-mode>` | :ref:`target<body-light-target>` | | |
| | | | :ref:`diffuse<body-light-diffuse>` | :ref:`specular<body-light-specular>` | :ref:`mode<body-light-mode>` | :ref:`target<body-light-target>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| body |br| |_| |L| | | .. table:: |
@@ -1287,6 +1210,83 @@
| | | | :ref:`act<keyframe-key-act>` | :ref:`mpos<keyframe-key-mpos>` | :ref:`mquat<keyframe-key-mquat>` | :ref:`ctrl<keyframe-key-ctrl>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| mujoco |br| |L| | | *no attributes* |
| :ref:`visual<visual>` | | |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| visual |br| |_| |L| | | .. table:: |
| :ref:`global | ? | :class: mjcf-attributes |
| <visual-global>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`fovy<visual-global-fovy>` | :ref:`ipd<visual-global-ipd>` | :ref:`azimuth<visual-global-azimuth>` | :ref:`elevation<visual-global-elevation>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`linewidth<visual-global-linewidth>` | :ref:`glow<visual-global-glow>` | :ref:`offwidth<visual-global-offwidth>` | :ref:`offheight<visual-global-offheight>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`realtime<visual-global-realtime>` | :ref:`ellipsoidinertia<visual-global-ellipsoidinertia>` | :ref:`bvactive<visual-global-bvactive>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| visual |br| |_| |L| | | .. table:: |
| :ref:`quality | ? | :class: mjcf-attributes |
| <visual-quality>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`shadowsize<visual-quality-shadowsize>` | :ref:`offsamples<visual-quality-offsamples>` | :ref:`numslices<visual-quality-numslices>` | :ref:`numstacks<visual-quality-numstacks>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`numquads<visual-quality-numquads>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| visual |br| |_| |L| | | .. table:: |
| :ref:`headlight | ? | :class: mjcf-attributes |
| <visual-headlight>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`ambient<visual-headlight-ambient>` | :ref:`diffuse<visual-headlight-diffuse>` | :ref:`specular<visual-headlight-specular>` | :ref:`active<visual-headlight-active>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| visual |br| |_| |L| | | .. table:: |
| :ref:`map | ? | :class: mjcf-attributes |
| <visual-map>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`stiffness<visual-map-stiffness>` | :ref:`stiffnessrot<visual-map-stiffnessrot>` | :ref:`force<visual-map-force>` | :ref:`torque<visual-map-torque>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`alpha<visual-map-alpha>` | :ref:`fogstart<visual-map-fogstart>` | :ref:`fogend<visual-map-fogend>` | :ref:`znear<visual-map-znear>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`zfar<visual-map-zfar>` | :ref:`haze<visual-map-haze>` | :ref:`shadowclip<visual-map-shadowclip>` | :ref:`shadowscale<visual-map-shadowscale>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`actuatortendon<visual-map-actuatortendon>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| visual |br| |_| |L| | | .. table:: |
| :ref:`scale | ? | :class: mjcf-attributes |
| <visual-scale>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`forcewidth<visual-scale-forcewidth>` | :ref:`contactwidth<visual-scale-contactwidth>` | :ref:`contactheight<visual-scale-contactheight>` | :ref:`connect<visual-scale-connect>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`com<visual-scale-com>` | :ref:`camera<visual-scale-camera>` | :ref:`light<visual-scale-light>` | :ref:`selectpoint<visual-scale-selectpoint>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`jointlength<visual-scale-jointlength>` | :ref:`jointwidth<visual-scale-jointwidth>` | :ref:`actuatorlength<visual-scale-actuatorlength>` | :ref:`actuatorwidth<visual-scale-actuatorwidth>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`framelength<visual-scale-framelength>` | :ref:`framewidth<visual-scale-framewidth>` | :ref:`constraint<visual-scale-constraint>` | :ref:`slidercrank<visual-scale-slidercrank>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`frustum<visual-scale-frustum>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| visual |br| |_| |L| | | .. table:: |
| :ref:`rgba | ? | :class: mjcf-attributes |
| <visual-rgba>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`fog<visual-rgba-fog>` | :ref:`haze<visual-rgba-haze>` | :ref:`force<visual-rgba-force>` | :ref:`inertia<visual-rgba-inertia>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`joint<visual-rgba-joint>` | :ref:`actuator<visual-rgba-actuator>` | :ref:`actuatornegative<visual-rgba-actuatornegative>` | :ref:`actuatorpositive<visual-rgba-actuatorpositive>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`com<visual-rgba-com>` | :ref:`camera<visual-rgba-camera>` | :ref:`light<visual-rgba-light>` | :ref:`selectpoint<visual-rgba-selectpoint>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`connect<visual-rgba-connect>` | :ref:`contactpoint<visual-rgba-contactpoint>` | :ref:`contactforce<visual-rgba-contactforce>` | :ref:`contactfriction<visual-rgba-contactfriction>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`contacttorque<visual-rgba-contacttorque>` | :ref:`contactgap<visual-rgba-contactgap>` | :ref:`rangefinder<visual-rgba-rangefinder>` | :ref:`constraint<visual-rgba-constraint>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`slidercrank<visual-rgba-slidercrank>` | :ref:`crankbroken<visual-rgba-crankbroken>` | :ref:`frustum<visual-rgba-frustum>` | :ref:`bv<visual-rgba-bv>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`bvactive<visual-rgba-bvactive>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| mujoco |br| |L| | | .. table:: |
| :ref:`default | R | :class: mjcf-attributes |
| <default>` | | |
@@ -1307,7 +1307,9 @@
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`texture<default-material-texture>` | :ref:`emission<default-material-emission>` | :ref:`specular<default-material-specular>` | :ref:`shininess<default-material-shininess>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`reflectance<default-material-reflectance>` | :ref:`rgba<default-material-rgba>` | :ref:`texrepeat<default-material-texrepeat>` | :ref:`texuniform<default-material-texuniform>` | |
| | | | :ref:`reflectance<default-material-reflectance>` | :ref:`metallic<default-material-metallic>` | :ref:`roughness<default-material-roughness>` | :ref:`rgba<default-material-rgba>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`texrepeat<default-material-texrepeat>` | :ref:`texuniform<default-material-texuniform>` | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| default |br| |_| |L| | | .. table:: |
@@ -1378,11 +1380,13 @@
| :ref:`light | ? | :class: mjcf-attributes |
| <default-light>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`pos<default-light-pos>` | :ref:`dir<default-light-dir>` | :ref:`directional<default-light-directional>` | :ref:`castshadow<default-light-castshadow>` | |
| | | | :ref:`pos<default-light-pos>` | :ref:`dir<default-light-dir>` | :ref:`bulbradius<default-light-bulbradius>` | :ref:`directional<default-light-directional>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`active<default-light-active>` | :ref:`attenuation<default-light-attenuation>` | :ref:`cutoff<default-light-cutoff>` | :ref:`exponent<default-light-exponent>` | |
| | | | :ref:`castshadow<default-light-castshadow>` | :ref:`active<default-light-active>` | :ref:`attenuation<default-light-attenuation>` | :ref:`cutoff<default-light-cutoff>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`ambient<default-light-ambient>` | :ref:`diffuse<default-light-diffuse>` | :ref:`specular<default-light-specular>` | :ref:`mode<default-light-mode>` | |
| | | | :ref:`exponent<default-light-exponent>` | :ref:`ambient<default-light-ambient>` | :ref:`diffuse<default-light-diffuse>` | :ref:`specular<default-light-specular>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`mode<default-light-mode>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| default |br| |_| |L| | | .. table:: |
+75 -13
View File
@@ -5,6 +5,64 @@ Changelog
Upcoming version (not yet released)
-----------------------------------
General
^^^^^^^
.. youtube:: 5k0_wsIRAFc
:align: right
:width: 240px
1. Added the :ref:`replicate<replicate>` to MJCF, a :ref:`meta-element<meta-element>` which permits to repeat a subtree
with incremental translational and rotational offsets.
2. Added ``mjModel.mesh_scale``: the scaling applied to asset vertices, as specified in the
:ref:`scale<asset-mesh-scale>` attribute.
3. Added visual properties which are ignored by the native renderer, but can be used by external renderers:
- :ref:`light/bulbradius<body-light-bulbradius>` attribute and corresponding ``mjModel.light_bulbradius`` field.
- :ref:`material/metallic<asset-material-metallic>` attribute and corresponding ``mjModel.material_metallic`` field.
- :ref:`material/roughness<asset-material-roughness>` attribute and corresponding ``mjModel.material_roughness``
field.
4. The type of the ``size`` argument of :ref:`mj_stackAllocNum` and :ref:`mj_stackAllocInt` was changed from ``int``
to ``size_t``.
5. Added support for gmsh format version 2.2 surface meshes in :ref:`flexcomp<body-flexcomp-file>`.
MJX
^^^
.. admonition:: Breaking API changes
:class: attention
6. Removed deprecated ``mjx.device_get_into`` and ``mjx.device_put`` functions as they lack critical new
functionality.
**Migration:** Use ``mjx.get_data_into`` instead of ``mjx.device_get_into``, and ``mjx.put_data`` instead of
``mjx.device_put``.
7. Added cylinder plane collisions.
8. Added ``efc_type`` to ``mjx.Data`` and ``dim``, ``efc_address`` to ``mjx.Contact``.
9. Added ``geom`` to ``mjx.Contact`` and marked ``geom1``, ``geom2`` deprecated.
10. Added ``ne``, ``nf``, ``nl``, ``nefc``, and ``ncon`` to ``mjx.Data`` to match ``mujoco.MjData``.
11. Given the above added fields, removed ``mjx.get_params``, ``mjx.ncon``, and ``mjx.count_constraints``.
12. Changed the way meshes are organized on device to speed up collision detection when a mesh is replicated for many
geoms.
13. Fixed a bug where capsules might be ignored in broadphase colliision checking.
14. Added cylinder collisions using SDFs.
15. Added support for all :ref:`condim <coContact>`: 1, 3, 4, 6.
16. Add support functions for ``id2name`` and ``name2id``, MJX versions of :ref:`mj_id2name` and :ref:`mj_name2id`.
17. Added support for :ref:`gravcomp<body-gravcomp>` and :ref:`actuatorgravcomp<body-joint-actuatorgravcomp>`.
18. Fixed a bug in ``mjx.ray`` for sometimes allowed negative distances for ray-mesh tests.
Bug fixes
^^^^^^^^^
19. Defaults of lights were not being saved, now fixed.
20. Prevent overwriting of frame names by body names when saving an XML. Bug introduced in 3.1.4.
21. Fixed bug in Python binding of :ref:`mj_saveModel`: ``buffer`` argument was documented as optional but was actually
not optional.
22. Fixed bug that prevented memory allocations larger than 2.15 GB.
Version 3.1.4 (April 10th, 2024)
--------------------------------
General
^^^^^^^
.. admonition:: Breaking API changes
@@ -14,7 +72,7 @@ General
:ref:`corresponding attribute<CSensor>` are kept and now function as a convenient location for the user to save
standard-deviation information for their own use. This feature was removed because:
- There was no mechanism to seed the randon noise generator.
- There was no mechanism to seed the random noise generator.
- It was not thread-safe, even if seeding would have been provided, sampling on multiple threads would lead to
non-reproducible results.
- This feature was seen as overreach by the engine. Adding noise should be the user's responsibility.
@@ -27,21 +85,25 @@ General
model
`refsite.xml <https://github.com/google-deepmind/mujoco/blob/main/test/engine/testdata/actuation/refsite.xml>`__,
which demostrates Cartesian actuation of an arm, has been updated to use this attribute.
3. Added support for gmsh format 2.2, as generated by e.g. `fTetwild <https://github.com/wildmeshing/fTetWild>`__.
3. Added support for gmsh format 2.2 , tetrahedral mesh, as generated by e.g. `fTetwild <https://github.com/wildmeshing/fTetWild>`__.
4. Added :ref:`mju_euler2Quat` for converting an Euler-angle sequence to quaternion.
MJX
^^^
4. Improved performance of SAT for convex collisions.
5. Fixed bug for sphere/capsule-convex deep penetration.
6. Fixed bug where ``mjx.Data`` produced by ``mjx.put_data`` had different treedef than ``mjx.make_data``.
7. Throw an error for margin/gap for convex mesh collisions, since they are not supported.
5. Improved performance of SAT for convex collisions.
6. Fixed bug for sphere/capsule-convex deep penetration.
7. Fixed bug where ``mjx.Data`` produced by ``mjx.put_data`` had different treedef than ``mjx.make_data``.
8. Throw an error for margin/gap for convex mesh collisions, since they are not supported.
9. Added ellipsoid plane collisions.
10. Added support for userdata.
11. Added ellipsoid-ellipsoid and ellipsoid-capsule collisions using signed distance functions (SDFs).
Simulate
^^^^^^^^
8. Fixed bug in order of enable flag strings. Before this change, using the simulate UI to toggle the
:ref:`invdiscrete<option-flag-invdiscrete>` or the (now removed) ``sensornoise`` flags would actually toggle the
other flag.
12. Fixed bug in order of enable flag strings. Before this change, using the simulate UI to toggle the
:ref:`invdiscrete<option-flag-invdiscrete>` or the (now removed) ``sensornoise`` flags would actually toggle the
other flag.
Python bindings
^^^^^^^^^^^^^^^
@@ -50,10 +112,10 @@ Python bindings
:align: right
:width: 240px
9. Added the ``mujoco.minimize`` Python module for nonlinear least-squares, designed for System Identification (sysID).
The sysID tutorial is work in progress, but a pedagogical colab notebook with examples, including Inverse
Kinematics, is available here: |ls_colab|
|br| The video on the right shows example clips from the tutorial.
13. Added the ``mujoco.minimize`` Python module for nonlinear least-squares, designed for System Identification (sysID).
The sysID tutorial is work in progress, but a pedagogical colab notebook with examples, including Inverse
Kinematics, is available here: |ls_colab|
|br| The video on the right shows example clips from the tutorial.
.. |ls_colab| image:: https://colab.research.google.com/assets/colab-badge.svg
:target: https://colab.research.google.com/github/google-deepmind/mujoco/blob/main/python/least_squares.ipynb
+14 -9
View File
@@ -22,8 +22,8 @@ been described elsewhere.
.. _Motivation:
Motivation for soft contact model
---------------------------------
Soft contact model
------------------
Robots as well as humans interact with their environment primarily through physical contact. Given the increasing
importance of physics modeling in robotics, machine learning, animation, virtual reality, biomechanics and other fields,
@@ -1557,8 +1557,10 @@ Forward dynamics
~~~~~~~~~~~~~~~~
The source file `engine_forward.c <https://github.com/google-deepmind/mujoco/blob/main/src/engine/engine_forward.c>`__
contains the high-level forward dynamics pipeline:
contains the high-level forward dynamics pipeline.
Top level
^^^^^^^^^
- The top-level function :ref:`mj_step` invokes the entire sequence of computations below.
- :ref:`mj_forward` invokes only stages **2-22**, computing the continuous-time forward dynamics, ending with the
acceleration ``mjData.qacc``.
@@ -1566,7 +1568,10 @@ contains the high-level forward dynamics pipeline:
distinct phases. This allows the user to write controllers that depend on quantities derived from the positions and
velocities (but not forces, since those have not yet been computed). Note that the :ref:`mj_step1` → :ref:`mj_step2`
pipeline does not support the Runge Kutta integrator.
- :ref:`mj_fwdPosition` invokes stages **2-11**, the position-dependent part of the pipeline.
Stages
^^^^^^
1. Check the positions and velocities for invalid or unacceptably large real values indicating divergence. If divergence
is detected, the state is automatically reset and the corresponding warning is raised:
:ref:`mj_checkPos`, :ref:`mj_checkVel`
@@ -1581,9 +1586,9 @@ contains the high-level forward dynamics pipeline:
8. Construct the list of active contacts. This includes both broad-phase and near-phase collision detection:
:ref:`mj_collision`
9. Construct the constraint Jacobian and compute the constraint residuals: :ref:`mj_makeConstraint`
10. Compute the matrices and vectors needed by the constraint solvers: :ref:`mj_projectConstraint`
11. Compute the tendon lengths and moment arms. This includes the computation of minimal-length paths for spatial
10. Compute the tendon lengths and moment arms. This includes the computation of minimal-length paths for spatial
tendons: :ref:`mj_transmission`
11. Compute the matrices and vectors needed by the constraint solvers: :ref:`mj_projectConstraint`
12. Compute sensor data that only depends on position, and the potential energy if enabled: :ref:`mj_sensorPos`,
:ref:`mj_energyPos`
13. Compute the tendon, flex edge and actuator velocities: :ref:`mj_fwdVelocity`
@@ -1658,10 +1663,10 @@ important caveats:
systems with contact. Contact events have high `Lyapunov exponents
<https://en.wikipedia.org/wiki/Lyapunov_exponent>`__; this is a property of any rigid-body simulator (and indeed of
`real-world physics <https://en.wikipedia.org/wiki/Roulette>`__) and is not MuJoCo-specific.
- Exact reproducibillity is only guaranteed within a **single version**. Small numerical differences are quite common
between versioned releases, for example due to code optimizations. This means that when saving an initial state and an
open-loop control sequence, the resulting rolled-out trajectory will be identical within the same version but will
likely be different between MuJoCo versions.
- Exact reproducibility is only guaranteed within a **single version**, on the **same architecture**. Small numerical
differences are quite common between versioned releases, for example due to code optimizations. This means that when
saving an initial state and an open-loop control sequence, the resulting rolled-out trajectory will be identical
within the same version, but will likely be different between MuJoCo versions or different operating systems.
.. _derivatives:
Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

+24 -14
View File
@@ -922,6 +922,7 @@ struct mjModel_ {
int nD; // number of non-zeros in sparse dof-dof matrix
int nB; // number of non-zeros in sparse body-dof matrix
int ntree; // number of kinematic trees under world body
int ngravcomp; // number of bodies with nonzero gravcomp
int nemax; // number of potential equality-constraint rows
int njmax; // number of available rows in constraint Jacobian
int nconmax; // number of potential contacts in contact list
@@ -1079,6 +1080,7 @@ struct mjModel_ {
int* light_targetbodyid; // id of targeted body; -1: none (nlight x 1)
mjtByte* light_directional; // directional light (nlight x 1)
mjtByte* light_castshadow; // does light cast shadows (nlight x 1)
float* light_bulbradius; // light radius for soft shadows (nlight x 1)
mjtByte* light_active; // is light on (nlight x 1)
mjtNum* light_pos; // position rel. to body frame (nlight x 3)
mjtNum* light_dir; // direction rel. to body frame (nlight x 3)
@@ -1165,6 +1167,7 @@ struct mjModel_ {
int* mesh_facenormal; // normal face data (nmeshface x 3)
int* mesh_facetexcoord; // texture face data (nmeshface x 3)
int* mesh_graph; // convex graph data (nmeshgraph x 1)
mjtNum* mesh_scale; // scaling applied to asset vertices (nmesh x 3)
mjtNum* mesh_pos; // translation applied to asset vertices (nmesh x 3)
mjtNum* mesh_quat; // rotation applied to asset vertices (nmesh x 4)
int* mesh_pathadr; // address of asset path for mesh; -1: none (nmesh x 1)
@@ -1194,20 +1197,20 @@ struct mjModel_ {
int* skin_pathadr; // address of asset path for skin; -1: none (nskin x 1)
// height fields
mjtNum* hfield_size; // (x, y, z_top, z_bottom) (nhfield x 4)
int* hfield_nrow; // number of rows in grid (nhfield x 1)
int* hfield_ncol; // number of columns in grid (nhfield x 1)
int* hfield_adr; // address in hfield_data (nhfield x 1)
float* hfield_data; // elevation data (nhfielddata x 1)
int* hfield_pathadr; // address of asset path for hfield; -1: none (nhfield x 1)
mjtNum* hfield_size; // (x, y, z_top, z_bottom) (nhfield x 4)
int* hfield_nrow; // number of rows in grid (nhfield x 1)
int* hfield_ncol; // number of columns in grid (nhfield x 1)
int* hfield_adr; // address in hfield_data (nhfield x 1)
float* hfield_data; // elevation data (nhfielddata x 1)
int* hfield_pathadr; // address of hfield asset path; -1: none (nhfield x 1)
// textures
int* tex_type; // texture type (mjtTexture) (ntex x 1)
int* tex_height; // number of rows in texture image (ntex x 1)
int* tex_width; // number of columns in texture image (ntex x 1)
int* tex_adr; // address in rgb (ntex x 1)
mjtByte* tex_rgb; // rgb (alpha = 1) (ntexdata x 1)
int* tex_pathadr; // address of asset path for texture; -1: none (ntex x 1)
int* tex_type; // texture type (mjtTexture) (ntex x 1)
int* tex_height; // number of rows in texture image (ntex x 1)
int* tex_width; // number of columns in texture image (ntex x 1)
int* tex_adr; // address in rgb (ntex x 1)
mjtByte* tex_rgb; // rgb (alpha = 1) (ntexdata x 1)
int* tex_pathadr; // address of texture asset path; -1: none (ntex x 1)
// materials
int* mat_texid; // texture id; -1: none (nmat x 1)
@@ -1217,6 +1220,8 @@ struct mjModel_ {
float* mat_specular; // specular (x white) (nmat x 1)
float* mat_shininess; // shininess coef (nmat x 1)
float* mat_reflectance; // reflectance (0: disable) (nmat x 1)
float* mat_metallic; // metallic coef (nmat x 1)
float* mat_roughness; // roughness coef (nmat x 1)
float* mat_rgba; // rgba (nmat x 4)
// predefined geom pairs for collision detection; has precedence over exclude
@@ -2020,6 +2025,7 @@ struct mjvLight_ { // OpenGL light
mjtByte headlight; // headlight
mjtByte directional; // directional light
mjtByte castshadow; // does light cast shadows
float bulbradius; // bulb radius for soft shadows
};
typedef struct mjvLight_ mjvLight;
struct mjvOption_ { // abstract visualization options
@@ -2234,6 +2240,7 @@ struct mjvSceneState_ {
mjtByte* light_directional;
mjtByte* light_castshadow;
float* light_bulbradius;
mjtByte* light_active;
float* light_attenuation;
float* light_cutoff;
@@ -2301,6 +2308,8 @@ struct mjvSceneState_ {
float* mat_specular;
float* mat_shininess;
float* mat_reflectance;
float* mat_metallic;
float* mat_roughness;
float* mat_rgba;
int* eq_type;
@@ -2445,8 +2454,8 @@ void mj_resetDataKeyframe(const mjModel* m, mjData* d, int key);
void mj_markStack(mjData* d);
void mj_freeStack(mjData* d);
void* mj_stackAllocByte(mjData* d, size_t bytes, size_t alignment);
mjtNum* mj_stackAllocNum(mjData* d, int size);
int* mj_stackAllocInt(mjData* d, int size);
mjtNum* mj_stackAllocNum(mjData* d, size_t size);
int* mj_stackAllocInt(mjData* d, size_t size);
void mj_deleteData(mjData* d);
void mj_resetCallbacks(void);
void mj_setConst(mjModel* m, mjData* d);
@@ -2742,6 +2751,7 @@ void mju_mat2Quat(mjtNum quat[4], const mjtNum mat[9]);
void mju_derivQuat(mjtNum res[4], const mjtNum quat[4], const mjtNum vel[3]);
void mju_quatIntegrate(mjtNum quat[4], const mjtNum vel[3], mjtNum scale);
void mju_quatZ2Vec(mjtNum quat[4], const mjtNum vec[3]);
void mju_euler2Quat(mjtNum quat[4], const mjtNum euler[3], const char* seq);
void mju_mulPose(mjtNum posres[3], mjtNum quatres[4],
const mjtNum pos1[3], const mjtNum quat1[4],
const mjtNum pos2[3], const mjtNum quat2[4]);
+17 -12
View File
@@ -66,25 +66,32 @@ directly from the top-level ``mjx`` module.
Structs
-------
Before running MJX functions on an accelerator device, structs must be copied onto the device via the ``mjx.device_put``
function. Placing an :ref:`mjModel` on device yields an ``mjx.Model``. Placing an :ref:`mjData` on device yields
Before running MJX functions on an accelerator device, structs must be copied onto the device via the ``mjx.put_model`` and ``mjx.put_data``
functions. Placing an :ref:`mjModel` on device yields an ``mjx.Model``. Placing an :ref:`mjData` on device yields
an ``mjx.Data``:
.. code-block:: python
model = mujoco.MjModel.from_xml_string("...")
data = mujoco.MjData(model)
mjx_model = mjx.device_put(model)
mjx_data = mjx.device_put(data)
mjx_model = mjx.put_model(model)
mjx_data = mjx.put_data(data)
These MJX variants mirror their MuJoCo counterparts but have three key differences:
These MJX variants mirror their MuJoCo counterparts but have a few key differences:
#. Fields in ``mjx.Model`` and ``mjx.Data`` are JAX arrays copied onto device, instead of numpy arrays.
#. ``mjx.Model`` and ``mjx.Data`` contain JAX arrays that are copied onto device.
#. Some fields are missing from ``mjx.Model`` and ``mjx.Data`` for features that are
:ref:`unsupported <mjxFeatureParity>` in MJX.
#. Arrays in ``mjx.Model`` and ``mjx.Data`` support adding batch dimensions. Batch dimensions are a natural way to
#. JAX arrays in ``mjx.Model`` and ``mjx.Data`` support adding batch dimensions. Batch dimensions are a natural way to
express domain randomization (in the case of ``mjx.Model``) or high-throughput simulation for reinforcement learning
(in the case of ``mjx.Data``).
#. Numpy arrays in ``mjx.Model`` and ``mjx.Data`` are structural fields that control the output of JIT compilation.
Modifying these arrays will force JAX to recompile MJX functions. As an example,
``jnt_limited`` is a numpy array passed by reference from :ref:`mjModel`, which determines if joint limit
constraints should be applied. If ``jnt_limited`` is modified, JAX will
re-compile MJX functions.
On the other hand, ``jnt_range`` is a JAX array that can be modified at runtime, and will only apply to joints with limits
as specified by the ``jnt_limited`` field.
Neither ``mjx.Model`` nor ``mjx.Data`` are meant to be constructed manually. An ``mjx.Data`` may be created by calling
@@ -191,7 +198,7 @@ The following features are **fully supported** in MJX:
* - :ref:`Geom <mjtGeom>`
- ``PLANE``, ``SPHERE``, ``CAPSULE``, ``BOX``, ``MESH``
* - :ref:`Constraint <mjtConstraint>`
- ``EQUALITY``, ``LIMIT_JOINT``, ``CONTACT_PYRAMIDAL``
- ``EQUALITY``, ``LIMIT_JOINT``, ``CONTACT_FRICTIONLESS``, ``CONTACT_PYRAMIDAL``
* - :ref:`Equality <mjtEq>`
- ``CONNECT``, ``WELD``, ``JOINT``
* - :ref:`Integrator <mjtIntegrator>`
@@ -199,7 +206,7 @@ The following features are **fully supported** in MJX:
* - :ref:`Cone <mjtCone>`
- ``PYRAMIDAL``
* - :ref:`Condim <coContact>`
- 3
- 1, 3, 4, 6
* - :ref:`Solver <mjtSolver>`
- ``CG``, ``NEWTON``
* - Fluid Model
@@ -217,10 +224,8 @@ The following features are **in development** and coming soon:
- Feature
* - :ref:`Geom <mjtGeom>`
- ``SDF``, ``HFIELD``, ``ELLIPSOID``, ``CYLINDER``
* - :ref:`Condim <coContact>`
- 1, 4, 6
* - :ref:`Constraint <mjtConstraint>`
- :ref:`Frictionloss <coFriction>`, ``CONTACT_FRICTIONLESS``, ``CONTACT_ELLIPTIC``, ``FRICTION_DOF``
- :ref:`Frictionloss <coFriction>`, ``CONTACT_ELLIPTIC``, ``FRICTION_DOF``
* - :ref:`Integrator <mjtIntegrator>`
- ``IMPLICIT``, ``IMPLICITFAST``
* - Dynamics
+14
View File
@@ -129,3 +129,17 @@ Drones
- Preview
* - `Skydio X2 <https://github.com/google-deepmind/mujoco_menagerie/tree/main/skydio_x2>`_
- .. image:: https://raw.githubusercontent.com/google-deepmind/mujoco_menagerie/main/skydio_x2/x2.png
* - `Bitcraze Crazyflie 2 <https://github.com/google-deepmind/mujoco_menagerie/tree/main/bitcraze_crazyflie_2>`_
- .. image:: https://raw.githubusercontent.com/google-deepmind/mujoco_menagerie/main/bitcraze_crazyflie_2/cf2.png
Biomechanical
^^^^^^^^^^^^^
.. list-table::
:header-rows: 1
* - Model
- Preview
* - `Fruitfly <https://github.com/google-deepmind/mujoco_menagerie/tree/main/flybody>`_
- .. image:: https://raw.githubusercontent.com/google-deepmind/mujoco_menagerie/main/flybody/flybody.png
+2 -2
View File
@@ -30,14 +30,14 @@ _____
The MuJoCo app needs to be run at least once before the native library can be used, in order to register the library as
a trusted binary. Then, copy the dynamic library file from
``/Applications/MuJoCo.app/Contents/Frameworks/mujoco.framework/Versions/Current/libmujoco.3.1.4.dylib`` (it can be
``/Applications/MuJoCo.app/Contents/Frameworks/mujoco.framework/Versions/Current/libmujoco.3.1.5.dylib`` (it can be
found by browsing the contents of ``MuJoCo.app``) and rename it as ``mujoco.dylib``.
Linux
_____
Expand the ``tar.gz`` archive to ``~/.mujoco``. Then copy the dynamic library from
``~/.mujoco/mujoco-3.1.4/lib/libmujoco.so.3.1.4`` and rename it as ``libmujoco.so``.
``~/.mujoco/mujoco-3.1.5/lib/libmujoco.so.3.1.5`` and rename it as ``libmujoco.so``.
Windows
_______
+17 -12
View File
@@ -637,6 +637,7 @@ struct mjModel_ {
int nD; // number of non-zeros in sparse dof-dof matrix
int nB; // number of non-zeros in sparse body-dof matrix
int ntree; // number of kinematic trees under world body
int ngravcomp; // number of bodies with nonzero gravcomp
int nemax; // number of potential equality-constraint rows
int njmax; // number of available rows in constraint Jacobian
int nconmax; // number of potential contacts in contact list
@@ -794,6 +795,7 @@ struct mjModel_ {
int* light_targetbodyid; // id of targeted body; -1: none (nlight x 1)
mjtByte* light_directional; // directional light (nlight x 1)
mjtByte* light_castshadow; // does light cast shadows (nlight x 1)
float* light_bulbradius; // light radius for soft shadows (nlight x 1)
mjtByte* light_active; // is light on (nlight x 1)
mjtNum* light_pos; // position rel. to body frame (nlight x 3)
mjtNum* light_dir; // direction rel. to body frame (nlight x 3)
@@ -880,6 +882,7 @@ struct mjModel_ {
int* mesh_facenormal; // normal face data (nmeshface x 3)
int* mesh_facetexcoord; // texture face data (nmeshface x 3)
int* mesh_graph; // convex graph data (nmeshgraph x 1)
mjtNum* mesh_scale; // scaling applied to asset vertices (nmesh x 3)
mjtNum* mesh_pos; // translation applied to asset vertices (nmesh x 3)
mjtNum* mesh_quat; // rotation applied to asset vertices (nmesh x 4)
int* mesh_pathadr; // address of asset path for mesh; -1: none (nmesh x 1)
@@ -909,20 +912,20 @@ struct mjModel_ {
int* skin_pathadr; // address of asset path for skin; -1: none (nskin x 1)
// height fields
mjtNum* hfield_size; // (x, y, z_top, z_bottom) (nhfield x 4)
int* hfield_nrow; // number of rows in grid (nhfield x 1)
int* hfield_ncol; // number of columns in grid (nhfield x 1)
int* hfield_adr; // address in hfield_data (nhfield x 1)
float* hfield_data; // elevation data (nhfielddata x 1)
int* hfield_pathadr; // address of asset path for hfield; -1: none (nhfield x 1)
mjtNum* hfield_size; // (x, y, z_top, z_bottom) (nhfield x 4)
int* hfield_nrow; // number of rows in grid (nhfield x 1)
int* hfield_ncol; // number of columns in grid (nhfield x 1)
int* hfield_adr; // address in hfield_data (nhfield x 1)
float* hfield_data; // elevation data (nhfielddata x 1)
int* hfield_pathadr; // address of hfield asset path; -1: none (nhfield x 1)
// textures
int* tex_type; // texture type (mjtTexture) (ntex x 1)
int* tex_height; // number of rows in texture image (ntex x 1)
int* tex_width; // number of columns in texture image (ntex x 1)
int* tex_adr; // address in rgb (ntex x 1)
mjtByte* tex_rgb; // rgb (alpha = 1) (ntexdata x 1)
int* tex_pathadr; // address of asset path for texture; -1: none (ntex x 1)
int* tex_type; // texture type (mjtTexture) (ntex x 1)
int* tex_height; // number of rows in texture image (ntex x 1)
int* tex_width; // number of columns in texture image (ntex x 1)
int* tex_adr; // address in rgb (ntex x 1)
mjtByte* tex_rgb; // rgb (alpha = 1) (ntexdata x 1)
int* tex_pathadr; // address of texture asset path; -1: none (ntex x 1)
// materials
int* mat_texid; // texture id; -1: none (nmat x 1)
@@ -932,6 +935,8 @@ struct mjModel_ {
float* mat_specular; // specular (x white) (nmat x 1)
float* mat_shininess; // shininess coef (nmat x 1)
float* mat_reflectance; // reflectance (0: disable) (nmat x 1)
float* mat_metallic; // metallic coef (nmat x 1)
float* mat_roughness; // roughness coef (nmat x 1)
float* mat_rgba; // rgba (nmat x 4)
// predefined geom pairs for collision detection; has precedence over exclude
+4
View File
@@ -263,6 +263,7 @@ struct mjvLight_ { // OpenGL light
mjtByte headlight; // headlight
mjtByte directional; // directional light
mjtByte castshadow; // does light cast shadows
float bulbradius; // bulb radius for soft shadows
};
typedef struct mjvLight_ mjvLight;
@@ -493,6 +494,7 @@ struct mjvSceneState_ {
mjtByte* light_directional;
mjtByte* light_castshadow;
float* light_bulbradius;
mjtByte* light_active;
float* light_attenuation;
float* light_cutoff;
@@ -560,6 +562,8 @@ struct mjvSceneState_ {
float* mat_specular;
float* mat_shininess;
float* mat_reflectance;
float* mat_metallic;
float* mat_roughness;
float* mat_rgba;
int* eq_type;
+5
View File
@@ -139,6 +139,7 @@
X ( njmax ) \
X ( nconmax ) \
XMJV( ntree ) \
X ( ngravcomp ) \
X ( nuserdata ) \
XMJV( nsensordata ) \
X ( npluginstate ) \
@@ -292,6 +293,7 @@
X ( int, light_targetbodyid, nlight, 1 ) \
XMJV( mjtByte, light_directional, nlight, 1 ) \
XMJV( mjtByte, light_castshadow, nlight, 1 ) \
XMJV( float, light_bulbradius, nlight, 1 ) \
XMJV( mjtByte, light_active, nlight, 1 ) \
X ( mjtNum, light_pos, nlight, 3 ) \
X ( mjtNum, light_dir, nlight, 3 ) \
@@ -365,6 +367,7 @@
XMJV( int, mesh_bvhadr, nmesh, 1 ) \
XMJV( int, mesh_bvhnum, nmesh, 1 ) \
XMJV( int, mesh_graphadr, nmesh, 1 ) \
X ( mjtNum, mesh_scale, nmesh, 3 ) \
X ( mjtNum, mesh_pos, nmesh, 3 ) \
X ( mjtNum, mesh_quat, nmesh, 4 ) \
X ( float, mesh_vert, nmeshvert, 3 ) \
@@ -416,6 +419,8 @@
XMJV( float, mat_specular, nmat, 1 ) \
XMJV( float, mat_shininess, nmat, 1 ) \
XMJV( float, mat_reflectance, nmat, 1 ) \
XMJV( float, mat_metallic, nmat, 1 ) \
XMJV( float, mat_roughness, nmat, 1 ) \
XMJV( float, mat_rgba, nmat, 4 ) \
X ( int, pair_dim, npair, 1 ) \
X ( int, pair_geom1, npair, 1 ) \
+6 -4
View File
@@ -24,7 +24,7 @@ extern "C" {
#endif
// header version; should match the library version as returned by mj_version()
#define mjVERSION_HEADER 314
#define mjVERSION_HEADER 315
// needed to define size_t, fabs and log10
#include <stdlib.h>
@@ -203,10 +203,10 @@ MJAPI void mj_freeStack(mjData* d);
MJAPI void* mj_stackAllocByte(mjData* d, size_t bytes, size_t alignment);
// Allocate array of mjtNums on mjData stack. Call mju_error on stack overflow.
MJAPI mjtNum* mj_stackAllocNum(mjData* d, int size);
MJAPI mjtNum* mj_stackAllocNum(mjData* d, size_t size);
// Allocate array of ints on mjData stack. Call mju_error on stack overflow.
MJAPI int* mj_stackAllocInt(mjData* d, int size);
MJAPI int* mj_stackAllocInt(mjData* d, size_t size);
// Free memory allocation in mjData.
MJAPI void mj_deleteData(mjData* d);
@@ -1033,7 +1033,6 @@ MJAPI void mju_transformSpatial(mjtNum res[6], const mjtNum vec[6], int flg_forc
const mjtNum newpos[3], const mjtNum oldpos[3],
const mjtNum rotnew2old[9]);
//---------------------------------- Quaternions ---------------------------------------------------
// Rotate vector by quaternion.
@@ -1072,6 +1071,9 @@ MJAPI void mju_quatIntegrate(mjtNum quat[4], const mjtNum vel[3], mjtNum scale);
// Construct quaternion performing rotation from z-axis to given vector.
MJAPI void mju_quatZ2Vec(mjtNum quat[4], const mjtNum vec[3]);
// Convert sequence of Euler angles (radians) to quaternion.
// seq[0,1,2] must be in 'xyzXYZ', lower/upper-case mean intrinsic/extrinsic rotations.
MJAPI void mju_euler2Quat(mjtNum quat[4], const mjtNum euler[3], const char* seq);
//---------------------------------- Poses ---------------------------------------------------------
+30 -2
View File
@@ -774,7 +774,7 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
),
FunctionParameterDecl(
name='size',
type=ValueType(name='int'),
type=ValueType(name='size_t'),
),
),
doc='Allocate array of mjtNums on mjData stack. Call mju_error on stack overflow.', # pylint: disable=line-too-long
@@ -794,7 +794,7 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
),
FunctionParameterDecl(
name='size',
type=ValueType(name='int'),
type=ValueType(name='size_t'),
),
),
doc='Allocate array of ints on mjData stack. Call mju_error on stack overflow.', # pylint: disable=line-too-long
@@ -6982,6 +6982,34 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([
),
doc='Construct quaternion performing rotation from z-axis to given vector.', # pylint: disable=line-too-long
)),
('mju_euler2Quat',
FunctionDecl(
name='mju_euler2Quat',
return_type=ValueType(name='void'),
parameters=(
FunctionParameterDecl(
name='quat',
type=ArrayType(
inner_type=ValueType(name='mjtNum'),
extents=(4,),
),
),
FunctionParameterDecl(
name='euler',
type=ArrayType(
inner_type=ValueType(name='mjtNum', is_const=True),
extents=(3,),
),
),
FunctionParameterDecl(
name='seq',
type=PointerType(
inner_type=ValueType(name='char', is_const=True),
),
),
),
doc="Convert sequence of Euler angles (radians) to quaternion. seq[0,1,2] must be in 'xyzXYZ', lower/upper-case mean intrinsic/extrinsic rotations.", # pylint: disable=line-too-long
)),
('mju_mulPose',
FunctionDecl(
name='mju_mulPose',
+71 -12
View File
@@ -1200,6 +1200,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([
type=ValueType(name='int'),
doc='number of kinematic trees under world body',
),
StructFieldDecl(
name='ngravcomp',
type=ValueType(name='int'),
doc='number of bodies with nonzero gravcomp',
),
StructFieldDecl(
name='nemax',
type=ValueType(name='int'),
@@ -2095,6 +2100,13 @@ STRUCTS: Mapping[str, StructDecl] = dict([
),
doc='does light cast shadows (nlight x 1)',
),
StructFieldDecl(
name='light_bulbradius',
type=PointerType(
inner_type=ValueType(name='float'),
),
doc='light radius for soft shadows (nlight x 1)',
),
StructFieldDecl(
name='light_active',
type=PointerType(
@@ -2655,6 +2667,13 @@ STRUCTS: Mapping[str, StructDecl] = dict([
),
doc='convex graph data (nmeshgraph x 1)', # pylint: disable=line-too-long
),
StructFieldDecl(
name='mesh_scale',
type=PointerType(
inner_type=ValueType(name='mjtNum'),
),
doc='scaling applied to asset vertices (nmesh x 3)',
),
StructFieldDecl(
name='mesh_pos',
type=PointerType(
@@ -2835,84 +2854,84 @@ STRUCTS: Mapping[str, StructDecl] = dict([
type=PointerType(
inner_type=ValueType(name='mjtNum'),
),
doc='(x, y, z_top, z_bottom) (nhfield x 4)',
doc='(x, y, z_top, z_bottom) (nhfield x 4)',
),
StructFieldDecl(
name='hfield_nrow',
type=PointerType(
inner_type=ValueType(name='int'),
),
doc='number of rows in grid (nhfield x 1)',
doc='number of rows in grid (nhfield x 1)',
),
StructFieldDecl(
name='hfield_ncol',
type=PointerType(
inner_type=ValueType(name='int'),
),
doc='number of columns in grid (nhfield x 1)',
doc='number of columns in grid (nhfield x 1)',
),
StructFieldDecl(
name='hfield_adr',
type=PointerType(
inner_type=ValueType(name='int'),
),
doc='address in hfield_data (nhfield x 1)',
doc='address in hfield_data (nhfield x 1)',
),
StructFieldDecl(
name='hfield_data',
type=PointerType(
inner_type=ValueType(name='float'),
),
doc='elevation data (nhfielddata x 1)', # pylint: disable=line-too-long
doc='elevation data (nhfielddata x 1)', # pylint: disable=line-too-long
),
StructFieldDecl(
name='hfield_pathadr',
type=PointerType(
inner_type=ValueType(name='int'),
),
doc='address of asset path for hfield; -1: none (nhfield x 1)',
doc='address of hfield asset path; -1: none (nhfield x 1)',
),
StructFieldDecl(
name='tex_type',
type=PointerType(
inner_type=ValueType(name='int'),
),
doc='texture type (mjtTexture) (ntex x 1)',
doc='texture type (mjtTexture) (ntex x 1)',
),
StructFieldDecl(
name='tex_height',
type=PointerType(
inner_type=ValueType(name='int'),
),
doc='number of rows in texture image (ntex x 1)',
doc='number of rows in texture image (ntex x 1)',
),
StructFieldDecl(
name='tex_width',
type=PointerType(
inner_type=ValueType(name='int'),
),
doc='number of columns in texture image (ntex x 1)',
doc='number of columns in texture image (ntex x 1)',
),
StructFieldDecl(
name='tex_adr',
type=PointerType(
inner_type=ValueType(name='int'),
),
doc='address in rgb (ntex x 1)',
doc='address in rgb (ntex x 1)',
),
StructFieldDecl(
name='tex_rgb',
type=PointerType(
inner_type=ValueType(name='mjtByte'),
),
doc='rgb (alpha = 1) (ntexdata x 1)', # pylint: disable=line-too-long
doc='rgb (alpha = 1) (ntexdata x 1)',
),
StructFieldDecl(
name='tex_pathadr',
type=PointerType(
inner_type=ValueType(name='int'),
),
doc='address of asset path for texture; -1: none (ntex x 1)',
doc='address of texture asset path; -1: none (ntex x 1)',
),
StructFieldDecl(
name='mat_texid',
@@ -2963,6 +2982,20 @@ STRUCTS: Mapping[str, StructDecl] = dict([
),
doc='reflectance (0: disable) (nmat x 1)',
),
StructFieldDecl(
name='mat_metallic',
type=PointerType(
inner_type=ValueType(name='float'),
),
doc='metallic coef (nmat x 1)',
),
StructFieldDecl(
name='mat_roughness',
type=PointerType(
inner_type=ValueType(name='float'),
),
doc='roughness coef (nmat x 1)',
),
StructFieldDecl(
name='mat_rgba',
type=PointerType(
@@ -5610,6 +5643,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([
type=ValueType(name='mjtByte'),
doc='does light cast shadows',
),
StructFieldDecl(
name='bulbradius',
type=ValueType(name='float'),
doc='bulb radius for soft shadows',
),
),
)),
('mjvOption',
@@ -6705,6 +6743,13 @@ STRUCTS: Mapping[str, StructDecl] = dict([
),
doc='',
),
StructFieldDecl(
name='light_bulbradius',
type=PointerType(
inner_type=ValueType(name='float'),
),
doc='',
),
StructFieldDecl(
name='light_active',
type=PointerType(
@@ -7132,6 +7177,20 @@ STRUCTS: Mapping[str, StructDecl] = dict([
),
doc='',
),
StructFieldDecl(
name='mat_metallic',
type=PointerType(
inner_type=ValueType(name='float'),
),
doc='',
),
StructFieldDecl(
name='mat_roughness',
type=PointerType(
inner_type=ValueType(name='float'),
),
doc='',
),
StructFieldDecl(
name='mat_rgba',
type=PointerType(
+2 -5
View File
@@ -16,12 +16,7 @@
# pylint:disable=g-importing-member
from mujoco.mjx._src.collision_driver import collision
from mujoco.mjx._src.collision_driver import get_params
from mujoco.mjx._src.collision_driver import ncon
from mujoco.mjx._src.constraint import count_constraints
from mujoco.mjx._src.constraint import make_constraint
from mujoco.mjx._src.device import device_get_into
from mujoco.mjx._src.device import device_put
from mujoco.mjx._src.forward import euler
from mujoco.mjx._src.forward import forward
from mujoco.mjx._src.forward import fwd_acceleration
@@ -47,7 +42,9 @@ from mujoco.mjx._src.smooth import rne
from mujoco.mjx._src.smooth import transmission
from mujoco.mjx._src.solver import solve
from mujoco.mjx._src.support import full_m
from mujoco.mjx._src.support import id2name
from mujoco.mjx._src.support import is_sparse
from mujoco.mjx._src.support import mul_m
from mujoco.mjx._src.support import name2id
from mujoco.mjx._src.test_util import benchmark
from mujoco.mjx._src.types import *
-67
View File
@@ -1,67 +0,0 @@
# Copyright 2023 DeepMind Technologies Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Collision base."""
import dataclasses
from typing import Dict, List, Optional, Tuple
import jax
# pylint: disable=g-importing-member
from mujoco.mjx._src.dataclasses import PyTreeNode
from mujoco.mjx._src.types import GeomType
# pylint: enable=g-importing-member
Contact = Tuple[jax.Array, jax.Array, jax.Array]
@dataclasses.dataclass
class Candidate:
geom1: int
geom2: int
ipair: int
geomp: int # priority geom
dim: int
CandidateSet = Dict[
Tuple[GeomType, GeomType, Tuple[int, ...], Tuple[int, ...]],
List[Candidate],
]
class GeomInfo(PyTreeNode):
"""Collision info for a geom."""
geom_id: jax.Array
pos: jax.Array
mat: jax.Array
size: jax.Array
face: Optional[jax.Array] = None
vert: Optional[jax.Array] = None
edge_dir: Optional[jax.Array] = None
facenorm: Optional[jax.Array] = None
edge: Optional[jax.Array] = None
edge_face_normal: Optional[jax.Array] = None
class SolverParams(PyTreeNode):
"""Contact solver params."""
friction: jax.Array
solref: jax.Array
solreffriction: jax.Array
solimp: jax.Array
margin: jax.Array
gap: jax.Array
+61 -27
View File
@@ -20,12 +20,53 @@ from typing import Tuple
import jax
from jax import numpy as jp
from mujoco.mjx._src import math
from mujoco.mjx._src import mesh
# pylint: disable=g-importing-member
from mujoco.mjx._src.collision_base import Contact
from mujoco.mjx._src.collision_base import GeomInfo
from mujoco.mjx._src.collision_types import Collision
from mujoco.mjx._src.collision_types import ConvexInfo
from mujoco.mjx._src.collision_types import FunctionKey
from mujoco.mjx._src.collision_types import GeomInfo
from mujoco.mjx._src.types import Data
from mujoco.mjx._src.types import GeomType
from mujoco.mjx._src.types import Model
# pylint: enable=g-importing-member
def collider(ncon: int):
"""Wraps collision functions for use by collision_driver."""
def wrapper(func):
def collide(
m: Model, d: Data, key: FunctionKey, geom: jax.Array
) -> Collision:
g1, g2 = geom.T
infos = [
GeomInfo(d.geom_xpos[g1], d.geom_xmat[g1], m.geom_size[g1]),
GeomInfo(d.geom_xpos[g2], d.geom_xmat[g2], m.geom_size[g2]),
]
in_axes = [0, 0]
for i in [0, 1]:
if key.types[i] == GeomType.BOX:
infos[i] = mesh.box(infos[i])
in_axes[i] = jax.tree_util.tree_map(lambda x: None, infos[i]).replace(
pos=0, mat=0, face=0, vert=0
)
elif key.types[i] == GeomType.MESH:
infos[i] = mesh.convex(m, key.data_ids[i], infos[i])
in_axes[i] = jax.tree_util.tree_map(lambda x: None, infos[i]).replace(
pos=0, mat=0
)
dist, pos, frame = jax.vmap(func, in_axes=in_axes)(*infos)
if ncon > 1:
return jax.tree_util.tree_map(jp.concatenate, (dist, pos, frame))
return dist, pos, frame
collide.ncon = ncon
return collide
return wrapper
def _closest_segment_point_plane(
a: jax.Array, b: jax.Array, p0: jax.Array, plane_normal: jax.Array
) -> jax.Array:
@@ -178,7 +219,8 @@ def _manifold_points(
return jp.array([a_idx, b_idx, c_idx, d_idx])
def plane_convex(plane: GeomInfo, convex: GeomInfo) -> Contact:
@collider(ncon=4)
def plane_convex(plane: GeomInfo, convex: ConvexInfo) -> Collision:
"""Calculates contacts between a plane and a convex object."""
vert = convex.vert
@@ -186,7 +228,7 @@ def plane_convex(plane: GeomInfo, convex: GeomInfo) -> Contact:
plane_pos = convex.mat.T @ (plane.pos - convex.pos)
n = convex.mat.T @ plane.mat[:, 2]
support = (plane_pos - vert) @ n
idx = _manifold_points(vert, support > 0, n)
idx = _manifold_points(vert, support > jp.maximum(0, support.max() - 1e-4), n)
pos = vert[idx]
# convert to world frame
@@ -200,10 +242,11 @@ def plane_convex(plane: GeomInfo, convex: GeomInfo) -> Contact:
return dist, pos, frame
def sphere_convex(sphere: GeomInfo, convex: GeomInfo) -> Contact:
@collider(ncon=1)
def sphere_convex(sphere: GeomInfo, convex: ConvexInfo) -> Collision:
"""Calculates contact between a sphere and a convex object."""
faces = convex.face
normals = convex.facenorm
normals = convex.face_normal
# Put sphere in convex frame.
sphere_pos = convex.mat.T @ (sphere.pos - convex.pos)
@@ -262,16 +305,15 @@ def sphere_convex(sphere: GeomInfo, convex: GeomInfo) -> Contact:
n = convex.mat @ n
pos = convex.mat @ pos + convex.pos
return jax.tree_map(
lambda x: jp.expand_dims(x, axis=0), (dist, pos, math.make_frame(n))
)
return dist, pos, math.make_frame(n)
def capsule_convex(cap: GeomInfo, convex: GeomInfo) -> Contact:
@collider(ncon=2)
def capsule_convex(cap: GeomInfo, convex: ConvexInfo) -> Collision:
"""Calculates contacts between a capsule and a convex object."""
# Get convex transformed normals, faces, and vertices.
faces = convex.face
normals = convex.facenorm
normals = convex.face_normal
# Put capsule in convex frame.
cap_pos = convex.mat.T @ (cap.pos - convex.pos)
@@ -347,7 +389,7 @@ def capsule_convex(cap: GeomInfo, convex: GeomInfo) -> Contact:
degenerate_edge_dir,
edge_closest_pt,
cap_closest_pt,
) = jax.tree_map(lambda x, i=e_idx: jp.take(x, i, axis=0), res)
) = jax.tree_util.tree_map(lambda x, i=e_idx: jp.take(x, i, axis=0), res)
edge_face_normals = edge_face_normal[e_idx]
edge_voronoi_front = ((edge_face_normals @ edge_axis) < 0).all()
@@ -869,10 +911,9 @@ def _sat_gaussmap(
return dist, pos, normal
def convex_convex(c1: GeomInfo, c2: GeomInfo) -> Contact:
@collider(ncon=4)
def convex_convex(c1: ConvexInfo, c2: ConvexInfo) -> Collision:
"""Calculates contacts between two convex objects."""
if c1.face is None or c2.face is None or c1.vert is None or c2.vert is None:
raise AssertionError('Mesh info missing.')
# pad face vertices so that we can broadcast between geom1 and geom2
# face has shape (n_face, n_vert, 3)
nvert1, nvert2 = c1.face.shape[1], c2.face.shape[1]
@@ -895,14 +936,14 @@ def convex_convex(c1: GeomInfo, c2: GeomInfo) -> Contact:
to_local_mat = c2.mat.T @ c1.mat
faces1 = to_local_pos + faces1 @ to_local_mat.T
normals1 = c1.facenorm @ to_local_mat.T
normals2 = c2.facenorm
normals1 = c1.face_normal @ to_local_mat.T
normals2 = c2.face_normal
vertices1 = to_local_pos + c1.vert @ to_local_mat.T
vertices2 = c2.vert
unique_edges1 = jp.take(vertices1, c1.edge, axis=0)
unique_edges2 = jp.take(vertices2, c2.edge, axis=0)
unique_edges1 = jp.take(vertices1, c1.edge_dir, axis=0)
unique_edges2 = jp.take(vertices2, c2.edge_dir, axis=0)
edges1 = jp.take(vertices1, c1.edge, axis=0)
edges2 = jp.take(vertices2, c2.edge, axis=0)
@@ -944,13 +985,6 @@ def convex_convex(c1: GeomInfo, c2: GeomInfo) -> Contact:
pos = c2.pos + pos @ c2.mat.T
normal = normal @ c2.mat.T
normal = -normal if swapped else normal
frame = jax.vmap(math.make_frame)(normal)
return dist, pos, frame
# store ncon as function attributes
plane_convex.ncon = 4
sphere_convex.ncon = 1
capsule_convex.ncon = 2
convex_convex.ncon = 4
+296 -332
View File
@@ -12,37 +12,63 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Collide geometries."""
"""Runs collision checking for all geoms in a Model.
from typing import Callable, Dict, List, Optional, Sequence, Tuple, Union
To do this, collision_driver builds a collision function table, and then runs
the collision functions serially on the parameters in the table.
For example, if a Model has three geoms:
geom | type
---------------
1 | sphere
2 | capsule
3 | sphere
collision_driver organizes it into these functions and runs them:
function | geom pair
--------------------------
sphere_sphere | (1, 3)
sphere_capsule | (1, 2), (2, 3)
Besides collision function, function tables are keyed on mesh id and condim,
in order to guarantee static shapes for contacts and jacobians.
"""
import itertools
from typing import Dict, Iterator, List, Tuple, Union
import jax
from jax import numpy as jp
import mujoco
from mujoco.mjx._src import collision_base
from mujoco.mjx._src import mesh
from mujoco.mjx._src import support
# pylint: disable=g-importing-member
from mujoco.mjx._src.collision_base import Candidate
from mujoco.mjx._src.collision_base import CandidateSet
from mujoco.mjx._src.collision_base import GeomInfo
from mujoco.mjx._src.collision_base import SolverParams
from mujoco.mjx._src.collision_convex import capsule_convex
from mujoco.mjx._src.collision_convex import convex_convex
from mujoco.mjx._src.collision_convex import plane_convex
from mujoco.mjx._src.collision_convex import sphere_convex
from mujoco.mjx._src.collision_primitive import capsule_capsule
from mujoco.mjx._src.collision_primitive import plane_capsule
from mujoco.mjx._src.collision_primitive import plane_cylinder
from mujoco.mjx._src.collision_primitive import plane_ellipsoid
from mujoco.mjx._src.collision_primitive import plane_sphere
from mujoco.mjx._src.collision_primitive import sphere_capsule
from mujoco.mjx._src.collision_primitive import sphere_sphere
from mujoco.mjx._src.collision_sdf import capsule_cylinder
from mujoco.mjx._src.collision_sdf import capsule_ellipsoid
from mujoco.mjx._src.collision_sdf import cylinder_cylinder
from mujoco.mjx._src.collision_sdf import ellipsoid_cylinder
from mujoco.mjx._src.collision_sdf import ellipsoid_ellipsoid
from mujoco.mjx._src.collision_types import FunctionKey
from mujoco.mjx._src.types import Contact
from mujoco.mjx._src.types import Data
from mujoco.mjx._src.types import DisableBit
from mujoco.mjx._src.types import GeomType
from mujoco.mjx._src.types import Model
# pylint: enable=g-importing-member
import numpy as np
# pair-wise collision functions
_COLLISION_FUNC = {
@@ -50,6 +76,7 @@ _COLLISION_FUNC = {
(GeomType.PLANE, GeomType.CAPSULE): plane_capsule,
(GeomType.PLANE, GeomType.BOX): plane_convex,
(GeomType.PLANE, GeomType.ELLIPSOID): plane_ellipsoid,
(GeomType.PLANE, GeomType.CYLINDER): plane_cylinder,
(GeomType.PLANE, GeomType.MESH): plane_convex,
(GeomType.SPHERE, GeomType.SPHERE): sphere_sphere,
(GeomType.SPHERE, GeomType.CAPSULE): sphere_capsule,
@@ -57,378 +84,315 @@ _COLLISION_FUNC = {
(GeomType.SPHERE, GeomType.MESH): sphere_convex,
(GeomType.CAPSULE, GeomType.CAPSULE): capsule_capsule,
(GeomType.CAPSULE, GeomType.BOX): capsule_convex,
(GeomType.CAPSULE, GeomType.ELLIPSOID): capsule_ellipsoid,
(GeomType.CAPSULE, GeomType.CYLINDER): capsule_cylinder,
(GeomType.CAPSULE, GeomType.MESH): capsule_convex,
(GeomType.ELLIPSOID, GeomType.ELLIPSOID): ellipsoid_ellipsoid,
(GeomType.ELLIPSOID, GeomType.CYLINDER): ellipsoid_cylinder,
(GeomType.CYLINDER, GeomType.CYLINDER): cylinder_cylinder,
(GeomType.BOX, GeomType.BOX): convex_convex,
(GeomType.BOX, GeomType.MESH): convex_convex,
(GeomType.MESH, GeomType.MESH): convex_convex,
}
def get_collision_fn(
key: Tuple[Union[GeomType, mujoco.mjtGeom], Union[GeomType, mujoco.mjtGeom]]
) -> Optional[Callable[[GeomInfo, GeomInfo], collision_base.Contact]]:
"""Returns a collision function given a pair of geom types."""
return _COLLISION_FUNC.get(key, None)
# geoms for which we ignore broadphase
_GEOM_NO_BROADPHASE = {GeomType.HFIELD, GeomType.PLANE}
def _add_candidate(
result: CandidateSet,
def has_collision_fn(t1: GeomType, t2: GeomType) -> bool:
"""Returns True if a collision function exists for a pair of geom types."""
return (t1, t2) in _COLLISION_FUNC
def geom_pairs(
m: Union[Model, mujoco.MjModel],
g1: int,
g2: int,
ipair: int = -1,
):
"""Adds a candidate to test for collision."""
t1, t2 = m.geom_type[g1], m.geom_type[g2]
if t1 > t2:
t1, t2, g1, g2 = t2, t1, g2, g1
) -> Iterator[Tuple[int, int, int]]:
"""Yields geom pairs to check for collisions.
# MuJoCo does not collide planes with other planes or hfields
if t1 == GeomType.PLANE and t2 == GeomType.PLANE:
return
if t1 == GeomType.PLANE and t2 == GeomType.HFIELD:
return
Args:
m: a MuJoCo or MJX model
def mesh_key(i):
convex_data = [[None] * m.ngeom] * 3
if isinstance(m, Model):
convex_data = [
m.geom_convex_face,
m.geom_convex_vert,
m.geom_convex_edge_dir,
]
elif isinstance(m, mujoco.MjModel):
kwargs = mesh.get(m)
convex_data = [
kwargs['geom_convex_face'],
kwargs['geom_convex_vert'],
kwargs['geom_convex_edge_dir'],
]
key = tuple((-1,) if v[i] is None else v[i].shape for v in convex_data)
return key
Yields:
geom1, geom2, and pair index if defined in <pair> (else -1)
"""
pairs = set()
k1, k2 = mesh_key(g1), mesh_key(g2)
for i in range(m.npair):
g1, g2 = m.pair_geom1[i], m.pair_geom2[i]
# order pairs by geom_type for correct function mapping
if m.geom_type[g1] > m.geom_type[g2]:
g1, g2 = g2, g1
pairs.add((g1, g2))
yield g1, g2, i
candidates = {(c.geom1, c.geom2) for c in result.get((t1, t2, k1, k2), [])}
if (g1, g2) in candidates:
return
if ipair > -1:
candidate = Candidate(g1, g2, ipair, -1, m.pair_dim[ipair])
elif m.geom_priority[g1] != m.geom_priority[g2]:
gp = g1 if m.geom_priority[g1] > m.geom_priority[g2] else g2
candidate = Candidate(g1, g2, -1, gp, m.geom_condim[gp])
else:
dim = max(m.geom_condim[g1], m.geom_condim[g2])
candidate = Candidate(g1, g2, -1, -1, dim)
result.setdefault((t1, t2, k1, k2), []).append(candidate)
def _pair_params(
m: Model,
candidates: Sequence[Candidate],
) -> SolverParams:
"""Gets solver params for pair geoms."""
ipair = jp.array([c.ipair for c in candidates])
friction = jp.clip(m.pair_friction[ipair], a_min=mujoco.mjMINMU)
solref = m.pair_solref[ipair]
solreffriction = m.pair_solreffriction[ipair]
solimp = m.pair_solimp[ipair]
margin = m.pair_margin[ipair]
gap = m.pair_gap[ipair]
return SolverParams(friction, solref, solreffriction, solimp, margin, gap)
def _priority_params(
m: Model,
candidates: Sequence[Candidate],
) -> SolverParams:
"""Gets solver params from priority geoms."""
geomp = jp.array([c.geomp for c in candidates])
friction = m.geom_friction[geomp][:, jp.array([0, 0, 1, 2, 2])]
solref = m.geom_solref[geomp]
solreffriction = jp.zeros(geomp.shape + (mujoco.mjNREF,))
solimp = m.geom_solimp[geomp]
g = jp.array([(c.geom1, c.geom2) for c in candidates])
margin = jp.amax(m.geom_margin[g.T], axis=0)
gap = jp.amax(m.geom_gap[g.T], axis=0)
return SolverParams(friction, solref, solreffriction, solimp, margin, gap)
def _dynamic_params(
m: Model,
candidates: Sequence[Candidate],
) -> SolverParams:
"""Gets solver params for dynamic geoms."""
g1 = jp.array([c.geom1 for c in candidates])
g2 = jp.array([c.geom2 for c in candidates])
friction = jp.maximum(m.geom_friction[g1], m.geom_friction[g2])
# copy friction terms for the full geom pair
friction = friction[:, jp.array([0, 0, 1, 2, 2])]
minval = jp.array(mujoco.mjMINVAL)
solmix1, solmix2 = m.geom_solmix[g1], m.geom_solmix[g2]
mix = solmix1 / (solmix1 + solmix2)
mix = jp.where((solmix1 < minval) & (solmix2 < minval), 0.5, mix)
mix = jp.where((solmix1 < minval) & (solmix2 >= minval), 0.0, mix)
mix_fn = jax.vmap(lambda a, b, m: m * a + (1 - m) * b)
solref1, solref2 = m.geom_solref[g1], m.geom_solref[g2]
solref = jp.minimum(solref1, solref2)
s_mix = mix_fn(solref1, solref2, mix)
solref = jp.where((solref1[0] > 0) & (solref2[0] > 0), s_mix, solref)
solreffriction = jp.zeros(g1.shape + (mujoco.mjNREF,))
solimp = mix_fn(m.geom_solimp[g1], m.geom_solimp[g2], mix)
margin = jp.maximum(m.geom_margin[g1], m.geom_margin[g2])
gap = jp.maximum(m.geom_gap[g1], m.geom_gap[g2])
return SolverParams(friction, solref, solreffriction, solimp, margin, gap)
def get_params(
m: Union[Model, mujoco.MjModel], candidates: Sequence[Candidate]
) -> Tuple[List[int], List[int], SolverParams]:
"""Gets solver params for a list of collision candidates."""
# group sol params by different candidate types
typ_cands = {}
for c in candidates:
typ = (c.ipair > -1, c.geomp > -1)
typ_cands.setdefault(typ, []).append(c)
geom1, geom2, params = [], [], []
for (pair, priority), candidates in typ_cands.items():
geom1.extend([c.geom1 for c in candidates])
geom2.extend([c.geom2 for c in candidates])
if pair:
params.append(_pair_params(m, candidates))
elif priority:
params.append(_priority_params(m, candidates))
else:
params.append(_dynamic_params(m, candidates))
params = jax.tree_map(lambda *x: jp.concatenate(x), *params)
return geom1, geom2, params
def _pair_info(
m: Model, d: Data, geom1: Sequence[int], geom2: Sequence[int]
) -> Tuple[GeomInfo, GeomInfo, Sequence[Dict[str, Optional[int]]]]:
"""Returns geom pair info for calculating collision."""
def mesh_info(geom):
g = jp.array(geom)
info = GeomInfo(
g,
d.geom_xpos[g],
d.geom_xmat[g],
m.geom_size[g],
)
in_axes = jax.tree_map(lambda x: 0, info)
is_mesh = m.geom_convex_face[geom[0]] is not None
if is_mesh:
info = info.replace(
face=jp.stack([m.geom_convex_face[i] for i in geom]),
vert=jp.stack([m.geom_convex_vert[i] for i in geom]),
edge_dir=jp.stack([m.geom_convex_edge_dir[i] for i in geom]),
facenorm=jp.stack([m.geom_convex_facenormal[i] for i in geom]),
edge=jp.stack([m.geom_convex_edge[i] for i in geom]),
edge_face_normal=jp.stack(
[m.geom_convex_edge_face_normal[i] for i in geom]
),
)
in_axes = in_axes.replace(
face=0,
vert=0,
edge_dir=0,
facenorm=0,
edge=0,
edge_face_normal=0,
)
return info, in_axes
info1, in_axes1 = mesh_info(geom1)
info2, in_axes2 = mesh_info(geom2)
return info1, info2, [in_axes1, in_axes2]
def _body_pair_filter(
m: Union[Model, mujoco.MjModel], b1: int, b2: int
) -> bool:
"""Filters body pairs for collision."""
dsbl_filterparent = m.opt.disableflags & DisableBit.FILTERPARENT
weld1 = m.body_weldid[b1]
weld2 = m.body_weldid[b2]
parent_weld1 = m.body_weldid[m.body_parentid[weld1]]
parent_weld2 = m.body_weldid[m.body_parentid[weld2]]
if weld1 == weld2:
# filter out self-collisions
return True
if (
not dsbl_filterparent
and weld1 != 0
and weld2 != 0
and (weld1 == parent_weld2 or weld2 == parent_weld1)
):
# filter out parent-child collisions
return True
return False
def _broadphase_enabled(
geom_types: Tuple[GeomType, GeomType],
n_pairs: int,
max_pairs: int,
) -> bool:
return (
GeomType.PLANE not in geom_types
and max_pairs > -1
and n_pairs > max_pairs
)
def _collide_geoms(
m: Model,
d: Data,
geom_types: Tuple[GeomType, GeomType],
candidates: Sequence[Candidate],
) -> Contact:
"""Collides a geom pair."""
fn = get_collision_fn(geom_types)
if not fn:
return Contact.zero()
geom1, geom2, params = get_params(m, candidates)
g1, g2, in_axes = _pair_info(m, d, geom1, geom2)
# Run a crude version of broadphase.
max_pairs = int(support.get_custom_numeric(m, 'max_geom_pairs'))
run_broadphase = _broadphase_enabled(geom_types, len(geom1), max_pairs)
n_pairs = max_pairs if run_broadphase else len(geom1)
if run_broadphase:
# broadphase over geom pairs, using bounding spheres
size1 = jp.max(m.geom_size[g1.geom_id], axis=-1)
size2 = jp.max(m.geom_size[g2.geom_id], axis=-1)
dists = jax.vmap(jp.linalg.norm)(g2.pos - g1.pos) - (size1 + size2)
_, idx = jax.lax.top_k(-dists, k=n_pairs)
g1, g2, params = jax.tree_map(
lambda x, idx=idx: x[idx, ...], (g1, g2, params)
)
# call contact function
res = jax.vmap(fn, in_axes=in_axes)(g1, g2)
dist, pos, frame = jax.tree_map(jp.concatenate, res)
# repeat params by the number of contacts per geom pair
geom1, geom2, params = jax.tree_map(
lambda x: jp.repeat(x, fn.ncon, axis=0), # pytype: disable=attribute-error
(g1.geom_id, g2.geom_id, params),
)
con = Contact(
dist=dist,
pos=pos,
frame=frame,
includemargin=params.margin - params.gap,
friction=params.friction,
solref=params.solref,
solreffriction=params.solreffriction,
solimp=params.solimp,
geom1=geom1,
geom2=geom2,
)
return con
def collision_candidates(m: Union[Model, mujoco.MjModel]) -> CandidateSet:
"""Returns candidates for collision checking."""
candidate_set = {}
for ipair in range(m.npair):
g1, g2 = m.pair_geom1[ipair], m.pair_geom2[ipair]
_add_candidate(candidate_set, m, g1, g2, ipair)
body_pairs = []
exclude_signature = set(m.exclude_signature)
geom_con = m.geom_contype | m.geom_conaffinity
filterparent = not (m.opt.disableflags & DisableBit.FILTERPARENT)
b_start = m.body_geomadr
b_end = b_start + m.body_geomnum
for b1 in range(m.nbody):
if not geom_con[b_start[b1]:b_end[b1]].any():
continue
w1 = m.body_weldid[b1]
w1_p = m.body_weldid[m.body_parentid[w1]]
for b2 in range(b1, m.nbody):
if not geom_con[b_start[b2]:b_end[b2]].any():
continue
signature = (b1 << 16) + (b2)
if signature in exclude_signature:
continue
if _body_pair_filter(m, b1, b2):
w2 = m.body_weldid[b2]
# ignore self-collisions
if w1 == w2:
continue
body_pairs.append((b1, b2))
w2_p = m.body_weldid[m.body_parentid[w2]]
# ignore parent-child collisions
if filterparent and w1 != 0 and w2 != 0 and (w1 == w2_p or w2 == w1_p):
continue
g1_range = [g for g in range(b_start[b1], b_end[b1]) if geom_con[g]]
g2_range = [g for g in range(b_start[b2], b_end[b2]) if geom_con[g]]
for b1, b2 in body_pairs:
for g1 in range(b_start[b1], b_end[b1]):
if not geom_con[g1]:
continue
for g2 in range(b_start[b2], b_end[b2]):
if not geom_con[g2]:
for g1, g2 in itertools.product(g1_range, g2_range):
t1, t2 = m.geom_type[g1], m.geom_type[g2]
# order pairs by geom_type for correct function mapping
if t1 > t2:
g1, g2, t1, t2 = g2, g1, t2, t1
# ignore plane<>plane and plane<>hfield
if (t1, t2) == (GeomType.PLANE, GeomType.PLANE):
continue
if (t1, t2) == (GeomType.PLANE, GeomType.HFIELD):
continue
# geoms must match contype and conaffinity on some bit
mask = m.geom_contype[g1] & m.geom_conaffinity[g2]
mask |= m.geom_contype[g2] & m.geom_conaffinity[g1]
if mask != 0:
_add_candidate(candidate_set, m, g1, g2)
if not mask:
continue
return candidate_set
if (g1, g2) not in pairs:
pairs.add((g1, g2))
yield g1, g2, -1
def ncon(m: Union[Model, mujoco.MjModel]) -> int:
"""Returns the number of contacts computed in MJX given a model."""
def _geom_groups(
m: Union[Model, mujoco.MjModel],
) -> Dict[FunctionKey, List[Tuple[int, int, int]]]:
"""Returns geom pairs to check for collision grouped by collision function.
The grouping consists of:
- The collision function to run, which is determined by geom types
- For mesh geoms, convex functions are run for each distinct mesh in the
model, because the convex functions expect static mesh size. If a sphere
collides with a cube and a tetrahedron, sphere_convex is called twice.
- The condim of the collision. This ensures that the size of the resulting
constraint jacobian is determined at compile time.
Args:
m: a MuJoCo or MJX model
Returns:
a dict with grouping key and values geom1, geom2, pair index
"""
groups = {}
for g1, g2, ip in geom_pairs(m):
types = m.geom_type[g1], m.geom_type[g2]
data_ids = m.geom_dataid[g1], m.geom_dataid[g2]
if ip > -1:
condim = m.pair_dim[ip]
elif m.geom_priority[g1] > m.geom_priority[g2]:
condim = m.geom_condim[g1]
elif m.geom_priority[g1] < m.geom_priority[g2]:
condim = m.geom_condim[g2]
else:
condim = max(m.geom_condim[g1], m.geom_condim[g2])
key = FunctionKey(types, data_ids, condim)
groups.setdefault(key, []).append((g1, g2, ip))
return groups
def _contact_groups(m: Model, d: Data) -> Dict[FunctionKey, Contact]:
"""Returns contact groups to check for collisions.
Contacts are grouped the same way as _geom_groups. Only one contact is
emitted per geom pair, even if the collision function emits multiple contacts.
Args:
m: MJX model
d: MJX data
Returns:
a dict where the key is the grouping and value is a Contact
"""
groups = {}
eps = mujoco.mjMINVAL
for key, geom_ids in _geom_groups(m).items():
geom = np.array(geom_ids)
geom1, geom2, ip = geom.T
geom1, geom2, ip = geom1[ip == -1], geom2[ip == -1], ip[ip != -1]
params = []
if ip.size > 0:
# pair contacts get their params from m.pair_* fields
params.append((
m.pair_margin[ip] - m.pair_gap[ip],
jp.clip(m.pair_friction[ip], a_min=eps),
m.pair_solref[ip],
m.pair_solreffriction[ip],
m.pair_solimp[ip]
))
if geom1.size > 0 and geom2.size > 0:
# other contacts get their params from geom fields
margin = jp.maximum(m.geom_margin[geom1], m.geom_margin[geom2])
gap = jp.maximum(m.geom_gap[geom1], m.geom_gap[geom2])
solmix1, solmix2 = m.geom_solmix[geom1], m.geom_solmix[geom2]
mix = solmix1 / (solmix1 + solmix2)
mix = jp.where((solmix1 < eps) & (solmix2 < eps), 0.5, mix)
mix = jp.where((solmix1 < eps) & (solmix2 >= eps), 0.0, mix)
mix = jp.where((solmix1 >= eps) & (solmix2 < eps), 1.0, mix)
mix = mix[:, None] # for correct broadcasting
# friction: max
friction = jp.maximum(m.geom_friction[geom1], m.geom_friction[geom2])
solref1, solref2 = m.geom_solref[geom1], m.geom_solref[geom2]
# reference standard: mix
solref_standard = mix * solref1 + (1 - mix) * solref2
# reference direct: min
solref_direct = jp.minimum(solref1, solref2)
is_standard = (solref1[:, [0, 0]] > 0) & (solref2[:, [0, 0]] > 0)
solref = jp.where(is_standard, solref_standard, solref_direct)
solreffriction = jp.zeros(geom1.shape + (mujoco.mjNREF,))
# impedance: mix
solimp = mix * m.geom_solimp[geom1] + (1 - mix) * m.geom_solimp[geom2]
pri = m.geom_priority[geom1] != m.geom_priority[geom2]
if pri.any():
# use priority geom when specified instead of mixing
gp1, gp2 = m.geom_priority[geom1], m.geom_priority[geom2]
gp = np.where(gp1 > gp2, geom1, geom2)[pri]
friction = friction.at[pri].set(m.geom_friction[gp])
solref = solref.at[pri].set(m.geom_solref[gp])
solimp = solimp.at[pri].set(m.geom_solimp[gp])
# unpack 5d friction:
friction = friction[:, [0, 0, 1, 2, 2]]
params.append((margin - gap, friction, solref, solreffriction, solimp))
params = map(jp.concatenate, zip(*params))
includemargin, friction, solref, solreffriction, solimp = params
groups[key] = Contact(
# dist, pos, frame get filled in by collision functions:
dist=None,
pos=None,
frame=None,
includemargin=includemargin,
friction=friction,
solref=solref,
solreffriction=solreffriction,
solimp=solimp,
dim=d.contact.dim,
geom1=jp.array(geom[:, 0]),
geom2=jp.array(geom[:, 1]),
geom=jp.array(geom[:, :2]),
efc_address=d.contact.efc_address,
)
return groups
def _numeric(m: Union[Model, mujoco.MjModel], name: str) -> int:
id_ = support.name2id(m, mujoco.mjtObj.mjOBJ_NUMERIC, name)
return int(m.numeric_data[id_]) if id_ >= 0 else -1
def make_condim(m: Union[Model, mujoco.MjModel]) -> np.ndarray:
"""Returns the dims of the contacts for a Model."""
if m.opt.disableflags & DisableBit.CONTACT:
return 0
return np.empty(0, dtype=int)
candidates = collision_candidates(m)
max_count = int(support.get_custom_numeric(m, 'max_contact_points'))
max_pairs = int(support.get_custom_numeric(m, 'max_geom_pairs'))
group_counts = {k: len(v) for k, v in _geom_groups(m).items()}
count = 0
for k, v in candidates.items():
fn = get_collision_fn(k[0:2])
if fn is None:
continue
run_broadphase = _broadphase_enabled((k[0], k[1]), len(v), max_pairs)
n_pair = max_pairs if run_broadphase else len(v)
count += n_pair * fn.ncon # pytype: disable=attribute-error
# max_geom_pairs limits the number of pairs we process in a collision function
# by first running a primitive broad phase culling on the pairs
max_geom_pairs = _numeric(m, 'max_geom_pairs')
return min(max_count, count) if max_count > -1 else count
if max_geom_pairs > -1:
for k in group_counts:
if set(k.types) & _GEOM_NO_BROADPHASE:
continue
group_counts[k] = min(group_counts[k], max_geom_pairs)
# max_contact_points limits the number of contacts emitted by selecting the
# contacts with the most penetration after calling collision functions
max_contact_points = _numeric(m, 'max_contact_points')
condim_counts = {}
for k, v in group_counts.items():
func = _COLLISION_FUNC[k.types]
num_contacts = condim_counts.get(k.condim, 0) + func.ncon * v # pytype: disable=attribute-error
if max_contact_points > -1:
num_contacts = min(max_contact_points, num_contacts)
condim_counts[k.condim] = num_contacts
dims = sum(([c] * condim_counts[c] for c in sorted(condim_counts)), [])
return np.array(dims)
def collision(m: Model, d: Data) -> Data:
"""Collides geometries."""
if ncon(m) == 0:
return d.replace(contact=Contact.zero())
if d.ncon == 0:
return d
candidate_set = collision_candidates(m)
groups = _contact_groups(m, d)
max_geom_pairs = _numeric(m, 'max_geom_pairs')
max_contact_points = _numeric(m, 'max_contact_points')
contacts = []
for key, candidates in candidate_set.items():
geom_types = key[0:2]
contacts.append(_collide_geoms(m, d, geom_types, candidates))
# run collision functions on groups
for key, contact in groups.items():
# determine which contacts we'll use for collision testing by running a
# broad phase cull if requested
if (
max_geom_pairs > -1
and contact.geom.shape[0] > max_geom_pairs
and not set(key.types) & _GEOM_NO_BROADPHASE
):
pos1, pos2 = d.geom_xpos[contact.geom.T]
size1, size2 = m.geom_rbound[contact.geom.T]
dist = jax.vmap(jp.linalg.norm)(pos2 - pos1) - (size1 + size2)
_, idx = jax.lax.top_k(-dist, k=max_geom_pairs)
contact = jax.tree_util.tree_map(lambda x, idx=idx: x[idx], contact)
if not contacts:
raise RuntimeError('No contacts found.')
# run the collision function specified by the grouping key
func = _COLLISION_FUNC[key.types]
dist, pos, frame = func(m, d, key, contact.geom)
ncon = func.ncon # pytype: disable=attribute-error
if ncon > 1:
# repeat contacts to match the number of collisions returned
repeat_fn = lambda x, r=ncon: jp.repeat(x, r, axis=0)
contact = jax.tree_util.tree_map(repeat_fn, contact)
groups[key] = contact.replace(dist=dist, pos=pos, frame=frame)
contact = jax.tree_map(lambda *x: jp.concatenate(x), *contacts)
# collapse contacts together, ensuring they are grouped by condim
condim_groups = {}
for key, contact in groups.items():
condim_groups.setdefault(key.condim, []).append(contact)
max_contact_points = int(support.get_custom_numeric(m, 'max_contact_points'))
if max_contact_points > -1 and contact.dist.shape[0] > max_contact_points:
# get top-k contacts
_, idx = jax.lax.top_k(-contact.dist, k=max_contact_points)
contact = jax.tree_map(lambda x, idx=idx: jp.take(x, idx, axis=0), contact)
# limit the number of contacts per condim group if requested
if max_contact_points > -1:
for key, contacts in condim_groups.items():
contact = jax.tree_util.tree_map(lambda *x: jp.concatenate(x), *contacts)
if contact.geom.shape[0] > max_contact_points:
_, idx = jax.lax.top_k(-contact.dist, k=max_contact_points)
contact = jax.tree_util.tree_map(lambda x, idx=idx: x[idx], contact)
condim_groups[key] = [contact]
contacts = sum([condim_groups[k] for k in sorted(condim_groups)], [])
contact = jax.tree_util.tree_map(lambda *x: jp.concatenate(x), *contacts)
return d.replace(contact=contact)
+133 -15
View File
@@ -220,6 +220,73 @@ class EllipsoidCollisionTest(parameterized.TestCase):
_assert_attr_eq(
dx.contact, d.contact, field.name, 'ellipsoid-plane', 1e-5)
_ELLIPSOID_ELLIPSOID = """
<mujoco>
<worldbody>
<body>
<geom size=".15 .03 .05" type="ellipsoid"/>
</body>
<body pos="0 0 0.09">
<freejoint/>
<geom size=".15 .03 .05" type="ellipsoid"/>
</body>
</worldbody>
</mujoco>
"""
def test_ellipsoid_ellipsoid(self):
"""Tests ellipsoid ellipsoid contact."""
d, dx = _collide(self._ELLIPSOID_ELLIPSOID)
self.assertLess(dx.contact.dist[0], 0)
for field in dataclasses.fields(Contact):
_assert_attr_eq(
dx.contact, d.contact, field.name, 'ellipsoid-ellipsoid', 1e-5)
_ELLIPSOID_CAPSULE = """
<mujoco>
<worldbody>
<body>
<geom size=".15 .03 .05" type="ellipsoid"/>
</body>
<body pos="0 0 0.0999">
<freejoint/>
<geom size=".05" fromto="-.1 0 0 .1 0 0" type="capsule"/>
</body>
</worldbody>
</mujoco>
"""
def test_capsule_ellipsoid(self):
"""Tests ellipsoid capsule contact."""
d, dx = _collide(self._ELLIPSOID_CAPSULE)
self.assertLess(dx.contact.dist[0], 0)
for field in dataclasses.fields(Contact):
_assert_attr_eq(
dx.contact, d.contact, field.name, 'ellipsoid-capsule', 1e-4)
_ELLIPSOID_CYLINDER = """
<mujoco>
<worldbody>
<body>
<geom size=".15 .05" type="cylinder"/>
</body>
<body pos="0 0 0.09">
<freejoint/>
<geom size=".15 .03 .05" type="ellipsoid"/>
</body>
</worldbody>
</mujoco>
"""
def test_ellipsoid_cylinder(self):
"""Tests ellipsoid cylinder contact."""
d, dx = _collide(self._ELLIPSOID_CYLINDER)
d.contact.pos[0][2] = 0.04 # MJX finds the deepest point on the surface
self.assertLess(dx.contact.dist[0], 0)
for field in dataclasses.fields(Contact):
_assert_attr_eq(
dx.contact, d.contact, field.name, 'ellipsoid-cylinder', 1e-4)
class CapsuleCollisionTest(parameterized.TestCase):
_CAP_PLANE = """
@@ -358,7 +425,8 @@ class CapsuleCollisionTest(parameterized.TestCase):
self.assertEqual(c.pos.shape[0], 2)
self.assertGreater(c.dist[1], 0)
# extract the contact point with penetration
c = jax.tree_map(lambda x: jp.take(x, 0, axis=0)[None], dx.contact)
c = jax.tree_util.tree_map(lambda x: x[:1], dx.contact)
c = c.replace(dim=c.dim[:1], efc_address=c.efc_address[:1])
for field in dataclasses.fields(Contact):
_assert_attr_eq(c, d.contact, field.name, 'capsule_convex_edge', 1e-4)
@@ -393,7 +461,8 @@ class CapsuleCollisionTest(parameterized.TestCase):
self.assertEqual(c.pos.shape[0], 2)
self.assertGreater(c.dist[1], 0)
# extract the contact point with penetration
c = jax.tree_map(lambda x: jp.take(x, 0, axis=0)[None], dx.contact)
c = jax.tree_util.tree_map(lambda x: x[:1], dx.contact)
c = c.replace(dim=c.dim[:1], efc_address=c.efc_address[:1])
for field in dataclasses.fields(Contact):
_assert_attr_eq(c, d.contact, field.name, 'edge_shallow_tip1', 1e-4)
np.testing.assert_array_almost_equal(
@@ -413,7 +482,8 @@ class CapsuleCollisionTest(parameterized.TestCase):
self.assertEqual(c.pos.shape[0], 2)
self.assertGreater(c.dist[1], 0)
# extract the contact point with penetration
c = jax.tree_map(lambda x: jp.take(x, 0, axis=0)[None], dx.contact)
c = jax.tree_util.tree_map(lambda x: x[:1], dx.contact)
c = c.replace(dim=c.dim[:1], efc_address=c.efc_address[:1])
for field in dataclasses.fields(Contact):
_assert_attr_eq(c, d.contact, field.name, 'edge_shallow_tip2', 1e-4)
np.testing.assert_array_almost_equal(
@@ -421,6 +491,51 @@ class CapsuleCollisionTest(parameterized.TestCase):
)
class CylinderTest(absltest.TestCase):
"""Tests the cylinder contact functions."""
_CYLINDER_PLANE = """
<mujoco>
<worldbody>
<geom size="40 40 40" type="plane"/>
<body pos="0 0 0.04">
<joint type="free"/>
<geom fromto="-0.1 0 0 0.1 0 0" size="0.05" type="cylinder"/>
</body>
</worldbody>
</mujoco>
"""
def test_cylinder_plane(self):
d, dx = _collide(self._CYLINDER_PLANE)
# cylinder is lying flat
np.testing.assert_array_less(dx.contact.dist[:2], 0)
np.testing.assert_array_less(-dx.contact.dist[2:], 0)
# sort position for comparison
idx = np.lexsort((dx.contact.pos[:, 0], dx.contact.pos[:, 1]))
dx = dx.tree_replace({'contact.pos': dx.contact.pos[idx]})
idx = np.lexsort((d.contact.pos[:, 0], d.contact.pos[:, 1]))
d.contact.pos[:] = d.contact.pos[idx]
# extract the contact points with penetration
c = jax.tree_util.tree_map(lambda x: x[:2], dx.contact)
c = c.replace(dim=c.dim[:2], efc_address=c.efc_address[:2])
for field in dataclasses.fields(Contact):
_assert_attr_eq(c, d.contact, field.name, 'cylinder_plane', 1e-5)
# cylinder is vertical
xml = self._CYLINDER_PLANE.replace(
'<geom fromto="-0.1 0 0 0.1 0 0"', '<geom fromto="0 0 -0.1 0 0 0.1"')
xml = xml.replace('pos="0 0 0.04"', 'pos="0 0 0.095"')
d, dx = _collide(xml)
np.testing.assert_array_less(dx.contact.dist, 0)
for field in dataclasses.fields(Contact):
_assert_attr_eq(dx.contact, d.contact, field.name, 'cylinder_plane', 1e-5)
class ConvexTest(absltest.TestCase):
"""Tests the convex contact functions."""
@@ -443,7 +558,8 @@ class ConvexTest(absltest.TestCase):
np.testing.assert_array_less(dx.contact.dist[:2], 0)
np.testing.assert_array_less(-dx.contact.dist[2:], 0)
# extract the contact points with penetration
c = jax.tree_map(lambda x: jp.take(x, jp.array([0, 1]), axis=0), dx.contact)
c = jax.tree_util.tree_map(lambda x: jp.take(x, jp.array([0, 1]), axis=0), dx.contact)
c = c.replace(dim=c.dim[[0, 1]], efc_address=c.efc_address[[0, 1]])
for field in dataclasses.fields(Contact):
_assert_attr_eq(c, d.contact, field.name, 'box_plane', 1e-5)
@@ -527,7 +643,8 @@ class ConvexTest(absltest.TestCase):
np.testing.assert_array_less(dx.contact.dist[:1], 0)
np.testing.assert_array_less(-dx.contact.dist[1:], 0)
# extract the contact point with penetration
c = jax.tree_map(lambda x: jp.take(x, 0, axis=0)[None], dx.contact)
c = jax.tree_util.tree_map(lambda x: x[:1], dx.contact)
c = c.replace(dim=c.dim[:1], efc_address=c.efc_address[:1])
for field in dataclasses.fields(Contact):
_assert_attr_eq(c, d.contact, field.name, 'box_box_edge', 1e-2)
@@ -675,28 +792,29 @@ class BodyPairFilterTest(absltest.TestCase):
self.assertEqual(dx.contact.pos.shape[0], 1)
class NconTest(parameterized.TestCase):
"""Tests ncon."""
class DimTest(parameterized.TestCase):
"""Tests contact dim."""
def test_ncon(self):
m = test_util.load_test_file('constraints.xml')
ncon = collision_driver.ncon(m)
self.assertEqual(ncon, 16)
dim = collision_driver.make_condim(m)
expected = [1] * 4 + [3] * 20 + [4] * 4 + [6] * 4
np.testing.assert_array_equal(dim, np.array(expected))
def test_disable_contact(self):
m = test_util.load_test_file('constraints.xml')
m.opt.disableflags |= DisableBit.CONTACT
ncon = collision_driver.ncon(m)
self.assertEqual(ncon, 0)
dim = collision_driver.make_condim(m)
self.assertEqual(dim.size, 0)
def test_ncon_meshes(self):
m = test_util.load_test_file('shadow_hand/scene_right.xml')
ncon = collision_driver.ncon(m)
ncon = collision_driver.make_condim(m).size
self.assertEqual(ncon, 15)
mx = mjx.put_model(m)
ncon = collision_driver.ncon(mx)
ncon = collision_driver.make_condim(mx).size
self.assertEqual(ncon, 15)
# get rid of max_contact_points, test only max_geom_pairs
@@ -707,11 +825,11 @@ class NconTest(parameterized.TestCase):
if name_ == 'max_contact_points':
m.numeric_data[m.numeric_adr[i]] = -1
ncon = collision_driver.ncon(m)
ncon = collision_driver.make_condim(m).size
self.assertEqual(ncon, 98)
mx = mjx.put_model(m)
ncon = collision_driver.ncon(mx)
ncon = collision_driver.make_condim(mx).size
self.assertEqual(ncon, 98)
+108 -45
View File
@@ -20,34 +20,53 @@ import jax
from jax import numpy as jp
from mujoco.mjx._src import math
# pylint: disable=g-importing-member
from mujoco.mjx._src.collision_base import Contact
from mujoco.mjx._src.collision_base import GeomInfo
from mujoco.mjx._src.collision_types import Collision
from mujoco.mjx._src.collision_types import GeomInfo
from mujoco.mjx._src.types import Data
from mujoco.mjx._src.types import Model
# pylint: enable=g-importing-member
def collider(ncon: int):
"""Wraps collision functions for use by collision_driver."""
def wrapper(func):
def collide(m: Model, d: Data, _, geom: jax.Array) -> Collision:
g1, g2 = geom.T
info1 = GeomInfo(d.geom_xpos[g1], d.geom_xmat[g1], m.geom_size[g1])
info2 = GeomInfo(d.geom_xpos[g2], d.geom_xmat[g2], m.geom_size[g2])
dist, pos, frame = jax.vmap(func)(info1, info2)
if ncon > 1:
return jax.tree_util.tree_map(jp.concatenate, (dist, pos, frame))
return dist, pos, frame
collide.ncon = ncon
return collide
return wrapper
def _plane_sphere(
plane_normal: jax.Array,
plane_pos: jax.Array,
sphere_pos: jax.Array,
radius: jax.Array,
sphere_radius: jax.Array,
) -> Tuple[jax.Array, jax.Array]:
"""Returns the penetration and contact point between a plane and sphere."""
cdist = jp.dot(sphere_pos - plane_pos, plane_normal)
dist = cdist - radius
pos = sphere_pos - plane_normal * (radius + 0.5 * dist)
"""Returns the distance and contact point between a plane and sphere."""
dist = jp.dot(sphere_pos - plane_pos, plane_normal) - sphere_radius
pos = sphere_pos - plane_normal * (sphere_radius + 0.5 * dist)
return dist, pos
def plane_sphere(plane: GeomInfo, sphere: GeomInfo) -> Contact:
@collider(ncon=1)
def plane_sphere(plane: GeomInfo, sphere: GeomInfo) -> Collision:
"""Calculates contact between a plane and a sphere."""
n = plane.mat[:, 2]
dist, pos = _plane_sphere(n, plane.pos, sphere.pos, sphere.size[0])
return jax.tree_map(
lambda x: jp.expand_dims(x, axis=0), (dist, pos, math.make_frame(n))
)
return dist, pos, math.make_frame(n)
def plane_capsule(plane: GeomInfo, cap: GeomInfo) -> Contact:
@collider(ncon=2)
def plane_capsule(plane: GeomInfo, cap: GeomInfo) -> Collision:
"""Calculates two contacts between a capsule and a plane."""
n, axis = plane.mat[:, 2], cap.mat[:, 2]
# align contact frames with capsule axis
@@ -56,16 +75,17 @@ def plane_capsule(plane: GeomInfo, cap: GeomInfo) -> Contact:
b = jp.where(b_norm < 0.5, jp.where((-0.5 < n[1]) & (n[1] < 0.5), y, z), b)
frame = jp.array([[n, b, jp.cross(n, b)]])
segment = axis * cap.size[1]
contacts = []
collisions = []
for offset in [segment, -segment]:
dist, pos = _plane_sphere(n, plane.pos, cap.pos + offset, cap.size[0])
dist = jp.expand_dims(dist, axis=0)
pos = jp.expand_dims(pos, axis=0)
contacts.append((dist, pos, frame))
return jax.tree_map(lambda *x: jp.concatenate(x), *contacts)
collisions.append((dist, pos, frame))
return jax.tree_util.tree_map(lambda *x: jp.concatenate(x), *collisions)
def plane_ellipsoid(plane: GeomInfo, ellipsoid: GeomInfo) -> Contact:
@collider(ncon=1)
def plane_ellipsoid(plane: GeomInfo, ellipsoid: GeomInfo) -> Collision:
"""Calculates one contact between an ellipsoid and a plane."""
n = plane.mat[:, 2]
size = ellipsoid.size
@@ -73,14 +93,72 @@ def plane_ellipsoid(plane: GeomInfo, ellipsoid: GeomInfo) -> Contact:
pos = ellipsoid.pos + ellipsoid.mat @ (sphere_support * size)
dist = jp.dot(n, pos - plane.pos)
pos = pos - n * dist * 0.5
return jax.tree_map(
lambda x: jp.expand_dims(x, axis=0), (dist, pos, math.make_frame(n))
return dist, pos, math.make_frame(n)
@collider(ncon=3)
def plane_cylinder(plane: GeomInfo, cylinder: GeomInfo) -> Collision:
"""Calculates one contact between an cylinder and a plane."""
n = plane.mat[:, 2]
axis = cylinder.mat[:, 2]
# make sure axis points towards plane
prjaxis = jp.dot(n, axis)
sign = -math.sign(prjaxis)
axis, prjaxis = axis * sign, prjaxis * sign
# compute normal distance to cylinder center
dist0 = jp.dot(cylinder.pos - plane.pos, n)
# remove component of -normal along axis, compute length
vec = axis * prjaxis - n
len_ = math.norm(vec)
vec = jp.where(
len_ < 1e-12,
# disk parallel to plane: pick x-axis of cylinder, scale by radius
cylinder.mat[:, 0] * cylinder.size[0],
# general configuration: normalize vector, scale by radius
vec / len_ * cylinder.size[0]
)
# project vector on normal
prjvec = jp.dot(vec, n)
# scale axis by half-length
axis *= cylinder.size[1]
prjaxis *= cylinder.size[1]
# compute sideways vector: vec1
prjvec1 = -prjvec * 0.5
vec1 = math.normalize(jp.cross(vec, axis)) * cylinder.size[0]
vec1 *= jp.sqrt(3.0) * 0.5
# disk parallel to plane
d1 = dist0 + prjaxis + prjvec
d2 = dist0 + prjaxis + prjvec1
dist = jp.array([d1, d2, d2])
pos = cylinder.pos + axis + jp.array([
vec - n * d1 * 0.5,
vec1 + vec * -0.5 - n * d2 * 0.5,
-vec1 + vec * -0.5 - n * d2 * 0.5,
])
# cylinder parallel to plane
cond = jp.abs(prjaxis) < 1e-3
d3 = dist0 - prjaxis + prjvec
dist = jp.where(cond, dist.at[1].set(d3), dist)
pos = jp.where(
cond, pos.at[1].set(cylinder.pos + vec - axis - n * d3 * 0.5), pos
)
frame = jp.stack([math.make_frame(n)] * 3, axis=0)
return dist, pos, frame
def _sphere_sphere(
pos1: jax.Array, radius1: jax.Array, pos2: jax.Array, radius2: jax.Array
) -> Contact:
) -> Tuple[jax.Array, jax.Array, jax.Array]:
"""Returns the penetration, contact point, and normal between two spheres."""
n, dist = math.normalize_with_norm(pos2 - pos1)
n = jp.where(dist == 0.0, jp.array([1.0, 0.0, 0.0]), n)
@@ -89,15 +167,15 @@ def _sphere_sphere(
return dist, pos, n
def sphere_sphere(s1: GeomInfo, s2: GeomInfo) -> Contact:
@collider(ncon=1)
def sphere_sphere(s1: GeomInfo, s2: GeomInfo) -> Collision:
"""Calculates contact between two spheres."""
dist, pos, n = _sphere_sphere(s1.pos, s1.size[0], s2.pos, s2.size[0])
return jax.tree_map(
lambda x: jp.expand_dims(x, axis=0), (dist, pos, math.make_frame(n))
)
return dist, pos, math.make_frame(n)
def sphere_capsule(sphere: GeomInfo, cap: GeomInfo) -> Contact:
@collider(ncon=1)
def sphere_capsule(sphere: GeomInfo, cap: GeomInfo) -> Collision:
"""Calculates one contact between a sphere and a capsule."""
axis, length = cap.mat[:, 2], cap.size[1]
segment = axis * length
@@ -105,19 +183,14 @@ def sphere_capsule(sphere: GeomInfo, cap: GeomInfo) -> Contact:
cap.pos - segment, cap.pos + segment, sphere.pos
)
dist, pos, n = _sphere_sphere(sphere.pos, sphere.size[0], pt, cap.size[0])
return jax.tree_map(
lambda x: jp.expand_dims(x, axis=0), (dist, pos, math.make_frame(n))
)
return dist, pos, math.make_frame(n)
def capsule_capsule(cap1: GeomInfo, cap2: GeomInfo) -> Contact:
@collider(ncon=1)
def capsule_capsule(cap1: GeomInfo, cap2: GeomInfo) -> Collision:
"""Calculates one contact between two capsules."""
axis1, length1, axis2, length2 = (
cap1.mat[:, 2],
cap1.size[1],
cap2.mat[:, 2],
cap2.size[1],
)
axis1, length1 = cap1.mat[:, 2], cap1.size[1]
axis2, length2 = cap2.mat[:, 2], cap2.size[1]
seg1, seg2 = axis1 * length1, axis2 * length2
pt1, pt2 = math.closest_segment_to_segment_points(
cap1.pos - seg1,
@@ -127,14 +200,4 @@ def capsule_capsule(cap1: GeomInfo, cap2: GeomInfo) -> Contact:
)
radius1, radius2 = cap1.size[0], cap2.size[0]
dist, pos, n = _sphere_sphere(pt1, radius1, pt2, radius2)
return jax.tree_map(
lambda x: jp.expand_dims(x, axis=0), (dist, pos, math.make_frame(n))
)
# store ncon as function attributes
plane_sphere.ncon = 1
plane_capsule.ncon = 2
plane_ellipsoid.ncon = 1
sphere_sphere.ncon = 1
sphere_capsule.ncon = 1
capsule_capsule.ncon = 1
return dist, pos, math.make_frame(n)
+243
View File
@@ -0,0 +1,243 @@
# Copyright 2023 DeepMind Technologies Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Collision functions for shapes represented as signed distance functions (SDF).
A signed distance function at a given point in space is the shortest distance to
a surface. This enables to define a geometry implicitly and exactly.
See https://iquilezles.org/articles/distfunctions/ for a list of analytic SDFs.
"""
import functools
from typing import Callable
from typing import Tuple
import jax
from jax import numpy as jp
from mujoco.mjx._src import math
# pylint: disable=g-importing-member
from mujoco.mjx._src.collision_types import Collision
from mujoco.mjx._src.collision_types import GeomInfo
from mujoco.mjx._src.dataclasses import PyTreeNode
from mujoco.mjx._src.types import Data
from mujoco.mjx._src.types import Model
# pylint: enable=g-importing-member
# the SDF function takes position in, and returns a distance or objective
SDFFn = Callable[[jax.Array], jax.Array]
def collider(ncon: int):
"""Wraps collision functions for use by collision_driver."""
def wrapper(func):
def collide(m: Model, d: Data, _, geom: jax.Array) -> Collision:
g1, g2 = geom.T
info1 = GeomInfo(d.geom_xpos[g1], d.geom_xmat[g1], m.geom_size[g1])
info2 = GeomInfo(d.geom_xpos[g2], d.geom_xmat[g2], m.geom_size[g2])
dist, pos, frame = jax.vmap(func)(info1, info2)
if ncon > 1:
return jax.tree_util.tree_map(jp.concatenate, (dist, pos, frame))
return dist, pos, frame
collide.ncon = ncon
return collide
return wrapper
def _plane(pos: jax.Array, size: jax.Array) -> jax.Array:
del size
return pos[2]
def _sphere(pos: jax.Array, size: jax.Array):
return math.norm(pos) - size[0]
def _capsule(pos: jax.Array, size: jax.Array):
pa = -size[1] * jp.array([0, 0, 1])
pb = size[1] * jp.array([0, 0, 1])
ab = pb - pa
ap = pos - pa
denom = ab.dot(ab)
denom = jp.where(jp.abs(denom) < 1e-12, 1e-12 * math.sign(denom), denom)
t = ab.dot(ap) / denom
t = jp.clip(t, 0, 1)
c = pa + t * ab
return math.norm(pos - c) - size[0]
def _ellipsoid(pos: jax.Array, size: jax.Array) -> jax.Array:
k0 = math.norm(pos / size)
k1 = math.norm(pos / (size*size))
return k0 * (k0 - 1.0) / (k1 + (k1 == 0.0) * 1e-12)
@jax.custom_jvp
def _cylinder(pos: jax.Array, size: jax.Array) -> jax.Array:
a0 = jp.sqrt(pos[0] * pos[0] + pos[1] * pos[1]) - size[0]
a1 = jp.abs(pos[2]) - size[1]
b0 = jp.maximum(a0, 0)
b1 = jp.maximum(a1, 0)
return jp.minimum(jp.maximum(a0, a1), 0) + jp.sqrt(b0 * b0 + b1 * b1)
def _cylinder_grad(x: jax.Array, size: jax.Array) -> jax.Array:
"""Gradient of the cylinder SDF wrt query point and singularities removed."""
c = jp.sqrt(x[0]*x[0]+x[1]*x[1])
e = jp.abs(x[2])
a = jp.array([c - size[0], e - size[1]])
b = jp.array([jp.maximum(a[0], 0), jp.maximum(a[1], 0)])
j = jp.argmax(a)
bnorm = jp.sqrt(b[0]*b[0] + b[1]*b[1])
bnorm += jp.allclose(bnorm, 0) * 1e-12
grada = jp.array([
x[0] / (c + jp.allclose(c, 0) * 1e-12),
x[1] / (c + jp.allclose(c, 0) * 1e-12),
x[2] / (e + jp.allclose(e, 0) * 1e-12),
])
gradm = jp.array([[grada[0], grada[1], 0], [0, 0, grada[2]]])
gradb = grada * b[jp.array([0, 0, 1])] / bnorm
return jp.where(a[j] < 0, gradm[j], gradb)
@_cylinder.defjvp
def cylinder_jvp(primals, tangents):
x, y = primals
x_dot, _ = tangents
primal_out = _cylinder(x, y)
tangent_out = jp.dot(_cylinder_grad(x, y), x_dot)
return primal_out, tangent_out
def _to_local(f: SDFFn, pos: jax.Array, mat: jax.Array)-> SDFFn:
return lambda p: f(mat.T @ (p - pos))
def _intersect(d1: SDFFn, d2: SDFFn) -> SDFFn:
return lambda p: jp.maximum(d1(p), d2(p))
def _clearance(d1: SDFFn, d2: SDFFn) -> SDFFn:
return lambda p: (d1(p) + d2(p) + jp.abs(_intersect(d1, d2)(p))).squeeze()
class GradientState(PyTreeNode):
dist: jax.Array
x: jax.Array
def _gradient_step(objective: SDFFn, state: GradientState) -> GradientState:
"""Performs a step of gradient descent."""
# TODO: find better parameters
amin = 1e-4 # minimum value for line search factor scaling the gradient
amax = 2. # maximum value for line search factor scaling the gradient
nlinesearch = 10 # line search points
grad = jax.grad(objective)(state.x)
alpha = jp.geomspace(amin, amax, nlinesearch).reshape(nlinesearch, -1)
candidates = state.x - alpha * grad.reshape(-1, 3)
values = jax.vmap(objective)(candidates)
idx = jp.argmin(values)
return state.replace(x=candidates[idx], dist=values[idx])
def _gradient_descent(
objective: SDFFn,
x: jax.Array,
niter: int,
) -> Tuple[jax.Array, jax.Array]:
"""Performs gradient descent with backtracking line search."""
state = GradientState(
dist=1e10,
x=x,
)
state, _ = jax.lax.scan(
lambda s, _: (_gradient_step(objective, s), None), state, (), length=niter
)
return state.dist, state.x
def _optim(
d1, d2, info1: GeomInfo, info2: GeomInfo, x0: jax.Array,
) -> Collision:
"""Optimizes the clearance function."""
d1 = functools.partial(d1, size=info1.size)
d1 = _to_local(d1, info1.pos, info1.mat)
d2 = functools.partial(d2, size=info2.size)
d2 = _to_local(d2, info2.pos, info2.mat)
fn = _clearance(d1, d2)
_, pos = _gradient_descent(fn, x0, 10)
dist = d1(pos) + d2(pos)
n = jax.grad(d1)(pos) - jax.grad(d2)(pos)
return dist, pos, math.make_frame(n)
@collider(ncon=1)
def capsule_ellipsoid(c: GeomInfo, e: GeomInfo) -> Collision:
""""Calculates contact between a capsule and an ellipsoid."""
x0 = 0.5 * (c.pos + e.pos)
return _optim(_capsule, _ellipsoid, c, e, x0)
@collider(ncon=2)
def capsule_cylinder(ca: GeomInfo, cy: GeomInfo) -> Collision:
""""Calculates contact between a capsule and a cylinder."""
# TODO: improve robustness
# Near sharp corners, the SDF might give the penetration depth with respect
# to a surface that is not in collision. Possible solutions is to find the
# contact points analytically or to change the SDF depending on the relative
# pose of the bodies.
mid = 0.5 * (ca.pos + cy.pos)
vec = ca.mat[:, 2] * ca.size[1]
x0 = jp.array([mid - vec, mid + vec])
optim_ = functools.partial(_optim, _capsule, _cylinder, ca, cy)
return jax.vmap(optim_)(x0)
@collider(ncon=1)
def ellipsoid_ellipsoid(e1: GeomInfo, e2: GeomInfo) -> Collision:
""""Calculates contact between two ellipsoids."""
x0 = 0.5 * (e1.pos + e2.pos)
return _optim(_ellipsoid, _ellipsoid, e1, e2, x0)
@collider(ncon=1)
def ellipsoid_cylinder(e: GeomInfo, c: GeomInfo) -> Collision:
""""Calculates contact between and ellipsoid and a cylinder."""
x0 = 0.5 * (e.pos + c.pos)
return _optim(_ellipsoid, _cylinder, e, c, x0)
@collider(ncon=4)
def cylinder_cylinder(c1: GeomInfo, c2: GeomInfo) -> Collision:
""""Calculates contact between a cylinder and a cylinder."""
# TODO: improve robustness
# Near sharp corners, the SDF might give the penetration depth with respect
# to a surface that is not in collision. Possible solutions is to find the
# contact points analytically or to change the SDF depending on the relative
# pose of the bodies.
basis = math.make_frame(c2.pos - c1.pos)
mid = 0.5 * (c1.pos + c2.pos)
r = jp.maximum(c1.size[0], c2.size[0])
x0 = jp.array([
mid + r * basis[1],
mid + r * basis[2],
mid - r * basis[1],
mid - r * basis[2],
])
optim_ = functools.partial(_optim, _cylinder, _cylinder, c1, c2)
return jax.vmap(optim_)(x0)
+65
View File
@@ -0,0 +1,65 @@
# Copyright 2023 DeepMind Technologies Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Collision base types."""
import dataclasses
from typing import Tuple
import jax
# pylint: disable=g-importing-member
from mujoco.mjx._src.dataclasses import PyTreeNode
# pylint: enable=g-importing-member
class GeomInfo(PyTreeNode):
"""Geom propertes of primitive and SDF shapes."""
pos: jax.Array
mat: jax.Array
size: jax.Array
class ConvexInfo(PyTreeNode):
"""Geom propertes of convex meshes."""
pos: jax.Array
mat: jax.Array
vert: jax.Array
face: jax.Array
face_normal: jax.Array
edge: jax.Array
edge_face_normal: jax.Array
edge_dir: jax.Array
# Collision returned by collision functions:
# - distance distance between nearest points; neg: penetration
# - position (3,) position of contact point: midpoint between geoms
# - frame (3, 3) normal is in [0, :], points from geom[0] to geom[1]
Collision = Tuple[jax.Array, jax.Array, jax.Array]
@dataclasses.dataclass(frozen=True)
class FunctionKey:
"""Specifies how geom pairs group into collision_driver's function table.
Attributes:
types: geom type pair, which determines the collision function
data_ids: geom data id pair: mesh id for mesh geoms, otherwise -1.
Meshes have distinct face/vertex counts, so must occupy distinct
entries in the collision function table.
condim: grouping by condim of the colliision ensures that the size of the
resulting constraint jacobian is determined at compile time.
"""
types: Tuple[int, int]
data_ids: Tuple[int, int]
condim: int
+93 -53
View File
@@ -24,6 +24,7 @@ from mujoco.mjx._src import math
from mujoco.mjx._src import support
# pylint: disable=g-importing-member
from mujoco.mjx._src.dataclasses import PyTreeNode
from mujoco.mjx._src.types import ConstraintType
from mujoco.mjx._src.types import Contact
from mujoco.mjx._src.types import Data
from mujoco.mjx._src.types import DisableBit
@@ -34,6 +35,9 @@ from mujoco.mjx._src.types import Model
import numpy as np
_CONDIM_EFC_COUNT = {1: 1, 3: 4, 4: 6, 6: 10}
class _Efc(PyTreeNode):
"""Support data for creating constraint matrices."""
J: jax.Array
@@ -111,7 +115,7 @@ def _instantiate_equality_connect(m: Model, d: Data) -> Optional[_Efc]:
return j, cpos, jp.repeat(math.norm(cpos), 3)
# concatenate to drop connect grouping dimension
j, pos, pos_norm = jax.tree_map(jp.concatenate, fn(data, id1, id2))
j, pos, pos_norm = jax.tree_util.tree_map(jp.concatenate, fn(data, id1, id2))
invweight = m.body_invweight0[id1, 0] + m.body_invweight0[id2, 0]
invweight = jp.repeat(invweight, 3)
solref = jp.tile(m.eq_solref[ids], (3, 1))
@@ -164,7 +168,7 @@ def _instantiate_equality_weld(m: Model, d: Data) -> Optional[_Efc]:
return j, pos, jp.repeat(math.norm(pos), 6)
# concatenate to drop weld grouping dimension
j, pos, pos_norm = jax.tree_map(jp.concatenate, fn(data, id1, id2))
j, pos, pos_norm = jax.tree_util.tree_map(jp.concatenate, fn(data, id1, id2))
invweight = m.body_invweight0[id1] + m.body_invweight0[id2]
invweight = jp.repeat(invweight, 3)
solref = jp.tile(m.eq_solref[ids], (6, 1))
@@ -277,73 +281,109 @@ def _instantiate_limit_slide_hinge(m: Model, d: Data) -> Optional[_Efc]:
def _instantiate_contact(m: Model, d: Data) -> Optional[_Efc]:
"""Calculates constraint rows for contacts."""
if collision_driver.ncon(m) == 0:
if d.ncon == 0:
return None
@jax.vmap
def fn(c: Contact):
dist = c.dist - c.includemargin
geom_bodyid = jp.array(m.geom_bodyid)
body1, body2 = geom_bodyid[c.geom1], geom_bodyid[c.geom2]
diff = support.jac_dif_pair(m, d, c.pos, body1, body2)
t = m.body_invweight0[body1, 0] + m.body_invweight0[body2, 0]
def contact_efc(c: Contact, condim: int):
# rotate Jacobian differences to contact frame
diff_con = c.frame @ diff.T
@jax.vmap
def fn(c: Contact):
dist = c.dist - c.includemargin
active = dist < 0
body1, body2 = jp.array(m.geom_bodyid)[c.geom]
jac1p, jac1r = support.jac(m, d, c.pos, body1)
jac2p, jac2r = support.jac(m, d, c.pos, body2)
diff = c.frame @ (jac2p - jac1p).T
if condim > 3: # only calculate rotational diff if needed
diff = jp.concatenate((diff, c.frame @ (jac2r - jac1r).T), axis=0)
tran = m.body_invweight0[body1, 0] + m.body_invweight0[body2, 0]
# TODO(robotics-simulation): add support for other friction dimensions
# 4 pyramidal friction directions
js, invweights = [], []
for diff_tan, friction in zip(diff_con[1:], c.friction[:2]):
for f in (friction, -friction):
js.append(diff_con[0] + diff_tan * f)
invweights.append((t + f * f * t) * 2 * f * f / m.opt.impratio)
if condim == 1:
return diff[0] * active, tran, dist * active, c.solref, c.solimp
active = dist < 0
j, invweight = jp.stack(js) * active, jp.stack(invweights)
pos = jp.repeat(dist, 4) * active
solref, solimp = jp.tile(c.solref, (4, 1)), jp.tile(c.solimp, (4, 1))
# a pair of opposing pyramid edges per friction dimension
# repeat friction directions with positive and negative sign
fri = jp.repeat(c.friction[: condim - 1], 2, axis=0).at[1::2].mul(-1)
# repeat condims of jacdiff to match +/- friction directions
j = diff[0] + jp.repeat(diff[1:condim], 2, axis=0) * fri[:, None]
# pyramidal has common invweight across all edges
diag_approx = tran + fri[0] * fri[0] * tran
inv_w = diag_approx * 2 * fri[0] * fri[0] / m.opt.impratio
repeat_fn = lambda x: jp.repeat(x[None], (condim - 1) * 2, axis=0)
inv_w, pos, solref, solimp = jax.tree_util.tree_map(
repeat_fn, (inv_w, dist, c.solref, c.solimp)
)
return j * active, inv_w, pos * active, solref, solimp
return j, invweight, pos, solref, solimp
return fn(c)
res = fn(d.contact)
# remove contact grouping dimension:
j, invweight, pos, solref, solimp = jax.tree_map(jp.concatenate, res)
# group efc calculations by condim
dims, begs = np.unique(d.contact.dim, return_index=True)
efcs = []
for i in range(len(dims)):
dim, beg = dims[i], begs[i]
end = begs[i + 1] if i < len(dims) - 1 else None
c = jax.tree_util.tree_map(lambda x, b=beg, e=end: x[b:e], d.contact)
efc = contact_efc(c, dim)
if dim > 1:
# remove efc grouping dimension
efc = jax.tree_util.tree_map(jp.concatenate, efc)
efcs.append(efc)
efc = jax.tree_util.tree_map(lambda *x: jp.concatenate(x), *efcs)
j, invweight, pos, solref, solimp = efc
frictionloss = jp.zeros_like(pos)
return _Efc(j, pos, pos, invweight, solref, solimp, frictionloss)
def count_constraints(
m: Union[Model, mujoco.MjModel], d: Optional[Data] = None
) -> Tuple[int, int, int, int]:
def counts(efc_type: np.ndarray) -> Tuple[int, int, int, int]:
"""Returns equality, friction, limit, and contact constraint counts."""
if m.opt.disableflags & DisableBit.CONSTRAINT:
return 0, 0, 0, 0
if m.opt.disableflags & DisableBit.EQUALITY:
ne = 0
else:
ne_connect = (m.eq_type == EqType.CONNECT).sum()
ne_weld = (m.eq_type == EqType.WELD).sum()
ne_joint = (m.eq_type == EqType.JOINT).sum()
ne = ne_connect * 3 + ne_weld * 6 + ne_joint
nf = 0
if m.opt.disableflags & DisableBit.LIMIT:
nl = 0
else:
nl = int(m.jnt_limited.sum())
if d is None:
nc = collision_driver.ncon(m) * 4
else:
nc = d.efc_J.shape[-2] - ne - nf - nl
ne = (efc_type == ConstraintType.EQUALITY).sum()
nf = 0 # no support for friction loss yet
nl = (efc_type == ConstraintType.LIMIT_JOINT).sum()
nc_f = (efc_type == ConstraintType.CONTACT_FRICTIONLESS).sum()
nc_p = (efc_type == ConstraintType.CONTACT_PYRAMIDAL).sum()
nc = nc_f + nc_p
return ne, nf, nl, nc
def make_efc_type(
m: Union[Model, mujoco.MjModel], dim: Optional[np.ndarray] = None
) -> np.ndarray:
"""Returns efc_type that outlines the type of each constraint row."""
if m.opt.disableflags & DisableBit.CONSTRAINT:
return np.empty(0, dtype=int)
dim = collision_driver.make_condim(m) if dim is None else dim
efc_types = []
if not m.opt.disableflags & DisableBit.EQUALITY:
num_rows = (m.eq_type == EqType.CONNECT).sum() * 3
num_rows += (m.eq_type == EqType.WELD).sum() * 6
num_rows += (m.eq_type == EqType.JOINT).sum()
efc_types.extend([ConstraintType.EQUALITY] * num_rows)
if not m.opt.disableflags & DisableBit.LIMIT:
efc_types.extend([ConstraintType.LIMIT_JOINT] * m.jnt_limited.sum())
if not m.opt.disableflags & DisableBit.CONTACT:
num_rows = sum(_CONDIM_EFC_COUNT[d] for d in dim)
efc_types.extend([ConstraintType.CONTACT_PYRAMIDAL] * num_rows)
return np.array(efc_types)
def make_efc_address(efc_type: np.ndarray, dim: np.ndarray) -> np.ndarray:
"""Returns efc_address that maps contacts to constraint row address."""
nc = (efc_type == ConstraintType.CONTACT_PYRAMIDAL).sum()
nc_start = efc_type.size - nc
offsets = np.cumsum([0] + [_CONDIM_EFC_COUNT[d] for d in dim])[:-1]
return nc_start + offsets
def make_constraint(m: Model, d: Data) -> Data:
"""Creates constraint jacobians and other supporting data."""
@@ -366,7 +406,7 @@ def make_constraint(m: Model, d: Data) -> Data:
d = d.replace(efc_D=z, efc_aref=z, efc_frictionloss=z)
return d
efc = jax.tree_map(lambda *x: jp.concatenate(x), *efcs)
efc = jax.tree_util.tree_map(lambda *x: jp.concatenate(x), *efcs)
@jax.vmap
def fn(efc):
+30 -12
View File
@@ -48,13 +48,31 @@ class ConstraintTest(absltest.TestCase):
mujoco.mj_forward(m, d)
mx = mjx.put_model(m)
dx = mjx.put_data(m, d)
dx = mjx.make_constraint(mx, dx)
nnz = dx.efc_J.any(axis=1)
_assert_eq(d.efc_J, dx.efc_J[nnz].reshape(-1), 'efc_J')
_assert_eq(d.efc_D, dx.efc_D[nnz], 'efc_D')
_assert_eq(d.efc_aref, dx.efc_aref[nnz], 'efc_aref')
_assert_eq(d.efc_frictionloss, dx.efc_frictionloss[nnz], 'efc_frictionloss')
d_efc_j = d.efc_J.reshape((-1, m.nv))
# ne, nf, nl order matches
efl = d.ne + d.nf + d.nl
_assert_eq(d_efc_j[:efl], dx.efc_J[:efl], 'efc_J')
_assert_eq(d.efc_D[:efl], dx.efc_D[:efl], 'efc_D')
_assert_eq(d.efc_aref[:efl], dx.efc_aref[:efl], 'efc_aref')
_assert_eq(dx.efc_frictionloss, 0, 'efc_frictionloss')
# contact order might not match, so check efcs contact by contact
for i in range(d.ncon):
geom_match = (dx.contact.geom == d.contact.geom[i]).all(axis=-1)
geom_match &= (dx.contact.pos == d.contact.pos[i]).all(axis=-1)
self.assertTrue(geom_match.any(), f'contact {i} not found in MJX contact')
j = np.nonzero(geom_match)[0][0]
self.assertEqual(d.contact.dim[i], dx.contact.dim[j])
nc = max(1, (d.contact.dim[i] - 1) * 2)
d_beg, dx_beg = d.contact.efc_address[i], dx.contact.efc_address[j]
d_end, dx_end = d_beg + nc, dx_beg + nc
_assert_eq(d_efc_j[d_beg:d_end], dx.efc_J[dx_beg:dx_end], 'efc_J')
_assert_eq(d.efc_D[d_beg:d_end], dx.efc_D[dx_beg:dx_end], 'efc_D')
d_efc_aref = d.efc_aref[d_beg:d_end]
dx_efc_aref = dx.efc_aref[dx_beg:dx_end]
_assert_eq(d_efc_aref, dx_efc_aref, 'efc_aref')
def test_disable_refsafe(self):
m = test_util.load_test_file('constraints.xml')
@@ -65,14 +83,14 @@ class ConstraintTest(absltest.TestCase):
pos = jp.ones(3)
m.opt.disableflags = m.opt.disableflags | mjx.DisableBit.REFSAFE
mx = mjx.device_put(m)
mx = mjx.put_model(m)
k, *_ = constraint._kbi(mx, solimp, solref, pos)
self.assertEqual(k, 1 / (0.99**2 * timeconst**2))
def test_disable_constraint(self):
m = test_util.load_test_file('constraints.xml')
m.opt.disableflags = m.opt.disableflags | mjx.DisableBit.CONSTRAINT
ne, nf, nl, nc = mjx.count_constraints(m)
ne, nf, nl, nc = constraint.counts(constraint.make_efc_type(m))
self.assertEqual(ne, 0)
self.assertEqual(nf, 0)
self.assertEqual(nl, 0)
@@ -83,18 +101,18 @@ class ConstraintTest(absltest.TestCase):
def test_disable_equality(self):
m = test_util.load_test_file('constraints.xml')
m.opt.disableflags = m.opt.disableflags | mjx.DisableBit.EQUALITY
ne, nf, nl, nc = mjx.count_constraints(m)
ne, nf, nl, nc = constraint.counts(constraint.make_efc_type(m))
self.assertEqual(ne, 0)
self.assertEqual(nf, 0)
self.assertEqual(nl, 2)
self.assertEqual(nc, 64)
self.assertEqual(nc, 148)
dx = constraint.make_constraint(mjx.put_model(m), mjx.make_data(m))
self.assertEqual(dx.efc_J.shape[0], 66) # only joint range, contact
self.assertEqual(dx.efc_J.shape[0], 150) # only joint range, contact
def test_disable_contact(self):
m = test_util.load_test_file('constraints.xml')
m.opt.disableflags = m.opt.disableflags | mjx.DisableBit.CONTACT
ne, nf, nl, nc = mjx.count_constraints(m)
ne, nf, nl, nc = constraint.counts(constraint.make_efc_type(m))
self.assertEqual(ne, 10)
self.assertEqual(nf, 0)
self.assertEqual(nl, 2)
+2 -2
View File
@@ -18,7 +18,7 @@ import copy
import dataclasses
import typing
from typing import Any, Dict, Optional, Sequence, TypeVar, Union
from typing import Dict, Optional, Sequence, Tuple, TypeVar, Union
import jax
import numpy as np
@@ -122,7 +122,7 @@ class PyTreeNode:
raise NotImplementedError
@classmethod
def fields(cls) -> tuple[dataclasses.Field[Any], ...]:
def fields(cls) -> Tuple[dataclasses.Field, ...]: # pylint: disable=g-bare-generic
return dataclasses.fields(cls)
def tree_replace(
-318
View File
@@ -1,318 +0,0 @@
# Copyright 2023 DeepMind Technologies Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Get and put mujoco data on/off device."""
import copy
import dataclasses
from typing import Any, Dict, Iterable, List, Union, overload
import warnings
import jax
from jax import numpy as jp
import mujoco
from mujoco.mjx._src import collision_driver
from mujoco.mjx._src import mesh
from mujoco.mjx._src import types
import numpy as np
_MJ_TYPE_ATTR = {
mujoco.mjtBias: (mujoco.MjModel.actuator_biastype,),
mujoco.mjtDyn: (mujoco.MjModel.actuator_dyntype,),
mujoco.mjtEq: (mujoco.MjModel.eq_type,),
mujoco.mjtGain: (mujoco.MjModel.actuator_gaintype,),
mujoco.mjtTrn: (mujoco.MjModel.actuator_trntype,),
mujoco.mjtCone: (
mujoco.MjModel.opt,
mujoco.MjOption.cone,
),
mujoco.mjtIntegrator: (
mujoco.MjModel.opt,
mujoco.MjOption.integrator,
),
mujoco.mjtSolver: (
mujoco.MjModel.opt,
mujoco.MjOption.solver,
),
}
_TYPE_MAP = {
mujoco._structs._MjContactList: types.Contact, # pylint: disable=protected-access
mujoco.MjData: types.Data,
mujoco.MjModel: types.Model,
mujoco.MjOption: types.Option,
mujoco.MjStatistic: types.Statistic,
mujoco.mjtBias: types.BiasType,
mujoco.mjtCone: types.ConeType,
mujoco.mjtDisableBit: types.DisableBit,
mujoco.mjtDyn: types.DynType,
mujoco.mjtEq: types.EqType,
mujoco.mjtGain: types.GainType,
mujoco.mjtIntegrator: types.IntegratorType,
mujoco.mjtSolver: types.SolverType,
mujoco.mjtTrn: types.TrnType,
}
_TRANSFORMS = {
(types.Data, 'ximat'): lambda x: x.reshape(x.shape[:-1] + (3, 3)),
(types.Data, 'xmat'): lambda x: x.reshape(x.shape[:-1] + (3, 3)),
(types.Data, 'geom_xmat'): lambda x: x.reshape(x.shape[:-1] + (3, 3)),
(types.Data, 'site_xmat'): lambda x: x.reshape(x.shape[:-1] + (3, 3)),
(types.Data, 'cam_xmat'): lambda x: x.reshape(x.shape[:-1] + (3, 3)),
(types.Model, 'cam_mat0'): lambda x: x.reshape(x.shape[:-1] + (3, 3)),
(types.Contact, 'frame'): (
lambda x: x.reshape(x.shape[:-1] + (3, 3)) # pylint: disable=g-long-lambda
if x is not None and x.shape[0]
else jp.zeros((0, 3, 3))
),
}
_INVERSE_TRANSFORMS = {
(types.Data, 'ximat'): lambda x: x.reshape(x.shape[:-2] + (9,)),
(types.Data, 'xmat'): lambda x: x.reshape(x.shape[:-2] + (9,)),
(types.Data, 'geom_xmat'): lambda x: x.reshape(x.shape[:-2] + (9,)),
(types.Data, 'site_xmat'): lambda x: x.reshape(x.shape[:-2] + (9,)),
(types.Data, 'cam_xmat'): lambda x: x.reshape(x.shape[:-2] + (9,)),
(types.Model, 'cam_mat0'): lambda x: x.reshape(x.shape[:-2] + (9,)),
(types.Contact, 'frame'): (
lambda x: x.reshape(x.shape[:-2] + (9,)) # pylint: disable=g-long-lambda
if x is not None and x.shape[0]
else jp.zeros((0, 9))
),
}
_DERIVED = mesh.DERIVED.union(
# efc_J is dense in MJX, sparse in MJ. ignore for now.
{(types.Data, 'efc_J'), (types.Option, 'has_fluid_params')}
)
def _model_derived(value: mujoco.MjModel) -> Dict[str, Any]:
return {k: jax.device_put(v) for k, v in mesh.get(value).items()}
def _data_derived(value: mujoco.MjData) -> Dict[str, Any]:
return {'efc_J': jax.device_put(value.efc_J)}
def _option_derived(value: types.Option) -> Dict[str, Any]:
has_fluid = (
value.density > 0 or value.viscosity > 0 or (value.wind != 0.0).any()
)
return {'has_fluid_params': has_fluid}
def _validate(m: mujoco.MjModel):
"""Validates that an mjModel is compatible with MJX."""
# check enum types
for mj_type, attrs in _MJ_TYPE_ATTR.items():
val = m
for attr in attrs:
val = attr.fget(val) # pytype: disable=attribute-error
typs = set(val) if isinstance(val, Iterable) else {val}
unsupported_typs = typs - set(_TYPE_MAP[mj_type])
unsupported = [mj_type(t) for t in unsupported_typs] # pylint: disable=too-many-function-args
if unsupported:
raise NotImplementedError(f'{unsupported} not implemented.')
# check condim
if any(dim != 3 for dim in m.geom_condim) or any(
dim != 3 for dim in m.pair_dim
):
raise NotImplementedError('Only condim=3 is supported.')
if m.ntendon:
raise NotImplementedError('Tendons are not supported.')
# check collision geom types
for (g1, g2, *_), c in collision_driver.collision_candidates(m).items():
g1, g2 = mujoco.mjtGeom(g1), mujoco.mjtGeom(g2)
if g1 == mujoco.mjtGeom.mjGEOM_PLANE and g2 in (
mujoco.mjtGeom.mjGEOM_PLANE,
mujoco.mjtGeom.mjGEOM_HFIELD,
):
# MuJoCo does not collide planes with other planes or hfields
continue
if collision_driver.get_collision_fn((g1, g2)) is None:
raise NotImplementedError(f'({g1}, {g2}) collisions not implemented.')
*_, params = collision_driver.get_params(m, c)
margin_gap = not np.allclose(np.concatenate([params.margin, params.gap]), 0)
if mujoco.mjtGeom.mjGEOM_MESH in (g1, g2) and margin_gap:
raise NotImplementedError(
f'Margin and gap not implemented for ({g1}, {g2})'
)
# TODO(erikfrey): warn for high solver iterations, nefc, etc.
# mjNDISABLE is not a DisableBit flag, so must be explicitly ignored
disablebit_members = set(mujoco.mjtDisableBit.__members__.values()) - {
mujoco.mjtDisableBit.mjNDISABLE}
unsupported_disable = disablebit_members - {
mujoco.mjtDisableBit(t.value) for t in types.DisableBit
}
for f in unsupported_disable:
if f & m.opt.disableflags:
warnings.warn(f'Ignoring disable flag {f.name}.')
# mjNENABLE is not an EnableBit flag, so must be explicitly ignored
unsupported_enable = set(mujoco.mjtEnableBit.__members__.values()) - {
mujoco.mjtEnableBit.mjNENABLE
}
for f in unsupported_enable:
if f & m.opt.enableflags:
warnings.warn(f'Ignoring enable flag {f.name}.')
if not np.allclose(m.dof_frictionloss, 0):
raise NotImplementedError('dof_frictionloss is not implemented.')
@overload
def device_put(value: mujoco.MjData) -> types.Data:
...
@overload
def device_put(value: mujoco.MjModel) -> types.Model:
...
def device_put(value):
"""Places mujoco data onto a device.
Args:
value: a mujoco struct to transfer
Returns:
on-device MJX struct reflecting the input value
"""
warnings.warn(
'device_put is deprecated, use put_model and put_data instead',
category=DeprecationWarning,
)
clz = _TYPE_MAP.get(type(value))
if clz is None:
raise NotImplementedError(f'{type(value)} is not supported for device_put.')
if isinstance(value, mujoco.MjModel):
_validate(value) # type: ignore
init_kwargs = {}
for f in dataclasses.fields(clz): # type: ignore
if (clz, f.name) in _DERIVED:
continue
field_value = getattr(value, f.name)
if (clz, f.name) in _TRANSFORMS:
field_value = _TRANSFORMS[(clz, f.name)](field_value)
if f.type is jax.Array:
field_value = jax.device_put(field_value)
elif type(field_value) in _TYPE_MAP.keys():
field_value = device_put(field_value)
init_kwargs[f.name] = copy.copy(field_value)
derived_kwargs = {}
if isinstance(value, mujoco.MjModel):
derived_kwargs = _model_derived(value)
elif isinstance(value, mujoco.MjData):
derived_kwargs = _data_derived(value)
elif isinstance(value, mujoco.MjOption):
derived_kwargs = _option_derived(value)
return clz(**init_kwargs, **derived_kwargs) # type: ignore
@overload
def device_get_into(
result: Union[mujoco.MjData, List[mujoco.MjData]], value: types.Data
):
...
def device_get_into(result, value):
"""Transfers data off device into a mujoco MjData.
Data on device often has a batch dimension which adds (N,) to the beginning
of each array shape where N = batch size.
If result is a single MjData, arrays are copied over with the batch dimension
intact. If result is a list, the list must be length N and will be populated
with distinct MjData structs where the batch dimension is stripped.
Args:
result: struct (or list of structs) to transfer into
value: device value to transfer
Raises:
RuntimeError: if result length doesn't match data batch size
"""
warnings.warn(
'device_get_into is deprecated, use get_data instead',
category=DeprecationWarning,
)
value = jax.device_get(value)
if isinstance(result, list):
array_shapes = [s.shape for s in jax.tree_util.tree_flatten(value)[0]]
if any(len(s) < 1 or s[0] != array_shapes[0][0] for s in array_shapes):
raise ValueError('unrecognizable batch dimension in value')
batch_size = array_shapes[0][0]
if len(result) != batch_size:
raise ValueError(
f"result length ({len(result)}) doesn't match value batch size"
f' ({batch_size})'
)
for i in range(batch_size):
value_i = jax.tree_map(lambda x, i=i: x[i], value)
device_get_into(result[i], value_i)
else:
if isinstance(result, mujoco.MjData):
ncon = value.contact.dist.shape[0]
nefc = value.efc_J.shape[0]
mujoco._functions._realloc_con_efc( # pylint: disable=protected-access
result, ncon=ncon, nefc=nefc
)
result.ncon = ncon
result.nefc = nefc
efc_start = nefc - ncon * 4
result.contact.efc_address[:] = np.arange(efc_start, nefc, 4)
result.contact.dim[:] = 3
for f in dataclasses.fields(value): # type: ignore
if (type(value), f.name) in _DERIVED:
continue
field_value = getattr(value, f.name)
if (type(value), f.name) in _INVERSE_TRANSFORMS:
field_value = _INVERSE_TRANSFORMS[(type(value), f.name)](field_value)
if type(field_value) in _TYPE_MAP.values():
device_get_into(getattr(result, f.name), field_value)
continue
try:
setattr(result, f.name, field_value)
except AttributeError:
getattr(result, f.name)[:] = field_value
-203
View File
@@ -1,203 +0,0 @@
# Copyright 2023 DeepMind Technologies Limited
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Tests for moving mujoco structs on and off device."""
import dataclasses
from absl.testing import absltest
from absl.testing import parameterized
import jax
from jax import numpy as jp
import mujoco
from mujoco import mjx
from mujoco.mjx._src import device
from mujoco.mjx._src import test_util
from mujoco.mjx._src import types
# pylint: disable=g-importing-member
from mujoco.mjx._src.dataclasses import PyTreeNode
# pylint: enable=g-importing-member
import numpy as np
def _assert_eq(testcase, a, b, attr=None, name=None):
if (type(a), attr) in device._DERIVED:
return
if attr:
a, b = getattr(a, attr), getattr(b, attr)
if isinstance(a, PyTreeNode):
for field in dataclasses.fields(a):
_assert_eq(testcase, a, b, field.name, type(a).__name__)
return
typ = {'Model': types.Model, 'Data': types.Data,
'Contact': types.Contact}.get(name)
if (typ, attr) in device._TRANSFORMS:
b = device._TRANSFORMS[(typ, attr)](b)
err_msg = f'mismatch: {attr} in {name}'
if not hasattr(b, 'shape') or not b.shape:
testcase.assertEqual(a, b, err_msg)
return
a, b = np.array(a), np.array(b)
np.testing.assert_allclose(a, b, err_msg=err_msg, atol=1e-8)
class DeviceTest(parameterized.TestCase):
@parameterized.parameters('constraints.xml', 'pendula.xml')
def testdevice_put(self, fname):
"""Test putting MjData and MjModel on device."""
m = test_util.load_test_file(fname)
# advance state to ensure non-zero fields
d = mujoco.MjData(m)
for _ in range(10):
mujoco.mj_step(m, d)
_assert_eq(self, mjx.device_put(d), d)
_assert_eq(self, mjx.device_put(m), m)
@parameterized.parameters('constraints.xml', 'pendula.xml')
def testdevice_get(self, fname):
"""Test getting MjData from a device."""
m = test_util.load_test_file(fname)
m.opt.jacobian = mujoco.mjtJacobian.mjJAC_SPARSE # force sparse for testing
mx = device.device_put(m)
dx = mjx.make_data(mx)
d = mujoco.MjData(m)
device.device_get_into(d, dx)
_assert_eq(self, dx, d)
@parameterized.parameters('constraints.xml', 'pendula.xml')
def testdevice_get_batched(self, fname):
"""Test getting MjData from a device."""
m = test_util.load_test_file(fname)
m.opt.jacobian = mujoco.mjtJacobian.mjJAC_SPARSE # force sparse for testing
mx = device.device_put(m)
batch_size = 32
# create mjx_data and batch it
dx = mjx.make_data(mx)
dx = jax.tree_map(
lambda x: jp.repeat(x, batch_size).reshape((batch_size,) + x.shape),
dx,
)
ds = [mujoco.MjData(m) for _ in range(batch_size - 1)]
with self.assertRaises(ValueError):
device.device_get_into(ds, dx)
ds = [mujoco.MjData(m) for _ in range(batch_size)]
device.device_get_into(ds, dx)
dx = jax.device_get(dx) # faster indexing for testing
for i in range(batch_size):
_assert_eq(self, jax.tree_map(lambda x, i=i: x[i], dx), ds[i])
class ValidateInputTest(absltest.TestCase):
def test_solver(self):
m = mujoco.MjModel.from_xml_string(
'<mujoco><option solver="PGS"/><worldbody/></mujoco>'
)
with self.assertRaises(NotImplementedError):
mjx.device_put(m)
def test_integrator(self):
m = mujoco.MjModel.from_xml_string(
'<mujoco><option integrator="implicit"/><worldbody/></mujoco>'
)
with self.assertRaises(NotImplementedError):
mjx.device_put(m)
def test_cone(self):
m = mujoco.MjModel.from_xml_string(
'<mujoco><option cone="elliptic"/><worldbody/></mujoco>'
)
with self.assertRaises(NotImplementedError):
mjx.device_put(m)
def test_dyn(self):
m = test_util.load_test_file('pendula.xml')
m.actuator_dyntype[0] = mujoco.mjtDyn.mjDYN_MUSCLE
with self.assertRaises(NotImplementedError):
mjx.device_put(m)
def test_gain(self):
m = test_util.load_test_file('pendula.xml')
m.actuator_gaintype[0] = mujoco.mjtGain.mjGAIN_MUSCLE
with self.assertRaises(NotImplementedError):
mjx.device_put(m)
def test_bias(self):
m = test_util.load_test_file('pendula.xml')
m.actuator_gaintype[0] = mujoco.mjtGain.mjGAIN_MUSCLE
with self.assertRaises(NotImplementedError):
mjx.device_put(m)
def test_condim(self):
m = test_util.load_test_file('constraints.xml')
for i in [1, 4, 6]:
m.geom_condim[0] = i
with self.assertRaises(NotImplementedError):
mjx.device_put(m)
def test_geoms(self):
m = mujoco.MjModel.from_xml_string("""
<mujoco>
<worldbody>
<body>
<joint axis="1 0 0" type="free"/>
<geom size="0.2 0.2 0.2" type="box"/>
</body>
<body>
<joint axis="1 0 0" type="free"/>
<geom size="0.1 0.1" type="cylinder"/>
</body>
</worldbody>
</mujoco>
""")
with self.assertRaises(NotImplementedError):
mjx.device_put(m)
def test_tendon(self):
m = mujoco.MjModel.from_xml_string("""
<mujoco>
<worldbody>
<body name="left_thigh" pos="0 0.1 -0.04">
<joint axis="0 1 0" name="left_hip_y" type="hinge"/>
<geom fromto="0 0 0 0 -0.01 -.34" name="left_thigh1" size="0.06" type="capsule"/>
<body name="left_shin" pos="0 -0.01 -0.403">
<joint axis="0 -1 0" name="left_knee" pos="0 0 .02" range="-160 -2" type="hinge"/>
<geom fromto="0 0 0 0 0 -.3" name="left_shin1" size="0.049" type="capsule"/>
</body>
</body>
</worldbody>
<tendon>
<fixed name="left_hipknee">
<joint coef="-1" joint="left_hip_y"/>
<joint coef="1" joint="left_knee"/>
</fixed>
</tendon>
</mujoco>
""")
with self.assertRaises(NotImplementedError):
mjx.device_put(m)
if __name__ == '__main__':
absltest.main()
+7 -6
View File
@@ -174,16 +174,17 @@ def fwd_actuation(m: Model, d: Data) -> Data:
qfrc_actuator = d.actuator_moment.T @ force
if m.ngravcomp:
# actuator-level gravity compensation, skip if added as passive force
qfrc_actuator += d.qfrc_gravcomp * m.jnt_actgravcomp[m.dof_jntid]
# clamp qfrc_actuator
actfrcrange = jp.where(
m.jnt_actfrclimited[:, None],
m.jnt_actfrcrange,
jp.array([-jp.inf, jp.inf]),
)
ids = sum(
([i] * JointType(j).dof_width() for i, j in enumerate(m.jnt_type)), []
)
actfrcrange = jp.take(actfrcrange, jp.array(ids), axis=0)
actfrcrange = actfrcrange[m.dof_jntid]
qfrc_actuator = jp.clip(qfrc_actuator, actfrcrange[:, 0], actfrcrange[:, 1])
d = d.replace(act_dot=act_dot, qfrc_actuator=qfrc_actuator)
@@ -310,7 +311,7 @@ def rungekutta4(m: Model, d: Data) -> Data:
kqvel = d.qvel # intermediate RK solution
# RK solutions sum
qvel, qacc, act_dot = jax.tree_map(
qvel, qacc, act_dot = jax.tree_util.tree_map(
lambda k: B[0] * k, (kqvel, d.qacc, d.act_dot)
)
integrate_fn = lambda *args: _integrate_pos(*args, dt=m.opt.timestep)
@@ -318,7 +319,7 @@ def rungekutta4(m: Model, d: Data) -> Data:
def f(carry, x):
qvel, qacc, act_dot, kqvel, d = carry
a, b, t = x # tableau numbers
dqvel, dqacc, dact_dot = jax.tree_map(
dqvel, dqacc, dact_dot = jax.tree_util.tree_map(
lambda k: a * k, (kqvel, d.qacc, d.act_dot)
)
# get intermediate RK solutions
+125 -136
View File
@@ -22,15 +22,14 @@ from jax import numpy as jp
import mujoco
from mujoco.mjx._src import collision_driver
from mujoco.mjx._src import constraint
from mujoco.mjx._src import mesh
from mujoco.mjx._src import support
from mujoco.mjx._src import types
import numpy as np
import scipy
def _put_option(o: mujoco.MjOption, device=None) -> types.Option:
"""Puts mujoco.MjOption onto a device, resulting in mjx.Option."""
def _make_option(o: mujoco.MjOption) -> types.Option:
"""Returns mjx.Option given mujoco.MjOption."""
if o.integrator not in set(types.IntegratorType):
raise NotImplementedError(f'{mujoco.mjtIntegrator(o.integrator)}')
@@ -47,38 +46,20 @@ def _put_option(o: mujoco.MjOption, device=None) -> types.Option:
if o.enableflags & 2**i:
raise NotImplementedError(f'{mujoco.mjtEnableBit(2 ** i)}')
static_fields = {
f.name: copy.copy(getattr(o, f.name))
for f in types.Option.fields()
if f.type in (int, bytes, np.ndarray)
}
static_fields['integrator'] = types.IntegratorType(o.integrator)
static_fields['cone'] = types.ConeType(o.cone)
static_fields['jacobian'] = types.JacobianType(o.jacobian)
static_fields['solver'] = types.SolverType(o.solver)
static_fields['disableflags'] = types.DisableBit(o.disableflags)
fields = {f.name: getattr(o, f.name, None) for f in types.Option.fields()}
fields['integrator'] = types.IntegratorType(o.integrator)
fields['cone'] = types.ConeType(o.cone)
fields['jacobian'] = types.JacobianType(o.jacobian)
fields['solver'] = types.SolverType(o.solver)
fields['disableflags'] = types.DisableBit(o.disableflags)
fields['has_fluid_params'] = o.density > 0 or o.viscosity > 0 or o.wind.any()
device_fields = {
f.name: copy.copy(getattr(o, f.name))
for f in types.Option.fields()
if f.type is jax.Array
}
device_fields = jax.device_put(device_fields, device=device)
has_fluid_params = o.density > 0 or o.viscosity > 0 or o.wind.any()
return types.Option(
has_fluid_params=has_fluid_params,
**static_fields,
**device_fields,
)
return types.Option(**fields)
def _put_statistic(s: mujoco.MjStatistic, device=None) -> types.Statistic:
def _make_statistic(s: mujoco.MjStatistic) -> types.Statistic:
"""Puts mujoco.MjStatistic onto a device, resulting in mjx.Statistic."""
return types.Statistic(
meaninertia=jax.device_put(s.meaninertia, device=device)
)
return types.Statistic(meaninertia=s.meaninertia)
def put_model(m: mujoco.MjModel, device=None) -> types.Model:
@@ -87,20 +68,21 @@ def put_model(m: mujoco.MjModel, device=None) -> types.Model:
if m.ntendon:
raise NotImplementedError('tendons are not supported')
if (m.geom_condim != 3).any() or (m.pair_dim != 3).any():
raise NotImplementedError('only condim=3 is supported')
# check collision geom types
for (g1, g2, *_), c in collision_driver.collision_candidates(m).items():
g1, g2 = mujoco.mjtGeom(g1), mujoco.mjtGeom(g2)
if collision_driver.get_collision_fn((g1, g2)) is None:
raise NotImplementedError(f'({g1}, {g2}) has no collision function')
*_, params = collision_driver.get_params(m, c)
margin_gap = not np.allclose(np.concatenate([params.margin, params.gap]), 0)
if mujoco.mjtGeom.mjGEOM_MESH in (g1, g2) and margin_gap:
raise NotImplementedError(
f'Margin and gap not implemented for ({g1}, {g2})'
)
for g1, g2, ip in collision_driver.geom_pairs(m):
t1, t2 = m.geom_type[[g1, g2]]
# check collision function exists for type pair
if not collision_driver.has_collision_fn(t1, t2):
t1, t2 = mujoco.mjtGeom(t1), mujoco.mjtGeom(t2)
raise NotImplementedError(f'({t1}, {t2}) collisions not implemented.')
# margin/gap not supported for geoms
if mujoco.mjtGeom.mjGEOM_MESH in (t1, t2):
if ip != -1:
margin = m.pair_margin[ip]
else:
margin = m.geom_margin[g1] + m.geom_margin[g2]
if margin.any():
t1, t2 = mujoco.mjtGeom(t1), mujoco.mjtGeom(t2)
raise NotImplementedError(f'({t1}, {t2}) margin/gap not implemented.')
for enum_field, enum_type, mj_type in (
(m.actuator_biastype, types.BiasType, mujoco.mjtBias),
@@ -118,40 +100,24 @@ def put_model(m: mujoco.MjModel, device=None) -> types.Model:
if not np.allclose(m.dof_frictionloss, 0):
raise NotImplementedError('dof_frictionloss is not implemented.')
opt = _put_option(m.opt, device=device)
stat = _put_statistic(m.stat, device=device)
fields = {f.name: getattr(m, f.name) for f in types.Model.fields()}
fields['geom_rgba'] = fields['geom_rgba'].reshape((-1, 4))
fields['mat_rgba'] = fields['mat_rgba'].reshape((-1, 4))
fields['cam_mat0'] = fields['cam_mat0'].reshape((-1, 3, 3))
fields['opt'] = _make_option(m.opt)
fields['stat'] = _make_statistic(m.stat)
model = types.Model(**{k: copy.copy(v) for k, v in fields.items()})
static_fields = {
f.name: getattr(m, f.name)
for f in types.Model.fields()
if f.type in (int, bytes, np.ndarray)
}
static_fields['geom_rgba'] = static_fields['geom_rgba'].reshape((-1, 4))
static_fields['mat_rgba'] = static_fields['mat_rgba'].reshape((-1, 4))
device_fields = {
f.name: copy.copy(getattr(m, f.name)) # copy because device_put is async
for f in types.Model.fields()
if f.type is jax.Array
}
device_fields['cam_mat0'] = device_fields['cam_mat0'].reshape((-1, 3, 3))
device_fields.update(mesh.get(m))
device_fields = jax.device_put(device_fields, device=device)
return types.Model(
opt=opt,
stat=stat,
**static_fields,
**device_fields,
)
return jax.device_put(model, device=device)
def make_data(m: Union[types.Model, mujoco.MjModel]) -> types.Data:
"""Allocate and initialize Data."""
ncon = collision_driver.ncon(m)
ne, nf, nl, nc = constraint.count_constraints(m)
nefc = ne + nf + nl + nc
dim = collision_driver.make_condim(m)
efc_type = constraint.make_efc_type(m, dim)
efc_address = constraint.make_efc_address(efc_type, dim)
ne, nf, nl, nc = constraint.counts(efc_type)
ncon, nefc = dim.size, ne + nf + nl + nc
zero_0 = jp.zeros(0, dtype=float)
zero_nv = jp.zeros(m.nv, dtype=float)
@@ -167,8 +133,28 @@ def make_data(m: Union[types.Model, mujoco.MjModel]) -> types.Data:
zero_njnt_3 = jp.zeros((m.njnt, 3), dtype=float)
zero_nm = jp.zeros(m.nM, dtype=float)
# create first d to get num contacts and nc
contact = types.Contact(
dist=jp.zeros(ncon),
pos=jp.zeros((ncon, 3)),
frame=jp.zeros((ncon, 3, 3)),
includemargin=jp.zeros(ncon),
friction=jp.zeros((ncon, 5)),
solref=jp.zeros((ncon, mujoco.mjNREF)),
solreffriction=jp.zeros((ncon, mujoco.mjNREF)),
solimp=jp.zeros((ncon, mujoco.mjNIMP)),
dim=dim,
geom1=jp.zeros(ncon, dtype=int) - 1,
geom2=jp.zeros(ncon, dtype=int) - 1,
geom=jp.zeros((ncon, 2), dtype=int) - 1,
efc_address=efc_address,
)
d = types.Data(
ne=ne,
nf=nf,
nl=nl,
nefc=nefc,
ncon=ncon,
solver_niter=jp.array(0, dtype=int),
time=jp.array(0.0),
qpos=jp.array(m.qpos0),
@@ -203,7 +189,8 @@ def make_data(m: Union[types.Model, mujoco.MjModel]) -> types.Data:
qM=zero_nm if support.is_sparse(m) else zero_nv_nv,
qLD=zero_nm if support.is_sparse(m) else zero_nv_nv,
qLDiagInv=zero_nv if support.is_sparse(m) else zero_0,
contact=types.Contact.zero(ncon),
contact=contact,
efc_type=efc_type,
efc_J=jp.zeros((nefc, m.nv), dtype=float),
efc_frictionloss=zero_nefc,
efc_D=zero_nefc,
@@ -211,6 +198,7 @@ def make_data(m: Union[types.Model, mujoco.MjModel]) -> types.Data:
cvel=zero_nbody_6,
cdof_dot=zero_nv_6,
qfrc_bias=zero_nv,
qfrc_gravcomp=zero_nv,
qfrc_passive=zero_nv,
efc_aref=zero_nefc,
qfrc_actuator=zero_nv,
@@ -219,16 +207,13 @@ def make_data(m: Union[types.Model, mujoco.MjModel]) -> types.Data:
qfrc_constraint=zero_nv,
qfrc_inverse=zero_nv,
efc_force=zero_nefc,
userdata=jp.zeros(m.nuserdata, dtype=float),
)
return d
def _get_contact(
c: mujoco._structs._MjContactList,
cx: types.Contact,
efc_start: int,
):
def _get_contact(c: mujoco._structs._MjContactList, cx: types.Contact):
"""Converts mjx.Contact to mujoco._structs._MjContactList."""
con_id = np.nonzero(cx.dist <= 0)[0]
for field in types.Contact.fields():
@@ -237,10 +222,6 @@ def _get_contact(
value = value.reshape((-1, 9))
getattr(c, field.name)[:] = value
ncon = cx.dist.shape[0]
c.efc_address[:] = np.arange(efc_start, efc_start + ncon * 4, 4)[con_id]
c.dim[:] = 3
def get_data(
m: mujoco.MjModel, d: types.Data
@@ -274,13 +255,6 @@ def get_data_into(
d = jax.device_get(d)
batch_size = d.qpos.shape[0] if batched else 1
ne, nf, nl, nc = constraint.count_constraints(m, d)
efc_type = np.array([
mujoco.mjtConstraint.mjCNSTR_EQUALITY,
mujoco.mjtConstraint.mjCNSTR_FRICTION_DOF,
mujoco.mjtConstraint.mjCNSTR_LIMIT_JOINT,
mujoco.mjtConstraint.mjCNSTR_CONTACT_PYRAMIDAL,
]).repeat([ne, nf, nl, nc])
dof_i, dof_j = [], []
for i in range(m.nv):
@@ -291,12 +265,11 @@ def get_data_into(
j = m.dof_parentid[j]
for i in range(batch_size):
d_i = jax.tree_map(lambda x, i=i: x[i], d) if batched else d
d_i = jax.tree_util.tree_map(lambda x, i=i: x[i], d) if batched else d
result_i = result[i] if batched else result
ncon = (d_i.contact.dist <= 0).sum()
efc_active = (d_i.efc_J != 0).any(axis=1)
efc_con = efc_type == mujoco.mjtConstraint.mjCNSTR_CONTACT_PYRAMIDAL
nefc, nc = int(efc_active.sum()), int((efc_active & efc_con).sum())
nefc = int(efc_active.sum())
result_i.nnzJ = nefc * m.nv
if ncon != result_i.ncon or nefc != result_i.nefc:
mujoco._functions._realloc_con_efc(result_i, ncon=ncon, nefc=nefc) # pylint: disable=protected-access
@@ -306,61 +279,77 @@ def get_data_into(
for field in types.Data.fields():
if field.name == 'contact':
_get_contact(result_i.contact, d_i.contact, nefc - nc)
_get_contact(result_i.contact, d_i.contact)
# efc_address must be updated because rows were deleted above:
efc_map = np.cumsum(efc_active) - 1
result_i.contact.efc_address[:] = efc_map[result_i.contact.efc_address]
continue
value = getattr(d_i, field.name)
if field.name in ('xmat', 'ximat', 'geom_xmat', 'site_xmat', 'cam_xmat'):
if field.name in ('nefc', 'ncon'):
value = {'nefc': nefc, 'ncon': ncon}[field.name]
elif field.name.endswith('xmat') or field.name == 'ximat':
value = value.reshape((-1, 9))
if field.name in ('efc_frictionloss', 'efc_D', 'efc_aref', 'efc_force'):
elif field.name.startswith('efc_'):
value = value[efc_active]
if field.name == 'efc_J':
value = value[efc_active].reshape(-1)
if field.name == 'qM' and not support.is_sparse(m):
if field.name == 'efc_J':
value = value.reshape(-1)
elif field.name == 'qM' and not support.is_sparse(m):
value = value[dof_i, dof_j]
if field.name == 'qLD' and not support.is_sparse(m):
elif field.name == 'qLD' and not support.is_sparse(m):
value = value[dof_i, dof_j]
if field.name == 'qLDiagInv' and not support.is_sparse(m):
elif field.name == 'qLDiagInv' and not support.is_sparse(m):
value = np.ones(m.nv)
if value.shape:
if isinstance(value, np.ndarray) and value.shape:
getattr(result_i, field.name)[:] = value
else:
setattr(result_i, field.name, value)
result_i.efc_type[:] = efc_type[efc_active]
def _put_contact(
c: mujoco._structs._MjContactList, ncon: int, device=None
def _make_contact(
c: mujoco._structs._MjContactList,
dim: np.ndarray,
efc_address: np.ndarray,
) -> types.Contact:
"""Puts mujoco.structs._MjContactList onto a device, resulting in mjx.Contact."""
fields = {
f.name: copy.copy(getattr(c, f.name)) for f in types.Contact.fields()
}
"""Converts mujoco.structs._MjContactList into mjx.Contact."""
fields = {f.name: getattr(c, f.name) for f in types.Contact.fields()}
fields['frame'] = fields['frame'].reshape((-1, 3, 3))
pad_size = ncon - c.dist.shape[0]
pad_fn = lambda x: np.concatenate(
(x, np.zeros((pad_size,) + x.shape[1:], dtype=x.dtype))
)
fields = jax.tree_map(pad_fn, fields)
fields['dist'][-pad_size:] = np.inf
fields = jax.device_put(fields, device=device)
# reorder contacts so that their condims match those specified in dim.
# if we have fewer Contacts for a condim range, pad the range with zeros
# build a map for where to find a dim-matching contact, or -1 if none
contact_map = np.zeros_like(dim) - 1
for i, di in enumerate(fields['dim']):
space = [j for j, dj in enumerate(dim) if di == dj and contact_map[j] == -1]
if not space:
# this can happen if max_geom_pairs or max_contact_points is too low
raise ValueError(f'unable to place Contact[{i}], no space in condim {di}')
contact_map[space[0]] = i
if contact_map.size > 0:
# reorganize contact according, with a zero contact at the end for -1
zero = jax.tree_util.tree_map(
lambda x: np.zeros((1,) + x.shape[1:], dtype=x.dtype), fields
)
zero['dist'][:] = np.finfo(float).max
fields = jax.tree_util.tree_map(lambda *x: np.concatenate(x), fields, zero)
fields = jax.tree_util.tree_map(lambda x: x[contact_map], fields)
fields['dim'] = dim
fields['efc_address'] = efc_address
return types.Contact(**fields)
def put_data(m: mujoco.MjModel, d: mujoco.MjData, device=None) -> types.Data:
"""Puts mujoco.MjData onto a device, resulting in mjx.Data."""
ncon = collision_driver.ncon(m)
ne, nf, nl, nc = constraint.count_constraints(m)
nefc = ne + nf + nl + nc
dim = collision_driver.make_condim(m)
efc_type = constraint.make_efc_type(m, dim)
efc_address = constraint.make_efc_address(efc_type, dim)
ne, nf, nl, nc = constraint.counts(efc_type)
ncon, nefc = dim.size, ne + nf + nl + nc
for d_val, val, name in (
(d.ncon, ncon, 'ncon'),
@@ -372,12 +361,9 @@ def put_data(m: mujoco.MjModel, d: mujoco.MjData, device=None) -> types.Data:
if d_val > val:
raise ValueError(f'd.{name} too high, d.{name} = {d_val}, model = {val}')
fields = {
f.name: copy.copy(getattr(d, f.name)) # copy because device_put is async
for f in types.Data.fields()
if f.type is jax.Array
}
fields = {f.name: getattr(d, f.name) for f in types.Data.fields()}
# MJX prefers square matrices for these fields:
for fname in ('xmat', 'ximat', 'geom_xmat', 'site_xmat', 'cam_xmat'):
fields[fname] = fields[fname].reshape((-1, 3, 3))
@@ -405,7 +391,7 @@ def put_data(m: mujoco.MjModel, d: mujoco.MjData, device=None) -> types.Data:
value_beg = sum([ne, nf, nl][:i])
d_beg = sum([d.ne, d.nf, d.nl][:i])
size = [d.ne, d.nf, d.nl, d.nefc - d.nl - d.nf - d.ne][i]
value[value_beg:value_beg+size] = fields[fname][d_beg:d_beg+size]
value[value_beg : value_beg + size] = fields[fname][d_beg : d_beg + size]
fields[fname] = value
# convert qM and qLD if jacobian is dense
@@ -420,7 +406,10 @@ def put_data(m: mujoco.MjModel, d: mujoco.MjData, device=None) -> types.Data:
fields['qLD'] = np.zeros((m.nv, m.nv))
fields['qLDiagInv'] = np.zeros(0)
fields = jax.device_put(fields, device=device)
fields['contact'] = _put_contact(d.contact, ncon, device=device)
fields['contact'] = _make_contact(d.contact, dim, efc_address)
fields.update(ne=ne, nf=nf, nl=nl, nefc=nefc, ncon=ncon, efc_type=efc_type)
return types.Data(**fields)
# copy because device_put is async:
data = types.Data(**{k: copy.copy(v) for k, v in fields.items()})
return jax.device_put(data, device=device)
+12 -32
View File
@@ -63,7 +63,7 @@ _MULTIPLE_CONVEX_OBJECTS = """
_MULTIPLE_CONSTRAINTS = """
<mujoco>
<worldbody>
<geom type="plane" size="3 3 .01"/>
<geom type="plane" size="3 3 .01" condim="6"/>
<body name="cap1" pos="-.3 -.3 .2">
<freejoint/>
<geom type="capsule" size=".2 .05"/>
@@ -108,10 +108,6 @@ class ModelIOTest(parameterized.TestCase):
np.testing.assert_allclose(mx.geom_bodyid, m.geom_bodyid)
np.testing.assert_almost_equal(mx.geom_solref, m.geom_solref)
np.testing.assert_almost_equal(mx.geom_pos, m.geom_pos)
self.assertLen(mx.geom_convex_face, 6)
self.assertLen(mx.geom_convex_vert, 6)
self.assertLen(mx.geom_convex_edge_dir, 6)
self.assertLen(mx.geom_convex_facenormal, 6)
np.testing.assert_allclose(mx.jnt_type, m.jnt_type)
np.testing.assert_allclose(mx.jnt_dofadr, m.jnt_dofadr)
@@ -177,22 +173,6 @@ class ModelIOTest(parameterized.TestCase):
</tendon>
</mujoco>"""))
def test_condim_not_implemented(self):
with self.assertRaises(NotImplementedError):
mjx.put_model(mujoco.MjModel.from_xml_string("""
<mujoco>
<worldbody>
<body>
<freejoint/>
<geom size="0.05" condim="1"/>
</body>
<body>
<freejoint/>
<geom size="0.05" condim="1"/>
</body>
</worldbody>
</mujoco>"""))
def test_cylinder_not_implemented(self):
with self.assertRaises(NotImplementedError):
mjx.put_model(mujoco.MjModel.from_xml_string("""
@@ -244,6 +224,7 @@ class DataIOTest(parameterized.TestCase):
nv = 19
nefc = 185
self.assertEqual(d.nefc, nefc)
self.assertEqual(d.qpos.shape, (nq,))
self.assertEqual(d.qvel.shape, (nv,))
self.assertEqual(d.act.shape, (0,))
@@ -336,21 +317,21 @@ class DataIOTest(parameterized.TestCase):
np.testing.assert_allclose(dx.site_xmat.reshape((1, 9)), d.site_xmat)
# efc_ are also shape transformed and padded
self.assertEqual(dx.efc_J.shape, (21, 8)) # nefc, nv
self.assertEqual(dx.efc_J.shape, (45, 8)) # nefc, nv
d_efc_j = d.efc_J.reshape((-1, 8))
np.testing.assert_allclose(dx.efc_J[:3], d_efc_j[:3]) # connect eq
np.testing.assert_allclose(dx.efc_J[3], d_efc_j[3]) # one active limit
np.testing.assert_allclose(dx.efc_J[4], 0) # one inactive limit
np.testing.assert_allclose(dx.efc_J[5:9], d_efc_j[4:8]) # contact
np.testing.assert_allclose(dx.efc_J[9:], 0) # no contact
np.testing.assert_allclose(dx.efc_J[5:15], d_efc_j[4:14]) # contact
np.testing.assert_allclose(dx.efc_J[15:], 0) # no contact
# check another efc_ too
self.assertEqual(dx.efc_aref.shape, (21,)) # nefc
self.assertEqual(dx.efc_aref.shape, (45,)) # nefc
np.testing.assert_allclose(dx.efc_aref[:3], d.efc_aref[:3])
np.testing.assert_allclose(dx.efc_aref[3], d.efc_aref[3])
np.testing.assert_allclose(dx.efc_aref[4], 0)
np.testing.assert_allclose(dx.efc_aref[5:9], d.efc_aref[4:8])
np.testing.assert_allclose(dx.efc_aref[9:], 0)
np.testing.assert_allclose(dx.efc_aref[5:15], d.efc_aref[4:14])
np.testing.assert_allclose(dx.efc_aref[15:], 0)
# check sparse transform is correct
m.opt.jacobian = mujoco.mjtJacobian.mjJAC_SPARSE
@@ -407,12 +388,11 @@ class DataIOTest(parameterized.TestCase):
np.testing.assert_allclose(d_2.site_xmat, d.site_xmat)
# efc_* are also shape transformed and filtered
self.assertEqual(d_2.efc_J.shape, (64,)) # nefc * nv
self.assertEqual(d_2.nefc, 14)
self.assertEqual(d_2.efc_J.shape, (112,)) # nefc * nv
np.testing.assert_allclose(d_2.efc_J, d.efc_J)
self.assertEqual(d_2.efc_aref.shape, (8,)) # nefc
self.assertEqual(d_2.efc_aref.shape, (14,)) # nefc
np.testing.assert_allclose(d_2.efc_aref, d.efc_aref)
# efc_address is created on demand
np.testing.assert_allclose(d_2.contact.efc_address, d.contact.efc_address)
def test_get_data_batched(self):
@@ -423,7 +403,7 @@ class DataIOTest(parameterized.TestCase):
mujoco.mj_step(m, d, 2)
dx = mjx.put_data(m, d)
# second data in batch has contact dist > 0, disables contact
dx_b = jax.tree_map(lambda x: jp.stack((x, x + 0.05)), dx)
dx_b = jax.tree_util.tree_map(lambda x: jp.stack((x, x + 0.05)), dx)
ds = mjx.get_data(m, dx_b)
self.assertLen(ds, 2)
np.testing.assert_allclose(ds[0].qpos, d.qpos)
+5
View File
@@ -245,6 +245,11 @@ def inert_mul(i: jax.Array, v: jax.Array) -> jax.Array:
return jp.concatenate((ang, vel))
def sign(x: jax.Array) -> jax.Array:
"""Returns the sign of x in the set {-1, 1}."""
return jp.where(x < 0, -1, 1)
def transform_motion(vel: jax.Array, offset: jax.Array, rotmat: jax.Array):
"""Transform spatial motion.
+102 -145
View File
@@ -15,14 +15,15 @@
"""Mesh processing."""
import collections
import dataclasses
import itertools
from typing import Dict, List, Optional, Sequence, Tuple
from typing import Tuple
import warnings
import mujoco
import jax
from jax import numpy as jp
# pylint: disable=g-importing-member
from mujoco.mjx._src.types import GeomType
from mujoco.mjx._src.collision_types import ConvexInfo
from mujoco.mjx._src.collision_types import GeomInfo
from mujoco.mjx._src.types import Model
# pylint: enable=g-importing-member
import numpy as np
@@ -30,37 +31,7 @@ from scipy import spatial
import trimesh
_BOX_CORNERS = list(itertools.product((-1, 1), (-1, 1), (-1, 1)))
# pyformat: disable
# Rectangular box faces using a counter-clockwise winding order convention.
_BOX_FACES = [
0, 4, 5, 1, # left
0, 2, 6, 4, # bottom
6, 7, 5, 4, # front
2, 3, 7, 6, # right
1, 5, 7, 3, # top
0, 1, 3, 2, # back
]
# pyformat: enable
_MAX_HULL_FACE_VERTICES = 20
_CONVEX_CACHE: Dict[Tuple[int, int], Dict[str, np.ndarray]] = {}
_DERIVED_ARGS = [
'geom_convex_face',
'geom_convex_vert',
'geom_convex_edge_dir',
'geom_convex_facenormal',
'geom_convex_edge',
'geom_convex_edge_face_normal',
]
DERIVED = {(Model, d) for d in _DERIVED_ARGS}
def _box(size: np.ndarray):
"""Creates a mesh for a box with rectangular faces."""
box_corners = np.array(_BOX_CORNERS)
vert = box_corners * size.reshape(-1, 3)
face = np.array([_BOX_FACES]).reshape(-1, 4)
return vert, face
def _get_face_norm(vert: np.ndarray, face: np.ndarray) -> np.ndarray:
@@ -170,16 +141,7 @@ def _convex_hull_2d(points: np.ndarray, normal: np.ndarray) -> np.ndarray:
return hull_point_idx
@dataclasses.dataclass
class MeshInfo:
name: str
vert: np.ndarray
face: np.ndarray
convex_vert: Optional[np.ndarray]
convex_face: Optional[np.ndarray]
def _merge_coplanar(tm: trimesh.Trimesh, mesh_info: MeshInfo) -> np.ndarray:
def _merge_coplanar(m: Model, tm: trimesh.Trimesh, meshid: int) -> np.ndarray:
"""Merges coplanar facets."""
if not tm.facets:
return tm.faces.copy() # no facets
@@ -204,9 +166,11 @@ def _merge_coplanar(tm: trimesh.Trimesh, mesh_info: MeshInfo) -> np.ndarray:
# resize faces that exceed max polygon vertices
if face.shape[0] > _MAX_HULL_FACE_VERTICES:
name = m.names[m.name_meshadr[meshid]:]
name = name[:name.find(b'\x00')].decode('utf-8')
warnings.warn(
f'Mesh "{mesh_info.name}" has a coplanar face with more than'
f' {_MAX_HULL_FACE_VERTICES} vertices. This may lead to performance '
f'Mesh "{name}" has a coplanar face with more than '
f'{_MAX_HULL_FACE_VERTICES} vertices. This may lead to performance '
'issues and inaccuracies in collision detection. Consider '
'decimating the mesh.'
)
@@ -231,106 +195,99 @@ def _merge_coplanar(tm: trimesh.Trimesh, mesh_info: MeshInfo) -> np.ndarray:
return np.concatenate([faces, facets])
def _mesh_info(
m: mujoco.MjModel,
) -> List[MeshInfo]:
"""Extracts mesh info from MjModel."""
mesh_infos = []
for i in range(m.nmesh):
name = mujoco.mj_id2name(m, mujoco.mjtObj.mjOBJ_MESH.value, i)
last = (i + 1) >= m.nmesh
face_start = m.mesh_faceadr[i]
face_end = m.mesh_faceadr[i + 1] if not last else m.mesh_face.shape[0]
face = m.mesh_face[face_start:face_end]
vert_start = m.mesh_vertadr[i]
vert_end = m.mesh_vertadr[i + 1] if not last else m.mesh_vert.shape[0]
vert = m.mesh_vert[vert_start:vert_end]
graphadr = m.mesh_graphadr[i]
if graphadr < 0:
mesh_infos.append(MeshInfo(name, vert, face, None, None))
continue
graph = m.mesh_graph[graphadr:]
numvert, numface = graph[0], graph[1]
# unused vert_edgeadr
# vert_edgeadr = graph[2 : numvert + 2]
last_idx = numvert + 2
vert_globalid = graph[last_idx : last_idx + numvert]
last_idx += numvert
# unused edge_localid
# edge_localid = graph[last_idx : last_idx + numvert + 3 * numface]
last_idx += numvert + 3 * numface
face_globalid = graph[last_idx : last_idx + 3 * numface]
face_globalid = face_globalid.reshape((numface, 3))
convex_vert = vert[vert_globalid]
vertex_map = dict(zip(vert_globalid, np.arange(vert_globalid.shape[0])))
convex_face = np.vectorize(vertex_map.get)(face_globalid)
mesh_infos.append(MeshInfo(name, vert, face, convex_vert, convex_face))
return mesh_infos
def _geom_mesh_kwargs(
mesh_info: MeshInfo,
) -> Dict[str, np.ndarray]:
"""Generates convex mesh attributes for mjx.Model."""
tm_convex = trimesh.Trimesh(
vertices=mesh_info.convex_vert, faces=mesh_info.convex_face
def box(info: GeomInfo) -> ConvexInfo:
"""Creates a box with rectangular faces."""
vert = np.array(
list(itertools.product((-1, 1), (-1, 1), (-1, 1))), dtype=float
)
# pyformat: disable
# rectangular box faces using a counter-clockwise winding order convention:
face = np.array(
[
0, 4, 5, 1, # left
0, 2, 6, 4, # bottom
6, 7, 5, 4, # front
2, 3, 7, 6, # right
1, 5, 7, 3, # top
0, 1, 3, 2, # back
]
).reshape((-1, 4))
# pyformat: enable
face_normal = _get_face_norm(vert, face)
edge, edge_face_normal = _get_edge_normals(face, face_normal)
edge_dir = _get_unique_edge_dir(vert, face)
face = vert[face] # materialize full nface x nvert matrix
c = ConvexInfo(
info.pos,
info.mat,
vert,
face,
face_normal,
edge,
edge_face_normal,
edge_dir,
)
c = jax.tree_util.tree_map(jp.array, c)
vert = jax.vmap(jp.multiply, in_axes=(None, 0))(c.vert, info.size)
face = jax.vmap(jp.multiply, in_axes=(None, 0))(c.face, info.size)
c = c.replace(vert=vert, face=face)
return c
def convex(m: Model, mesh_id: int, info: GeomInfo) -> ConvexInfo:
"""Processes a mesh for use in convex collision algorithms.
Args:
m: an MJX model
mesh_id: the mesh id to process
info: pos, mat, size of this geom
Returns:
a convex mesh info
"""
vert_beg = m.mesh_vertadr[mesh_id]
vert_end = m.mesh_vertadr[mesh_id + 1] if mesh_id < m.nmesh - 1 else None
vert = m.mesh_vert[vert_beg:vert_end]
graphadr = m.mesh_graphadr[mesh_id]
graph = m.mesh_graph[graphadr:]
graph_idx = 0
numvert, numface = graph[0], graph[1]
graph_idx += 2
# skip vert_edgeadr (numvert,)
graph_idx += numvert
vert_globalid = graph[graph_idx : graph_idx + numvert]
graph_idx += numvert
# skip edge_localid (numvert, 3)
graph_idx += numvert + 3 * numface
face_globalid = graph[graph_idx : graph_idx + 3 * numface].reshape((-1, 3))
vert = vert[vert_globalid]
vertex_map = dict(zip(vert_globalid, np.arange(vert_globalid.shape[0])))
face = np.vectorize(vertex_map.get)(face_globalid)
tm_convex = trimesh.Trimesh(vertices=vert, faces=face)
vert = np.array(tm_convex.vertices)
face = _merge_coplanar(tm_convex, mesh_info)
facenormal = _get_face_norm(vert, face)
edge, edge_face_normal = _get_edge_normals(face, facenormal)
return {
'geom_convex_face': vert[face],
'geom_convex_face_vert_idx': face,
'geom_convex_vert': vert,
'geom_convex_edge_dir': _get_unique_edge_dir(vert, face),
'geom_convex_facenormal': facenormal,
'geom_convex_edge': edge,
'geom_convex_edge_face_normal': edge_face_normal,
}
face = _merge_coplanar(m, tm_convex, mesh_id)
face_normal = _get_face_norm(vert, face)
edge, edge_face_normal = _get_edge_normals(face, face_normal)
edge_dir = _get_unique_edge_dir(vert, face)
face = vert[face] # materialize full nface x nvert matrix
c = ConvexInfo(
info.pos,
info.mat,
vert,
face,
face_normal,
edge,
edge_face_normal,
edge_dir,
)
def get(m: mujoco.MjModel) -> Dict[str, Sequence[Optional[np.ndarray]]]:
"""Derives geom mesh attributes for mjx.Model from MjModel."""
kwargs = {k: [] for k in _DERIVED_ARGS}
mesh_infos = _mesh_info(m)
geom_con = m.geom_conaffinity | m.geom_contype
for geomid in range(m.ngeom):
mesh_info = None
dataid = m.geom_dataid[geomid]
if not geom_con[geomid]:
# ignore visual-only meshes
kwargs = {k: kwargs[k] + [None] for k in _DERIVED_ARGS}
continue
elif m.geom_type[geomid] == GeomType.BOX:
vert, face = _box(m.geom_size[geomid])
mesh_info = MeshInfo(
name='box',
vert=vert,
face=face,
convex_vert=vert,
convex_face=face,
)
elif dataid < 0:
kwargs = {k: kwargs[k] + [None] for k in _DERIVED_ARGS}
continue
mesh_info = mesh_info or mesh_infos[dataid]
vert, face = mesh_info.vert, mesh_info.face
key = (hash(vert.data.tobytes()), hash(face.data.tobytes()))
if key not in _CONVEX_CACHE:
_CONVEX_CACHE[key] = _geom_mesh_kwargs(mesh_info)
kwargs = {k: kwargs[k] + [_CONVEX_CACHE[key][k]] for k in _DERIVED_ARGS}
return kwargs
return jax.tree_util.tree_map(jp.array, c)
+10 -17
View File
@@ -20,7 +20,7 @@ import numpy as np
import trimesh
class GeomMeshKwargsTest(absltest.TestCase):
class MeshTest(absltest.TestCase):
def test_pyramid(self):
"""Tests that a triangulated pyramid converts to merged coplanar faces."""
@@ -37,29 +37,21 @@ class GeomMeshKwargsTest(absltest.TestCase):
tm = trimesh.Trimesh(vertices=vert, faces=face)
tm_convex = trimesh.convex.convex_hull(tm)
convex_vert = np.array(tm_convex.vertices)
convex_face = np.array(tm_convex.faces)
mesh_info = mesh.MeshInfo(
name='test',
vert=vert,
face=face,
convex_vert=convex_vert,
convex_face=convex_face,
)
h = mesh._geom_mesh_kwargs(mesh_info)
convex_face = mesh._merge_coplanar(None, tm_convex, 0)
# get index of vertices in h['geom_convex_vert'] for vertices in vert
dist = np.repeat(vert, vert.shape[0], axis=0) - np.tile(
h['geom_convex_vert'], (vert.shape[0], 1)
convex_vert, (vert.shape[0], 1)
)
dist = (dist**2).sum(axis=1).reshape((vert.shape[0], -1))
vidx = np.argmin(dist, axis=0)
# check verts
np.testing.assert_array_equal(h['geom_convex_vert'], vert[vidx])
np.testing.assert_array_equal(convex_vert, vert[vidx])
# check face vertices
map_ = {v: k for k, v in enumerate(vidx)}
h_face = np.vectorize(map_.get)(h['geom_convex_face_vert_idx'])
h_face = np.vectorize(map_.get)(convex_face)
face_verts = sorted([tuple(sorted(set(s))) for s in h_face.tolist()])
expected_face_verts = sorted([
(0, 3, 4), (1, 3, 4), (0, 2, 4), (0, 1, 2, 3), (1, 2, 4)])
@@ -69,7 +61,8 @@ class GeomMeshKwargsTest(absltest.TestCase):
)
# check edges
unique_edge = np.vectorize(map_.get)(h['geom_convex_edge_dir'])
edge_dir = mesh._get_unique_edge_dir(convex_vert, convex_face)
unique_edge = np.vectorize(map_.get)(edge_dir)
unique_edge = np.array(sorted(unique_edge.tolist()))
np.testing.assert_array_equal(
unique_edge,
@@ -77,10 +70,11 @@ class GeomMeshKwargsTest(absltest.TestCase):
)
# face normals
self.assertEqual(h['geom_convex_facenormal'].shape, (5, 3))
face_normal = mesh._get_face_norm(convex_vert, convex_face)
self.assertEqual(face_normal.shape, (5, 3))
# face edges
edges = h['geom_convex_edge']
edges, edge_normal = mesh._get_edge_normals(convex_face, face_normal)
edges = np.vectorize(map_.get)(edges)
mask = edges[:, 0] != edges[:, 1]
edges = edges[mask]
@@ -103,7 +97,6 @@ class GeomMeshKwargsTest(absltest.TestCase):
)
# face edge normals
edge_normal = h['geom_convex_edge_face_normal']
edge_normal = edge_normal[mask]
edge_normal = np.take_along_axis(
edge_normal, sort_col_idx[..., None], axis=1
+94 -68
View File
@@ -29,6 +29,100 @@ from mujoco.mjx._src.types import Model
# pylint: enable=g-importing-member
def _spring_damper(m: Model, d: Data) -> jax.Array:
"""Applies joint level spring and damping forces."""
def fn(jnt_typs, stiffness, qpos_spring, qpos):
qpos_i = 0
qfrcs = []
for i in range(len(jnt_typs)):
jnt_typ = JointType(jnt_typs[i])
q = qpos[qpos_i : qpos_i + jnt_typ.qpos_width()]
qs = qpos_spring[qpos_i : qpos_i + jnt_typ.qpos_width()]
qfrc = jp.zeros(jnt_typ.dof_width())
if jnt_typ == JointType.FREE:
qfrc = qfrc.at[:3].set(-stiffness[i] * (q[:3] - qs[:3]))
qfrc = qfrc.at[3:6].set(-stiffness[i] * math.quat_sub(q[3:7], qs[3:7]))
elif jnt_typ == JointType.BALL:
qfrc = -stiffness[i] * math.quat_sub(q, qs)
elif jnt_typ in (
JointType.SLIDE,
JointType.HINGE,
):
qfrc = -stiffness[i] * (q - qs)
else:
raise RuntimeError(f'unrecognized joint type: {jnt_typ}')
qfrcs.append(qfrc)
qpos_i += jnt_typ.qpos_width()
return jp.concatenate(qfrcs)
# dof-level springs
qfrc = scan.flat(
m,
fn,
'jjqq',
'v',
m.jnt_type,
m.jnt_stiffness,
m.qpos_spring,
d.qpos,
)
# dof-level dampers
qfrc -= m.dof_damping * d.qvel
return qfrc
def _gravcomp(m: Model, d: Data) -> jax.Array:
"""Applies body-level gravity compensation."""
force = -m.opt.gravity * (m.body_mass * m.body_gravcomp)[:, None]
apply_f = lambda f, pos, body_id: support.jac(m, d, pos, body_id)[0] @ f
qfrc = jax.vmap(apply_f)(force, d.xipos, jp.arange(m.nbody)).sum(axis=0)
return qfrc
def _fluid(m: Model, d: Data) -> jax.Array:
"""Applies body-level viscosity, lift and drag."""
force, torque = jax.vmap(
_inertia_box_fluid_model, in_axes=(None, 0, 0, 0, 0, 0, 0)
)(
m,
m.body_inertia,
m.body_mass,
d.subtree_com[jp.array(m.body_rootid)],
d.xipos,
d.ximat,
d.cvel,
)
qfrc = jax.vmap(support.apply_ft, in_axes=(None, None, 0, 0, 0, 0))(
m, d, force, torque, d.xipos, jp.arange(m.nbody)
)
return jp.sum(qfrc, axis=0)
def passive(m: Model, d: Data) -> Data:
"""Adds all passive forces."""
if m.opt.disableflags & DisableBit.PASSIVE:
return d.replace(qfrc_passive=jp.zeros(m.nv), qfrc_gravcomp=jp.zeros(m.nv))
qfrc_passive = _spring_damper(m, d)
qfrc_gravcomp = jp.zeros(m.nv)
if m.ngravcomp and not m.opt.disableflags & DisableBit.GRAVITY:
qfrc_gravcomp = _gravcomp(m, d)
# add gravcomp unless added via actuators
qfrc_passive += qfrc_gravcomp * (1 - m.jnt_actgravcomp[m.dof_jntid])
if m.opt.has_fluid_params:
qfrc_passive += _fluid(m, d)
d = d.replace(qfrc_passive=qfrc_passive, qfrc_gravcomp=qfrc_gravcomp)
return d
def _inertia_box_fluid_model(
m: Model,
inertia: jax.Array,
@@ -71,71 +165,3 @@ def _inertia_box_fluid_model(
force, torque = ximat @ lfrc_vel, ximat @ lfrc_ang
return force, torque
def passive(m: Model, d: Data) -> Data:
"""Adds all passive forces."""
if m.opt.disableflags & DisableBit.PASSIVE:
return d.replace(qfrc_passive=jp.zeros(m.nv))
# joint-level springs
def fn(jnt_typs, stiffness, qpos_spring, qpos):
qpos_i = 0
qfrcs = []
for i in range(len(jnt_typs)):
jnt_typ = JointType(jnt_typs[i])
q = qpos[qpos_i : qpos_i + jnt_typ.qpos_width()]
qs = qpos_spring[qpos_i : qpos_i + jnt_typ.qpos_width()]
qfrc = jp.zeros(jnt_typ.dof_width())
if jnt_typ == JointType.FREE:
qfrc = qfrc.at[:3].set(-stiffness[i] * (q[:3] - qs[:3]))
qfrc = qfrc.at[3:6].set(-stiffness[i] * math.quat_sub(q[3:7], qs[3:7]))
elif jnt_typ == JointType.BALL:
qfrc = -stiffness[i] * math.quat_sub(q, qs)
elif jnt_typ in (
JointType.SLIDE,
JointType.HINGE,
):
qfrc = -stiffness[i] * (q - qs)
else:
raise RuntimeError(f'unrecognized joint type: {jnt_typ}')
qfrcs.append(qfrc)
qpos_i += jnt_typ.qpos_width()
return jp.concatenate(qfrcs)
qfrc_passive = scan.flat(
m,
fn,
'jjqq',
'v',
m.jnt_type,
m.jnt_stiffness,
m.qpos_spring,
d.qpos,
)
# dof-level dampers
qfrc_passive -= m.dof_damping * d.qvel
# TODO(robotics-simulation): body-level gravity compensation
# body-level viscosity, lift and drag
if m.opt.has_fluid_params:
force, torque = jax.vmap(
_inertia_box_fluid_model, in_axes=(None, 0, 0, 0, 0, 0, 0)
)(
m,
m.body_inertia,
m.body_mass,
d.subtree_com[jp.array(m.body_rootid)],
d.xipos,
d.ximat,
d.cvel,
)
qfrc_target = jax.vmap(support.apply_ft, in_axes=(None, None, 0, 0, 0, 0))(
m, d, force, torque, d.xipos, jp.arange(m.nbody)
)
qfrc_passive += jp.sum(qfrc_target, axis=0)
d = d.replace(qfrc_passive=qfrc_passive)
return d
+5 -1
View File
@@ -42,13 +42,14 @@ class PassiveTest(absltest.TestCase):
m = test_util.load_test_file('pendula.xml')
d = mujoco.MjData(m)
# give the system a little kick to ensure we have non-identity rotations
d.ctrl = np.array([0.1, -0.1, 0.2, 0.3, -0.4])
d.ctrl = np.array([0.1, -0.1, 0.2, 0.3, -0.4, 0.5, -0.6, 0.1])
mujoco.mj_step(m, d, 10) # let dynamics get state significantly non-zero
mujoco.mj_forward(m, d)
mx = mjx.put_model(m)
dx = jax.jit(mjx.passive)(mx, mjx.put_data(m, d))
_assert_attr_eq(d, dx, 'qfrc_passive')
_assert_attr_eq(d, dx, 'qfrc_gravcomp')
# test with fluid forces
m.opt.density = 0.01
@@ -56,18 +57,21 @@ class PassiveTest(absltest.TestCase):
mx = mjx.put_model(m)
dx = jax.jit(mjx.passive)(mx, mjx.put_data(m, d))
_assert_attr_eq(d, dx, 'qfrc_passive')
_assert_attr_eq(d, dx, 'qfrc_gravcomp')
m.opt.viscosity = 0.02
mujoco.mj_forward(m, d)
mx = mjx.put_model(m)
dx = jax.jit(mjx.passive)(mx, mjx.put_data(m, d))
_assert_attr_eq(d, dx, 'qfrc_passive')
_assert_attr_eq(d, dx, 'qfrc_gravcomp')
m.opt.wind = np.array([0.03, 0.04, 0.05])
mujoco.mj_forward(m, d)
mx = mjx.put_model(m)
dx = jax.jit(mjx.passive)(mx, mjx.put_data(m, d))
_assert_attr_eq(d, dx, 'qfrc_passive')
_assert_attr_eq(d, dx, 'qfrc_gravcomp')
# test disable passive
mx = mx.tree_replace({'opt.disableflags': mjx.DisableBit.PASSIVE})
+19 -28
View File
@@ -134,32 +134,27 @@ def _ray_triangle(
vert: jax.Array,
pnt: jax.Array,
vec: jax.Array,
b0: jax.Array,
b1: jax.Array,
basis: jax.Array,
) -> jax.Array:
"""Returns the distance at which a ray intersects with a triangle."""
# project difference vectors in ray normal plane
planar = jp.dot(jp.array([b0, b1]), (vert - pnt).T)
planar = jp.dot(vert - pnt, basis)
# determine if origin is inside planar projection of triangle
# A = (p0-p2, p1-p2), b = -p2, solve A*t = b
A = jp.array( # pylint: disable=invalid-name
[planar[:, 0] - planar[:, 2], planar[:, 1] - planar[:, 2]]
).T.flatten()
b = -planar[:, 2]
det = A[0] * A[3] - A[1] * A[2]
valid = jp.abs(det) >= mujoco.mjMINVAL
A = planar[0:2] - planar[2] # pylint: disable=invalid-name
b = -planar[2]
det = A[0, 0] * A[1, 1] - A[1, 0] * A[0, 1]
t0 = (A[3] * b[0] - A[1] * b[1]) / det
t1 = (-A[2] * b[0] + A[0] * b[1]) / det
valid &= (t0 >= 0) & (t1 >= 0) & (t0 + t1 <= 1)
t0 = (A[1, 1] * b[0] - A[1, 0] * b[1]) / det
t1 = (-A[0, 1] * b[0] + A[0, 0] * b[1]) / det
valid = (t0 >= 0) & (t1 >= 0) & (t0 + t1 <= 1)
# intersect ray with plane of triangle
nrm = jp.cross(vert[0] - vert[2], vert[1] - vert[2])
denom = jp.dot(vec, nrm)
valid &= jp.abs(denom) >= mujoco.mjMINVAL
dist = jp.where(valid, -jp.dot(pnt - vert[2], nrm) / denom, jp.inf)
dist = jp.dot(vert[2] - pnt, nrm) / jp.dot(vec, nrm)
valid &= dist >= 0
dist = jp.where(valid, dist, jp.inf)
return dist
@@ -174,33 +169,29 @@ def _ray_mesh(
"""Returns the best distance and geom_id for ray mesh intersections."""
data_id = m.geom_dataid[geom_id]
ray_basis = lambda x: math.orthogonals(math.normalize(x))
b0, b1 = jax.vmap(ray_basis)(vec)
ray_basis = lambda x: jp.array(math.orthogonals(math.normalize(x))).T
basis = jax.vmap(ray_basis)(vec)
faceadr = np.append(m.mesh_faceadr, m.nmeshface)
vertadr = np.append(m.mesh_vertadr, m.nmeshvert)
dists = []
dists, geom_ids = [], []
for i, id_ in enumerate(data_id):
face = m.mesh_face[faceadr[id_] : faceadr[id_ + 1]]
vert = m.mesh_vert[vertadr[id_] : vertadr[id_ + 1]]
dist = jax.vmap(_ray_triangle, in_axes=(0, None, None, None, None))(
vert[face], pnt[i], vec[i], b0[i], b1[i]
vert = jp.array(vert[face])
dist = jax.vmap(_ray_triangle, in_axes=(0, None, None, None))(
vert, pnt[i], vec[i], basis[i]
)
dists.append(dist)
# map the triangle id to data id
tri_id = np.append(0, (faceadr[data_id + 1] - faceadr[data_id]).cumsum())
tri_data_id = np.zeros(tri_id[-1], dtype=np.int32)
tri_data_id[tri_id[:-1]] = 1
tri_data_id = tri_data_id.cumsum() - 1
geom_ids.append(np.repeat(geom_id[i], dist.size))
dists = jp.concatenate(dists)
min_id = jp.argmin(dists)
# Grab the best distance amongst all meshes, bypassing the argmin in `ray`.
# This avoids having to compute the best distance per mesh.
dist = dists[min_id, None]
id_ = jp.array(geom_id)[jp.array(tri_data_id)[min_id], None]
id_ = jp.array(np.concatenate(geom_ids))[min_id, None]
return dist, id_
+8 -10
View File
@@ -152,11 +152,7 @@ class RayTest(absltest.TestCase):
mx, dx = mjx.put_model(m), mjx.put_data(m, d)
# look at the tetrahedron
pnt, vec = jp.array([2.0, 2.0, 2.0]), -jp.array([
1.0,
1.0,
1.0,
])
pnt, vec = jp.array([2.0, 2.0, 2.0]), -jp.array([1.0, 1.0, 1.0])
vec /= jp.linalg.norm(vec)
dist, geomid = jax.jit(mjx.ray)(mx, dx, pnt, vec)
_assert_eq(geomid, 4, 'geom_id')
@@ -166,12 +162,14 @@ class RayTest(absltest.TestCase):
_assert_eq(geomid, 4, 'geom_id')
_assert_eq(dist, mj_dist, 'dist-tetrahedron')
# look away from the dodecahedron
pnt, vec = jp.array([4.0, 2.0, 2.0]), jp.array([2.0, 1.0, 1.0])
vec /= jp.linalg.norm(vec)
_, geomid = jax.jit(mjx.ray)(mx, dx, pnt, vec)
_assert_eq(geomid, -1, 'geom_id')
# look at the dodecahedron
pnt, vec = jp.array([4.0, 2.0, 2.0]), -jp.array([
2.0,
1.0,
1.0,
])
pnt, vec = jp.array([4.0, 2.0, 2.0]), -jp.array([2.0, 1.0, 1.0])
vec /= jp.linalg.norm(vec)
dist, geomid = jax.jit(mjx.ray)(mx, dx, pnt, vec)
_assert_eq(geomid, 5, 'geom_id')
+9 -9
View File
@@ -62,7 +62,7 @@ def _take(obj: Y, idx: np.ndarray) -> Y:
x = x.take(jp.array(idx), axis=0, mode='wrap')
return x
return jax.tree_map(take, obj)
return jax.tree_util.tree_map(take, obj)
def _q_bodyid(m: Model) -> np.ndarray:
@@ -120,7 +120,7 @@ def _nvmap(f: Callable[..., Y], *args) -> Y:
args = [a if n is None else None for n, a in zip(np_args, args)]
# remove empty args that we should not vmap over
args = jax.tree_map(lambda a: a if a.shape[0] else None, args)
args = jax.tree_util.tree_map(lambda a: a if a.shape[0] else None, args)
in_axes = [None if a is None else 0 for a in args]
def outer_f(*args, np_args=np_args):
@@ -322,7 +322,7 @@ def flat(
[v if typ in flat_ else jp.concatenate(v) for v, typ in zip(y, out_types)]
for y in ys
]
ys = jax.tree_map(lambda *x: jp.concatenate(x), *ys)
ys = jax.tree_util.tree_map(lambda *x: jp.concatenate(x), *ys)
# put concatenated results back in order
reordered_ys = []
@@ -465,15 +465,15 @@ def body_tree(
def index_sum(x, i=id_map, s=body_ids.size):
return jax.ops.segment_sum(x, i, s)
y = jax.tree_map(index_sum, y)
carry = y if carry is None else jax.tree_map(jp.add, carry, y)
y = jax.tree_util.tree_map(index_sum, y)
carry = y if carry is None else jax.tree_util.tree_map(jp.add, carry, y)
elif key in key_parents:
ys = [key_y[p] for p in key_parents[key]]
y = jax.tree_map(lambda *x: jp.concatenate(x), *ys)
y = jax.tree_util.tree_map(lambda *x: jp.concatenate(x), *ys)
body_ids = np.concatenate([key_body_ids[p] for p in key_parents[key]])
parent_ids = m.body_parentid[key_body_ids[key]]
take_fn = lambda x, i=_index(body_ids, parent_ids): _take(x, i)
carry = jax.tree_map(take_fn, y)
carry = jax.tree_util.tree_map(take_fn, y)
f_args = [_take(arg, ids) for arg, ids in zip(args, key_in_take[key])]
key_y[key] = _nvmap(f, carry, *f_args)
@@ -488,8 +488,8 @@ def body_tree(
if len(out_types) > 1:
y_typ = [y_[i] for y_ in y_typ]
if typ != 'b':
y_typ = jax.tree_map(jp.concatenate, y_typ)
y_typ = jax.tree_map(lambda *x: jp.concatenate(x), *y_typ)
y_typ = jax.tree_util.tree_map(jp.concatenate, y_typ)
y_typ = jax.tree_util.tree_map(lambda *x: jp.concatenate(x), *y_typ)
y_take = np.argsort(np.concatenate([key_y_take[key][i] for key in keys]))
_check_output(y_typ, y_take, typ, i)
y.append(_take(y_typ, y_take))
+4 -4
View File
@@ -55,7 +55,7 @@ class ScanTest(absltest.TestCase):
<worldbody/>
</mujoco>
""")
m = mjx.device_put(m)
m = mjx.put_model(m)
def fn(body_id):
return body_id + 1
@@ -69,7 +69,7 @@ class ScanTest(absltest.TestCase):
def test_flat_joints(self):
"""Tests scanning over bodies with joints of different types."""
m = mujoco.MjModel.from_xml_string(self._MULTI_DOF_XML)
m = mjx.device_put(m)
m = mjx.put_model(m)
# we will test two functions:
# 1) j_fn receives jnt_types as a jp array
@@ -105,7 +105,7 @@ class ScanTest(absltest.TestCase):
def test_body_tree(self):
"""Tests tree scanning over bodies with different joint counts."""
m = mujoco.MjModel.from_xml_string(self._MULTI_DOF_XML)
m = mjx.device_put(m)
m = mjx.put_model(m)
# we will test two functions:
# 1) j_fn receives jnt_pos which is a jp array
@@ -196,7 +196,7 @@ class ScanTest(absltest.TestCase):
def test_scan_actuators(self):
"""Tests scanning over actuators."""
m = mujoco.MjModel.from_xml_string(self._MULTI_ACT_XML)
m = mjx.device_put(m)
m = mjx.put_model(m)
fn = lambda *args: args
args = (
+14 -19
View File
@@ -17,7 +17,6 @@
import jax
from jax import numpy as jp
import mujoco
from mujoco.mjx._src import constraint
from mujoco.mjx._src import math
from mujoco.mjx._src import smooth
from mujoco.mjx._src import support
@@ -81,7 +80,7 @@ class _Context(PyTreeNode):
prev_cost=0.0,
solver_niter=0,
)
ctx = _update_constraint(m, d, ctx)
ctx = _update_constraint(d, ctx)
if grad:
ctx = _update_gradient(m, d, ctx)
ctx = ctx.replace(search=-ctx.Mgrad) # start with preconditioned gradient
@@ -107,7 +106,7 @@ class _LSPoint(PyTreeNode):
@classmethod
def create(
cls,
m: Model,
d: Data,
ctx: _Context,
alpha: jax.Array,
jv: jax.Array,
@@ -118,8 +117,7 @@ class _LSPoint(PyTreeNode):
# roughly corresponds to CGEval in mujoco/src/engine/engine_solver.c
# TODO(robotics-team): change this to support friction constraints
ne, nf, *_ = constraint.count_constraints(m)
active = ((ctx.Jaref + alpha * jv) < 0).at[:ne + nf].set(True)
active = ((ctx.Jaref + alpha * jv) < 0).at[:d.ne + d.nf].set(True)
quad = jax.vmap(jp.multiply)(quad, active) # only active
quad_total = quad_gauss + jp.sum(quad, axis=0)
@@ -161,13 +159,12 @@ def _while_loop_scan(cond_fun, body_fun, init_val, max_iter):
return jax.lax.scan(_fun, init, None, length=max_iter)[0][0]
def _update_constraint(m: Model, d: Data, ctx: _Context) -> _Context:
def _update_constraint(d: Data, ctx: _Context) -> _Context:
"""Updates constraint force and resulting cost given latst solver iteration.
Corresponds to CGupdateConstraint in mujoco/src/engine/engine_solver.c
Args:
m: model defining constraints
d: data which contains latest qacc and smooth terms
ctx: current solver context
@@ -177,8 +174,7 @@ def _update_constraint(m: Model, d: Data, ctx: _Context) -> _Context:
# TODO(robotics-team): add friction constraints
# only count active constraints
ne, nf, *_ = constraint.count_constraints(m)
active = (ctx.Jaref < 0).at[:ne + nf].set(True)
active = (ctx.Jaref < 0).at[:d.ne + d.nf].set(True)
efc_force = d.efc_D * -ctx.Jaref * active
qfrc_constraint = d.efc_J.T @ efc_force
@@ -217,8 +213,7 @@ def _update_gradient(m: Model, d: Data, ctx: _Context) -> _Context:
if m.opt.solver == SolverType.CG:
mgrad = smooth.solve_m(m, d, grad)
elif m.opt.solver == SolverType.NEWTON:
ne, nf, *_ = constraint.count_constraints(m)
active = (ctx.Jaref < 0).at[: ne + nf].set(True)
active = (ctx.Jaref < 0).at[: d.ne + d.nf].set(True)
h = (d.efc_J.T * d.efc_D * active) @ d.efc_J
h = support.full_m(m, d) + h
h_ = jax.scipy.linalg.cho_factor(h)
@@ -262,7 +257,7 @@ def _linesearch(m: Model, d: Data, ctx: _Context) -> _Context:
quad = jp.stack((0.5 * ctx.Jaref * ctx.Jaref, jv * ctx.Jaref, 0.5 * jv * jv))
quad = (quad * d.efc_D).T
point_fn = lambda a: _LSPoint.create(m, ctx, a, jv, quad, quad_gauss)
point_fn = lambda a: _LSPoint.create(d, ctx, a, jv, quad, quad_gauss)
def cond(ctx: _LSContext) -> jax.Array:
done = ctx.ls_iter >= m.opt.ls_iterations
@@ -283,14 +278,14 @@ def _linesearch(m: Model, d: Data, ctx: _Context) -> _Context:
# 1) they are not correctly at a bracket boundary (e.g. lo.deriv_0 > 0), OR
# 2) if moving to next or mid narrows the bracket
swap_lo_next = (lo.deriv_0 > 0) | (lo.deriv_0 < lo_next.deriv_0)
lo = jax.tree_map(lambda x, y: jp.where(swap_lo_next, y, x), lo, lo_next)
lo = jax.tree_util.tree_map(lambda x, y: jp.where(swap_lo_next, y, x), lo, lo_next)
swap_lo_mid = (mid.deriv_0 < 0) & (lo.deriv_0 < mid.deriv_0)
lo = jax.tree_map(lambda x, y: jp.where(swap_lo_mid, y, x), lo, mid)
lo = jax.tree_util.tree_map(lambda x, y: jp.where(swap_lo_mid, y, x), lo, mid)
swap_hi_next = (hi.deriv_0 < 0) | (hi.deriv_0 > hi_next.deriv_0)
hi = jax.tree_map(lambda x, y: jp.where(swap_hi_next, y, x), hi, hi_next)
hi = jax.tree_util.tree_map(lambda x, y: jp.where(swap_hi_next, y, x), hi, hi_next)
swap_hi_mid = (mid.deriv_0 > 0) & (hi.deriv_0 > mid.deriv_0)
hi = jax.tree_map(lambda x, y: jp.where(swap_hi_mid, y, x), hi, mid)
hi = jax.tree_util.tree_map(lambda x, y: jp.where(swap_hi_mid, y, x), hi, mid)
swap = swap_lo_next | swap_lo_mid | swap_hi_next | swap_hi_mid
@@ -302,8 +297,8 @@ def _linesearch(m: Model, d: Data, ctx: _Context) -> _Context:
p0 = point_fn(jp.array(0.0))
lo = point_fn(p0.alpha - p0.deriv_0 / p0.deriv_1)
lesser_fn = lambda x, y: jp.where(lo.deriv_0 < p0.deriv_0, x, y)
hi = jax.tree_map(lesser_fn, p0, lo)
lo = jax.tree_map(lesser_fn, lo, p0)
hi = jax.tree_util.tree_map(lesser_fn, p0, lo)
lo = jax.tree_util.tree_map(lesser_fn, lo, p0)
ls_ctx = _LSContext(lo=lo, hi=hi, swap=jp.array(True), ls_iter=0)
ls_ctx = _while_loop_scan(cond, body, ls_ctx, m.opt.ls_iterations)
@@ -336,7 +331,7 @@ def solve(m: Model, d: Data) -> Data:
def body(ctx: _Context) -> _Context:
ctx = _linesearch(m, d, ctx)
prev_grad, prev_Mgrad = ctx.grad, ctx.Mgrad # pylint: disable=invalid-name
ctx = _update_constraint(m, d, ctx)
ctx = _update_constraint(d, ctx)
ctx = _update_gradient(m, d, ctx)
# polak-ribiere:
+89 -20
View File
@@ -25,6 +25,7 @@ from mujoco.mjx._src.types import Data
from mujoco.mjx._src.types import JacobianType
from mujoco.mjx._src.types import Model
# pylint: enable=g-importing-member
import numpy as np
def is_sparse(m: Union[mujoco.MjModel, Model]) -> bool:
@@ -149,19 +150,6 @@ def jac(
return jacp, jacr
def jac_dif_pair(
m: Model,
d: Data,
pos: jax.Array,
body_1: jax.Array,
body_2: jax.Array,
) -> jax.Array:
"""Compute Jacobian difference for two body points."""
jacp2, _ = jac(m, d, pos, body_2)
jacp1, _ = jac(m, d, pos, body_1)
return jacp2 - jacp1
def apply_ft(
m: Model,
d: Data,
@@ -200,11 +188,92 @@ def local_to_global(
return pos, mat
def get_custom_numeric(m: Union[Model, mujoco.MjModel], name: str) -> float:
"""Returns a custom numeric given an MjModel or mjx.Model."""
for i in range(m.nnumeric):
name_ = m.names[m.name_numericadr[i] :].decode('utf-8').split('\x00', 1)[0]
if name_ == name:
return m.numeric_data[m.numeric_adr[i]]
def _getnum(m: Union[Model, mujoco.MjModel], obj: mujoco._enums.mjtObj) -> int:
"""Gets the number of objects for the given object type."""
return {
mujoco.mjtObj.mjOBJ_BODY: m.nbody,
mujoco.mjtObj.mjOBJ_JOINT: m.njnt,
mujoco.mjtObj.mjOBJ_GEOM: m.ngeom,
mujoco.mjtObj.mjOBJ_SITE: m.nsite,
mujoco.mjtObj.mjOBJ_CAMERA: m.ncam,
mujoco.mjtObj.mjOBJ_MESH: m.nmesh,
mujoco.mjtObj.mjOBJ_PAIR: m.npair,
mujoco.mjtObj.mjOBJ_EQUALITY: m.neq,
mujoco.mjtObj.mjOBJ_ACTUATOR: m.nu,
mujoco.mjtObj.mjOBJ_SENSOR: m.nsensor,
mujoco.mjtObj.mjOBJ_NUMERIC: m.nnumeric,
mujoco.mjtObj.mjOBJ_TUPLE: m.ntuple,
mujoco.mjtObj.mjOBJ_KEY: m.nkey,
}.get(obj, 0)
return -1
def _getadr(
m: Union[Model, mujoco.MjModel], obj: mujoco._enums.mjtObj
) -> np.ndarray:
"""Gets the name addresses for the given object type."""
return {
mujoco.mjtObj.mjOBJ_BODY: m.name_bodyadr,
mujoco.mjtObj.mjOBJ_JOINT: m.name_jntadr,
mujoco.mjtObj.mjOBJ_GEOM: m.name_geomadr,
mujoco.mjtObj.mjOBJ_SITE: m.name_siteadr,
mujoco.mjtObj.mjOBJ_CAMERA: m.name_camadr,
mujoco.mjtObj.mjOBJ_MESH: m.name_meshadr,
mujoco.mjtObj.mjOBJ_PAIR: m.name_pairadr,
mujoco.mjtObj.mjOBJ_EQUALITY: m.name_eqadr,
mujoco.mjtObj.mjOBJ_ACTUATOR: m.name_actuatoradr,
mujoco.mjtObj.mjOBJ_SENSOR: m.name_sensoradr,
mujoco.mjtObj.mjOBJ_NUMERIC: m.name_numericadr,
mujoco.mjtObj.mjOBJ_TUPLE: m.name_tupleadr,
mujoco.mjtObj.mjOBJ_KEY: m.name_keyadr,
}[obj]
def id2name(
m: Union[Model, mujoco.MjModel], typ: mujoco._enums.mjtObj, i: int
) -> Optional[str]:
"""Gets the name of an object with the specified mjtObj type and id.
See mujoco.id2name for more info.
Args:
m: mujoco.MjModel or mjx.Model
typ: mujoco.mjtObj type
i: the id
Returns:
the name string, or None if not found
"""
num = _getnum(m, typ)
if i < 0 or i >= num:
return None
adr = _getadr(m, typ)
name = m.names[adr[i] :].decode('utf-8').split('\x00', 1)[0]
return name or None
def name2id(
m: Union[Model, mujoco.MjModel], typ: mujoco._enums.mjtObj, name: str
) -> int:
"""Gets the id of an object with the specified mjtObj type and name.
See mujoco.mj_name2id for more info.
Args:
m: mujoco.MjModel or mjx.Model
typ: mujoco.mjtObj type
name: the name of the object
Returns:
the id, or -1 if not found
"""
num = _getnum(m, typ)
adr = _getadr(m, typ)
# TODO: consider using MjModel.names_map instead
names_map = {
m.names[adr[i] :].decode('utf-8').split('\x00', 1)[0]: i
for i in range(num)
}
return names_map.get(name, -1)
+31 -4
View File
@@ -116,7 +116,7 @@ class SupportTest(parameterized.TestCase):
np.testing.assert_almost_equal(qfrc, qfrc_expected, 6)
def test_custom_numeric(self):
def test_custom(self):
xml = """
<mujoco model="right_shadow_hand">
<custom>
@@ -126,9 +126,36 @@ class SupportTest(parameterized.TestCase):
</mujoco>
"""
m = mujoco.MjModel.from_xml_string(xml)
self.assertEqual(support.get_custom_numeric(m, 'something'), -1)
self.assertEqual(support.get_custom_numeric(m, 'max_contact_points'), 15)
self.assertEqual(support.get_custom_numeric(m, 'max_geom_pairs'), 42)
def _get_numeric(m, name):
id_ = support.name2id(m, mujoco.mjtObj.mjOBJ_NUMERIC, name)
return int(m.numeric_data[id_]) if id_ >= 0 else -1
self.assertEqual(_get_numeric(m, 'something'), -1)
self.assertEqual(_get_numeric(m, 'max_contact_points'), 15)
self.assertEqual(_get_numeric(m, 'max_geom_pairs'), 42)
mx = mjx.put_model(m)
self.assertEqual(_get_numeric(mx, 'something'), -1)
self.assertEqual(_get_numeric(mx, 'max_contact_points'), 15)
self.assertEqual(_get_numeric(mx, 'max_geom_pairs'), 42)
def test_names_and_ids(self):
m = test_util.load_test_file('pendula.xml')
mx = mjx.put_model(m)
nums = {
mujoco.mjtObj.mjOBJ_JOINT: m.njnt,
mujoco.mjtObj.mjOBJ_GEOM: m.ngeom,
mujoco.mjtObj.mjOBJ_BODY: m.nbody,
}
for obj in nums:
names = [mujoco.mj_id2name(m, obj.value, i) for i in range(nums[obj])]
for i, n in enumerate(names):
self.assertEqual(support.id2name(mx, obj, i), n)
i = i if n is not None else -1
self.assertEqual(support.name2id(mx, obj, n), i)
if __name__ == '__main__':
+15 -5
View File
@@ -17,7 +17,7 @@
import os
import sys
import time
from typing import Dict, Tuple
from typing import Dict, Optional, Tuple
from xml.etree import ElementTree as ET
from etils import epath
@@ -152,6 +152,7 @@ def _make_joint(joint_type: str, name: str) -> Dict[str, str]:
joint_attr['damping'] = '{:.2f}'.format(np.random.uniform() * 20)
joint_attr['stiffness'] = '{:.2f}'.format(np.random.uniform() * 20)
joint_attr['actuatorgravcomp'] = np.random.choice(['true', 'false'])
return joint_attr
@@ -201,9 +202,9 @@ def _make_geom(
def _make_actuator(
actuator_type: str,
joint: str | None = None,
site: str | None = None,
refsite: str | None = None,
joint: Optional[str] = None,
site: Optional[str] = None,
refsite: Optional[str] = None,
) -> Dict[str, str]:
"""Returns attributes for an actuator."""
if joint:
@@ -334,7 +335,16 @@ def create_mjcf(
z_pos = np.random.uniform(low=-1, high=1) * 0.01 # small jitter
pos = f'{body_pos[0]:.3f} {body_pos[1]:.3f} {body_pos[2] + z_pos:.3f}'
n_bodies = len(list(mjcf.iter('body')))
child = ET.SubElement(body, 'body', {'pos': pos, 'name': f'body{n_bodies}'})
gravcomp = np.random.uniform() * p(50)
child = ET.SubElement(
body,
'body',
{
'pos': pos,
'name': f'body{n_bodies}',
'gravcomp': f'{gravcomp:.3f}',
},
)
ET.SubElement(child, 'site', {'name': f'site{n_bodies}'})
n_joints = len(list(mjcf.iter('joint')))
+109 -41
View File
@@ -15,10 +15,8 @@
"""Base types used in MJX."""
import enum
from typing import List, Optional
import jax
import jax.numpy as jp
import mujoco
from mujoco.mjx._src.dataclasses import PyTreeNode # pylint: disable=g-importing-member
import numpy as np
@@ -100,6 +98,7 @@ class GeomType(enum.IntEnum):
CYLINDER: cylinder
BOX: box
MESH: mesh
SDF: signed distance field
"""
PLANE = mujoco.mjtGeom.mjGEOM_PLANE
@@ -213,6 +212,24 @@ class BiasType(enum.IntEnum):
# unsupported: MUSCLE, USER
class ConstraintType(enum.IntEnum):
"""Type of constraint.
Attributes:
EQUALITY: equality constraint
LIMIT_JOINT: joint limit
CONTACT_FRICTIONLESS: frictionless contact
CONTACT_PYRAMIDAL: frictional contact, pyramidal friction cone
"""
EQUALITY = mujoco.mjtConstraint.mjCNSTR_EQUALITY
# unsupported: FRICTION_DOF, FRICTION_TENDON
LIMIT_JOINT = mujoco.mjtConstraint.mjCNSTR_LIMIT_JOINT
# unsupported: LIMIT_TENDON
CONTACT_FRICTIONLESS = mujoco.mjtConstraint.mjCNSTR_CONTACT_FRICTIONLESS
CONTACT_PYRAMIDAL = mujoco.mjtConstraint.mjCNSTR_CONTACT_PYRAMIDAL
# unsupported: CONTACT_ELLIPTIC
class CamLightType(enum.IntEnum):
"""Type of camera light.
@@ -309,7 +326,12 @@ class Model(PyTreeNode):
npair: number of predefined geom pairs
nexclude: number of excluded geom pairs
neq: number of equality constraints
ngravcomp: number of bodies with nonzero gravcomp
nnumeric: number of numeric custom fields
ntuple: number of tuple custom fields
nsensor: number of sensors
nkey: number of keyframes
nuserdata: size of userdata array
nM: number of non-zeros in sparse inertia matrix
opt: physics options
stat: model statistics
@@ -331,6 +353,7 @@ class Model(PyTreeNode):
body_mass: mass (nbody,)
body_subtreemass: mass of subtree starting at this body (nbody,)
body_inertia: diagonal inertia in ipos/iquat frame (nbody, 3)
body_gravcomp: antigravity force, units of body weight (nbody,)
body_invweight0: mean inv inert in qpos0 (trn, rot) (nbody, 2)
jnt_type: type of joint (mjtJoint) (njnt,)
jnt_qposadr: start addr in 'qpos' for joint's data (njnt,)
@@ -338,6 +361,8 @@ class Model(PyTreeNode):
jnt_bodyid: id of joint's body (njnt,)
jnt_group: group for visibility (njnt,)
jnt_limited: does joint have limits (njnt,)
jnt_actfrclimited: does joint have actuator force limits (njnt,)
jnt_actgravcomp: is gravcomp force applied via actuators (njnt,)
jnt_solref: constraint solver reference: limit (njnt, mjNREF)
jnt_solimp: constraint solver impedance: limit (njnt, mjNIMP)
jnt_pos: local anchor position (njnt, 3)
@@ -370,6 +395,7 @@ class Model(PyTreeNode):
geom_solref: constraint solver reference: contact (ngeom, mjNREF)
geom_solimp: constraint solver impedance: contact (ngeom, mjNIMP)
geom_size: geom-specific size parameters (ngeom, 3)
geom_rbound: radius of bounding sphere (ngeom,)
geom_pos: local position offset rel. to body (ngeom, 3)
geom_quat: local orientation offset rel. to body (ngeom, 4)
geom_friction: friction for (slide, spin, roll) (ngeom, 3)
@@ -387,17 +413,13 @@ class Model(PyTreeNode):
cam_poscom0: global position rel. to sub-com in qpos0 (ncam, 3)
cam_pos0: global position rel. to body in qpos0 (ncam, 3)
cam_mat0: global orientation in qpos0 (ncam, 9)
mat_rgba: rgba (nmat, 4)
mesh_vertadr: first vertex address (nmesh x 1)
mesh_faceadr: first face address (nmesh x 1)
mesh_vertadr: first vertex address (nmesh,)
mesh_faceadr: first face address (nmesh,)
mesh_graphadr: graph data address; -1: no graph (nmesh,)
mesh_vert: vertex positions for all meshes (nmeshvert, 3)
mesh_face: vertex face data (nmeshface, 3)
geom_convex_face: vertex face data, MJX only (ngeom,)
geom_convex_vert: vertex data, MJX only (ngeom,)
geom_convex_edge_dir: unique edge direction, MJX only (ngeom,)
geom_convex_facenormal: normal face data, MJX only (ngeom,)
geom_convex_face_edge: edges for each face (ngeom,)
geom_convex_face_edge_normal: edge normals for each face (ngeom,)
mesh_graph: convex graph data (nmeshgraph,)
mat_rgba: rgba (nmat, 4)
pair_dim: contact dimensionality (npair,)
pair_geom1: id of geom1 (npair,)
pair_geom2: id of geom2 (npair,)
@@ -434,7 +456,24 @@ class Model(PyTreeNode):
actuator_gear: scale length and transmitted force (nu, 6)
numeric_adr: address of field in numeric_data (nnumeric,)
numeric_data: array of all numeric fields (nnumericdata,)
tuple_adr: address of text in text_data (ntuple,)
tuple_size: number of objects in tuple (ntuple,)
tuple_objtype: array of object types in all tuples (ntupledata,)
tuple_objid: array of object ids in all tuples (ntupledata,)
tuple_objprm: array of object params in all tuples (ntupledata,)
name_bodyadr: body name pointers (nbody,)
name_jntadr: joint name pointers (njnt,)
name_geomadr: geom name pointers (ngeom,)
name_siteadr: site name pointers (nsite,)
name_camadr: camera name pointers (ncam,)
name_meshadr: mesh name pointers (nmesh,)
name_pairadr: geom pair name pointers (npair,)
name_eqadr: equality constraint name pointers (neq,)
name_actuatoradr: actuator name pointers (nu,)
name_sensoradr: sensor name pointers (nsensor,)
name_numericadr: numeric name pointers (nnumeric,)
name_tupleadr: tuple name pointers (ntuple,)
name_keyadr: keyframe name pointers (nkey,)
names: names of all objects, 0-terminated (nnames,)
"""
nq: int
@@ -453,7 +492,12 @@ class Model(PyTreeNode):
npair: int
nexclude: int
neq: int
ngravcomp: int
nnumeric: int
nuserdata: int
ntuple: int
nsensor: int
nkey: int
nM: int # pylint:disable=invalid-name
opt: Option
stat: Statistic
@@ -475,6 +519,7 @@ class Model(PyTreeNode):
body_mass: jax.Array
body_subtreemass: jax.Array
body_inertia: jax.Array
body_gravcomp: jax.Array
body_invweight0: jax.Array
jnt_type: np.ndarray
jnt_qposadr: np.ndarray
@@ -482,6 +527,7 @@ class Model(PyTreeNode):
jnt_bodyid: np.ndarray
jnt_limited: np.ndarray
jnt_actfrclimited: np.ndarray
jnt_actgravcomp: np.ndarray
jnt_solref: jax.Array
jnt_solimp: jax.Array
jnt_pos: jax.Array
@@ -514,6 +560,7 @@ class Model(PyTreeNode):
geom_solref: jax.Array
geom_solimp: jax.Array
geom_size: jax.Array
geom_rbound: jax.Array
geom_pos: jax.Array
geom_quat: jax.Array
geom_friction: jax.Array
@@ -533,18 +580,14 @@ class Model(PyTreeNode):
cam_mat0: jax.Array
mesh_vertadr: np.ndarray
mesh_faceadr: np.ndarray
mesh_graphadr: np.ndarray
mesh_vert: np.ndarray
mesh_face: np.ndarray
mesh_graph: np.ndarray
mat_rgba: np.ndarray
pair_dim: np.ndarray
pair_geom1: np.ndarray
pair_geom2: np.ndarray
geom_convex_face: List[Optional[jax.Array]]
geom_convex_vert: List[Optional[jax.Array]]
geom_convex_edge_dir: List[Optional[jax.Array]]
geom_convex_facenormal: List[Optional[jax.Array]]
geom_convex_edge: List[Optional[jax.Array]]
geom_convex_edge_face_normal: List[Optional[jax.Array]]
pair_solref: jax.Array
pair_solreffriction: jax.Array
pair_solimp: jax.Array
@@ -578,7 +621,24 @@ class Model(PyTreeNode):
actuator_gear: jax.Array
numeric_adr: np.ndarray
numeric_data: np.ndarray
tuple_adr: np.ndarray
tuple_size: np.ndarray
tuple_objtype: np.ndarray
tuple_objid: np.ndarray
tuple_objprm: np.ndarray
name_bodyadr: np.ndarray
name_jntadr: np.ndarray
name_geomadr: np.ndarray
name_siteadr: np.ndarray
name_camadr: np.ndarray
name_meshadr: np.ndarray
name_pairadr: np.ndarray
name_eqadr: np.ndarray
name_actuatoradr: np.ndarray
name_sensoradr: np.ndarray
name_numericadr: np.ndarray
name_tupleadr: np.ndarray
name_keyadr: np.ndarray
names: bytes
@@ -594,8 +654,11 @@ class Contact(PyTreeNode):
solref: constraint solver reference, normal direction (mjNREF,)
solreffriction: constraint solver reference, friction directions (mjNREF,)
solimp: constraint solver impedance (mjNIMP,)
geom1: id of geom 1
geom2: id of geom 2
dim: contact space dimensionality: 1, 3, 4, or 6
geom1: id of geom 1; deprecated, use geom[0]
geom2: id of geom 2; deprecated, use geom[1]
geom: geom ids (2,)
efc_address: address in efc; -1: not included
"""
dist: jax.Array
pos: jax.Array
@@ -605,33 +668,25 @@ class Contact(PyTreeNode):
solref: jax.Array
solreffriction: jax.Array
solimp: jax.Array
# unsupported: mu, H, dim
# unsupported: mu, H
dim: np.ndarray
geom1: jax.Array
geom2: jax.Array
# unsupported: efc_address, exclude
@classmethod
def zero(cls, ncon: int = 0) -> 'Contact':
"""Returns a contact filled with zeros."""
return Contact(
dist=jp.zeros(ncon),
pos=jp.zeros((ncon, 3,)),
frame=jp.zeros((ncon, 3, 3)),
includemargin=jp.zeros(ncon),
friction=jp.zeros((ncon, 5)),
solref=jp.zeros((ncon, mujoco.mjNREF)),
solreffriction=jp.zeros((ncon, mujoco.mjNREF)),
solimp=jp.zeros((ncon, mujoco.mjNIMP,)),
geom1=jp.zeros(ncon, dtype=int),
geom2=jp.zeros(ncon, dtype=int),
)
geom: jax.Array
# unsupported: flex, elem, vert, exclude
efc_address: np.ndarray
class Data(PyTreeNode):
r"""Dynamic state that updates each step.\
r"""\Dynamic state that updates each step.
Attributes:
solver_niter: number of solver iterations, per island (mjNISLAND,)
ne: number of equality constraints
nf: number of friction constraints
nl: number of limit constraints
nefc: number of constraints
ncon: number of contacts
solver_niter: number of solver iterations
time: simulation time
qpos: position (nq,)
qvel: velocity (nv,)
@@ -661,14 +716,15 @@ class Data(PyTreeNode):
cinert: com-based body inertia and mass (nbody, 10)
actuator_length: actuator lengths (nu,)
actuator_moment: actuator moments (nu, nv)
crb: com-based composite inertia and mass (nbody, 10)
crb: com-based composite inertia and mass (nbody, 10) \
qM: total inertia if sparse: (nM,)
if dense: (nv, nv)
qLD: L'*D*L (or Cholesky) factorization of M. if sparse: (nM,)
if dense: (nv, nv)
qLDiagInv: 1/diag(D) if sparse: (nv,)
if dense: (0,)
contact: list of all detected contacts (ncon,)
contact: all detected contacts (ncon,)
efc_type: constraint type (nefc,)
efc_J: constraint Jacobian (nefc, nv)
efc_frictionloss: frictionloss (friction) (nefc,)
efc_D: constraint mass (nefc,)
@@ -676,6 +732,7 @@ class Data(PyTreeNode):
cvel: com-based velocity [3D rot; 3D tran] (nbody, 6)
cdof_dot: time-derivative of cdof (nv, 6)
qfrc_bias: C(qpos,qvel) (nv,)
qfrc_gravcomp: passive gravity compensation force (nv,)
qfrc_passive: passive force (nv,)
efc_aref: reference pseudo-acceleration (nefc,)
qfrc_actuator: actuator force (nv,)
@@ -685,7 +742,14 @@ class Data(PyTreeNode):
qfrc_inverse: net external force; should equal: (nv,)
qfrc_applied + J'*xfrc_applied + qfrc_actuator
efc_force: constraint force in constraint space (nefc,)
userdata: user data, not touched by engine (nuserdata,)
"""
# constant sizes:
ne: int
nf: int
nl: int
nefc: int
ncon: int
# solver statistics:
solver_niter: jax.Array
# global properties:
@@ -703,6 +767,8 @@ class Data(PyTreeNode):
# dynamics:
qacc: jax.Array
act_dot: jax.Array
# user data:
userdata: jax.Array
# position dependent:
xpos: jax.Array
xquat: jax.Array
@@ -727,6 +793,7 @@ class Data(PyTreeNode):
qLD: jax.Array # pylint:disable=invalid-name
qLDiagInv: jax.Array # pylint:disable=invalid-name
contact: Contact
efc_type: np.ndarray
efc_J: jax.Array # pylint:disable=invalid-name
efc_frictionloss: jax.Array
efc_D: jax.Array # pylint:disable=invalid-name
@@ -736,6 +803,7 @@ class Data(PyTreeNode):
cdof_dot: jax.Array
qfrc_bias: jax.Array
qfrc_passive: jax.Array
qfrc_gravcomp: jax.Array
efc_aref: jax.Array
# position, velcoity, control & acceleration dependent:
qfrc_actuator: jax.Array
@@ -30,7 +30,10 @@ import numpy as np
def _assert_attr_eq(mjx_d, mj_d, attr, name, atol):
if attr == 'efc_address':
# we do not test efc_address since it gets set in constraint logic
# contact order not guaranteed to match
np.testing.assert_array_equal(
np.sort(mjx_d.efc_address), np.sort(mj_d.efc_address)
)
return
err_msg = f'mismatch: {attr} in run: {name}'
mjx_d, mj_d = getattr(mjx_d, attr), getattr(mj_d, attr)
@@ -79,9 +82,12 @@ class CollisionDriverIntegrationTest(parameterized.TestCase):
self.assertSequenceEqual(set(idx_mjx), set(idx_mj))
idx = sorted(range(len(idx_mj)), key=lambda x: idx_mj.index(idx_mjx[x]))
mjx_contact = jax.tree_map(
mjx_contact = jax.tree_util.tree_map(
lambda x: x.take(np.array(idx), axis=0), dx.contact
)
mjx_contact = mjx_contact.replace(
dim=mjx_contact.dim[idx], efc_address=mjx_contact.efc_address[idx]
)
for field in dataclasses.fields(Contact):
_assert_attr_eq(mjx_contact, d.contact, field.name, seed, 1e-7)
@@ -68,10 +68,10 @@ class TransmissionIntegrationTest(parameterized.TestCase):
mujoco.mj_transmission(m, d)
dx = transmission_jit_fn(mx, dx)
_assert_attr_eq(d, dx, 'actuator_length', seed, f'transmission{seed}')
_assert_attr_eq(
d, dx, 'actuator_moment', seed, f'transmission{seed}', atol=1e-4
)
for field in ['actuator_length', 'actuator_moment']:
_assert_attr_eq(
d, dx, field, seed, f'transmission{seed}', atol=1e-4
)
if __name__ == '__main__':
+21 -1
View File
@@ -14,7 +14,7 @@
</default>
<worldbody>
<geom pos="0 0 -1" type="plane" size="10 10 .01"/>
<geom pos="0 0 -1" type="plane" size="20 20 .01" condim="1"/>
<body name="anchor1" pos="-3 0 0"/>
<body name="beam1" pos="-3 0 0">
@@ -37,6 +37,26 @@
<joint name="joint4" axis="1 0 0" type="hinge" damping="1.0"/> <!-- tests no joint range -->
<geom class="box"/>
</body>
<body name="box_condim1" pos="4 0 0">
<freejoint/>
<geom class="box" condim="1"/>
</body>
<body name="box_condim3" pos="5 0 0">
<freejoint/>
<geom class="box" condim="3"/>
</body>
<body name="box_condim4" pos="6 0 0">
<freejoint/>
<geom class="box" condim="4"/>
</body>
<body name="box_condim6" pos="6 0 0">
<freejoint/>
<geom class="box" condim="6"/>
</body>
</worldbody>
<equality>
+17
View File
@@ -110,6 +110,20 @@
</body>
</body>
</body>
<!-- triple pendulum of hinges with gravcomp -->
<body pos="3.0 0 0" gravcomp="1">
<joint name="joint15" axis="0.1 0.2 0.3" type="hinge"/>
<geom/>
<body pos="0 0 -0.8" gravcomp="2">
<joint name="joint16" axis="0.4 0.5 0.6" type="hinge" armature="0.02" range="-20 20"/>
<geom/>
<body pos="0 -0.7 0" gravcomp="3">
<joint name="joint17" axis="0.7 0.8 0.9" type="hinge" damping="0.75" range="-30 30" actuatorgravcomp="true"/>
<geom/>
</body>
</body>
</body>
</worldbody>
<actuator>
@@ -118,5 +132,8 @@
<motor gear="0 0 300" joint="joint1" name="act3"/>
<motor gear="275" joint="joint2" name="act4"/>
<motor gear="275" joint="joint3" name="act5"/>
<motor gear="150" joint="joint15" name="act6"/>
<motor gear="150" joint="joint16" name="act7"/>
<motor gear="150" joint="joint17" name="act8"/>
</actuator>
</mujoco>
+3 -2
View File
@@ -47,8 +47,9 @@ _OUTPUT = flags.DEFINE_enum(
def _main(argv: Sequence[str]):
"""Runs testpeed function."""
base_path = _BASE_PATH.value or epath.resource_path('mujoco.mjx')
f = base_path / 'test_data' / _MJCF.value
path = epath.resource_path('mujoco.mjx') / 'test_data'
path = _BASE_PATH.value or path
f = epath.Path(path) / _MJCF.value
m = mujoco.MjModel.from_xml_path(f.as_posix())
print(f'Rolling out {_NSTEP.value} steps at dt = {m.opt.timestep:.3f}...')
+1 -1
View File
@@ -53,7 +53,7 @@ def _main(argv: Sequence[str]) -> None:
start = time.time()
# TODO(robotics-simulation): recompile when changing disable flags, etc.
dx = dx.replace(ctrl=d.ctrl, xfrc_applied=d.xfrc_applied)
dx = dx.replace(ctrl=d.ctrl, act=d.act, xfrc_applied=d.xfrc_applied)
dx = dx.replace(qpos=d.qpos, qvel=d.qvel, time=d.time) # handle resets
mx = mx.tree_replace({
'opt.gravity': m.opt.gravity,
+4 -4
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name="mujoco-mjx"
version = "3.1.4"
version = "3.1.5"
authors = [
{name = "Google DeepMind", email = "mujoco@deepmind.com"},
]
@@ -31,7 +31,7 @@ dependencies = [
"etils[epath]",
"jax",
"jaxlib",
"mujoco>=3.1.4.dev0",
"mujoco>=3.1.5.dev0",
"scipy",
"trimesh",
]
@@ -42,6 +42,6 @@ mjx-viewer = "mujoco.mjx.viewer:main"
[project.urls]
Homepage = "https://github.com/google-deepmind/mujoco/tree/main/mjx"
Documentation = "https://mujoco.readthedocs.io/en/3.1.4"
Documentation = "https://mujoco.readthedocs.io/en/3.1.5"
Repository = "https://github.com/google-deepmind/mujoco/tree/main/mjx"
Changelog = "https://mujoco.readthedocs.io/en/3.1.4/changelog.html"
Changelog = "https://mujoco.readthedocs.io/en/3.1.5/changelog.html"
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -829,7 +829,7 @@
"\n",
" friction, gain, bias = rand(rng)\n",
"\n",
" in_axes = jax.tree_map(lambda x: None, sys)\n",
" in_axes = jax.tree_util.tree_map(lambda x: None, sys)\n",
" in_axes = in_axes.tree_replace({\n",
" 'geom_friction': 0,\n",
" 'actuator_gainprm': 0,\n",
File diff suppressed because it is too large Load Diff
+76 -436
View File
@@ -27,20 +27,20 @@
<default>
<geom solimp=".9 .9 .01"/>
<default class="object1">
<geom type="capsule" material="object1" size="0.1 0.05"/>
<default class="capsule">
<geom type="capsule" material="capsule" size="0.1 0.05"/>
</default>
<default class="object2">
<geom type="ellipsoid" material="object2" size="0.15 0.1 0.07"/>
<default class="ellipsoid">
<geom type="ellipsoid" material="ellipsoid" size="0.15 0.1 0.07"/>
</default>
<default class="object3">
<geom type="box" material="object3" size="0.15 0.1 0.05"/>
<default class="box">
<geom type="box" material="box" size="0.15 0.1 0.05"/>
</default>
<default class="object4">
<geom type="cylinder" material="object4" size="0.1 0.05" condim="4" friction="1 .01 .01"/>
<default class="cylinder">
<geom type="cylinder" material="cylinder" size="0.1 0.05" condim="4" friction="1 .01 .01"/>
</default>
<default class="object5">
<geom type="sphere" material="object5" size="0.1"/>
<default class="sphere">
<geom type="sphere" material="sphere" size="0.1"/>
</default>
<default class="border">
<geom type="capsule" size="0.4" rgba=".4 .4 .4 1"/>
@@ -56,13 +56,13 @@
rgb1="0.6 0.6 0.6" rgb2="0.6 0.6 0.6" markrgb="1 1 1"/>
<texture name="texplane" type="2d" builtin="checker" rgb1=".4 .4 .4" rgb2=".6 .6 .6"
width="512" height="512"/>
<material name='MatPlane' reflectance='0.3' texture="texplane" texrepeat="1 1" texuniform="true"
<material name="MatPlane" reflectance="0.3" texture="texplane" texrepeat="1 1" texuniform="true"
rgba=".7 .7 .7 1"/>
<material name='object1' texture="texgeom" texuniform="true" rgba=".4 .9 .6 1" />
<material name='object2' texture="texgeom" texuniform="true" rgba=".4 .6 .9 1" />
<material name='object3' texture="texgeom" texuniform="true" rgba=".4 .9 .9 1" />
<material name='object4' texture="texgeom" texuniform="true" rgba=".8 .6 .8 1" />
<material name='object5' texture="texgeom" texuniform="true" rgba=".9 .1 .1 1" />
<material name="capsule" texture="texgeom" texuniform="true" rgba=".4 .9 .6 1" />
<material name="ellipsoid" texture="texgeom" texuniform="true" rgba=".4 .6 .9 1" />
<material name="box" texture="texgeom" texuniform="true" rgba=".4 .9 .9 1" />
<material name="cylinder" texture="texgeom" texuniform="true" rgba=".8 .6 .8 1" />
<material name="sphere" texture="texgeom" texuniform="true" rgba=".9 .1 .1 1" />
</asset>
<visual>
@@ -84,430 +84,70 @@
<geom class="borderpost" pos="3 -3 0"/>
<geom class="borderpost" pos="-3 -3 0"/>
<body pos="-2 -2 1" quat="-0.632456 -0.632456 0.316228 0.316228">
<freejoint/>
<geom class="object1"/>
</body>
<body pos="-2 -2 2" quat="-0.5547 -0.5547 0.5547 0.27735">
<freejoint/>
<geom class="object1"/>
</body>
<body pos="-2 -2 3" quat="-0.471405 -0.471405 0.707107 0.235702">
<freejoint/>
<geom class="object1"/>
</body>
<body pos="-2 -2 4" quat="-0.471405 -0.471405 0.707107 0.235702">
<freejoint/>
<geom class="object1"/>
</body>
<frame pos="-2 -2 2.5">
<replicate count="5" offset="0 1 0" euler="0 180 0">
<frame pos="0 0 -1.5">
<replicate count="4" offset="0 0 1" euler="0 0 60">
<body euler="30 40 0">
<freejoint/>
<geom class="capsule"/>
</body>
</replicate>
</frame>
</replicate>
</frame>
<body pos="-2 -1 1" quat="-0.755929 -0.377964 0.377964 0.377964">
<freejoint/>
<geom class="object1"/>
</body>
<body pos="-2 -1 2" quat="-0.632456 -0.316228 0.632456 0.316228">
<freejoint/>
<geom class="object1"/>
</body>
<body pos="-2 -1 3" quat="-0.516398 -0.258199 0.774597 0.258199">
<freejoint/>
<geom class="object1"/>
</body>
<body pos="-2 -1 4" quat="-0.516398 -0.258199 0.774597 0.258199">
<freejoint/>
<geom class="object1"/>
</body>
<frame pos="-1 -2 2.5">
<replicate count="5" offset="0 1 0" euler="0 180 0">
<frame pos="0 0 -1.5">
<replicate count="4" offset="0 0 1" euler="0 0 60">
<body euler="20 40 60">
<freejoint/>
<geom class="ellipsoid"/>
</body>
</replicate>
</frame>
</replicate>
</frame>
<body pos="-2 0 1" quat="-0.816497 0 0.408248 0.408248">
<freejoint/>
<geom class="object1"/>
</body>
<body pos="-2 0 2" quat="-0.666667 0 0.666667 0.333333">
<freejoint/>
<geom class="object1"/>
</body>
<body pos="-2 0 3" quat="-0.534522 0 0.801784 0.267261">
<freejoint/>
<geom class="object1"/>
</body>
<body pos="-2 0 4" quat="-0.534522 0 0.801784 0.267261">
<freejoint/>
<geom class="object1"/>
</body>
<frame pos="0 -2 3.5">
<replicate count="5" offset="0 1 0" euler="0 180 0">
<frame pos="0 0 -1.5">
<replicate count="4" offset="0 0 1" euler="0 0 60">
<body euler="30 70 110">
<freejoint/>
<geom class="box"/>
</body>
</replicate>
</frame>
</replicate>
</frame>
<body pos="-2 1 1" quat="-0.755929 0.377964 0.377964 0.377964">
<freejoint/>
<geom class="object1"/>
</body>
<body pos="-2 1 2" quat="-0.632456 0.316228 0.632456 0.316228">
<freejoint/>
<geom class="object1"/>
</body>
<body pos="-2 1 3" quat="-0.516398 0.258199 0.774597 0.258199">
<freejoint/>
<geom class="object1"/>
</body>
<body pos="-2 1 4" quat="-0.516398 0.258199 0.774597 0.258199">
<freejoint/>
<geom class="object1"/>
</body>
<frame pos="1 -2 2.5">
<replicate count="5" offset="0 1 0" euler="0 180 0">
<frame pos="0 0 -1.5">
<replicate count="4" offset="0 0 1" euler="0 0 60">
<body euler="60 30 0">
<freejoint/>
<geom class="cylinder"/>
</body>
</replicate>
</frame>
</replicate>
</frame>
<body pos="-2 2 1" quat="-0.632456 0.632456 0.316228 0.316228">
<freejoint/>
<geom class="object1"/>
</body>
<body pos="-2 2 2" quat="-0.5547 0.5547 0.5547 0.27735">
<freejoint/>
<geom class="object1"/>
</body>
<body pos="-2 2 3" quat="-0.471405 0.471405 0.707107 0.235702">
<freejoint/>
<geom class="object1"/>
</body>
<body pos="-2 2 4" quat="-0.471405 0.471405 0.707107 0.235702">
<freejoint/>
<geom class="object1"/>
</body>
<body pos="-1 -2 1" quat="-0.377964 -0.755929 0.377964 0.377964">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 -2 2" quat="-0.316228 -0.632456 0.632456 0.316228">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 -2 3" quat="-0.258199 -0.516398 0.774597 0.258199">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 -2 4" quat="-0.258199 -0.516398 0.774597 0.258199">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 -1 1" quat="-0.5 -0.5 0.5 0.5">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 -1 2" quat="-0.377964 -0.377964 0.755929 0.377964">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 -1 3" quat="-0.288675 -0.288675 0.866025 0.288675">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 -1 4" quat="-0.288675 -0.288675 0.866025 0.288675">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 0 1" quat="-0.57735 0 0.57735 0.57735">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 0 2" quat="-0.408248 0 0.816497 0.408248">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 0 3" quat="-0.301511 0 0.904534 0.301511">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 0 4" quat="-0.301511 0 0.904534 0.301511">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 1 1" quat="-0.5 0.5 0.5 0.5">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 1 2" quat="-0.377964 0.377964 0.755929 0.377964">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 1 3" quat="-0.288675 0.288675 0.866025 0.288675">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 1 4" quat="-0.288675 0.288675 0.866025 0.288675">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 2 1" quat="-0.377964 0.755929 0.377964 0.377964">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 2 2" quat="-0.316228 0.632456 0.632456 0.316228">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 2 3" quat="-0.258199 0.516398 0.774597 0.258199">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="-1 2 4" quat="-0.258199 0.516398 0.774597 0.258199">
<freejoint/>
<geom class="object2"/>
</body>
<body pos="0 -2 1" quat="0 -0.816497 0.408248 0.408248">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 -2 2" quat="0 -0.666667 0.666667 0.333333">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 -2 3" quat="0 -0.534522 0.801784 0.267261">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 -2 4" quat="0 -0.534522 0.801784 0.267261">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 -1 1" quat="0 -0.57735 0.57735 0.57735">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 -1 2" quat="0 -0.408248 0.816497 0.408248">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 -1 3" quat="0 -0.301511 0.904534 0.301511">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 -1 4" quat="0 -0.301511 0.904534 0.301511">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0.3 0 1" quat="0 0 0.707107 0.707107">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 0 2" quat="0 0 0.894427 0.447214">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 0 3" quat="0 0 0.948683 0.316228">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 0 4" quat="0 0 0.948683 0.316228">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 1 1" quat="0 0.57735 0.57735 0.57735">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 1 2" quat="0 0.408248 0.816497 0.408248">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 1 3" quat="0 0.301511 0.904534 0.301511">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 1 4" quat="0 0.301511 0.904534 0.301511">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 2 1" quat="0 0.816497 0.408248 0.408248">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 2 2" quat="0 0.666667 0.666667 0.333333">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 2 3" quat="0 0.534522 0.801784 0.267261">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="0 2 4" quat="0 0.534522 0.801784 0.267261">
<freejoint/>
<geom class="object3"/>
</body>
<body pos="1 -2 1" quat="0.377964 -0.755929 0.377964 0.377964">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 -2 2" quat="0.316228 -0.632456 0.632456 0.316228">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 -2 3" quat="0.258199 -0.516398 0.774597 0.258199">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 -2 4" quat="0.258199 -0.516398 0.774597 0.258199">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 -1 1" quat="0.5 -0.5 0.5 0.5">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 -1 2" quat="0.377964 -0.377964 0.755929 0.377964">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 -1 3" quat="0.288675 -0.288675 0.866025 0.288675">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 -1 4" quat="0.288675 -0.288675 0.866025 0.288675">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 0 1" quat="0.57735 0 0.57735 0.57735">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 0 2" quat="0.408248 0 0.816497 0.408248">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 0 3" quat="0.301511 0 0.904534 0.301511">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 0 4" quat="0.301511 0 0.904534 0.301511">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 1 1" quat="0.5 0.5 0.5 0.5">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 1 2" quat="0.377964 0.377964 0.755929 0.377964">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 1 3" quat="0.288675 0.288675 0.866025 0.288675">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 1 4" quat="0.288675 0.288675 0.866025 0.288675">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 2 1" quat="0.377964 0.755929 0.377964 0.377964">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 2 2" quat="0.316228 0.632456 0.632456 0.316228">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 2 3" quat="0.258199 0.516398 0.774597 0.258199">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="1 2 4" quat="0.258199 0.516398 0.774597 0.258199">
<freejoint/>
<geom class="object4"/>
</body>
<body pos="2 -2 1" quat="0.632456 -0.632456 0.316228 0.316228">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 -2 2" quat="0.5547 -0.5547 0.5547 0.27735">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 -2 3" quat="0.471405 -0.471405 0.707107 0.235702">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 -2 4" quat="0.471405 -0.471405 0.707107 0.235702">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 -1 1" quat="0.755929 -0.377964 0.377964 0.377964">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 -1 2" quat="0.632456 -0.316228 0.632456 0.316228">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 -1 3" quat="0.516398 -0.258199 0.774597 0.258199">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 -1 4" quat="0.516398 -0.258199 0.774597 0.258199">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 0 1" quat="0.816497 0 0.408248 0.408248">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 0 2" quat="0.666667 0 0.666667 0.333333">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 0 3" quat="0.534522 0 0.801784 0.267261">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 0 4" quat="0.534522 0 0.801784 0.267261">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 1 1" quat="0.755929 0.377964 0.377964 0.377964">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 1 2" quat="0.632456 0.316228 0.632456 0.316228">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 1 3" quat="0.516398 0.258199 0.774597 0.258199">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 1 4" quat="0.516398 0.258199 0.774597 0.258199">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 2 1" quat="0.632456 0.632456 0.316228 0.316228">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 2 2" quat="0.5547 0.5547 0.5547 0.27735">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 2 3" quat="0.471405 0.471405 0.707107 0.235702">
<freejoint/>
<geom class="object5"/>
</body>
<body pos="2 2 4" quat="0.471405 0.471405 0.707107 0.235702">
<freejoint/>
<geom class="object5"/>
</body>
<frame pos="2 -2 2.5">
<replicate count="5" offset="0 1 0">
<frame pos="0 0 -1.5">
<replicate count="4" offset="0 0 1">
<body>
<freejoint/>
<geom class="sphere"/>
</body>
</replicate>
</frame>
</replicate>
</frame>
</worldbody>
</mujoco>
+27
View File
@@ -0,0 +1,27 @@
# Replicate
Models in this directory provide usage examples of the `<replicate>` meta-element, see
[documentation](https://mujoco.readthedocs.io/en/stable/XMLreference.html#replicate-r).
The models contain several categories:
1. Constructing static geometries:
- cylinder.xml
- bowl.xml
- helix.xml
- container.xml
2. Replicating moving bodies:
- particle.xml
- bunnies.xml
- leaves.xml
- stonehenge.xml
3. Replicas that include tendons:
- newton_cradle.xml
- tendon.xml
4. Replicas with multiple referencing elements:
- references.xml
[![Replicate showcase](https://img.youtube.com/vi/5k0_wsIRAFc/0.jpg)](https://www.youtube.com/watch?v=5k0_wsIRAFc)
+46
View File
@@ -0,0 +1,46 @@
<mujoco model="Bowl">
<statistic meansize=".05"/>
<worldbody>
<light pos=".1 0 .6"/>
<light pos="-.1 0 .6"/>
<geom type="plane" size="1 1 .01"/>
<body pos="-.1 0 .04" euler="20 10 0">
<freejoint/>
<geom type="cylinder" size="0.03 0.002" rgba=".8 0 0 1"/>
<replicate count="20" euler="0 0 18">
<frame pos="-.025 0 .03" euler="0 15 0">
<replicate count="6" euler="0 15 0">
<geom type="box" size=".0044 .009 .002" pos="0 0 -.03" rgba=".8 0 0 1"/>
</replicate>
</frame>
</replicate>
</body>
<body pos="0 0 .02" euler="20 20 0">
<freejoint/>
<geom type="cylinder" size="0.01 0.0015" rgba="0 .8 0 1"/>
<replicate count="20" euler="0 0 18">
<frame pos="-.005 0 .03" euler="0 15 0">
<replicate count="6" euler="0 15 0">
<geom type="box" size=".0044 .006 .0015" pos="0 0 -.03" rgba="0 .8 0 1"/>
</replicate>
</frame>
</replicate>
</body>
<body pos=".09 0 .04" euler="30 30 0">
<freejoint/>
<geom type="cylinder" size="0.03 0.002" rgba="0 0 .8 1"/>
<replicate count="20" euler="0 0 18">
<frame pos="-.025 0 .03" euler="0 15 0">
<replicate count="4" euler="0 15 0">
<geom type="box" size=".0044 .009 .002" pos="0 0 -.03" rgba="0 0 .8 1"/>
</replicate>
</frame>
</replicate>
</body>
</worldbody>
</mujoco>
+25
View File
@@ -0,0 +1,25 @@
<mujoco model="Bunnies">
<include file="container.xml"/>
<size memory="1G"/>
<option solver="CG" tolerance="1e-6" timestep=".01"/>
<asset>
<mesh file="bunny.obj"/>
</asset>
<worldbody>
<replicate count="5" offset=".17 0 0">
<replicate count="5" offset="0 .17 0">
<replicate count="5" offset="0 0 .17">
<body pos="-0.35 -0.35 0.65">
<freejoint/>
<geom type="mesh" mesh="bunny" rgba="0.8 0.2 0.1 1"/>
</body>
</replicate>
</replicate>
</replicate>
</worldbody>
</mujoco>
File diff suppressed because it is too large Load Diff
+31
View File
@@ -0,0 +1,31 @@
<mujoco model="Container">
<statistic extent="1.5" meansize=".05"/>
<visual>
<rgba haze="0.15 0.25 0.35 1"/>
<map shadowscale="0.5" zfar="40"/>
</visual>
<asset>
<texture type="skybox" builtin="gradient" rgb1="0.3 0.5 0.7" rgb2="0 0 0" width="64" height="64"/>
<texture name="texplane" type="2d" builtin="checker" rgb1=".2 .3 .4" rgb2=".1 0.15 0.2" width="512" height="512" mark="cross" markrgb=".8 .8 .8"/>
<material name="matplane" reflectance="0.3" texture="texplane" texrepeat="1 1" texuniform="true"/>
</asset>
<worldbody>
<light directional="true" diffuse=".4 .4 .4" specular="0.1 0.1 0.1" pos="0 0 5" castshadow="false"/>
<light diffuse=".6 .6 .6" specular="0.2 0.2 0.2" pos="0 0 4"/>
<geom name="ground" type="plane" size="0 0 1" pos="0 0 0" quat="1 0 0 0" material="matplane" condim="1"/>
<body mocap="true" pos="-.1 .05 0" zaxis=".5 0 1">
<geom type="capsule" size=".1 .1" group="1" condim="1"/>
</body>
</worldbody>
<worldbody>
<replicate count="4" euler="0 0 90">
<geom type="plane" size=".5 .5 .05" zaxis="1 0 0" pos="-.5 0 -.25"/>
</replicate>
</worldbody>
</mujoco>
+31
View File
@@ -0,0 +1,31 @@
<mujoco model="Cylinder">
<statistic meansize=".05"/>
<worldbody>
<light pos=".5 0 3"/>
<light pos="-.5 0 3"/>
<geom type="plane" size="2 2 .01"/>
<body pos="-.3 0 .3" euler="80 10 0">
<freejoint/>
<replicate count="30" euler="0 0 12">
<geom type="box" pos="0 -.12 0" size=".01315 .005 .1" rgba=".8 0 0 1"/>
</replicate>
</body>
<body pos="0 0 .4" euler="20 20 0">
<freejoint/>
<replicate count="10" euler="0 0 36">
<geom type="box" pos="0 -0.067 0" size=".025 .01 .1" rgba="0 .8 0 1"/>
</replicate>
</body>
<body pos=".3 0 .3" euler="30 30 0">
<freejoint/>
<replicate count="4" euler="0 0 90">
<geom type="box" pos="0.005 -0.05 0" size=".05 .005 .08" rgba="0 0 .8 1"/>
</replicate>
</body>
</worldbody>
</mujoco>
+25
View File
@@ -0,0 +1,25 @@
<mujoco model="Helix">
<statistic meansize=".05"/>
<worldbody>
<light pos=".5 0 3"/>
<light pos="-.5 0 3"/>
<geom type="plane" size="2 2 .01"/>
<body pos="0 0 .3">
<freejoint/>
<replicate count="50" offset="0 0 0.01" euler="0 0 25">
<geom type="capsule" size=".01 .05" pos="0 -.23 -.2" euler="0 84.5 0" rgba="0 0 1 1"/>
</replicate>
</body>
<body pos=".6 0 .3" euler="0 0 90">
<freejoint/>
<replicate count="50" offset="0 0 0.01" euler="0 0 15">
<geom type="box" pos=".025 -.2 -.2" size=".032 .04 .005" euler="-24 -12 2" rgba=".8 0 0 1"/>
</replicate>
</body>
</worldbody>
</mujoco>
+50
View File
@@ -0,0 +1,50 @@
<mujoco model="Leaves">
<statistic extent="1.5"/>
<visual>
<rgba haze="0.15 0.25 0.35 1"/>
<map shadowscale="0.5" zfar="40"/>
</visual>
<asset>
<texture type="skybox" builtin="gradient" rgb1="0.3 0.5 0.7" rgb2="0 0 0" width="64" height="64"/>
<texture name="texplane" type="2d" builtin="checker" rgb1=".2 .3 .4" rgb2=".1 0.15 0.2" width="512" height="512" mark="cross" markrgb=".8 .8 .8"/>
<material name="matplane" reflectance="0.3" texture="texplane" texrepeat="1 1" texuniform="true"/>
</asset>
<size memory="1G"/>
<option solver="CG" tolerance="1e-6" timestep=".003" density="1.225" viscosity="1.8e-5" integrator="implicitfast"/>
<visual>
<global realtime="0.2"/>
</visual>
<default>
<default class="leaf">
<geom type="ellipsoid" size=".025 .01 0.001" mass="1e-4" fluidshape="ellipsoid" rgba="0 .4 0 1"/>
</default>
</default>
<worldbody>
<camera name="Cinematic" pos="0 -1.6 4.5" xyaxes="1 0 0 0 0.6 0.8" mode="trackcom"/>
<light diffuse="1 1 1" attenuation=".4 .2 0" pos="-1 -1 6" mode="trackcom" dir="1 1 -3"/>
<light diffuse="1 1 1" attenuation=".4 .2 0" pos="-.98 -1.02 6" mode="trackcom" dir="1 1 -3"/>
<geom name="ground" type="plane" size="5 5 .01" pos="0 0 0" quat="1 0 0 0" material="matplane" condim="1"/>
<replicate count="10" offset="0 0 0.07" euler="0 0 30">
<replicate count="6" euler="-3 0 0">
<frame pos="-.3 -.3 3">
<replicate count="5" offset=".05 0 0" euler="50 0 0">
<body euler="50 50 0">
<freejoint/>
<geom class="leaf" pos=".025 0 0" euler="10 0 0"/>
<geom class="leaf" pos="-.025 0 0" euler="-14 0 0" mass="1.2e-4"/>
</body>
</replicate>
</frame>
</replicate>
</replicate>
</worldbody>
</mujoco>
+69
View File
@@ -0,0 +1,69 @@
<mujoco model="Newton's Cradle">
<option timestep="1e-4"/>
<visual>
<global realtime="0.2"/>
</visual>
<asset>
<texture type="skybox" builtin="gradient" rgb1="0.3 0.5 0.7" rgb2="0 0 0" width="64" height="64"/>
<texture name="texplane" type="2d" builtin="checker" rgb1=".2 .3 .4" rgb2=".1 0.15 0.2" width="512" height="512" mark="cross" markrgb=".8 .8 .8"/>
<material name="matplane" reflectance="0.5" texture="texplane" texrepeat="3 3" texuniform="true"/>
</asset>
<default>
<geom friction=".5" solref="-1e8 -0" solimp="0 .01 1e-3"/>
<tendon limited="true" range="0 0.146" width="0.001" solreflimit="1e-3 1" rgba="0 0 .7 1"/>
<default class="steel ball">
<geom size=".02" rgba=".7 .7 0 1" density="8000" friction=".4" solref="-1e8 -0" solimp="0 .01 1e-3"/>
</default>
</default>
<worldbody>
<light pos=".5 0 1" dir="-1 0 -2"/>
<light pos="-.5 0 1" dir="1 0 -2"/>
<geom type="plane" size="4 4 .1" material="matplane"/>
<replicate count="5" offset="-.04 0 0">
<site name="a" pos="0 -.04 .2"/>
<site name="c" pos="0 .04 .2"/>
<body name="equilibrium" pos="0 0 .04">
<freejoint/>
<geom class="steel ball"/>
<site name="b" pos="0 -.008 .018"/>
<site name="d" pos="0 .008 .018"/>
</body>
</replicate>
<replicate count="2" offset=".04 0 0">
<site name="e" pos=".04 -.04 .2"/>
<site name="g" pos=".04 .04 .2"/>
<body name="perturbed" pos=".12 0 .061" euler="0 -34 0">
<freejoint/>
<geom class="steel ball"/>
<site name="f" pos="0 -.008 .018"/>
<site name="h" pos="0 .008 .018"/>
</body>
</replicate>
</worldbody>
<tendon>
<spatial>
<site site="a"/>
<site site="b"/>
</spatial>
<spatial>
<site site="c"/>
<site site="d"/>
</spatial>
<spatial>
<site site="e"/>
<site site="f"/>
</spatial>
<spatial>
<site site="g"/>
<site site="h"/>
</spatial>
</tendon>
</mujoco>
+23
View File
@@ -0,0 +1,23 @@
<mujoco model="Particle">
<include file="container.xml"/>
<size memory="1G"/>
<option solver="CG" tolerance="1e-6" timestep=".01"/>
<worldbody>
<replicate count="10" offset=".07 0 0">
<replicate count="10" offset="0 .07 0">
<replicate count="10" offset="0 0 .07">
<body pos="-0.35 -0.35 0.65">
<joint type="slide" axis="1 0 0" group="3"/>
<joint type="slide" axis="0 1 0" group="3"/>
<joint type="slide" axis="0 0 1" group="3"/>
<geom size="0.025" condim="1" priority="1" rgba="0.8 0.2 0.1 1"/>
</body>
</replicate>
</replicate>
</replicate>
</worldbody>
</mujoco>
+40
View File
@@ -0,0 +1,40 @@
<mujoco model="Stonehenge">
<statistic meansize="1"/>
<worldbody>
<light directional="true" pos="-50 -20 10" dir="5 2 -1" diffuse="1 1 1"/>
<geom type="plane" size="30 30 .1" rgba="0 1 0 1"/>
<geom type="plane" pos="38 0 5" size="10 30 .1" euler="0 -30 0" rgba="0 .8 0 1"/>
<body name="wrecking ball" pos="45 0 16">
<freejoint/>
<geom size="5.5"/>
</body>
<replicate count="30" euler="0 0 12">
<body pos="0 -15 2">
<freejoint/>
<geom type="box" size="1 .5 2"/>
</body>
<body pos="1.5 -15 4.4" euler="0 0 6">
<freejoint/>
<geom type="box" size="1.5 .5 .4"/>
</body>
</replicate>
<replicate count="5" euler="0 0 45">
<body pos="-1.2 -10 3">
<freejoint/>
<geom type="box" size=".9 .5 3"/>
</body>
<body pos="1.2 -10 3">
<freejoint/>
<geom type="box" size=".9 .5 3"/>
</body>
<body pos="0 -10 6.4">
<freejoint/>
<geom type="box" size="2.4 .5 .4"/>
</body>
</replicate>
</worldbody>
</mujoco>
+47
View File
@@ -0,0 +1,47 @@
<mujoco model="Tendon">
<option solver="PGS"/>
<default>
<site size=".02" rgba="0 .8 0 1"/>
<geom condim="1"/>
</default>
<worldbody>
<light pos="1 0 4" dir="-1 0 -4"/>
<light pos="-1 0 4" dir="1 0 -4"/>
<geom type="plane" size="2 2 .01"/>
<site name="ceiling" pos="0.01 0.02 2.4"/>
<body pos="0 0 1.5">
<freejoint/>
<site name="hook" pos="0 0 .3"/>
<site name="bulb"/>
<geom name="bulb" size=".3" rgba="0 0 .8 1" density="100" contype="0" conaffinity="0"/>
</body>
<replicate count="18" euler="0 0 20">
<body pos=".2 0 1.5">
<freejoint/>
<site name="a"/>
<geom rgba=".8 0 .4 1" size=".03"/>
</body>
<body pos="1.2 0 1.5">
<freejoint/>
<site name="b"/>
<geom rgba=".8 .4 0 1" size=".1"/>
</body>
</replicate>
</worldbody>
<tendon>
<spatial springlength="0 0.5" stiffness="10000">
<site site="ceiling"/>
<site site="hook"/>
</spatial>
<spatial range="0 0.5">
<site site="a"/>
<geom geom="bulb" sidesite="bulb"/>
<site site="b"/>
</spatial>
</tendon>
</mujoco>
+3 -3
View File
@@ -84,7 +84,7 @@ if(NOT TARGET mujoco)
if(MUJOCO_FRAMEWORK)
message("MuJoCo framework is at ${MUJOCO_FRAMEWORK}/mujoco.framework")
set(MUJOCO_LIBRARY
${MUJOCO_FRAMEWORK}/mujoco.framework/Versions/A/libmujoco.3.1.4.dylib
${MUJOCO_FRAMEWORK}/mujoco.framework/Versions/A/libmujoco.3.1.5.dylib
)
target_compile_options(mujoco INTERFACE -F${MUJOCO_FRAMEWORK})
endif()
@@ -92,7 +92,7 @@ if(NOT TARGET mujoco)
if(NOT MUJOCO_FRAMEWORK)
find_library(
MUJOCO_LIBRARY mujoco mujoco.3.1.4 HINTS ${MUJOCO_LIBRARY_DIR} REQUIRED
MUJOCO_LIBRARY mujoco mujoco.3.1.5 HINTS ${MUJOCO_LIBRARY_DIR} REQUIRED
)
find_path(MUJOCO_INCLUDE mujoco/mujoco.h HINTS ${MUJOCO_INCLUDE_DIR} REQUIRED)
message("MuJoCo is at ${MUJOCO_LIBRARY}")
@@ -191,7 +191,7 @@ findorfetch(
GIT_REPO
https://github.com/pybind/pybind11
GIT_TAG
8a099e44b3d5f85b20f05828d919d2332a8de841 # v2.11.1
3e9dfa2866941655c56877882565e7577de6fc7b # v2.12.0
TARGETS
pybind11::pybind11_headers
EXCLUDE_FROM_ALL
+17
View File
@@ -1303,6 +1303,23 @@ Euler integrator, semi-implicit in velocity.
mat = np.array([[1., 2., 3.], [4., 5., 6.], [7., 8., 9.]])
self.assertEqual(mujoco.mju_mulVecMatVec(vec1, mat, vec2), 204.)
def test_mju_euler_to_quat(self):
quat = np.zeros(4)
euler = np.array([0, np.pi/2, 0])
seq = 'xyz'
mujoco.mju_euler2Quat(quat, euler, seq)
expected_quat = np.array([np.sqrt(0.5), 0, np.sqrt(0.5), 0.])
np.testing.assert_almost_equal(quat, expected_quat)
error = 'mju_euler2Quat: seq must contain exactly 3 characters'
with self.assertRaisesWithLiteralMatch(mujoco.FatalError, error):
mujoco.mju_euler2Quat(quat, euler, 'xy')
with self.assertRaisesWithLiteralMatch(mujoco.FatalError, error):
mujoco.mju_euler2Quat(quat, euler, 'xyzy')
error = 'mju_euler2Quat: seq[2] is \'p\', should be one of x, y, z, X, Y, Z'
with self.assertRaisesWithLiteralMatch(mujoco.FatalError, error):
mujoco.mju_euler2Quat(quat, euler, 'xYp')
@parameterized.product(flg_html=(False, True), flg_pad=(False, True))
def test_mj_printSchema(self, flg_html, flg_pad): # pylint: disable=invalid-name
# Make sure that mj_printSchema doesn't raise an exception
+11 -6
View File
@@ -106,11 +106,11 @@ PYBIND11_MODULE(_functions, pymodule) {
Def<traits::mj_defaultOption>(pymodule);
Def<traits::mj_defaultVisual>(pymodule);
// Skipped: mj_copyModel (have MjModel.__copy__, memory managed by MjModel)
DEF_WITH_OMITTED_PY_ARGS(traits::mj_saveModel, "buffer_sz")(
pymodule,
[](const raw::MjModel* m, const std::optional<std::string>& filename,
pymodule.def(
"mj_saveModel",
[](const MjModelWrapper& m, const std::optional<std::string>& filename = std::nullopt,
std::optional<
Eigen::Ref<Eigen::Vector<std::uint8_t, Eigen::Dynamic>>> buffer) {
Eigen::Ref<Eigen::Vector<std::uint8_t, Eigen::Dynamic>>> buffer = std::nullopt) {
void* buffer_ptr = nullptr;
int buffer_sz = 0;
if (buffer.has_value()) {
@@ -118,9 +118,13 @@ PYBIND11_MODULE(_functions, pymodule) {
buffer_sz = buffer->size();
}
return InterceptMjErrors(::mj_saveModel)(
m, filename.has_value() ? filename->c_str() : nullptr,
m.get(), filename.has_value() ? filename->c_str() : nullptr,
buffer_ptr, buffer_sz);
});
},
py::arg("m"), py::arg_v("filename", std::nullopt),
py::arg_v("buffer", std::nullopt),
py::doc(traits::mj_saveModel::doc),
py::call_guard<py::gil_scoped_release>());
// Skipped: mj_loadModel (have MjModel.from_binary_path)
// Skipped: mj_deleteModel (have MjModel.__del__)
Def<traits::mj_sizeModel>(pymodule);
@@ -995,6 +999,7 @@ PYBIND11_MODULE(_functions, pymodule) {
Def<traits::mju_derivQuat>(pymodule);
Def<traits::mju_quatIntegrate>(pymodule);
Def<traits::mju_quatZ2Vec>(pymodule);
Def<traits::mju_euler2Quat>(pymodule);
// Poses
Def<traits::mju_mulPose>(pymodule);
+4 -4
View File
@@ -7,13 +7,13 @@
<key>CFBundleIdentifier</key>
<string>org.mujoco.mjpython</string>
<key>CFBundleVersion</key>
<string>3.1.4</string>
<string>3.1.5</string>
<key>CFBundleGetInfoString</key>
<string>3.1.4</string>
<string>3.1.5</string>
<key>CFBundleLongVersionString</key>
<string>3.1.4</string>
<string>3.1.5</string>
<key>CFBundleShortVersionString</key>
<string>3.1.4</string>
<string>3.1.5</string>
<key>CFBundleExecutable</key>
<string>mjpython</string>
<key>CFBundleIconFile</key>
+9 -4
View File
@@ -78,8 +78,11 @@ the clause:
# Create render contexts.
# TODO(nimrod): Figure out why pytype doesn't like gl_context.GLContext
self._gl_context = gl_context.GLContext(width, height) # type: ignore
self._gl_context.make_current()
self._gl_context = None # type: ignore
if gl_context.GLContext is not None:
self._gl_context = gl_context.GLContext(width, height)
if self._gl_context:
self._gl_context.make_current()
self._mjr_context = _render.MjrContext(
model, _enums.mjtFontScale.mjFONTSCALE_150.value
)
@@ -148,9 +151,11 @@ the clause:
self._scene.flags[_enums.mjtRndFlag.mjRND_SEGMENT] = True
self._scene.flags[_enums.mjtRndFlag.mjRND_IDCOLOR] = True
if self._gl_context is None:
if self._mjr_context is None:
raise RuntimeError('render cannot be called after close.')
self._gl_context.make_current()
if self._gl_context:
self._gl_context.make_current()
if self._depth_rendering:
out_shape = (self._height, self._width)
+1
View File
@@ -2226,6 +2226,7 @@ This is useful for example when the MJB is not available as a file on disk.)"));
X(headlight);
X(directional);
X(castshadow);
X(bulbradius);
#undef X
#define X(var) DefinePyArray(mjvLight, #var, &MjvLightWrapper::var)
+3 -3
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "mujoco"
version = "3.1.4"
version = "3.1.5"
authors = [
{name = "Google DeepMind", email = "mujoco@deepmind.com"},
]
@@ -36,9 +36,9 @@ dynamic = ["readme", "scripts"]
[project.urls]
Homepage = "https://github.com/google-deepmind/mujoco"
Documentation = "https://mujoco.readthedocs.io/en/3.1.4"
Documentation = "https://mujoco.readthedocs.io/en/3.1.5"
Repository = "https://github.com/google-deepmind/mujoco"
Changelog = "https://mujoco.readthedocs.io/en/3.1.4/changelog.html"
Changelog = "https://mujoco.readthedocs.io/en/3.1.5/changelog.html"
[tool.setuptools]
include-package-data = false
+1 -1
View File
@@ -24,7 +24,7 @@ set(MSVC_INCREMENTAL_DEFAULT ON)
project(
mujoco_samples
VERSION 3.1.4
VERSION 3.1.5
DESCRIPTION "MuJoCo samples binaries"
HOMEPAGE_URL "https://mujoco.org"
)
+2 -1
View File
@@ -89,7 +89,8 @@ int filetype(const char* filename) {
// main function
int main(int argc, const char** argv) {
int main(int argc, char** argv) {
// model and error
mjModel* m = 0;
char error[1000];
+1 -1
View File
@@ -29,7 +29,7 @@ set(MUJOCO_DEP_VERSION_lodepng
project(
mujoco_simulate
VERSION 3.1.4
VERSION 3.1.5
DESCRIPTION "MuJoCo simulate binaries"
HOMEPAGE_URL "https://mujoco.org"
)
+1
View File
@@ -163,6 +163,7 @@ void mjccd_support(const void *obj, const ccd_vec3_t *_dir, ccd_vec3_t *vec) {
break;
case mjGEOM_MESH:
case mjGEOM_SDF:
// init search
vertdata = m->mesh_vert + 3*m->mesh_vertadr[m->geom_dataid[g]];
tmp = -1E+10;
+1 -1
View File
@@ -40,7 +40,7 @@
// table of pair-wise collision functions
mjfCollision mjCOLLISIONFUNC[mjNGEOMTYPES][mjNGEOMTYPES] = {
/* PLANE HFIELD SPHERE CAPSULE ELLIPSOID CYLINDER BOX MESH SDF */
/*PLANE */ {0, 0, mjc_PlaneSphere, mjc_PlaneCapsule, mjc_PlaneConvex, mjc_PlaneCylinder, mjc_PlaneBox, mjc_PlaneConvex, mjc_SDF},
/*PLANE */ {0, 0, mjc_PlaneSphere, mjc_PlaneCapsule, mjc_PlaneConvex, mjc_PlaneCylinder, mjc_PlaneBox, mjc_PlaneConvex, mjc_PlaneConvex},
/*HFIELD */ {0, 0, mjc_ConvexHField, mjc_ConvexHField, mjc_ConvexHField, mjc_ConvexHField, mjc_ConvexHField, mjc_ConvexHField, mjc_HFieldSDF},
/*SPHERE */ {0, 0, mjc_SphereSphere, mjc_SphereCapsule, mjc_Convex, mjc_SphereCylinder, mjc_SphereBox, mjc_Convex, mjc_SDF},
/*CAPSULE */ {0, 0, 0, mjc_CapsuleCapsule, mjc_Convex, mjc_Convex, mjc_CapsuleBox, mjc_Convex, mjc_SDF},
+18 -21
View File
@@ -256,6 +256,21 @@ static void clampVec(mjtNum* vec, const mjtNum* range, const mjtByte* limited, i
// return number of dofs given joint type
static int jnt_dofnum(mjtJoint type) {
if (type == mjJNT_FREE) {
return 6;
}
if (type == mjJNT_BALL) {
return 3;
}
return 1;
}
// (qpos, qvel, ctrl, act) => (qfrc_actuator, actuator_force, act_dot)
void mj_fwdActuation(const mjModel* m, mjData* d) {
TM_START;
@@ -469,7 +484,7 @@ void mj_fwdActuation(const mjModel* m, mjData* d) {
mju_mulMatTVec(d->qfrc_actuator, moment, force, nu, nv);
// actuator-level gravity compensation
if (!mjDISABLED(mjDSBL_GRAVITY) && mju_norm3(m->opt.gravity)) {
if (m->ngravcomp && !mjDISABLED(mjDSBL_GRAVITY) && mju_norm3(m->opt.gravity)) {
int njnt = m->njnt;
for (int i=0; i < njnt; i++) {
// skip if gravcomp added as passive force
@@ -477,28 +492,10 @@ void mj_fwdActuation(const mjModel* m, mjData* d) {
continue;
}
// get number of dofs for this joint
int dofnum;
switch (m->jnt_type[i]) {
case mjJNT_HINGE:
case mjJNT_SLIDE:
dofnum = 1;
break;
case mjJNT_BALL:
dofnum = 3;
break;
case mjJNT_FREE:
dofnum = 6;
break;
}
// add gravcomp force
int dofnum = jnt_dofnum(m->jnt_type[i]);
int dofadr = m->jnt_dofadr[i];
for (int j=0; j < dofnum; j++) {
d->qfrc_actuator[dofadr+j] += d->qfrc_gravcomp[dofadr+j];
}
mju_addTo(d->qfrc_actuator + dofadr, d->qfrc_gravcomp + dofadr, dofnum);
}
}
+17 -7
View File
@@ -534,11 +534,15 @@ mjModel* mj_makeModel(
m->nuser_actuator = nuser_actuator;
m->nuser_sensor = nuser_sensor;
m->nnames = nnames;
m->nnames_map = mjLOAD_MULTIPLE
* (nbody + njnt + ngeom + nsite + ncam + nlight + nflex + nmesh
+ nskin + nhfield + ntex + nmat + npair + nexclude + neq
+ ntendon + nu + nsensor + nnumeric + ntext + ntuple
+ nkey + nplugin);
long nnames_map = (long)nbody + njnt + ngeom + nsite + ncam + nlight + nflex + nmesh + nskin +
nhfield + ntex + nmat + npair + nexclude + neq + ntendon + nu + nsensor +
nnumeric + ntext + ntuple + nkey + nplugin;
if (nnames_map >= INT_MAX / mjLOAD_MULTIPLE) {
mju_free(m);
mju_warning("Invalid model: size of nnames_map is larger than INT_MAX");
return 0;
}
m->nnames_map = mjLOAD_MULTIPLE * nnames_map;
m->npaths = npaths;
#define X(name) \
@@ -1520,14 +1524,20 @@ void* mj_stackAllocByte(mjData* d, size_t bytes, size_t alignment) {
// allocate mjtNums on the stack
mjtNum* mj_stackAllocNum(mjData* d, int size) {
mjtNum* mj_stackAllocNum(mjData* d, size_t size) {
if (mjUNLIKELY(size >= SIZE_MAX / sizeof(mjtNum))) {
mjERROR("requested size is too large.");
}
return (mjtNum*) stackalloc(d, size * sizeof(mjtNum), _Alignof(mjtNum));
}
// allocate ints on the stack
int* mj_stackAllocInt(mjData* d, int size) {
int* mj_stackAllocInt(mjData* d, size_t size) {
if (mjUNLIKELY(size >= SIZE_MAX / sizeof(int))) {
mjERROR("requested size is too large.");
}
return (int*) stackalloc(d, size * sizeof(int), _Alignof(int));
}
+2 -2
View File
@@ -129,10 +129,10 @@ void mj__freeStack(mjData* d) __attribute__((noinline));
MJAPI void* mj_stackAllocByte(mjData* d, size_t bytes, size_t alignment);
// mjData stack allocate for array of mjtNums
MJAPI mjtNum* mj_stackAllocNum(mjData* d, int size);
MJAPI mjtNum* mj_stackAllocNum(mjData* d, size_t size);
// mjData stack allocate for array of ints
MJAPI int* mj_stackAllocInt(mjData* d, int size);
MJAPI int* mj_stackAllocInt(mjData* d, size_t size);
// de-allocate data
MJAPI void mj_deleteData(mjData* d);
+1 -1
View File
@@ -175,7 +175,7 @@ static void mj_springdamper(const mjModel* m, mjData* d) {
// body-level gravity compensation, return 1 if any, 0 otherwise
static int mj_gravcomp(const mjModel* m, mjData* d) {
if (mjDISABLED(mjDSBL_GRAVITY) || mju_norm3(m->opt.gravity) == 0) {
if (!m->ngravcomp || mjDISABLED(mjDSBL_GRAVITY) || mju_norm3(m->opt.gravity) == 0) {
return 0;
}
+25 -5
View File
@@ -1033,11 +1033,31 @@ void mj_printFormattedData(const mjModel* m, mjData* d, const char* filename,
fprintf(fp, "CONTACT\n");
for (int i=0; i < d->ncon; i++) {
fprintf(fp, " %d:\n dim %d\n", i, d->contact[i].dim);
fprintf(fp, " gfev %d %d %d %d : %d %d %d %d\n",
d->contact[i].geom[0], d->contact[i].flex[0],
d->contact[i].elem[0], d->contact[i].vert[0],
d->contact[i].geom[1], d->contact[i].flex[1],
d->contact[i].elem[1], d->contact[i].vert[1]);
int g1 = d->contact[i].geom[0];
int g2 = d->contact[i].geom[1];
// special case for geom-geom contacts
if (g1 > -1 && g2 > -1) {
fprintf(fp, " geoms ");
const char* geom1 = mj_id2name(m, mjOBJ_GEOM, g1);
const char* geom2 = mj_id2name(m, mjOBJ_GEOM, g2);
if (geom1) {
fprintf(fp, "%s : ", geom1);
} else {
fprintf(fp, "%d : ", g1);
}
if (geom2) {
fprintf(fp, "%s\n", geom2);
} else {
fprintf(fp, "%d\n", g2);
}
} else {
fprintf(fp, " gfev %d %d %d %d : %d %d %d %d\n",
d->contact[i].geom[0], d->contact[i].flex[0],
d->contact[i].elem[0], d->contact[i].vert[0],
d->contact[i].geom[1], d->contact[i].flex[1],
d->contact[i].elem[1], d->contact[i].vert[1]);
}
fprintf(fp, " exclude %d\n efc_address %d\n",
d->contact[i].exclude, d->contact[i].efc_address);
printVector(" solref ", d->contact[i].solref, mjNREF, fp, float_format);
+10
View File
@@ -15,6 +15,7 @@
#include "engine/engine_setconst.h"
#include <stdio.h>
#include <string.h>
#include <mujoco/mjdata.h>
#include <mujoco/mjmacro.h>
@@ -92,6 +93,11 @@ static void set0(mjModel* m, mjData* d) {
// compute dof_M0 for CRB algorithm
mj_setM0(m, d);
// save flex_rigid, temporarily make all flexes non-rigid
mjtByte* rigid = mju_malloc(m->nflex);
memcpy(rigid, m->flex_rigid, m->nflex);
memset(m->flex_rigid, 0, m->nflex);
// run remaining computations
mj_crb(m, d);
mj_factorM(m, d);
@@ -99,6 +105,10 @@ static void set0(mjModel* m, mjData* d) {
mj_tendon(m, d);
mj_transmission(m, d);
// restore flex rigidity
memcpy(m->flex_rigid, rigid, m->nflex);
mju_free(rigid);
// restore camera and light mode
for (int i=0; i < m->ncam; i++) {
m->cam_mode[i] = cammode[i];
+2 -2
View File
@@ -38,8 +38,8 @@
//-------------------------- Constants -------------------------------------------------------------
#define mjVERSION 314
#define mjVERSIONSTRING "3.1.4"
#define mjVERSION 315
#define mjVERSIONSTRING "3.1.5"
// names of disable flags
const char* mjDISABLESTRING[mjNDISABLE] = {
+38
View File
@@ -511,3 +511,41 @@ void mju_makeFrame(mjtNum frame[9]) {
// zaxis = cross(xaxis, yaxis)
mju_cross(frame+6, frame, frame+3);
}
// convert sequence of Euler angles (radians) to quaternion
// seq[0,1,2] must be in 'xyzXYZ', lower/upper-case mean intrinsic/extrinsic rotations
void mju_euler2Quat(mjtNum quat[4], const mjtNum euler[3], const char* seq) {
if (strnlen(seq, 4) != 3) {
mjERROR("seq must contain exactly 3 characters");
}
// init
double tmp[4] = {1, 0, 0, 0};
// loop over euler angles, accumulate rotations
for (int i=0; i<3; i++) {
// construct quaternion rotation
mjtNum rot[4] = {cos(euler[i]/2), 0, 0, 0};
mjtNum sa = sin(euler[i]/2);
if (seq[i]=='x' || seq[i]=='X') {
rot[1] = sa;
} else if (seq[i]=='y' || seq[i]=='Y') {
rot[2] = sa;
} else if (seq[i]=='z' || seq[i]=='Z') {
rot[3] = sa;
} else {
mjERROR("seq[%d] is '%c', should be one of x, y, z, X, Y, Z", i, seq[i]);
}
// accumulate rotation
if (seq[i]=='x' || seq[i]=='y' || seq[i]=='z') {
mju_mulQuat(tmp, tmp, rot); // moving axes: post-multiply
} else {
mju_mulQuat(tmp, rot, tmp); // fixed axes: pre-multiply
}
}
mju_copy4(quat, tmp);
}
+3
View File
@@ -75,6 +75,9 @@ MJAPI void mju_negPose(mjtNum posres[3], mjtNum quatres[4],
MJAPI void mju_trnVecPose(mjtNum res[3], const mjtNum pos[3], const mjtNum quat[4],
const mjtNum vec[3]);
// convert sequence of Euler angles (radians) to quaternion
// seq[0,1,2] must be in 'xyzXYZ', lower/upper-case mean intrinsic/extrinsic rotations
MJAPI void mju_euler2Quat(mjtNum quat[4], const mjtNum euler[3], const char* seq);
//------------------------------ spatial algebra ---------------------------------------------------
+1
View File
@@ -2091,6 +2091,7 @@ void mjv_makeLights(const mjModel* m, mjData* d, mjvScene* scn) {
memset(thislight, 0, sizeof(mjvLight));
thislight->directional = m->light_directional[i];
thislight->castshadow = m->light_castshadow[i];
thislight->bulbradius = m->light_bulbradius[i];
if (!thislight->directional) {
f2f(thislight->attenuation, m->light_attenuation+3*i, 3);
thislight->exponent = m->light_exponent[i];
+1 -1
View File
@@ -21,7 +21,7 @@ set(MUJOCO_USER_SRCS
user_composite.h
user_flexcomp.cc
user_flexcomp.h
user_init.cc
user_init.c
user_mesh.cc
user_model.cc
user_model.h
+34 -3
View File
@@ -91,6 +91,17 @@ int mjs_attachBody(mjsFrame* parent, const mjsBody* child,
// attach frame to a parent body
int mjs_attachFrame(mjsBody* parent, const mjsFrame* child,
const char* prefix, const char* suffix) {
mjCBody* body_parent = static_cast<mjCBody*>(parent->element);
mjCFrame* child_frame = static_cast<mjCFrame*>(child->element);
*body_parent += std::string(prefix) + *child_frame + std::string(suffix);
return 0;
}
// get error message from model
const char* mjs_getError(mjSpec* s) {
mjCModel* modelC = static_cast<mjCModel*>(s->element);
@@ -99,6 +110,17 @@ const char* mjs_getError(mjSpec* s) {
// Detach body from mjSpec, return 0 if success.
int mjs_detachBody(mjSpec* s, mjsBody* b) {
mjCModel* model = static_cast<mjCModel*>(s->element);
mjCBody* body = static_cast<mjCBody*>(b->element);
*model -= *body;
mjs_deleteBody(b);
return 0;
}
// check if model has warnings
int mjs_isWarning(mjSpec* s) {
mjCModel* modelC = static_cast<mjCModel*>(s->element);
@@ -115,6 +137,14 @@ void mjs_deleteSpec(mjSpec* s) {
// delete body
void mjs_deleteBody(mjsBody* b) {
mjCBody* body = static_cast<mjCBody*>(b->element);
delete body;
}
// add child body to body, return child spec
mjsBody* mjs_addBody(mjsBody* bodyspec, mjsDefault* defspec) {
mjCDef* def = defspec ? static_cast<mjCDef*>(defspec->element) : 0;
@@ -650,11 +680,12 @@ void mjs_setActivePlugins(mjSpec* s, void* activeplugins) {
// compute full inertia
const char* mjs_setFullInertia(mjsBody* bodyspec, double quat[4], double inertia[3]) {
mjCBody* body = static_cast<mjCBody*>(bodyspec->element);
return body->FullInertia(quat, inertia);
const char* mjs_fullInertia(double quat[4], double inertia[3], const double fullinertia[6]) {
return FullInertia(quat, inertia, fullinertia);
}
// -------------------------- GLOBAL ASSET CACHE -------------------------------
void mj_setCacheSize(mjCache cache, std::size_t size) {
+221 -170
View File
@@ -15,6 +15,7 @@
#ifndef MUJOCO_SRC_USER_USER_API_H_
#define MUJOCO_SRC_USER_USER_API_H_
#include <math.h>
#include <stddef.h>
#include <mujoco/mjexport.h>
#include <mujoco/mjmodel.h>
@@ -26,6 +27,8 @@
extern "C" {
#endif
#define mjNAN NAN // used to mark undefined fields
//---------------------------------- handles to internal objects -----------------------------------
@@ -40,42 +43,51 @@ typedef struct _mjDoubleVec* mjDoubleVec;
//---------------------------------- enum types (mjt) ----------------------------------------------
typedef enum _mjtGeomInertia { // type of inertia inference
mjINERTIA_VOLUME, // mass distributed in the volume
mjINERTIA_SHELL, // mass distributed on the surface
typedef enum _mjtGeomInertia { // type of inertia inference
mjINERTIA_VOLUME, // mass distributed in the volume
mjINERTIA_SHELL, // mass distributed on the surface
} mjtGeomInertia;
typedef enum _mjtBuiltin { // type of built-in procedural texture
mjBUILTIN_NONE = 0, // no built-in texture
mjBUILTIN_GRADIENT, // gradient: rgb1->rgb2
mjBUILTIN_CHECKER, // checker pattern: rgb1, rgb2
mjBUILTIN_FLAT // 2d: rgb1; cube: rgb1-up, rgb2-side, rgb3-down
typedef enum _mjtBuiltin { // type of built-in procedural texture
mjBUILTIN_NONE = 0, // no built-in texture
mjBUILTIN_GRADIENT, // gradient: rgb1->rgb2
mjBUILTIN_CHECKER, // checker pattern: rgb1, rgb2
mjBUILTIN_FLAT // 2d: rgb1; cube: rgb1-up, rgb2-side, rgb3-down
} mjtBuiltin;
typedef enum _mjtMark { // mark type for procedural textures
mjMARK_NONE = 0, // no mark
mjMARK_EDGE, // edges
mjMARK_CROSS, // cross
mjMARK_RANDOM // random dots
typedef enum _mjtMark { // mark type for procedural textures
mjMARK_NONE = 0, // no mark
mjMARK_EDGE, // edges
mjMARK_CROSS, // cross
mjMARK_RANDOM // random dots
} mjtMark;
typedef enum _mjtLimited { // type of limit specification
mjLIMITED_FALSE = 0, // not limited
mjLIMITED_TRUE, // limited
mjLIMITED_AUTO, // limited inferred from presence of range
typedef enum _mjtLimited { // type of limit specification
mjLIMITED_FALSE = 0, // not limited
mjLIMITED_TRUE, // limited
mjLIMITED_AUTO, // limited inferred from presence of range
} mjtLimited;
typedef enum _mjtInertiaFromGeom {
mjINERTIAFROMGEOM_FALSE = 0, // do not use; inertial element required
mjINERTIAFROMGEOM_TRUE, // always use; overwrite inertial element
mjINERTIAFROMGEOM_AUTO // use only if inertial element is missing
typedef enum _mjtInertiaFromGeom { // whether to infer body inertias from child geoms
mjINERTIAFROMGEOM_FALSE = 0, // do not use; inertial element required
mjINERTIAFROMGEOM_TRUE, // always use; overwrite inertial element
mjINERTIAFROMGEOM_AUTO // use only if inertial element is missing
} mjtInertiaFromGeom;
typedef enum _mjtOrientation { // type of orientation specifier
mjORIENTATION_QUAT = 0, // quaternion
mjORIENTATION_AXISANGLE, // axis and angle
mjORIENTATION_XYAXES, // x and y axes
mjORIENTATION_ZAXIS, // z axis (minimal rotation)
mjORIENTATION_EULER, // Euler angles
} mjtOrientation;
//---------------------------------- attribute structs (mjs) ---------------------------------------
typedef struct _mjElement { // element type, do not modify
@@ -84,8 +96,8 @@ typedef struct _mjElement { // element type, do not modify
typedef struct _mjSpec { // model specification
mjElement* element; // object type
mjStatistic stat; // statistics override (if defined)
mjElement* element; // element type
mjString modelname; // model name
// compiler settings
mjtByte autolimits; // infer "limited" attribute based on range
@@ -109,46 +121,47 @@ typedef struct _mjSpec { // model specification
mjLROpt LRopt; // options for lengthrange computation
// engine data
mjString modelname; // model name
mjOption option; // options
mjVisual visual; // visual options
size_t memory; // size of arena+stack memory in bytes
int nemax; // max number of equality constraints
int njmax; // max number of constraints (Jacobian rows)
int nconmax; // max number of detected contacts (mjContact array size)
size_t nstack; // (deprecated) number of fields in mjData stack
int nuserdata; // number extra fields in mjData
int nuser_body; // number of mjtNums in body_user
int nuser_jnt; // number of mjtNums in jnt_user
int nuser_geom; // number of mjtNums in geom_user
int nuser_site; // number of mjtNums in site_user
int nuser_cam; // number of mjtNums in cam_user
int nuser_tendon; // number of mjtNums in tendon_user
int nuser_actuator; // number of mjtNums in actuator_user
int nuser_sensor; // number of mjtNums in sensor_user
mjOption option; // physics options
mjVisual visual; // visual options
mjStatistic stat; // statistics override (if defined)
// sizes
int nkey; // number of keyframes
size_t memory; // number of bytes in arena+stack memory
int nemax; // max number of equality constraints
int nuserdata; // number of mjtNums in userdata
int nuser_body; // number of mjtNums in body_user
int nuser_jnt; // number of mjtNums in jnt_user
int nuser_geom; // number of mjtNums in geom_user
int nuser_site; // number of mjtNums in site_user
int nuser_cam; // number of mjtNums in cam_user
int nuser_tendon; // number of mjtNums in tendon_user
int nuser_actuator; // number of mjtNums in actuator_user
int nuser_sensor; // number of mjtNums in sensor_user
int nkey; // number of keyframes
int njmax; // (deprecated) max number of constraints
int nconmax; // (deprecated) max number of detected contacts
size_t nstack; // (deprecated) number of mjtNums in mjData stack
// global data
mjString comment; // comment at top of XML
mjString modelfiledir; // path to model file
mjString comment; // comment at top of XML
mjString modelfiledir; // path to model file
// other
bool hasImplicitPluginElem; // already encountered an implicit plugin sensor/actuator
mjtByte hasImplicitPluginElem; // already encountered an implicit plugin sensor/actuator
} mjSpec;
typedef struct _mjsOrientation { // alternative orientation specifiers
double axisangle[4]; // rotation axis and angle
mjtOrientation type; // active orientation specifier
double axisangle[4]; // axis and angle
double xyaxes[6]; // x and y axes
double zaxis[3]; // z axis (use minimal rotation)
double euler[3]; // euler angles
double zaxis[3]; // z axis (minimal rotation)
double euler[3]; // Euler angles
} mjsOrientation;
typedef struct _mjsPlugin { // plugin specification
mjElement* instance; // object type
mjElement* instance; // element type
mjString name; // name
mjString instance_name; // instance name
int plugin_slot; // global registered slot number of the plugin
@@ -158,7 +171,7 @@ typedef struct _mjsPlugin { // plugin specification
typedef struct _mjsBody { // body specification
mjElement* element; // object type
mjElement* element; // element type
mjString name; // name
mjString childclass; // childclass name
@@ -186,7 +199,7 @@ typedef struct _mjsBody { // body specification
typedef struct _mjsFrame { // frame specification
mjElement* element; // object type
mjElement* element; // element type
mjString name; // name
mjString childclass; // childclass name
double pos[3]; // position
@@ -197,7 +210,7 @@ typedef struct _mjsFrame { // frame specification
typedef struct _mjsJoint { // joint specification
mjElement* element; // object type
mjElement* element; // element type
mjString name; // name
mjString classname; // class name
mjtJoint type; // joint type
@@ -238,7 +251,7 @@ typedef struct _mjsJoint { // joint specification
typedef struct _mjsGeom { // geom specification
mjElement* element; // object type
mjElement* element; // element type
mjString name; // name
mjString classname; // classname
mjtGeom type; // geom type
@@ -287,7 +300,7 @@ typedef struct _mjsGeom { // geom specification
typedef struct _mjsSite { // site specification
mjElement* element; // object type
mjElement* element; // element type
mjString name; // name
mjString classname; // class name
@@ -311,7 +324,7 @@ typedef struct _mjsSite { // site specification
typedef struct _mjsCamera { // camera specification
mjElement* element; // object type
mjElement* element; // element type
mjString name; // name
mjString classname; // class name
@@ -340,7 +353,7 @@ typedef struct _mjsCamera { // camera specification
typedef struct _mjsLight { // light specification
mjElement* element; // object type
mjElement* element; // element type
mjString name; // name
mjString classname; // class name
@@ -354,6 +367,7 @@ typedef struct _mjsLight { // light specification
mjtByte active; // is light active
mjtByte directional; // is light directional or spot
mjtByte castshadow; // does light cast shadows
double bulbradius; // bulb radius, for soft shadows
float attenuation[3]; // OpenGL attenuation (quadratic model)
float cutoff; // OpenGL cutoff
float exponent; // OpenGL exponent
@@ -367,7 +381,7 @@ typedef struct _mjsLight { // light specification
typedef struct _mjsFlex {
mjElement* element; // object type
mjElement* element; // element type
mjString name; // name
mjString classname; // class name
@@ -408,7 +422,7 @@ typedef struct _mjsFlex {
typedef struct _mjsMesh { // mesh specification
mjElement* element; // object type
mjElement* element; // element type
mjString name; // name
mjString classname; // class name
mjString content_type; // content type of file
@@ -429,7 +443,7 @@ typedef struct _mjsMesh { // mesh specification
typedef struct _mjsHField { // height field specification
mjElement* element; // object type
mjElement* element; // element type
mjString name; // name
mjString content_type; // content type of file
mjString file; // file: (nrow, ncol, [elevation data])
@@ -443,7 +457,7 @@ typedef struct _mjsHField { // height field specification
typedef struct _mjsSkin { // skin specification
mjElement* element; // object type
mjElement* element; // element type
mjString name; // name
mjString classname; // class name
mjString file; // skin file
@@ -470,7 +484,7 @@ typedef struct _mjsSkin { // skin specification
typedef struct _mjsTexture { // texture specification
mjElement* element; // object type
mjElement* element; // element type
mjString name; // name
mjString classname; // class name
mjtTexture type; // texture type
@@ -504,7 +518,7 @@ typedef struct _mjsTexture { // texture specification
typedef struct _mjsMaterial { // material specification
mjElement* element; // object type
mjElement* element; // element type
mjString name; // name
mjString classname; // class name
mjString texture; // name of texture (empty: none)
@@ -514,13 +528,15 @@ typedef struct _mjsMaterial { // material specification
float specular; // specular
float shininess; // shininess
float reflectance; // reflectance
float metallic; // metallic
float roughness; // roughness
float rgba[4]; // rgba
mjString info; // message appended to compiler errors
} mjsMaterial;
typedef struct _mjsPair {
mjElement* element; // object type
mjElement* element; // element type
mjString name; // name
mjString classname; // class name
mjString geomname1; // name of geom 1
@@ -539,7 +555,7 @@ typedef struct _mjsPair {
typedef struct _mjsExclude {
mjElement* element; // object type
mjElement* element; // element type
mjString name; // name
mjString bodyname1; // name of geom 1
mjString bodyname2; // name of geom 2
@@ -548,7 +564,7 @@ typedef struct _mjsExclude {
typedef struct _mjsEquality { // equality specification
mjElement* element; // object type
mjElement* element; // element type
mjString name; // name
mjString classname; // class name
mjtEq type; // constraint type
@@ -563,7 +579,7 @@ typedef struct _mjsEquality { // equality specification
typedef struct _mjsTendon { // tendon specification
mjElement* element; // object type
mjElement* element; // element type
mjString name; // name
mjString classname; // class name
@@ -595,13 +611,13 @@ typedef struct _mjsTendon { // tendon specification
typedef struct _mjsWrap { // wrapping object specification
mjElement* element; // object type
mjElement* element; // element type
mjString info; // message appended to errors
} mjsWrap;
typedef struct _mjsActuator { // actuator specification
mjElement* element; // object type
mjElement* element; // element type
mjString name; // name
mjString classname; // class name
@@ -645,7 +661,7 @@ typedef struct _mjsActuator { // actuator specification
typedef struct _mjsSensor { // sensor specification
mjElement* element; // object type
mjElement* element; // element type
mjString name; // name
mjString classname; // class name
@@ -673,7 +689,7 @@ typedef struct _mjsSensor { // sensor specification
typedef struct _mjsNumeric { // custom numeric field specification
mjElement* element; // object type
mjElement* element; // element type
mjString name; // name
mjDoubleVec data; // initialization data
int size; // array size, can be bigger than data size
@@ -682,7 +698,7 @@ typedef struct _mjsNumeric { // custom numeric field specification
typedef struct _mjsText { // custom text specification
mjElement* element; // object type
mjElement* element; // element type
mjString name; // name
mjString data; // text string
mjString info; // message appended to compiler errors
@@ -690,7 +706,7 @@ typedef struct _mjsText { // custom text specification
typedef struct _mjsTuple { // tuple specification
mjElement* element; // object type
mjElement* element; // element type
mjString name; // name
mjIntVec objtype; // object types
mjStringVec objname; // object names
@@ -700,7 +716,7 @@ typedef struct _mjsTuple { // tuple specification
typedef struct _mjsKey { // keyframe specification
mjElement* element; // object type
mjElement* element; // element type
mjString name; // name
double time; // time
mjDoubleVec qpos; // qpos
@@ -714,8 +730,8 @@ typedef struct _mjsKey { // keyframe specification
typedef struct _mjsDefault { // default specification
mjString name; // name
mjElement* element; // object type
mjElement* element; // element type
mjString name; // class name
mjsJoint* joint; // joint defaults
mjsGeom* geom; // geom defaults
mjsSite* site; // site defaults
@@ -731,34 +747,47 @@ typedef struct _mjsDefault { // default specification
} mjsDefault;
//---------------------------------- API functions -------------------------------------------------
//---------------------------------- Top-level spec manipulation -----------------------------------
// Create model.
MJAPI mjSpec* mjs_createSpec();
// Create spec.
MJAPI mjSpec* mjs_createSpec(void);
// Copy model.
MJAPI mjSpec* mjs_copySpec(const mjSpec* s);
// Copy back model.
MJAPI void mjs_copyBack(mjSpec* s, const mjModel* m);
// Compile model.
// Compile spec to model.
MJAPI mjModel* mjs_compile(mjSpec* s, const mjVFS* vfs);
// Attach child body to a frame of the parent, return 0 if success
// Copy spec.
MJAPI mjSpec* mjs_copySpec(const mjSpec* s);
// Get compiler error message from spec.
MJAPI const char* mjs_getError(mjSpec* s);
// Return 1 if compiler error is a warning.
MJAPI int mjs_isWarning(mjSpec* s);
// Copy model fields back into spec.
MJAPI void mjs_copyBack(mjSpec* s, const mjModel* m);
// Delete spec.
MJAPI void mjs_deleteSpec(mjSpec* s);
//---------------------------------- Attachment ----------------------------------------------------
// Attach child body to a parent frame, return 0 on success.
MJAPI int mjs_attachBody(mjsFrame* parent, const mjsBody* child,
const char* prefix, const char* suffix);
// Get error message from model.
MJAPI const char* mjs_getError(mjSpec* s);
// Attach child frame to a parent body, return 0 on success.
MJAPI int mjs_attachFrame(mjsBody* parent, const mjsFrame* child,
const char* prefix, const char* suffix);
// Return 1 if model has warnings.
MJAPI int mjs_isWarning(mjSpec* s);
// Detach body from mjSpec, remove all references and delete the body, return 0 on success.
MJAPI int mjs_detachBody(mjSpec* s, mjsBody* b);
// Delete model.
MJAPI void mjs_deleteSpec(mjSpec* s);
// Add child body to body, return child spec.
//---------------------------------- Add tree elements ---------------------------------------------
// Add child body to body, return child.
MJAPI mjsBody* mjs_addBody(mjsBody* body, mjsDefault* def);
// Add site to body, return site spec.
@@ -782,34 +811,31 @@ MJAPI mjsLight* mjs_addLight(mjsBody* body, mjsDefault* def);
// Add frame to body.
MJAPI mjsFrame* mjs_addFrame(mjsBody* body, mjsFrame* parentframe);
// Add flex to model.
// Delete body. TODO: make this a general mjs_deleteElement function
MJAPI void mjs_deleteBody(mjsBody* b);
//---------------------------------- Add non-tree elements -----------------------------------------
// Add actuator.
MJAPI mjsActuator* mjs_addActuator(mjSpec* s, mjsDefault* def);
// Add sensor.
MJAPI mjsSensor* mjs_addSensor(mjSpec* s);
// Add flex.
MJAPI mjsFlex* mjs_addFlex(mjSpec* s);
// Add mesh to model.
MJAPI mjsMesh* mjs_addMesh(mjSpec* s, mjsDefault* def);
// Add height field to model.
MJAPI mjsHField* mjs_addHField(mjSpec* s);
// Add skin to model.
MJAPI mjsSkin* mjs_addSkin(mjSpec* s);
// Add texture to model.
MJAPI mjsTexture* mjs_addTexture(mjSpec* s);
// Add material to model.
MJAPI mjsMaterial* mjs_addMaterial(mjSpec* s, mjsDefault* def);
// Add pair to model.
// Add contact pair.
MJAPI mjsPair* mjs_addPair(mjSpec* s, mjsDefault* def);
// Add excluded body pair to model.
// Add excluded body pair.
MJAPI mjsExclude* mjs_addExclude(mjSpec* s);
// Add equality to model.
// Add equality.
MJAPI mjsEquality* mjs_addEquality(mjSpec* s, mjsDefault* def);
// Add tendon to model.
// Add tendon.
MJAPI mjsTendon* mjs_addTendon(mjSpec* s, mjsDefault* def);
// Wrap site using tendon.
@@ -824,42 +850,48 @@ MJAPI mjsWrap* mjs_wrapJoint(mjsTendon* tendon, const char* name, double coef);
// Wrap pulley using tendon.
MJAPI mjsWrap* mjs_wrapPulley(mjsTendon* tendon, double divisor);
// Add actuator to model.
MJAPI mjsActuator* mjs_addActuator(mjSpec* s, mjsDefault* def);
// Add sensor to model.
MJAPI mjsSensor* mjs_addSensor(mjSpec* s);
// Add numeric to model.
// Add numeric.
MJAPI mjsNumeric* mjs_addNumeric(mjSpec* s);
// Add text to model.
// Add text.
MJAPI mjsText* mjs_addText(mjSpec* s);
// Add tuple to model.
// Add tuple.
MJAPI mjsTuple* mjs_addTuple(mjSpec* s);
// Add keyframe to model.
// Add keyframe.
MJAPI mjsKey* mjs_addKey(mjSpec* s);
// Add plugin to model.
// Add plugin.
MJAPI mjsPlugin* mjs_addPlugin(mjSpec* s);
// Add default to model.
// Add default.
MJAPI mjsDefault* mjs_addDefault(mjSpec* s, const char* classname, int parentid, int* id);
// Get model spec from body.
//---------------------------------- Add assets ----------------------------------------------------
// Add mesh.
MJAPI mjsMesh* mjs_addMesh(mjSpec* s, mjsDefault* def);
// Add height field.
MJAPI mjsHField* mjs_addHField(mjSpec* s);
// Add skin.
MJAPI mjsSkin* mjs_addSkin(mjSpec* s);
// Add texture.
MJAPI mjsTexture* mjs_addTexture(mjSpec* s);
// Add material.
MJAPI mjsMaterial* mjs_addMaterial(mjSpec* s, mjsDefault* def);
//---------------------------------- Find/get utilities --------------------------------------------
// Get spec from body.
MJAPI mjSpec* mjs_getSpec(mjsBody* body);
// Get default corresponding to an mjElement.
MJAPI mjsDefault* mjs_getDefault(mjElement* element);
// Find default in model by class name.
MJAPI mjsDefault* mjs_findDefault(mjSpec* s, const char* classname);
// Get global default from model.
MJAPI mjsDefault* mjs_getSpecDefault(mjSpec* s);
// Find body in model by name.
MJAPI mjsBody* mjs_findBody(mjSpec* s, const char* name);
@@ -872,9 +904,21 @@ MJAPI mjsMesh* mjs_findMesh(mjSpec* s, const char* name);
// Find frame by name.
MJAPI mjsFrame* mjs_findFrame(mjSpec* s, const char* name);
// Get default corresponding to an element.
MJAPI mjsDefault* mjs_getDefault(mjElement* element);
// Find default in model by class name.
MJAPI mjsDefault* mjs_findDefault(mjSpec* s, const char* classname);
// Get global default from model.
MJAPI mjsDefault* mjs_getSpecDefault(mjSpec* s);
// Get element id.
MJAPI int mjs_getId(mjElement* element);
//---------------------------------- Attribute setters ---------------------------------------------
// Copy text to string.
MJAPI void mjs_setString(mjString dest, const char* text);
@@ -902,110 +946,117 @@ MJAPI void mjs_appendFloatVec(mjFloatVecVec dest, const float* array, int size);
// Copy double array to vector.
MJAPI void mjs_setDouble(mjDoubleVec dest, const double* array, int size);
// Set plugin attributes.
MJAPI void mjs_setPluginAttributes(mjsPlugin* plugin, void* attributes);
//---------------------------------- Attribute getters ---------------------------------------------
// Get string contents.
MJAPI const char* mjs_getString(mjString source);
// Get double array contents and optionally its size.
MJAPI const double* mjs_getDouble(mjDoubleVec source, int* size);
// Set plugin attributes.
MJAPI void mjs_setPluginAttributes(mjsPlugin* plugin, void* attributes);
//---------------------------------- Other utilities -----------------------------------------------
// Set active plugins.
MJAPI void mjs_setActivePlugins(mjSpec* s, void* activeplugins);
// Set default.
// Set element's default.
MJAPI void mjs_setDefault(mjElement* element, mjsDefault* def);
// Set frame.
// Set element's enlcosing frame.
MJAPI void mjs_setFrame(mjElement* dest, mjsFrame* frame);
// Resolve alternative orientations to quat.
// Resolve alternative orientations to quat, return error if any.
MJAPI const char* mjs_resolveOrientation(double quat[4], mjtByte degree, const char* sequence,
const mjsOrientation* orientation);
// Compute quat and inertia from body->fullinertia.
MJAPI const char* mjs_setFullInertia(mjsBody* body, double quat[4], double inertia[3]);
// Compute quat and diag inertia from full inertia matrix, return error if any.
MJAPI const char* mjs_fullInertia(double quat[4], double inertia[3], const double fullinertia[6]);
//---------------------------------- Initialization functions --------------------------------------
//---------------------------------- Initialization -----------------------------------------------
// Default model attributes.
MJAPI void mjs_defaultSpec(mjSpec& model);
// Default spec attributes.
MJAPI void mjs_defaultSpec(mjSpec* spec);
// Default orientation attributes.
MJAPI void mjs_defaultOrientation(mjsOrientation& orient);
MJAPI void mjs_defaultOrientation(mjsOrientation* orient);
// Default body attributes.
MJAPI void mjs_defaultBody(mjsBody& body);
MJAPI void mjs_defaultBody(mjsBody* body);
// Default frame attributes.
MJAPI void mjs_defaultFrame(mjsFrame& frame);
MJAPI void mjs_defaultFrame(mjsFrame* frame);
// Default joint attributes.
MJAPI void mjs_defaultJoint(mjsJoint& joint);
MJAPI void mjs_defaultJoint(mjsJoint* joint);
// Default geom attributes.
MJAPI void mjs_defaultGeom(mjsGeom& geom);
MJAPI void mjs_defaultGeom(mjsGeom* geom);
// Default site attributes.
MJAPI void mjs_defaultSite(mjsSite& site);
MJAPI void mjs_defaultSite(mjsSite* site);
// Default camera attributes.
MJAPI void mjs_defaultCamera(mjsCamera& camera);
MJAPI void mjs_defaultCamera(mjsCamera* camera);
// Default light attributes.
MJAPI void mjs_defaultLight(mjsLight& light);
MJAPI void mjs_defaultLight(mjsLight* light);
// Default flex attributes.
MJAPI void mjs_defaultFlex(mjsFlex& flex);
MJAPI void mjs_defaultFlex(mjsFlex* flex);
// Default mesh attributes.
MJAPI void mjs_defaultMesh(mjsMesh& mesh);
MJAPI void mjs_defaultMesh(mjsMesh* mesh);
// Default height field attributes.
MJAPI void mjs_defaultHField(mjsHField& hfield);
MJAPI void mjs_defaultHField(mjsHField* hfield);
// Default skin attributes.
MJAPI void mjs_defaultSkin(mjsSkin& skin);
MJAPI void mjs_defaultSkin(mjsSkin* skin);
// Default texture attributes.
MJAPI void mjs_defaultTexture(mjsTexture& texture);
MJAPI void mjs_defaultTexture(mjsTexture* texture);
// Default material attributes.
MJAPI void mjs_defaultMaterial(mjsMaterial& material);
MJAPI void mjs_defaultMaterial(mjsMaterial* material);
// Default pair attributes.
MJAPI void mjs_defaultPair(mjsPair& pair);
MJAPI void mjs_defaultPair(mjsPair* pair);
// Default equality attributes.
MJAPI void mjs_defaultEquality(mjsEquality& equality);
MJAPI void mjs_defaultEquality(mjsEquality* equality);
// Default tendon attributes.
MJAPI void mjs_defaultTendon(mjsTendon& tendon);
MJAPI void mjs_defaultTendon(mjsTendon* tendon);
// Default actuator attributes.
MJAPI void mjs_defaultActuator(mjsActuator& actuator);
MJAPI void mjs_defaultActuator(mjsActuator* actuator);
// Default sensor attributes.
MJAPI void mjs_defaultSensor(mjsSensor& sensor);
MJAPI void mjs_defaultSensor(mjsSensor* sensor);
// Default numeric attributes.
MJAPI void mjs_defaultNumeric(mjsNumeric& numeric);
MJAPI void mjs_defaultNumeric(mjsNumeric* numeric);
// Default text attributes.
MJAPI void mjs_defaultText(mjsText& text);
MJAPI void mjs_defaultText(mjsText* text);
// Default tuple attributes.
MJAPI void mjs_defaultTuple(mjsTuple& tuple);
MJAPI void mjs_defaultTuple(mjsTuple* tuple);
// Default keyframe attributes.
MJAPI void mjs_defaultKey(mjsKey& key);
MJAPI void mjs_defaultKey(mjsKey* key);
// Default plugin attributes.
MJAPI void mjs_defaultPlugin(mjsPlugin& plugin);
MJAPI void mjs_defaultPlugin(mjsPlugin* plugin);
//------------------------- Cache functions ------------------------------------
//---------------------------------- Compiler cache ------------------------------------------------
typedef struct _mjCache* mjCache;
@@ -1013,7 +1064,7 @@ typedef struct _mjCache* mjCache;
MJAPI void mj_setCacheSize(mjCache cache, size_t size);
// Get internal global cache context.
MJAPI mjCache mj_globalCache();
MJAPI mjCache mj_globalCache(void);
#ifdef __cplusplus
}
+8
View File
@@ -50,6 +50,8 @@ template std::size_t mjCAsset::Add(const std::string& name,
const uint8_t* data, std::size_t n);
template std::size_t mjCAsset::Add(const std::string& name,
const int* data, std::size_t n);
template std::size_t mjCAsset::Add(const std::string& name,
const unsigned int* data, std::size_t n);
template std::size_t mjCAsset::Add(const std::string& name,
const float* data, std::size_t n);
template std::size_t mjCAsset::Add(const std::string& name,
@@ -67,6 +69,8 @@ template std::size_t mjCAsset::AddVector(const std::string& name,
const std::vector<uint8_t>& v);
template std::size_t mjCAsset::AddVector(const std::string& name,
const std::vector<int>& v);
template std::size_t mjCAsset::AddVector(const std::string& name,
const std::vector<unsigned int>& v);
template std::size_t mjCAsset::AddVector(const std::string& name,
const std::vector<float>& v);
template std::size_t mjCAsset::AddVector(const std::string& name,
@@ -101,6 +105,8 @@ const uint8_t* mjCAsset::Get(const std::string& name, std::size_t* n) const;
template
const int* mjCAsset::Get(const std::string& name, std::size_t* n) const;
template
const unsigned int* mjCAsset::Get(const std::string& name, std::size_t* n) const;
template
const float* mjCAsset::Get(const std::string& name, std::size_t* n) const;
template
const double* mjCAsset::Get(const std::string& name, std::size_t* n) const;
@@ -122,6 +128,8 @@ template std::optional<std::vector<uint8_t>>
mjCAsset::GetVector(const std::string& name) const;
template std::optional<std::vector<int>>
mjCAsset::GetVector(const std::string& name) const;
template std::optional<std::vector<unsigned int>>
mjCAsset::GetVector(const std::string& name) const;
template std::optional<std::vector<float>>
mjCAsset::GetVector(const std::string& name) const;
template std::optional<std::vector<double>>

Some files were not shown because too many files have changed in this diff Show More