Merge branch 'main' of https://github.com/google-deepmind/mujoco into feature/unity-hmap-dyn

Merge with main to keep branch up to date
This commit is contained in:
Bálint Hodossy
2023-12-21 11:45:49 +00:00
81 changed files with 1563 additions and 706 deletions
+1 -1
View File
@@ -28,7 +28,7 @@ set(MSVC_INCREMENTAL_DEFAULT ON)
project(
mujoco
VERSION 3.0.2
VERSION 3.1.2
DESCRIPTION "MuJoCo Physics Simulator"
HOMEPAGE_URL "https://mujoco.org"
)
+1 -1
View File
@@ -164,7 +164,7 @@ These packages give users of various languages access to MuJoCo functionality:
by [Manoj Velmurugan](https://github.com/vmanoj1996).
- **Swift**: [swift-mujoco](https://github.com/liuliu/swift-mujoco)
- **Java**: [mujoco-java](https://github.com/CommonWealthRobotics/mujoco-java)
- **Julia**: [Lyceum](https://github.com/Lyceum/MuJoCo.jl) (unmaintained)
- **Julia**: [MuJoCo.jl](https://github.com/JamieMair/MuJoCo.jl)
### Converters
+2 -2
View File
@@ -39,7 +39,7 @@ set(MUJOCO_DEP_VERSION_qhull
CACHE STRING "Version of `qhull` to be fetched."
)
set(MUJOCO_DEP_VERSION_Eigen3
aa6964bf3a34fd607837dd8123bc42465185c4f8
454f89af9d6f3525b1df5f9ef9c86df58bf2d4d3
CACHE STRING "Version of `Eigen3` to be fetched."
)
@@ -54,7 +54,7 @@ set(MUJOCO_DEP_VERSION_gtest
)
set(MUJOCO_DEP_VERSION_benchmark
344117638c8ff7e239044fd0fa7085839fc03021 # v1.8.3
e45585a4b8e75c28479fa4107182c28172799640 # v1.8.3
CACHE STRING "Version of `benchmark` to be fetched."
)
+4 -4
View File
@@ -1,6 +1,6 @@
1 VERSIONINFO
FILEVERSION 3,0,2,0
PRODUCTVERSION 3,0,2,0
FILEVERSION 3,1,2,0
PRODUCTVERSION 3,1,2,0
FILEOS 0x4
FILETYPE 0x1
{
@@ -9,9 +9,9 @@ FILETYPE 0x1
BLOCK "040904b0"
{
VALUE "ProductName", "MuJoCo"
VALUE "ProductVersion", "3.0.2"
VALUE "ProductVersion", "3.1.2"
VALUE "FileDescription", "MuJoCo"
VALUE "FileVersion", "3.0.2"
VALUE "FileVersion", "3.1.2"
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,0,2,0
PRODUCTVERSION 3,0,2,0
FILEVERSION 3,1,2,0
PRODUCTVERSION 3,1,2,0
FILEOS 0x4
FILETYPE 0x1
{
@@ -11,9 +11,9 @@ FILETYPE 0x1
BLOCK "040904b0"
{
VALUE "ProductName", "MuJoCo"
VALUE "ProductVersion", "3.0.2"
VALUE "ProductVersion", "3.1.2"
VALUE "FileDescription", "MuJoCo"
VALUE "FileVersion", "3.0.2"
VALUE "FileVersion", "3.1.2"
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``
- 302
- 312
- 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.
+3 -3
View File
@@ -1219,9 +1219,9 @@ a frame at the center-of-mass of the local kinematic subtree (``mjData.subtree_c
This choice increases the precision of kinematic computations for mechanisms that are distant from the global origin.
``cdof``:
These 6D motion vectors describe the instantaneous axis of a degree-of-freedom and are used by all Jacobian functions.
Therefore, the minimal computation required for analytic Jacobians is :ref:`mj_kinematics` followed by
:ref:`mj_comPos`.
These 6D motion vectors (3 rotation, 3 translation) describe the instantaneous axis of a degree-of-freedom and are
used by all Jacobian functions. The minimal computation required for analytic Jacobians is :ref:`mj_kinematics`
followed by :ref:`mj_comPos`.
``cinert``:
These 10-vectors describe the inertial properties of a body in the c-frame and are used by the Composite Rigid Body
+4 -4
View File
@@ -172,7 +172,7 @@ This element does not strictly belong to MJCF. Instead it is a meta-element, use
files in a single document object model (DOM) before parsing. The included file must be a valid XML file with a unique
top-level element. This top-level element is removed by the parser, and the elements below it are inserted at the
location of the :el:`include` element. At least one element must be inserted as a result of this procedure. The
:el:`include` element can be used where ever an XML element is expected in the MJFC file. Nested includes are allowed,
:el:`include` element can be used where ever an XML element is expected in the MJCF file. Nested includes are allowed,
however a given XML file can be included at most once in the entire model. After all the included XML files have been
assembled into a single DOM, it must correspond to a valid MJCF model. Other than that, it is up to the user to decide
how to use includes and how to modularize large files if desired.
@@ -1378,9 +1378,9 @@ also known as terrain map, is a 2D matrix of elevation data. The data can be spe
| For collision detection, a height field is treated as a union of triangular prisms. Collisions between height fields
and other geoms (except for planes and other height fields which are not supported) are computed by first selecting
the sub-grid of prisms that could collide with the geom based on its bounding box, and then using the general convex
collider. The number of possible contacts between a height field and a geom is limited to 9; any contacts beyond that
are discarded. To avoid penetration due to discarded contacts, the spatial features of the height field should be
large compared to the geoms it collides with.
collider. The number of possible contacts between a height field and a geom is limited to 50
(:ref:`mjMAXCONPAIR <glNumeric>`); any contacts beyond that are discarded. To avoid penetration due to discarded
contacts, the spatial features of the height field should be large compared to the geoms it collides with.
.. _asset-hfield-name:
+47 -25
View File
@@ -5,45 +5,67 @@ Changelog
Upcoming version (not yet released)
-----------------------------------
MJX
^^^
1. Add :ref:`dyntype<actuator-general-dyntype>` ``filterexact``.
2. Add :at:`site` transmission.
Version 3.1.1 (December 18, 2023)
-----------------------------------
Bug fixes
^^^^^^^^^
1. Fixed a bug (introduced in 3.1.0) where box-box collisions produced no contacts if one box was deeply embedded in the other.
2. Fixed a bug in :ref:`simulate<saSimulate>` where the "LOADING..." message was not showing correctly.
3. Fixed a crash in the Python :ref:`passive viewer<PyViewerPassive>`, when used with models containing Flex objects.
4. Fixed a bug in MJX where ``site_xmat`` was ignored in ``get_data`` and ``put_data``
5. Fixed a bug in MJX where ``efc_address`` was sometimes incorrectly calculated in ``get_data``.
Version 3.1.0 (December 12, 2023)
---------------------------------
General
^^^^^^^
- Improved convergence of Signed Distance Function (SDF) collisions by using line search and a new objective function
for the optimization. This allows to decrease the number of initial points needed for finding the contacts and is more
robust for very small or large geom sizes.
- Added :ref:`frame<frame>` to MJCF, a :ref:`meta-element<meta-element>` which defines a pure coordinate transformation
on its direct children, without requiring a :ref:`body<body>`.
- Added the :at:`kv` attribute to the :ref:`position<actuator-position>` and :ref:`intvelocity<actuator-intvelocity>`
actuators, for specifying actuator-applied damping. This can be used to implement a PD controller with 0 reference
velocity. When using this attribute, it is recommended to use the implicitfast or implicit
:ref:`integrators<geIntegration>`.
1. Improved convergence of Signed Distance Function (SDF) collisions by using line search and a new objective function
for the optimization. This allows to decrease the number of initial points needed for finding the contacts and is more
robust for very small or large geom sizes.
2. Added :ref:`frame<frame>` to MJCF, a :ref:`meta-element<meta-element>` which defines a pure coordinate transformation
on its direct children, without requiring a :ref:`body<body>`.
3. Added the :at:`kv` attribute to the :ref:`position<actuator-position>` and :ref:`intvelocity<actuator-intvelocity>`
actuators, for specifying actuator-applied damping. This can be used to implement a PD controller with 0 reference
velocity. When using this attribute, it is recommended to use the implicitfast or implicit
:ref:`integrators<geIntegration>`.
Plugins
^^^^^^^
- Allow actuator plugins to use activation variables in ``mjData.act`` as their internal state, rather than
``mjData.plugin_state``. Actuator plugins can now specify :ref:`callbacks<mjpPlugin>` that compute activation
variables, and they can be used with built-in :ref:`dyntype<actuator-plugin-dyntype>` actuator dynamics.
4. Allow actuator plugins to use activation variables in ``mjData.act`` as their internal state, rather than
``mjData.plugin_state``. Actuator plugins can now specify :ref:`callbacks<mjpPlugin>` that compute activation
variables, and they can be used with built-in :ref:`dyntype<actuator-plugin-dyntype>` actuator dynamics.
- Added the `pid <https://github.com/deepmind/mujoco/blob/main/plugin/actuator/README.md>`__ actuator plugin, a
configurable PID controller that implements the Integral term, which is not available with native MuJoCo actuators.
5. Added the `pid <https://github.com/deepmind/mujoco/blob/main/plugin/actuator/README.md>`__ actuator plugin, a
configurable PID controller that implements the Integral term, which is not available with native MuJoCo actuators.
MJX
^^^
- Added ``site_xpos`` and ``site_xmat`` to MJX.
- Added ``put_data``, ``put_model``, ``get_data`` to replace ``device_put`` and ``device_get_into``, which will be
deprecated. These new functions correctly translate fields that are the result of intermediate calculations such as
``efc_J``.
6. Added ``site_xpos`` and ``site_xmat`` to MJX.
7. Added ``put_data``, ``put_model``, ``get_data`` to replace ``device_put`` and ``device_get_into``, which will be
deprecated. These new functions correctly translate fields that are the result of intermediate calculations such as
``efc_J``.
Bug fixes
^^^^^^^^^
- Fix bug in Cartesian actuation with movable refsite, as when using body-centric Cartesian actuators on a quadruped.
Before this fix such actuators could lead to non-conservation of momentum.
- Fix bug that prevented using flex with the :ref:`passive viewer<PyViewerPassive>`.
- Fix bug that prevented the use of elasticity plugins in combination with pinned flex vertices.
- Release Python wheels targeting macOS 10.16 to support x86_64 systems where SYSTEM_VERSION_COMPAT is set. The minimum
supported version is still 11.0, but we release these wheels to fix compatibility for those users. See
:github:issue:`1213`.
8. Fix bug in Cartesian actuation with movable refsite, as when using body-centric Cartesian actuators on a quadruped.
Before this fix such actuators could lead to non-conservation of momentum.
9. Fix bug that prevented using flex with :ref:`simulate<saSimulate>`.
10. Fix bug that prevented the use of elasticity plugins in combination with pinned flex vertices.
11. Release Python wheels targeting macOS 10.16 to support x86_64 systems where SYSTEM_VERSION_COMPAT is set. The minimum
supported version is still 11.0, but we release these wheels to fix compatibility for those users. See
:github:issue:`1213`.
Version 3.0.1 (November 15, 2023)
---------------------------------
+34 -34
View File
@@ -300,19 +300,22 @@ is attached; the possible attachment object types are :at:`joint`, :at:`tendon`,
Slider-cranks can also be modeled explicitly by creating MuJoCo bodies and coupling them with equality constraints to
the rest of the system, but that would be less efficient.
:at:`site`
:at:`site` transmission (without a :at:`refsite`, see below) and :at:`body` transmission targets have a fixed zero
length :math:`l_i(q) = 0`. They can therefore not be used to maintain a desired length, but can be used to apply
forces. Site transmissions correspond to applying a Cartsian force/torque at the site, and are useful for modeling
jets and propellors. :el:`body` transmissions correspond to applying forces at contact points belonging to a body, in
:at:`body`
:el:`body` transmission corresponds to applying forces at contact points belonging to a body, in
order to model vacuum grippers and biomechanical adhesive appendages. For more information about adhesion, see the
:ref:`adhesion<actuator-adhesion>` actuator documentation.
:ref:`adhesion<actuator-adhesion>` actuator documentation. These transmission targets have a fixed zero length
:math:`l_i(q) = 0`.
If a :at:`site` transmission target is defined with the optional :at:`refsite` attribute, forces and torques are
applied in the frame of the reference site rather than the site's own frame. If a reference site is defined then
the length of the actuator is nonzero and corresponds to the pose difference of the two sites. This length can then
be controlled with a :el:`position` actuator, enabling Cartesian end-effector control. See the
:ref:`refsite<actuator-general-refsite>` documentation for more details.
:at:`site`
Site transmissions correspond to applying a Cartsian force/torque in the frame of a site. When a :at:`refsite` is not
defined (see below), these targets have a fixed zero length :math:`l_i(q) = 0` and are useful for modeling jets and
propellors: forces and torques which are fixed to the site frame.
If a :at:`site` transmission is defined with the optional :at:`refsite` attribute, forces and torques are applied in
the frame of the reference site rather than the site's own frame. If a reference site is defined, the length of the
actuator is nonzero and corresponds to the pose difference of the two sites, projected onto a chosen direction in the
reference frame. This length can then be controlled with a :el:`position` actuator, allowing for Cartesian
end-effector control. See the :ref:`refsite<actuator-general-refsite>` documentation for more details.
.. _geActivation:
@@ -1455,48 +1458,45 @@ others can be pruned quickly without a detailed check. MuJoCo has flexible mecha
checked in detail. The decision process involves two stages: generation and filtering.
Generation
First we generate a list of candidate geom pairs in one of two ways: "pair" or "dynamic". The user can also specify
"all" which merges both sources (and is the default). This is done via the setting ``mjModel.opt.collision``. "Pair"
refers to an explicit list of geom pairs defined with the :ref:`pair <contact-pair>` element in MJCF. It gives the
user full control, however it is a static mechanism (independent of the spatial arrangement of the geoms at runtime)
and can be tedious for large models. It is normally used to supplement the output of the "dynamic" mechanism. Dynamic
generation works with bodies rather than geoms; when a body pair is included this means that all geoms attached to
one body can collide with all geoms attached to the other body.
First we generate a list of candidate geom pairs by merging from two sources: pairs of bodies that might contain
colliding geoms and the explicit list of geom pairs defined with the :ref:`pair <contact-pair>` element in MJCF.
The body pairs are generated via broad-phase collision detection based on a modified sweep-and-prune algorithm. The
modification is that the axis for sorting is chosen as the principal eigenvector of the covariance matrix of all geom
centers - which maximizes the spread. Then, for each body pair, a mid-phase collision detection using a static
bounding volume hierarchy (a BVH binary tree) of axis-aligned bounding boxes (AABB) is performed. Each body is
equipped with an AABB tree of its geoms, aligned with the body inertial or geom frames for all inner or leaf nodes,
centers -- which maximizes the spread. Then, for each body pair, mid-phase collision detection is performed using a
static bounding volume hierarchy (a BVH binary tree) of axis-aligned bounding boxes (AABB). Each body is equipped
with an AABB tree of its geoms, aligned with the body inertial or geom frames for all inner or leaf nodes,
respectively.
Finally, the user can explicitly exclude certain body pairs using the :ref:`exclude <contact-exclude>` element
in MJCF. Exclusion is applied when "dynamic" or "all" are selected, but not when "pair" is selected. At the end of
this step we have a list of geoms pairs that is typically much smaller than :math:`n (n-1)/2`, but can still be
pruned further before detailed collision checking.
Finally, the user can explicitly exclude certain body pairs using the :ref:`exclude <contact-exclude>` element in
MJCF. At the end of this step we have a list of geoms pairs that is typically much smaller than :math:`n (n-1)/2`,
but can still be pruned further before detailed collision checking.
Filtering
Next we apply four filters to the list generated in the previous step. Filters 1 and 2 are applied to all geom pairs.
Filters 3 and 4 are applied only to pairs generated by the "dynamic" mechanism, thereby allowing the user to bypass
Filters 3 and 4 are applied only to pairs generated by the body-pair mechanism, thereby allowing the user to bypass
those filters by specifying geom pairs explicitly.
#. The types of the two geoms must correspond to a collision function that is capable of performing the detailed
1. The types of the two geoms must correspond to a collision function that is capable of performing the detailed
check. This is usually the case but there are exceptions (for example plane-plane collisions are not supported),
and furthermore the user may override the default table of collision functions with NULL pointers, effectively
disabling collisions between certain geom types.
#. A bounding sphere test is applied, taking into account the contact margin. If one of the geoms in the pair is a
2. A bounding sphere test is applied, taking into account the contact margin. If one of the geoms in the pair is a
plane, this becomes a plane-sphere test.
#. The two geoms cannot belong to the same body. Furthermore, they cannot belong to a parent and a child body, unless
3. The two geoms cannot belong to the same body. Furthermore, they cannot belong to a parent and a child body, unless
the parent is the world body. The motivation is to avoid permanent contacts within bodies and joints. Note that if
several bodies are welded together in the sense that there are no joints between them, they are treated as a
single body for the purposes of this test. The parent-filter test can be disabled by the user, while the same-body
test cannot be disabled.
#. The two geoms must be "compatible" in the following sense. Each geom has integer parameters ``contype`` and
4. The two geoms must be "compatible" in the following sense. Each geom has integer parameters ``contype`` and
``conaffinity``. The boolean expression below must be true for the test to pass:
``(contype1 & conaffinity2) || (contype2 & conaffinity1)`` This requires the ``contype`` of one geom and the
``conaffinity`` of the other geom to have a common bit set to 1. This is a powerful mechanism borrowed from the
Open Dynamics Engine. The default setting for all geoms is ``contype = conaffinity = 1`` which always passes the
test, so the user can ignore this mechanism if it is confusing at first.
``(contype1 & conaffinity2) || (contype2 & conaffinity1)``
This requires the ``contype`` of one geom and the ``conaffinity`` of the other geom to have a common bit set to 1.
This is a powerful mechanism borrowed from Open Dynamics Engine. The default setting for all geoms is
``contype = conaffinity = 1`` which always passes the test, so the user can ignore this mechanism if it is
confusing at first.
.. _coChecking:
+11 -5
View File
@@ -237,7 +237,7 @@ struct mjData_ {
// computed by mj_fwdPosition/mj_comPos
mjtNum* subtree_com; // center of mass of each subtree (nbody x 3)
mjtNum* cdof; // com-based motion axis of each dof (nv x 6)
mjtNum* cdof; // com-based motion axis of each dof (rot:lin) (nv x 6)
mjtNum* cinert; // com-based body inertia and mass (nbody x 10)
// computed by mj_fwdPosition/mj_flex
@@ -285,8 +285,8 @@ struct mjData_ {
mjtNum* actuator_velocity; // actuator velocities (nu x 1)
// computed by mj_fwdVelocity/mj_comVel
mjtNum* cvel; // com-based velocity [3D rot; 3D tran] (nbody x 6)
mjtNum* cdof_dot; // time-derivative of cdof (nv x 6)
mjtNum* cvel; // com-based velocity (rot:lin) (nbody x 6)
mjtNum* cdof_dot; // time-derivative of cdof (rot:lin) (nv x 6)
// computed by mj_fwdVelocity/mj_rne (without acceleration)
mjtNum* qfrc_bias; // C(qpos,qvel) (nv x 1)
@@ -459,10 +459,11 @@ typedef enum mjtGeom_ { // type of geometric shape
mjGEOM_ARROW1, // arrow without wedges
mjGEOM_ARROW2, // arrow in both directions
mjGEOM_LINE, // line
mjGEOM_LINEBOX, // box with line edges
mjGEOM_FLEX, // flex
mjGEOM_SKIN, // skin
mjGEOM_LABEL, // text label
mjGEOM_TRIANGLE, // triangle connecting a frame
mjGEOM_TRIANGLE, // triangle
mjGEOM_NONE = 1001 // missing geom type
} mjtGeom;
@@ -570,7 +571,9 @@ typedef enum mjtObj_ { // type of MujoCo object
mjOBJ_TEXT, // text
mjOBJ_TUPLE, // tuple
mjOBJ_KEY, // keyframe
mjOBJ_PLUGIN // plugin instance
mjOBJ_PLUGIN, // plugin instance
mjNOBJECT // number of object types
} mjtObj;
typedef enum mjtConstraint_ { // type of constraint
mjCNSTR_EQUALITY = 0, // equality constraint
@@ -2167,6 +2170,7 @@ struct mjvSceneState_ {
int nnames;
int npaths;
int nsensordata;
int narena;
mjOption opt;
mjVisual vis;
@@ -2380,6 +2384,7 @@ struct mjvSceneState_ {
mjtNum* ten_length;
mjtNum* wrap_xpos;
mjtNum* bvh_aabb_dyn;
mjtByte* bvh_active;
int* island_dofadr;
int* island_dofind;
@@ -2391,6 +2396,7 @@ struct mjvSceneState_ {
mjContact* contact;
mjtNum* efc_force;
void* arena;
} data;
};
typedef struct mjvSceneState_ mjvSceneState;
+4 -4
View File
@@ -181,9 +181,9 @@ The following features are **fully supported** in MJX:
* - :ref:`Joint <mjtJoint>`
- ``FREE``, ``BALL``, ``SLIDE``, ``HINGE``
* - :ref:`Transmission <mjtTrn>`
- ``TRN_JOINT``
- ``TRN_JOINT``, ``TRN_SITE``
* - :ref:`Actuator Dynamics <mjtDyn>`
- ``NONE``, ``INTEGRATOR``, ``FILTER``
- ``NONE``, ``INTEGRATOR``, ``FILTER``, ``FILTEREXACT``
* - :ref:`Actuator Gain <mjtGain>`
- ``FIXED``, ``AFFINE``
* - :ref:`Actuator Bias <mjtBias>`
@@ -257,9 +257,9 @@ The following features are **unsupported**:
* - Category
- Feature
* - :ref:`Transmission <mjtTrn>`
- ``TRN_JOINTINPARENT``, ``TRN_SLIDERCRANK``, ``TRN_SITE``, ``TRN_BODY``
- ``TRN_JOINTINPARENT``, ``TRN_SLIDERCRANK``, ``TRN_BODY``
* - :ref:`Actuator Dynamics <mjtDyn>`
- ``FILTEREXACT``, ``USER``
- ``USER``
* - :ref:`Actuator Gain <mjtGain>`
- ``USER``
* - :ref:`Actuator Bias <mjtBias>`
+1 -1
View File
@@ -403,7 +403,7 @@ and the damping ratio is ignored. Equivalently, in the direct format, the :math:
.. math::
\begin{aligned}
b &= \text{damping} / d_\text{width} \\
k &= \text{stiffness} / d_\text{width}^2 \\
k &= \text{stiffness} \cdot d(r) / d_\text{width}^2 \\
\end{aligned}
.. tip::
+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.0.2.dylib`` (it can be
``/Applications/MuJoCo.app/Contents/Frameworks/mujoco.framework/Versions/Current/libmujoco.3.1.2.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.0.2/lib/libmujoco.so.3.0.2`` and rename it as ``libmujoco.so``.
``~/.mujoco/mujoco-3.1.2/lib/libmujoco.so.3.1.2`` and rename it as ``libmujoco.so``.
Windows
_______
+3 -3
View File
@@ -265,7 +265,7 @@ struct mjData_ {
// computed by mj_fwdPosition/mj_comPos
mjtNum* subtree_com; // center of mass of each subtree (nbody x 3)
mjtNum* cdof; // com-based motion axis of each dof (nv x 6)
mjtNum* cdof; // com-based motion axis of each dof (rot:lin) (nv x 6)
mjtNum* cinert; // com-based body inertia and mass (nbody x 10)
// computed by mj_fwdPosition/mj_flex
@@ -313,8 +313,8 @@ struct mjData_ {
mjtNum* actuator_velocity; // actuator velocities (nu x 1)
// computed by mj_fwdVelocity/mj_comVel
mjtNum* cvel; // com-based velocity [3D rot; 3D tran] (nbody x 6)
mjtNum* cdof_dot; // time-derivative of cdof (nv x 6)
mjtNum* cvel; // com-based velocity (rot:lin) (nbody x 6)
mjtNum* cdof_dot; // time-derivative of cdof (rot:lin) (nv x 6)
// computed by mj_fwdVelocity/mj_rne (without acceleration)
mjtNum* qfrc_bias; // C(qpos,qvel) (nv x 1)
+5 -2
View File
@@ -109,10 +109,11 @@ typedef enum mjtGeom_ { // type of geometric shape
mjGEOM_ARROW1, // arrow without wedges
mjGEOM_ARROW2, // arrow in both directions
mjGEOM_LINE, // line
mjGEOM_LINEBOX, // box with line edges
mjGEOM_FLEX, // flex
mjGEOM_SKIN, // skin
mjGEOM_LABEL, // text label
mjGEOM_TRIANGLE, // triangle connecting a frame
mjGEOM_TRIANGLE, // triangle
mjGEOM_NONE = 1001 // missing geom type
} mjtGeom;
@@ -246,7 +247,9 @@ typedef enum mjtObj_ { // type of MujoCo object
mjOBJ_TEXT, // text
mjOBJ_TUPLE, // tuple
mjOBJ_KEY, // keyframe
mjOBJ_PLUGIN // plugin instance
mjOBJ_PLUGIN, // plugin instance
mjNOBJECT // number of object types
} mjtObj;
+3
View File
@@ -436,6 +436,7 @@ struct mjvSceneState_ {
int nnames;
int npaths;
int nsensordata;
int narena;
mjOption opt;
mjVisual vis;
@@ -649,6 +650,7 @@ struct mjvSceneState_ {
mjtNum* ten_length;
mjtNum* wrap_xpos;
mjtNum* bvh_aabb_dyn;
mjtByte* bvh_active;
int* island_dofadr;
int* island_dofind;
@@ -660,6 +662,7 @@ struct mjvSceneState_ {
mjContact* contact;
mjtNum* efc_force;
void* arena;
} data;
};
typedef struct mjvSceneState_ mjvSceneState;
+1 -1
View File
@@ -614,7 +614,7 @@
X ( mjtNum, qLD, nM, 1 ) \
X ( mjtNum, qLDiagInv, nv, 1 ) \
X ( mjtNum, qLDiagSqrtInv, nv, 1 ) \
X ( mjtNum, bvh_aabb_dyn, nbvhdynamic, 6 ) \
XMJV( mjtNum, bvh_aabb_dyn, nbvhdynamic, 6 ) \
XMJV( mjtByte, bvh_active, nbvh, 1 ) \
X ( mjtNum, flexedge_velocity, nflexedge, 1 ) \
X ( mjtNum, ten_velocity, ntendon, 1 ) \
+1 -1
View File
@@ -24,7 +24,7 @@ extern "C" {
#endif
// header version; should match the library version as returned by mj_version()
#define mjVERSION_HEADER 302
#define mjVERSION_HEADER 312
// needed to define size_t, fabs and log10
#include <stdlib.h>
+6 -4
View File
@@ -90,10 +90,11 @@ ENUMS: Mapping[str, EnumDecl] = dict([
('mjGEOM_ARROW1', 101),
('mjGEOM_ARROW2', 102),
('mjGEOM_LINE', 103),
('mjGEOM_FLEX', 104),
('mjGEOM_SKIN', 105),
('mjGEOM_LABEL', 106),
('mjGEOM_TRIANGLE', 107),
('mjGEOM_LINEBOX', 104),
('mjGEOM_FLEX', 105),
('mjGEOM_SKIN', 106),
('mjGEOM_LABEL', 107),
('mjGEOM_TRIANGLE', 108),
('mjGEOM_NONE', 1001),
]),
)),
@@ -265,6 +266,7 @@ ENUMS: Mapping[str, EnumDecl] = dict([
('mjOBJ_TUPLE', 23),
('mjOBJ_KEY', 24),
('mjOBJ_PLUGIN', 25),
('mjNOBJECT', 26),
]),
)),
('mjtConstraint',
+1 -1
View File
@@ -61,7 +61,7 @@ class EnumsTest(absltest.TestCase):
self.assertEqual(enum_decl.values['mjGEOM_ARROW'], 100)
self.assertEqual(enum_decl.values['mjGEOM_ARROW1'], 101)
self.assertEqual(enum_decl.values['mjGEOM_ARROW2'], 102)
self.assertEqual(enum_decl.values['mjGEOM_TRIANGLE'], 107)
self.assertEqual(enum_decl.values['mjGEOM_TRIANGLE'], 108)
# Skip a few...
self.assertEqual(enum_decl.values['mjGEOM_NONE'], 1001)
+22 -3
View File
@@ -4493,7 +4493,7 @@ STRUCTS: Mapping[str, StructDecl] = dict([
type=PointerType(
inner_type=ValueType(name='mjtNum'),
),
doc='com-based motion axis of each dof (nv x 6)', # pylint: disable=line-too-long
doc='com-based motion axis of each dof (rot:lin) (nv x 6)', # pylint: disable=line-too-long
),
StructFieldDecl(
name='cinert',
@@ -4703,14 +4703,14 @@ STRUCTS: Mapping[str, StructDecl] = dict([
type=PointerType(
inner_type=ValueType(name='mjtNum'),
),
doc='com-based velocity [3D rot; 3D tran] (nbody x 6)', # pylint: disable=line-too-long
doc='com-based velocity (rot:lin) (nbody x 6)', # pylint: disable=line-too-long
),
StructFieldDecl(
name='cdof_dot',
type=PointerType(
inner_type=ValueType(name='mjtNum'),
),
doc='time-derivative of cdof (nv x 6)', # pylint: disable=line-too-long
doc='time-derivative of cdof (rot:lin) (nv x 6)', # pylint: disable=line-too-long
),
StructFieldDecl(
name='qfrc_bias',
@@ -6334,6 +6334,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([
type=ValueType(name='int'),
doc='',
),
StructFieldDecl(
name='narena',
type=ValueType(name='int'),
doc='',
),
StructFieldDecl(
name='opt',
type=ValueType(name='mjOption'),
@@ -7596,6 +7601,13 @@ STRUCTS: Mapping[str, StructDecl] = dict([
),
doc='',
),
StructFieldDecl(
name='bvh_aabb_dyn',
type=PointerType(
inner_type=ValueType(name='mjtNum'),
),
doc='',
),
StructFieldDecl(
name='bvh_active',
type=PointerType(
@@ -7659,6 +7671,13 @@ STRUCTS: Mapping[str, StructDecl] = dict([
),
doc='',
),
StructFieldDecl(
name='arena',
type=PointerType(
inner_type=ValueType(name='void'),
),
doc='',
),
),
),
doc='',
-6
View File
@@ -129,12 +129,6 @@ class ValidateInputTest(absltest.TestCase):
with self.assertRaises(NotImplementedError):
mjx.device_put(m)
def test_trn(self):
m = test_util.load_test_file('pendula.xml')
m.actuator_trntype[0] = mujoco.mjtTrn.mjTRN_SITE
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
+30 -11
View File
@@ -107,7 +107,7 @@ def fwd_actuation(m: Model, d: Data) -> Data:
act_dot = jp.array(0.0)
elif dyn_typ == DynType.INTEGRATOR:
act_dot = ctrl
elif dyn_typ == DynType.FILTER:
elif dyn_typ in (DynType.FILTER, DynType.FILTEREXACT):
act_dot = (ctrl - act) / jp.clip(dyn_prm[0], mujoco.mjMINVAL)
else:
raise NotImplementedError(f'dyntype {dyn_typ.name} not implemented.')
@@ -228,6 +228,34 @@ def _integrate_pos(
return jp.concatenate(qs) if qs else jp.empty((0,))
def _next_activation(m: Model, d: Data, act_dot: jax.Array) -> jax.Array:
"""Returns the next act given the current act_dot, after clamping."""
act = d.act
if not m.na:
return act
actrange = jp.where(
m.actuator_actlimited[:, None],
m.actuator_actrange,
jp.array([-jp.inf, jp.inf]),
)
def fn(dyntype, dynprm, act, act_dot, actrange):
if dyntype == DynType.FILTEREXACT:
tau = jp.clip(dynprm[0], a_min=mujoco.mjMINVAL)
act = act + act_dot * tau * (1 - jp.exp(-m.opt.timestep / tau))
else:
act = act + act_dot * m.opt.timestep
act = jp.clip(act, actrange[0], actrange[1])
return act
args = (m.actuator_dyntype, m.actuator_dynprm, act, act_dot, actrange)
act = scan.flat(m, fn, 'uuaau', 'a', *args, group_by='u')
return act.reshape(m.na)
@named_scope
def _advance(
m: Model,
@@ -237,16 +265,7 @@ def _advance(
qvel: Optional[jax.Array] = None,
) -> Data:
"""Advance state and time given activation derivatives and acceleration."""
act = d.act
if m.na:
act = d.act + act_dot * m.opt.timestep
actrange = jp.where(
m.actuator_actlimited[:, None],
m.actuator_actrange,
jp.array([-jp.inf, jp.inf]),
)
fn = lambda act, actrange: jp.clip(act, actrange[0], actrange[1])
act = scan.flat(m, fn, 'au', 'a', act, actrange, group_by='u')
act = _next_activation(m, d, act_dot)
# advance velocities
d = d.replace(qvel=d.qvel + qacc * m.opt.timestep)
+40
View File
@@ -134,5 +134,45 @@ class ForwardTest(absltest.TestCase):
np.testing.assert_allclose(dx.qvel, 1 + m.opt.timestep)
class ActuatorTest(absltest.TestCase):
_DYN_XML = """
<mujoco>
<compiler autolimits="true"/>
<worldbody>
<body name="box">
<joint name="slide1" type="slide" axis="1 0 0" />
<joint name="slide2" type="slide" axis="0 1 0" />
<joint name="slide3" type="slide" axis="0 0 1" />
<joint name="slide4" type="slide" axis="1 1 0" />
<geom type="box" size=".05 .05 .05" mass="1"/>
</body>
</worldbody>
<actuator>
<general joint="slide1" dynprm="0.1" gainprm="1.1" />
<general joint="slide2" dyntype="integrator" dynprm="0.1" gainprm="1.1" />
<general joint="slide3" dyntype="filter" dynprm="0.1" gainprm="1.1" />
<general joint="slide4" dyntype="filterexact" dynprm="0.1" gainprm="1.1" />
</actuator>
</mujoco>
"""
def test_dyntype(self):
m = mujoco.MjModel.from_xml_string(self._DYN_XML)
d = mujoco.MjData(m)
d.ctrl = np.array([1.5, 1.5, 1.5, 1.5])
d.act = np.array([0.5, 0.5, 0.5])
mx = mjx.put_model(m)
dx = mjx.put_data(m, d)
mujoco.mj_fwdActuation(m, d)
dx = jax.jit(mjx.fwd_actuation)(mx, dx)
_assert_attr_eq(d, dx, 'act_dot')
mujoco.mj_Euler(m, d)
dx = jax.jit(mjx.euler)(mx, dx)
_assert_attr_eq(d, dx, 'act')
if __name__ == '__main__':
absltest.main()
+7 -3
View File
@@ -103,6 +103,10 @@ def put_model(m: mujoco.MjModel, device=None) -> types.Model:
f'{[mj_type(m) for m in missing]} not supported'
)
# TODO: implement reference sites.
if any(m.actuator_trnid[:, 1] != -1):
raise NotImplementedError('refsite is not supported')
opt = _put_option(m.opt, device=device)
stat = _put_statistic(m.stat, device=device)
@@ -217,7 +221,7 @@ def _get_contact(
value = value.reshape((-1, 9))
getattr(c, field.name)[:] = value
ncon = con_id.shape[0]
ncon = cx.dist.shape[0]
c.efc_address[:] = np.arange(efc_start, efc_start + ncon * 4, 4)[con_id]
@@ -257,7 +261,7 @@ def get_data(
value = getattr(dx_i, field.name)
if field.name in ('xmat', 'ximat', 'geom_xmat'):
if field.name in ('xmat', 'ximat', 'geom_xmat', 'site_xmat'):
value = value.reshape((-1, 9))
if field.name in ('efc_frictionloss', 'efc_D', 'efc_aref', 'efc_force'):
@@ -318,7 +322,7 @@ def put_data(m: mujoco.MjModel, d: mujoco.MjData, device=None) -> types.Data:
if f.type is jax.Array
}
for fname in ('xmat', 'ximat', 'geom_xmat'):
for fname in ('xmat', 'ximat', 'geom_xmat', 'site_xmat'):
fields[fname] = fields[fname].reshape((-1, 3, 3))
# pad efc fields: MuJoCo efc arrays are sparse for inactive constraints.
+39 -9
View File
@@ -71,6 +71,7 @@ _MULTIPLE_CONSTRAINTS = """
<joint axis="0 1 0" type="hinge" range="-45 45"/>
<joint axis="1 0 0" type="hinge" range="-0.001 0.001"/>
<geom type="capsule" size=".2 .05"/>
<site pos="-0.214 -0.078 0" quat="0.664 0.664 -0.242 -0.242"/>
</body>
</body>
</worldbody>
@@ -81,7 +82,8 @@ _MULTIPLE_CONSTRAINTS = """
"""
class IoTest(parameterized.TestCase):
class ModelIOTest(parameterized.TestCase):
"""IO tests for mjx.Model."""
def test_put_model(self):
m = mujoco.MjModel.from_xml_string(_MULTIPLE_CONVEX_OBJECTS)
@@ -132,7 +134,7 @@ class IoTest(parameterized.TestCase):
)
self.assertTrue(m.opt.has_fluid_params)
def test_put_model_implicit_not_implemented(self):
def test_implicit_not_implemented(self):
"""Test that MJX guards against models with unimplemented features."""
with self.assertRaises(NotImplementedError):
@@ -142,7 +144,7 @@ class IoTest(parameterized.TestCase):
)
)
def test_put_model_cone_not_implemented(self):
def test_cone_not_implemented(self):
with self.assertRaises(NotImplementedError):
mjx.put_model(
mujoco.MjModel.from_xml_string(
@@ -150,7 +152,7 @@ class IoTest(parameterized.TestCase):
)
)
def test_put_model_pgs_not_implemented(self):
def test_pgs_not_implemented(self):
with self.assertRaises(NotImplementedError):
mjx.put_model(
mujoco.MjModel.from_xml_string(
@@ -158,7 +160,7 @@ class IoTest(parameterized.TestCase):
)
)
def test_put_model_site_actuator_not_implemented(self):
def test_site_actuator_not_implemented(self):
with self.assertRaises(NotImplementedError):
mjx.put_model(mujoco.MjModel.from_xml_string("""
<mujoco>
@@ -173,7 +175,7 @@ class IoTest(parameterized.TestCase):
</actuator>
</mujoco>"""))
def test_put_model_tendon_not_implemented(self):
def test_tendon_not_implemented(self):
with self.assertRaises(NotImplementedError):
mjx.put_model(mujoco.MjModel.from_xml_string("""
<mujoco>
@@ -190,7 +192,7 @@ class IoTest(parameterized.TestCase):
</tendon>
</mujoco>"""))
def test_put_model_condim_not_implemented(self):
def test_condim_not_implemented(self):
with self.assertRaises(NotImplementedError):
mjx.put_model(mujoco.MjModel.from_xml_string("""
<mujoco>
@@ -206,7 +208,7 @@ class IoTest(parameterized.TestCase):
</worldbody>
</mujoco>"""))
def test_put_model_cylinder_not_implemented(self):
def test_cylinder_not_implemented(self):
with self.assertRaises(NotImplementedError):
mjx.put_model(mujoco.MjModel.from_xml_string("""
<mujoco>
@@ -222,6 +224,29 @@ class IoTest(parameterized.TestCase):
</worldbody>
</mujoco>"""))
def test_refsite_not_implemented(self):
"""Tests that site transmissions with refsites are not implemented."""
with self.assertRaises(NotImplementedError):
mjx.put_model(mujoco.MjModel.from_xml_string("""
<mujoco>
<compiler autolimits="true"/>
<worldbody>
<body name="box">
<site name="site1"/>
<site name="site2" pos="0.2 0.1 0.05"/>
<joint name="slide" type="slide" axis="1 0 0" />
<geom type="box" size=".05 .05 .05" mass="1"/>
</body>
</worldbody>
<actuator>
<position site="site2" refsite="site1"/>
</actuator>
</mujoco>"""))
class DataIOTest(parameterized.TestCase):
"""IO tests for mjx.Data."""
def test_make_data(self):
"""Test that make_data returns the correct shapes."""
@@ -318,9 +343,11 @@ class IoTest(parameterized.TestCase):
self.assertEqual(dx.xmat.shape, (3, 3, 3))
self.assertEqual(dx.ximat.shape, (3, 3, 3))
self.assertEqual(dx.geom_xmat.shape, (3, 3, 3))
self.assertEqual(dx.site_xmat.shape, (1, 3, 3))
np.testing.assert_allclose(dx.xmat.reshape((3, 9)), d.xmat)
np.testing.assert_allclose(dx.ximat.reshape((3, 9)), d.ximat)
np.testing.assert_allclose(dx.geom_xmat.reshape((3, 9)), d.geom_xmat)
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
@@ -369,13 +396,15 @@ class IoTest(parameterized.TestCase):
self.assertEqual(d_2.contact.frame.shape, (1, 9))
np.testing.assert_allclose(d_2.contact.frame, d.contact.frame)
# xmat, ximat, geom_xmat are all shape transformed
# xmat, ximat, geom_xmat, site_xmat are all shape transformed
self.assertEqual(d_2.xmat.shape, (3, 9))
self.assertEqual(d_2.ximat.shape, (3, 9))
self.assertEqual(d_2.geom_xmat.shape, (3, 9))
self.assertEqual(d_2.site_xmat.shape, (1, 9))
np.testing.assert_allclose(d_2.xmat, d.xmat)
np.testing.assert_allclose(d_2.ximat, d.ximat)
np.testing.assert_allclose(d_2.geom_xmat, d.geom_xmat)
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
@@ -402,5 +431,6 @@ class IoTest(parameterized.TestCase):
self.assertEqual(ds[0].ncon, 1)
self.assertEqual(ds[1].ncon, 0)
if __name__ == '__main__':
absltest.main()
+31 -9
View File
@@ -49,7 +49,9 @@ def _take(obj: Y, idx: np.ndarray) -> Y:
def take(x):
# TODO(erikfrey): if this helps perf, add support for striding too
if (
if not x.shape[0]:
return x
elif (
len(idx.shape) == 1
and idx.size > 0
and (idx == np.arange(idx[0], idx[0] + idx.size)).all()
@@ -113,8 +115,12 @@ def _nvmap(f: Callable[..., Y], *args) -> Y:
if isinstance(arg, np.ndarray) and not np.all(arg == arg[0]):
raise RuntimeError(f'numpy arg elements do not match: {arg}')
# split out numpy and jax args
np_args = [a[0] if isinstance(a, np.ndarray) else None for a in args]
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)
in_axes = [None if a is None else 0 for a in args]
def outer_f(*args, np_args=np_args):
@@ -126,7 +132,15 @@ def _nvmap(f: Callable[..., Y], *args) -> Y:
def _check_input(m: Model, args: Any, in_types: str) -> None:
"""Checks that scan input has the right shape."""
size = {'b': m.nbody, 'j': m.njnt, 'q': m.nq, 'v': m.nv, 'u': m.nu, 'a': m.na}
size = {
'b': m.nbody,
'j': m.njnt,
'q': m.nq,
'v': m.nv,
'u': m.nu,
'a': m.na,
's': m.nsite,
}
for idx, (arg, typ) in enumerate(zip(args, in_types)):
if len(arg) != size[typ]:
raise IndexError(
@@ -162,7 +176,7 @@ def flat(
) -> Y:
r"""Scan a function across bodies or actuators.
Scan group data according to type and batch shape then calls vmap(f) on it.
Scan group data according to type and batch shape then calls vmap(f) on it.\
Args:
m: an mjx model
@@ -223,14 +237,22 @@ def flat(
'j': (
m.actuator_trnid[i, 0]
if m.actuator_trntype[i] == TrnType.JOINT
else np.array(-1)
else -1
),
's': (
m.actuator_trnid[i, 0]
if m.actuator_trntype[i] == TrnType.SITE
else -1
),
}
# v/q associated with joint transmissions
typ_ids.update({
'v': np.nonzero(m.dof_jntid == typ_ids['j'])[0],
'q': np.nonzero(_q_jointid(m) == typ_ids['j'])[0],
})
v, q = np.array([-1]), np.array([-1])
if m.actuator_trntype[i] == TrnType.JOINT:
# v/q are associated with the joint transmissions only
v = np.nonzero(m.dof_jntid == typ_ids['j'])[0]
q = np.nonzero(_q_jointid(m) == typ_ids['j'])[0]
typ_ids.update({'v': v, 'q': q})
return typ_ids
# build up a grouping of type take-ids in body/actuator order
+37 -18
View File
@@ -19,11 +19,13 @@ from jax import numpy as jp
import mujoco
from mujoco.mjx._src import math
from mujoco.mjx._src import scan
from mujoco.mjx._src import support
# pylint: disable=g-importing-member
from mujoco.mjx._src.types import Data
from mujoco.mjx._src.types import DisableBit
from mujoco.mjx._src.types import JointType
from mujoco.mjx._src.types import Model
from mujoco.mjx._src.types import TrnType
# pylint: enable=g-importing-member
@@ -432,38 +434,55 @@ def rne(m: Model, d: Data) -> Data:
def transmission(m: Model, d: Data) -> Data:
"""Computes actuator/transmission lengths and moments."""
# TODO: consider combining transmission calculation into fwd_actuation.
if not m.nu:
return d
def fn(gear, jnt_typ, m_j, qpos):
# handles joint transmissions only
if jnt_typ == JointType.FREE:
def fn(trntype, trnid, gear, jnt_typ, m_j, qpos, site_xpos, site_xmat):
if trntype == TrnType.JOINT:
if jnt_typ == JointType.FREE:
length = jp.zeros(1)
moment = gear
m_j = m_j + jp.arange(6)
elif jnt_typ == JointType.BALL:
axis, angle = math.quat_to_axis_angle(qpos)
length = jp.dot(axis * angle, gear[:3])[None]
moment = gear[:3]
m_j = m_j + jp.arange(3)
elif jnt_typ in (JointType.SLIDE, JointType.HINGE):
length = qpos * gear[0]
moment = gear[:1]
m_j = m_j[None]
else:
raise RuntimeError(f'unrecognized joint type: {JointType(jnt_typ)}')
moment = jp.zeros((m.nv,)).at[m_j].set(moment)
elif trntype == TrnType.SITE:
length = jp.zeros(1)
moment = gear
m_j = m_j + jp.arange(6)
elif jnt_typ == JointType.BALL:
axis, angle = math.quat_to_axis_angle(qpos)
length = jp.dot(axis * angle, gear[:3])[None]
moment = gear[:3]
m_j = m_j + jp.arange(3)
elif jnt_typ in (JointType.SLIDE, JointType.HINGE):
length = qpos * gear[0]
moment = gear[:1]
m_j = m_j[None]
jacp, jacr = support.jac(
m, d, site_xpos, jp.array(m.site_bodyid)[trnid[0]]
)
jac = jp.concatenate((jacp, jacr), axis=1)
wrench = jp.concatenate((site_xmat @ gear[:3], site_xmat @ gear[3:]))
moment = jac @ wrench
else:
raise RuntimeError(f'unrecognized joint type: {jnt_typ}')
moment = jp.zeros((m.nv,)).at[m_j].set(moment)
raise RuntimeError(f'unrecognized trntype: {TrnType(trntype)}')
return length, moment
length, moment = scan.flat(
m,
fn,
'ujjq',
'uuuu',
'uuujjqss',
'uu',
m.actuator_trntype,
jp.array(m.actuator_trnid),
m.actuator_gear,
m.jnt_type,
jp.array(m.jnt_dofadr),
d.qpos,
d.site_xpos,
d.site_xmat,
group_by='u',
)
length = length.reshape((m.nu,))
+35
View File
@@ -132,5 +132,40 @@ class SmoothTest(absltest.TestCase):
dx = jax.jit(mjx.rne)(mx, dx)
np.testing.assert_allclose(dx.qfrc_bias, 0)
def test_site_transmission(self):
m = mujoco.MjModel.from_xml_string("""
<mujoco>
<compiler autolimits="true"/>
<worldbody>
<body>
<joint type="free"/>
<geom type="box" size=".05 .05 .05" mass="1"/>
<site name="site1"/>
<site name="site2" pos="0.1 0.2 0.3"/>
</body>
<body pos="1 0 0">
<joint name="slide" type="hinge"/>
<geom type="box" size=".05 .05 .05" mass="1"/>
</body>
</worldbody>
<actuator>
<position site="site1" gear="1 2 3 0 0 0"/>
<position site="site1" gear="0 0 0 1 2 3"/>
<position site="site2" gear="0 3 0 0 0 1"/>
<position joint="slide"/>
</actuator>
</mujoco>
""")
d = mujoco.MjData(m)
mujoco.mj_forward(m, d)
mx = mjx.put_model(m)
dx = mjx.put_data(m, d)
mujoco.mj_transmission(m, d)
dx = mjx.transmission(mx, dx)
_assert_attr_eq(d, dx, 'actuator_length')
_assert_attr_eq(d, dx, 'actuator_moment')
if __name__ == '__main__':
absltest.main()
+37 -8
View File
@@ -29,6 +29,8 @@ TEST_FILES: List[str] = [
]
_ACTUATOR_TYPES = ['motor', 'velocity', 'position', 'general', 'intvelocity']
_DYN_TYPES = ['none', 'integrator', 'filter', 'filterexact']
_DYN_PRMS = ['0.189', '2.1']
_JOINT_TYPES = ['free', 'hinge', 'slide', 'ball']
_JOINT_AXES = ['1 0 0', '0 1 0', '0 0 1']
_FRICTIONS = ['1.2 0.003 0.0002', '0.2 0.0001 0.0005']
@@ -45,7 +47,7 @@ _SOLIMPS = [
_DIMS = ['3']
_MARGINS = ['0.0', '0.01', '0.02']
_GAPS = ['0.0', '0.005']
_GEARS = ['20', '50', '100']
_GEARS = ['2.1 0.0 3.3 0 2.3 0', '5.0 3.1 0 2.3 0.0 1.1']
def p(pct: int) -> bool:
@@ -121,12 +123,21 @@ def _make_geom(
return attr
def _make_actuator(actuator_type: str, joint: str) -> Dict[str, str]:
def _make_actuator(
actuator_type: str, joint: str | None = None, site: str | None = None
) -> Dict[str, str]:
"""Returns attributes for an actuator."""
attr = {'joint': joint}
if actuator_type == 'motor':
attr['gear'] = np.random.choice(_GEARS)
elif actuator_type == 'position':
if joint:
attr = {'joint': joint}
elif site:
attr = {'site': site}
else:
raise ValueError('must provide a joint or site name')
attr['gear'] = np.random.choice(_GEARS)
# set actuator type
if actuator_type == 'position':
attr['kp'] = np.random.choice(_KP_POS)
elif actuator_type == 'general':
attr['biastype'] = 'affine'
@@ -139,10 +150,18 @@ def _make_actuator(actuator_type: str, joint: str) -> Dict[str, str]:
elif actuator_type == 'velocity':
attr['kv'] = np.random.choice(_KV_VEL)
# set dyntype
if actuator_type == 'general':
attr['dyntype'] = np.random.choice(_DYN_TYPES)
if attr['dyntype'] != 'none':
attr['dynprm'] = np.random.choice(_DYN_PRMS)
# ctrlrange
if p(50) and actuator_type != 'intvelocity':
lb, ub = -np.random.uniform(), np.random.uniform()
attr['ctrlrange'] = f'{lb:.2f} {ub:.2f}'
# forcerange
if p(50):
lb, ub = -np.random.uniform(), np.random.uniform()
attr['forcerange'] = f'{lb*10:.2f} {ub*10:.2f}'
@@ -233,6 +252,7 @@ def create_mjcf(
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}'})
ET.SubElement(child, 'site', {'name': f'site{n_bodies}'})
n_joints = len(list(mjcf.iter('joint')))
for nj in range(np.random.randint(1, max_stacked_joints + 1)):
@@ -270,17 +290,28 @@ def create_mjcf(
for _ in range(num_trees):
make_tree(world, 0)
bodies = list(mjcf.iter('body'))
n_bodies = len(bodies)
# actuators
if add_actuators:
actuator = ET.SubElement(mjcf, 'actuator')
n_joints = len(list(mjcf.iter('joint')))
nu = np.random.randint(1, n_joints + 1)
actuators = []
# joint transmission
for i in range(nu):
actuator_type = np.random.choice(_ACTUATOR_TYPES)
attr = _make_actuator(actuator_type, joint=f'joint{i}')
actuators.append((actuator_type, attr))
# site transmission
for i in range(np.random.randint(0, n_bodies)):
actuator_type = np.random.choice(_ACTUATOR_TYPES)
attr = _make_actuator(actuator_type, site=f'site{i}')
actuators.append((actuator_type, attr))
np.random.shuffle(actuators)
for typ, attr in actuators:
ET.SubElement(actuator, typ, attr)
@@ -308,9 +339,7 @@ def create_mjcf(
ET.SubElement(contact, 'pair', attr)
# exclude contacts
bodies = list(mjcf.iter('body'))
body_names = [b.get('name') for b in bodies]
n_bodies = len(bodies)
for _ in range(min(max_contact_excludes, (n_bodies * (n_bodies - 1) // 2))):
if p(50):
continue
+8 -6
View File
@@ -20,10 +20,7 @@ from typing import Sequence
import jax
import jax.numpy as jp
import mujoco
# pylint: disable=g-importing-member
from mujoco.mjx._src import dataclasses
from mujoco.mjx._src.dataclasses import PyTreeNode
# pylint: enable=g-importing-member
from mujoco.mjx._src.dataclasses import PyTreeNode # pylint: disable=g-importing-member
import numpy as np
@@ -156,9 +153,11 @@ class TrnType(enum.IntEnum):
Attributes:
JOINT: force on joint
SITE: force on site
"""
JOINT = mujoco.mjtTrn.mjTRN_JOINT
# unsupported: JOINTINPARENT, SLIDERCRANK, TENDON, SITE, BODY
SITE = mujoco.mjtTrn.mjTRN_SITE
# unsupported: JOINTINPARENT, SLIDERCRANK, TENDON, BODY
class DynType(enum.IntEnum):
@@ -167,11 +166,14 @@ class DynType(enum.IntEnum):
Attributes:
NONE: no internal dynamics; ctrl specifies force
INTEGRATOR: integrator: da/dt = u
FILTER: linear filter: da/dt = (u-a) / tau
FILTEREXACT: linear filter: da/dt = (u-a) / tau, with exact integration
"""
NONE = mujoco.mjtDyn.mjDYN_NONE
INTEGRATOR = mujoco.mjtDyn.mjDYN_INTEGRATOR
FILTER = mujoco.mjtDyn.mjDYN_FILTER
# unsupported: FILTEREXACT, MUSCLE, USER
FILTEREXACT = mujoco.mjtDyn.mjDYN_FILTEREXACT
# unsupported: MUSCLE, USER
class GainType(enum.IntEnum):
+4 -4
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name="mujoco-mjx"
version = "3.0.2"
version = "3.1.2"
authors = [
{name = "Google DeepMind", email = "mujoco@deepmind.com"},
]
@@ -31,13 +31,13 @@ dependencies = [
"etils[epath]",
"jax",
"jaxlib",
"mujoco>=3.0.2.dev0",
"mujoco>=3.1.2.dev0",
"scipy",
"trimesh",
]
[project.urls]
Homepage = "https://github.com/google-deepmind/mujoco/tree/main/mjx"
Documentation = "https://mujoco.readthedocs.io/en/3.0.2"
Documentation = "https://mujoco.readthedocs.io/en/3.1.2"
Repository = "https://github.com/google-deepmind/mujoco/tree/main/mjx"
Changelog = "https://mujoco.readthedocs.io/en/3.0.2/changelog.html"
Changelog = "https://mujoco.readthedocs.io/en/3.1.2/changelog.html"
+27 -25
View File
@@ -416,10 +416,8 @@
" forward_reward = self._forward_reward_weight * velocity[0]\n",
"\n",
" min_z, max_z = self._healthy_z_range\n",
" is_healthy = jp.where(data.qpos[2] \u003c min_z, x=0.0, y=1.0)\n",
" is_healthy = jp.where(\n",
" data.qpos[2] \u003e max_z, x=0.0, y=is_healthy\n",
" )\n",
" is_healthy = jp.where(data.qpos[2] \u003c min_z, 0.0, 1.0)\n",
" is_healthy = jp.where(data.qpos[2] \u003e max_z, 0.0, is_healthy)\n",
" if self._terminate_when_unhealthy:\n",
" healthy_reward = self._healthy_reward\n",
" else:\n",
@@ -923,7 +921,9 @@
" 'physics_steps_per_control_step', physics_steps_per_control_step)\n",
" super().__init__(mj_model=mj_model, **kwargs)\n",
"\n",
" self.torso_idx = 1\n",
" self.torso_idx = mujoco.mj_name2id(\n",
" mj_model, mujoco.mjtObj.mjOBJ_BODY.value, 'torso'\n",
" )\n",
" self._action_scale = action_scale\n",
" self._obs_noise = obs_noise\n",
" self._reset_horizon = 500\n",
@@ -940,6 +940,7 @@
" self.reward_config = get_config()\n",
" self.lowers = self._default_ap_pose - jp.array([0.2, 0.8, 0.8] * 4)\n",
" self.uppers = self._default_ap_pose + jp.array([0.2, 0.8, 0.8] * 4)\n",
" self._foot_radius = 0.014\n",
"\n",
" def sample_command(self, rng: jax.Array) -\u003e jax.Array:\n",
" lin_vel_x = [-0.6, 1.0] # min max [m/s]\n",
@@ -1023,12 +1024,15 @@
" joint_vel = qvel[6:]\n",
"\n",
" # foot contact data based on z-position\n",
" foot_contact = 0.017 - self._get_feet_pos_vel(x, xd)[0][:, 2]\n",
" contact = foot_contact \u003e -1e-3 # a mm or less off the floor\n",
" foot_contact_pos = (\n",
" self._get_feet_pos_vel(x, xd)[0][:, 2]\n",
" - self._foot_radius\n",
" )\n",
" contact = foot_contact_pos \u003c 1e-3 # a mm or less off the floor\n",
" contact_filt_mm = jp.logical_or(contact, state.info['last_contact'])\n",
" contact_filt_cm = jp.logical_or(\n",
" foot_contact \u003e -1e-2, state.info['last_contact']\n",
" )\n",
" foot_contact_pos \u003c 3e-2, state.info['last_contact']\n",
" ) # 3cm or less off the floor\n",
" first_contact = (state.info['feet_air_time'] \u003e 0) * (contact_filt_mm)\n",
" state.info['feet_air_time'] += self.dt\n",
"\n",
@@ -1100,29 +1104,26 @@
" state.info.update(rng=rng)\n",
"\n",
" # resetting logic if joint limits are reached or robot is falling\n",
" done = 0.0\n",
" up = jp.array([0.0, 0.0, 1.0])\n",
" done = jp.where(jp.dot(math.rotate(up, x.rot[0]), up) \u003c 0, 1.0, done)\n",
" done = jp.where(jp.logical_or(\n",
" jp.any(joint_angles \u003c .98 * self.lowers),\n",
" jp.any(joint_angles \u003e .98 * self.uppers)), 1.0, done)\n",
" done = jp.where(x.pos[self.torso_idx, 2] \u003c 0.18, 1.0, done)\n",
" done = jp.dot(math.rotate(up, x.rot[0]), up) \u003c 0\n",
" done |= jp.any(joint_angles \u003c 0.98 * self.lowers)\n",
" done |= jp.any(joint_angles \u003e 0.98 * self.uppers)\n",
" done |= x.pos[0, 2] \u003c 0.18\n",
"\n",
" # termination reward\n",
" reward += jp.where(\n",
" (done == 1.0) \u0026 (state.info['step'] \u003c self._reset_horizon),\n",
" self.reward_config.rewards.scales.termination,\n",
" 0.0,\n",
" reward += (\n",
" done * (state.info['step'] \u003c self._reset_horizon) *\n",
" self.reward_config.rewards.scales.termination\n",
" )\n",
"\n",
" # when done, sample new command if more than _reset_horizon timesteps\n",
" # achieved\n",
" state.info['command'] = jp.where(\n",
" (done == 1.0) \u0026 (state.info['step'] \u003e self._reset_horizon),\n",
" done \u0026 (state.info['step'] \u003e self._reset_horizon),\n",
" self.sample_command(cmd_rng), state.info['command'])\n",
" # reset the step counter when done\n",
" state.info['step'] = jp.where(\n",
" (done == 1.0) | (state.info['step'] \u003e self._reset_horizon), 0,\n",
" done | (state.info['step'] \u003e self._reset_horizon), 0,\n",
" state.info['step']\n",
" )\n",
"\n",
@@ -1133,7 +1134,7 @@
"\n",
" state = state.replace(\n",
" pipeline_state=data, obs=obs + obs_noise, reward=reward,\n",
" done=done)\n",
" done=done * 1.0)\n",
" return state\n",
"\n",
" def _get_obs(self, qpos: jax.Array, x: Transform, xd: Motion,\n",
@@ -1233,7 +1234,8 @@
" self, x: Transform, xd: Motion) -\u003e Tuple[jax.Array, jax.Array]:\n",
" offset = Transform.create(pos=self._feet_pos)\n",
" pos = x.take(self._feet_index).vmap().do(offset).pos\n",
" vel = offset.vmap().do(xd.take(self._feet_index)).vel\n",
" world_offset = Transform.create(pos=pos - x.take(self._feet_index).pos)\n",
" vel = world_offset.vmap().do(xd.take(self._feet_index)).vel\n",
" return pos, vel\n",
"\n",
" def _reward_foot_slip(\n",
@@ -1405,8 +1407,8 @@
"private_outputs": true,
"provenance": [
{
"file_id": "1brcF4_qCRS2ASc-QQw1rsEwl5IjzGvq2",
"timestamp": 1697763780236
"file_id": "1QsuS7EJhdPEHxxAu9XwozvA7eb4ZnlAb",
"timestamp": 1701993737024
}
],
"toc_visible": true
+7 -13
View File
@@ -38,16 +38,10 @@ You can use it like:
The available options are:
|Attribute|Default |Meaning |
|---------|--------|-------------------------------------------------------------------------------------------------------------------------------------|
|`kp` |0 |**P** gain for the controller. |
|`ki` |0 |**I** gain for the controller. |
: : : :
: : :If nonzero, one activation variable will be added to `mjData.act`, containing the current I term (in units of force). :
|`kd` |0 |**D** gain for the controller. |
|`imax` |Optional|If specified, the force produced by the I term will be clipped to the range `[-imax, -imax]`. |
|`slewmax`|Optional|The maximum rate at which the setpoint for the PID controller can change. |
: : : :
: : :If a bigger change is requested between two timesteps, it will be clipped to the range `[ctrl - slewmax * dt, ctrl + slewmax * dt]` :
: : : :
: : :If specified, one activation variable will be added to `mjData.act` containing the previous value of `ctrl`. :
|Attribute | Default | Meaning |
|----------|---------|---------|
|`kp` | 0 | **P** gain for the controller. |
|`ki` | 0 | **I** gain for the controller.<p/>If nonzero, one activation variable will be added to `mjData.act`, containing the current I term (in units of force). |
|`kd` | 0 | **D** gain for the controller. |
|`imax` | Optional | If specified, the force produced by the I term will be clipped to the range `[-imax, -imax]`. |
|`slewmax` | Optional | The maximum rate at which the setpoint for the PID controller can change.<p/>If a bigger change is requested between two timesteps, it will be clipped to the range `[ctrl - slewmax * dt, ctrl + slewmax * dt]`<p/>If specified, one activation variable will be added to `mjData.act` containing the previous value of `ctrl`. |
+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.0.2.dylib
${MUJOCO_FRAMEWORK}/mujoco.framework/Versions/A/libmujoco.3.1.2.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.0.2 HINTS ${MUJOCO_LIBRARY_DIR} REQUIRED
MUJOCO_LIBRARY mujoco mujoco.3.1.2 HINTS ${MUJOCO_LIBRARY_DIR} REQUIRED
)
find_path(MUJOCO_INCLUDE mujoco/mujoco.h HINTS ${MUJOCO_INCLUDE_DIR} REQUIRED)
message("MuJoCo is at ${MUJOCO_LIBRARY}")
@@ -173,7 +173,7 @@ findorfetch(
GIT_REPO
https://gitlab.com/libeigen/eigen
GIT_TAG
aa6964bf3a34fd607837dd8123bc42465185c4f8
454f89af9d6f3525b1df5f9ef9c86df58bf2d4d3
TARGETS
Eigen3::Eigen
EXCLUDE_FROM_ALL
+1 -1
View File
@@ -840,7 +840,7 @@ Euler integrator, semi-implicit in velocity.
self.assertEqual(mujoco.mjtGeom.mjGEOM_ARROW, 100)
self.assertEqual(mujoco.mjtGeom.mjGEOM_ARROW1, 101)
self.assertEqual(mujoco.mjtGeom.mjGEOM_ARROW2, 102)
self.assertEqual(mujoco.mjtGeom.mjGEOM_TRIANGLE, 107)
self.assertEqual(mujoco.mjtGeom.mjGEOM_TRIANGLE, 108)
self.assertEqual(mujoco.mjtGeom.mjGEOM_NONE, 1001)
def test_enum_from_int(self):
+11 -8
View File
@@ -57,15 +57,18 @@ class GLContext:
def free(self):
"""Frees resources associated with this context."""
if self._context:
cgl.CGLUnlockContext(self._context)
cgl.CGLSetCurrentContext(None)
cgl.CGLReleaseContext(self._context)
self._context = None
try:
if self._context:
cgl.CGLUnlockContext(self._context)
cgl.CGLSetCurrentContext(None)
cgl.CGLReleaseContext(self._context)
self._context = None
if self._pix:
cgl.CGLReleasePixelFormat(self._pix)
self._context = None
if self._pix:
cgl.CGLReleasePixelFormat(self._pix)
self._pix = None
except Exception: # pylint: disable=broad-exception-caught
pass
def __del__(self):
self.free()
+1 -1
View File
@@ -35,7 +35,7 @@ class GLContext:
if glfw.get_current_context() == self._context:
glfw.make_context_current(None)
glfw.destroy_window(self._context)
self._context = None
self._context = None
def __del__(self):
self.free()
+4 -4
View File
@@ -7,13 +7,13 @@
<key>CFBundleIdentifier</key>
<string>org.mujoco.mjpython</string>
<key>CFBundleVersion</key>
<string>3.0.2</string>
<string>3.1.2</string>
<key>CFBundleGetInfoString</key>
<string>3.0.2</string>
<string>3.1.2</string>
<key>CFBundleLongVersionString</key>
<string>3.0.2</string>
<string>3.1.2</string>
<key>CFBundleShortVersionString</key>
<string>3.0.2</string>
<string>3.1.2</string>
<key>CFBundleExecutable</key>
<string>mjpython</string>
<key>CFBundleIconFile</key>
+37
View File
@@ -136,6 +136,9 @@ the clause:
A new numpy array holding the pixels with shape `(H, W)` or `(H, W, 3)`,
depending on the value of `self._depth_rendering` unless
`out is None`, in which case a reference to `out` is returned.
Raises:
RuntimeError: if this method is called after the close method.
"""
original_flags = self._scene.flags.copy()
@@ -145,6 +148,8 @@ the clause:
self._scene.flags[_enums.mjtRndFlag.mjRND_SEGMENT] = True
self._scene.flags[_enums.mjtRndFlag.mjRND_IDCOLOR] = True
if self._gl_context is None:
raise RuntimeError('render cannot be called after close.')
self._gl_context.make_current()
if self._depth_rendering:
@@ -288,3 +293,35 @@ the clause:
camera, _enums.mjtCatBit.mjCAT_ALL.value,
self._scene,
)
def close(self) -> None:
"""Frees the resources used by the renderer.
This method can be used directly:
```python
renderer = Renderer(...)
# Use renderer.
renderer.close()
```
or via a context manager:
```python
with Renderer(...) as renderer:
# Use renderer.
```
"""
if self._gl_context:
self._gl_context.free()
self._gl_context = None
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
del exc_type, exc_value, traceback # Unused.
self.close()
def __del__(self) -> None:
self.close()
+51 -53
View File
@@ -33,10 +33,10 @@ class MuJoCoRendererTest(parameterized.TestCase):
"""
model = mujoco.MjModel.from_xml_string(xml)
data = mujoco.MjData(model)
renderer = mujoco.Renderer(model, 50, 50)
mujoco.mj_forward(model, data)
with self.assertRaisesRegex(ValueError, r'camera "b" does not exist'):
renderer.update_scene(data, 'b')
with mujoco.Renderer(model, 50, 50) as renderer:
mujoco.mj_forward(model, data)
with self.assertRaisesRegex(ValueError, r'camera "b" does not exist'):
renderer.update_scene(data, 'b')
def test_renderer_camera_under_range(self):
xml = """
@@ -48,10 +48,10 @@ class MuJoCoRendererTest(parameterized.TestCase):
"""
model = mujoco.MjModel.from_xml_string(xml)
data = mujoco.MjData(model)
renderer = mujoco.Renderer(model, 50, 50)
mujoco.mj_forward(model, data)
with self.assertRaisesRegex(ValueError, '-2 is out of range'):
renderer.update_scene(data, -2)
with mujoco.Renderer(model, 50, 50) as renderer:
mujoco.mj_forward(model, data)
with self.assertRaisesRegex(ValueError, '-2 is out of range'):
renderer.update_scene(data, -2)
def test_renderer_camera_over_range(self):
xml = """
@@ -63,10 +63,10 @@ class MuJoCoRendererTest(parameterized.TestCase):
"""
model = mujoco.MjModel.from_xml_string(xml)
data = mujoco.MjData(model)
renderer = mujoco.Renderer(model, 50, 50)
mujoco.mj_forward(model, data)
with self.assertRaisesRegex(ValueError, '1 is out of range'):
renderer.update_scene(data, 1)
with mujoco.Renderer(model, 50, 50) as renderer:
mujoco.mj_forward(model, data)
with self.assertRaisesRegex(ValueError, '1 is out of range'):
renderer.update_scene(data, 1)
def test_renderer_renders_scene(self):
xml = """
@@ -79,19 +79,19 @@ class MuJoCoRendererTest(parameterized.TestCase):
"""
model = mujoco.MjModel.from_xml_string(xml)
data = mujoco.MjData(model)
renderer = mujoco.Renderer(model, 50, 50)
mujoco.mj_forward(model, data)
renderer.update_scene(data, 'closeup')
with mujoco.Renderer(model, 50, 50) as renderer:
mujoco.mj_forward(model, data)
renderer.update_scene(data, 'closeup')
pixels = renderer.render().flatten()
not_all_black = False
pixels = renderer.render().flatten()
not_all_black = False
# Pixels should all be a neutral color.
for pixel in pixels:
if pixel > 0:
not_all_black = True
break
self.assertTrue(not_all_black)
# Pixels should all be a neutral color.
for pixel in pixels:
if pixel > 0:
not_all_black = True
break
self.assertTrue(not_all_black)
def test_renderer_output_without_out(self):
xml = """
@@ -105,25 +105,25 @@ class MuJoCoRendererTest(parameterized.TestCase):
model = mujoco.MjModel.from_xml_string(xml)
data = mujoco.MjData(model)
mujoco.mj_forward(model, data)
renderer = mujoco.Renderer(model, 50, 50)
renderer.update_scene(data, 'closeup')
pixels = [renderer.render()]
colors = (
(1.0, 0.0, 0.0, 1.0),
(0.0, 1.0, 0.0, 1.0),
(0.0, 0.0, 1.0, 1.0),
)
for i, color in enumerate(colors):
model.geom_rgba[0, :] = color
mujoco.mj_forward(model, data)
with mujoco.Renderer(model, 50, 50) as renderer:
renderer.update_scene(data, 'closeup')
pixels.append(renderer.render())
self.assertIsNot(pixels[-2], pixels[-1])
pixels = [renderer.render()]
# Pixels should change over steps.
self.assertFalse((pixels[i + 1] == pixels[i]).all())
colors = (
(1.0, 0.0, 0.0, 1.0),
(0.0, 1.0, 0.0, 1.0),
(0.0, 0.0, 1.0, 1.0),
)
for i, color in enumerate(colors):
model.geom_rgba[0, :] = color
mujoco.mj_forward(model, data)
renderer.update_scene(data, 'closeup')
pixels.append(renderer.render())
self.assertIsNot(pixels[-2], pixels[-1])
# Pixels should change over steps.
self.assertFalse((pixels[i + 1] == pixels[i]).all())
def test_renderer_output_with_out(self):
xml = """
@@ -139,23 +139,21 @@ class MuJoCoRendererTest(parameterized.TestCase):
model = mujoco.MjModel.from_xml_string(xml)
data = mujoco.MjData(model)
mujoco.mj_forward(model, data)
renderer = mujoco.Renderer(model, *render_size)
renderer.update_scene(data, 'closeup')
with mujoco.Renderer(model, *render_size) as renderer:
renderer.update_scene(data, 'closeup')
self.assertTrue(np.all(render_out == 0))
self.assertTrue(np.all(render_out == 0))
pixels = renderer.render(out=render_out)
pixels = renderer.render(out=render_out)
# Pixels should always refer to the same `render_out` array.
self.assertIs(pixels, render_out)
self.assertFalse(np.all(render_out == 0))
# Pixels should always refer to the same `render_out` array.
self.assertIs(pixels, render_out)
self.assertFalse(np.all(render_out == 0))
failing_render_size = (10, 10)
self.assertNotEqual(failing_render_size, render_size)
with self.assertRaises(ValueError):
pixels = renderer.render(
out=np.zeros((*failing_render_size, 3), np.uint8)
)
failing_render_size = (10, 10)
self.assertNotEqual(failing_render_size, render_size)
with self.assertRaises(ValueError):
renderer.render(out=np.zeros((*failing_render_size, 3), np.uint8))
if __name__ == '__main__':
+3 -3
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "mujoco"
version = "3.0.2"
version = "3.1.2"
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.0.2"
Documentation = "https://mujoco.readthedocs.io/en/3.1.2"
Repository = "https://github.com/google-deepmind/mujoco"
Changelog = "https://mujoco.readthedocs.io/en/3.0.2/changelog.html"
Changelog = "https://mujoco.readthedocs.io/en/3.1.2/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.0.2
VERSION 3.1.2
DESCRIPTION "MuJoCo samples binaries"
HOMEPAGE_URL "https://mujoco.org"
)
+1 -1
View File
@@ -29,7 +29,7 @@ set(MUJOCO_DEP_VERSION_lodepng
project(
mujoco_simulate
VERSION 3.0.2
VERSION 3.1.2
DESCRIPTION "MuJoCo simulate binaries"
HOMEPAGE_URL "https://mujoco.org"
)
+14 -4
View File
@@ -1817,6 +1817,9 @@ void Simulate::Sync() {
if (!m_) {
return;
}
if (this->exitrequest.load()) {
return;
}
bool update_profiler = this->profiler && (this->pause_update || this->run);
bool update_sensor = this->sensor && (this->pause_update || this->run);
@@ -2476,7 +2479,14 @@ void Simulate::Render() {
// show pause/loading label
if (!this->run || this->loadrequest) {
const char* label = this->loadrequest ? "LOADING..." : "PAUSE";
char label[30] = {'\0'};
if (this->loadrequest) {
std::snprintf(label, sizeof(label), "LOADING...");
} else if (this->scrub_index == 0) {
std::snprintf(label, sizeof(label), "PAUSE");
} else {
std::snprintf(label, sizeof(label), "PAUSE (%d)", this->scrub_index);
}
mjr_overlay(mjFONT_BIG, mjGRID_TOP, smallrect, label, nullptr,
&this->platform_ui->mjr_context());
}
@@ -2707,9 +2717,9 @@ void Simulate::RenderLoop() {
}
}
if (!is_passive_){
mjv_freeScene(&this->scn);
} else {
const MutexLock lock(this->mtx);
mjv_freeScene(&this->scn);
if (is_passive_) {
mjv_freeSceneState(&scnstate_);
}
+36 -2
View File
@@ -1524,16 +1524,49 @@ void mj_collideGeoms(const mjModel* m, mjData* d, int g1, int g2) {
mjERROR("too many contacts returned by collision function");
}
// remove repeated contacts in box-box
// remove bad and repeated contacts in box-box
if (type1 == mjGEOM_BOX && type2 == mjGEOM_BOX) {
// use dim field to mark: -1: bad, 0: good
for (int i=0; i < num; i++) {
con[i].dim = 0;
}
// find bad
// get box info
const mjtNum* pos1 = d->geom_xpos + 3 * g1;
const mjtNum* mat1 = d->geom_xmat + 9 * g1;
const mjtNum* size1 = m->geom_size + 3 * g1;
const mjtNum* pos2 = d->geom_xpos + 3 * g2;
const mjtNum* mat2 = d->geom_xmat + 9 * g2;
const mjtNum* size2 = m->geom_size + 3 * g2;
// find bad: contacts outside one of the boxes
for (int i=0; i < num; i++) {
// box sizes with margin
mjtNum sz1[3] = {size1[0] + margin, size1[1] + margin, size1[2] + margin};
mjtNum sz2[3] = {size2[0] + margin, size2[1] + margin, size2[2] + margin};
// relative distance from surface (1%) outside of which box-box contacts are removed
static mjtNum kRemoveRatio = 1.01;
// is the contact outside: 1, inside: -1, within the removal width: 0
int out1 = mju_outsideBox(con[i].pos, pos1, mat1, sz1, kRemoveRatio);
int out2 = mju_outsideBox(con[i].pos, pos2, mat2, sz2, kRemoveRatio);
// mark as bad if outside one box and not inside the other box
if ((out1 == 1 && out2 != -1) || (out2 == 1 && out1 != -1)) {
con[i].dim = -1;
}
}
// find duplicates
for (int i=0; i < num-1; i++) {
if (con[i].dim == -1) {
continue; // already marked bad: skip
}
for (int j=i+1; j < num; j++) {
if (con[j].dim == -1) {
continue; // already marked bad: skip
}
if (con[i].pos[0] == con[j].pos[0] &&
con[i].pos[1] == con[j].pos[1] &&
con[i].pos[2] == con[j].pos[2]) {
@@ -1546,6 +1579,7 @@ void mj_collideGeoms(const mjModel* m, mjData* d, int g1, int g2) {
// consolidate good
int i = 0;
for (int j=0; j < num; j++) {
// good: maybe copy
if (con[j].dim == 0) {
// different: copy
if (i < j) {
+23 -22
View File
@@ -16,6 +16,7 @@
#define MUJOCO_SRC_ENGINE_ENGINE_COLLISION_PRIMITIVE_H_
#include <mujoco/mjdata.h>
#include <mujoco/mjexport.h>
#include <mujoco/mjmodel.h>
// define and extract geom info
@@ -47,32 +48,32 @@ int mjraw_SphereTriangle(mjContact* con, mjtNum margin,
const mjtNum* t1, const mjtNum* t2, const mjtNum* t3, mjtNum rt);
// plane collisions
int mjc_PlaneSphere (const mjModel* m, const mjData* d,
mjContact* con, int g1, int g2, mjtNum margin);
int mjc_PlaneCapsule (const mjModel* m, const mjData* d,
mjContact* con, int g1, int g2, mjtNum margin);
int mjc_PlaneCylinder (const mjModel* m, const mjData* d,
mjContact* con, int g1, int g2, mjtNum margin);
int mjc_PlaneBox (const mjModel* m, const mjData* d,
mjContact* con, int g1, int g2, mjtNum margin);
MJAPI int mjc_PlaneSphere (const mjModel* m, const mjData* d,
mjContact* con, int g1, int g2, mjtNum margin);
MJAPI int mjc_PlaneCapsule (const mjModel* m, const mjData* d,
mjContact* con, int g1, int g2, mjtNum margin);
MJAPI int mjc_PlaneCylinder (const mjModel* m, const mjData* d,
mjContact* con, int g1, int g2, mjtNum margin);
MJAPI int mjc_PlaneBox (const mjModel* m, const mjData* d,
mjContact* con, int g1, int g2, mjtNum margin);
// sphere and capsule collisions
int mjc_SphereSphere (const mjModel* m, const mjData* d,
mjContact* con, int g1, int g2, mjtNum margin);
int mjc_SphereCapsule (const mjModel* m, const mjData* d,
mjContact* con, int g1, int g2, mjtNum margin);
int mjc_SphereCylinder (const mjModel* m, const mjData* d,
mjContact* con, int g1, int g2, mjtNum margin);
int mjc_CapsuleCapsule (const mjModel* m, const mjData* d,
mjContact* con, int g1, int g2, mjtNum margin);
MJAPI int mjc_SphereSphere (const mjModel* m, const mjData* d,
mjContact* con, int g1, int g2, mjtNum margin);
MJAPI int mjc_SphereCapsule (const mjModel* m, const mjData* d,
mjContact* con, int g1, int g2, mjtNum margin);
MJAPI int mjc_SphereCylinder (const mjModel* m, const mjData* d,
mjContact* con, int g1, int g2, mjtNum margin);
MJAPI int mjc_CapsuleCapsule (const mjModel* m, const mjData* d,
mjContact* con, int g1, int g2, mjtNum margin);
// box collisions: from engine_collision_box.c
int mjc_CapsuleBox (const mjModel* m, const mjData* d,
mjContact* con, int g1, int g2, mjtNum margin);
int mjc_SphereBox (const mjModel* m, const mjData* d,
mjContact* con, int g1, int g2, mjtNum margin);
int mjc_BoxBox (const mjModel* m, const mjData* d,
mjContact* con, int g1, int g2, mjtNum margin);
MJAPI int mjc_CapsuleBox (const mjModel* m, const mjData* d,
mjContact* con, int g1, int g2, mjtNum margin);
MJAPI int mjc_SphereBox (const mjModel* m, const mjData* d,
mjContact* con, int g1, int g2, mjtNum margin);
MJAPI int mjc_BoxBox (const mjModel* m, const mjData* d,
mjContact* con, int g1, int g2, mjtNum margin);
#ifdef __cplusplus
}
+19 -6
View File
@@ -1325,6 +1325,19 @@ static void getposdim(const mjModel* m, const mjData* d, int i, mjtNum* pos, int
// return a to the power of b, quick return for powers 1 and 2
// solimp[4] == 2 is the default, so these branches are common
static mjtNum power(mjtNum a, mjtNum b) {
if (b == 1) {
return a;
} else if (b == 2) {
return a*a;
}
return mju_pow(a, b);
}
// compute impedance and derivative for one constraint
static void getimpedance(const mjtNum* solimp, mjtNum pos, mjtNum margin,
mjtNum* imp, mjtNum* impP) {
@@ -1359,16 +1372,16 @@ static void getimpedance(const mjtNum* solimp, mjtNum pos, mjtNum margin,
// y(x) = a*x^p if x<=midpoint
else if (x <= solimp[3]) {
mjtNum a = 1/mju_pow(solimp[3], solimp[4]-1);
y = a*mju_pow(x, solimp[4]);
yP = solimp[4] * a*mju_pow(x, solimp[4]-1);
mjtNum a = 1/power(solimp[3], solimp[4]-1);
y = a*power(x, solimp[4]);
yP = solimp[4] * a*power(x, solimp[4]-1);
}
// y(x) = 1-b*(1-x)^p is x>midpoint
else {
mjtNum b = 1/mju_pow(1-solimp[3], solimp[4]-1);
y = 1-b*mju_pow(1-x, solimp[4]);
yP = solimp[4] * b*mju_pow(1-x, solimp[4]-1);
mjtNum b = 1/power(1-solimp[3], solimp[4]-1);
y = 1-b*power(1-x, solimp[4]);
yP = solimp[4] * b*power(1-x, solimp[4]-1);
}
// scale
+2
View File
@@ -1858,6 +1858,8 @@ static int numObjects(const mjModel* m, mjtObj objtype) {
return m->nkey;
case mjOBJ_PLUGIN:
return m->nplugin;
case mjNOBJECT:
return -2;
}
return -2;
}
+2 -2
View File
@@ -38,8 +38,8 @@
//-------------------------- Constants -------------------------------------------------------------
#define mjVERSION 302
#define mjVERSIONSTRING "3.0.2"
#define mjVERSION 312
#define mjVERSIONSTRING "3.1.2"
// names of disable flags
const char* mjDISABLESTRING[mjNDISABLE] = {
+46
View File
@@ -837,6 +837,52 @@ mjtNum mju_springDamper(mjtNum pos0, mjtNum vel0, mjtNum k, mjtNum b, mjtNum t)
// return 1 if point is outside box given by pos, mat, size * inflate
// return -1 if point is inside box given by pos, mat, size / inflate
// return 0 if point is between the inflated and deflated boxes
int mju_outsideBox(const mjtNum point[3], const mjtNum pos[3], const mjtNum mat[9],
const mjtNum size[3], mjtNum inflate) {
// check inflation coefficient
if (inflate < 1) {
mjERROR("inflation coefficient must be >= 1")
}
// vector from pos to point, projected to box frame
mjtNum vec[3] = {point[0]-pos[0], point[1]-pos[1], point[2]-pos[2]};
mju_rotVecMatT(vec, vec, mat);
// big: inflated box
mjtNum big[3] = {size[0], size[1], size[2]};
if (inflate > 1) {
mju_scl3(big, big, inflate);
}
// check if outside big box
if (vec[0] > big[0] || vec[0] < -big[0] ||
vec[1] > big[1] || vec[1] < -big[1] ||
vec[2] > big[2] || vec[2] < -big[2]) {
return 1;
}
// quick return if no inflation
if (inflate == 1) {
return -1;
}
// check if inside small (deflated) box
mjtNum small[3] = {size[0]/inflate, size[1]/inflate, size[2]/inflate};
if (vec[0] < small[0] && vec[0] > -small[0] &&
vec[1] < small[1] && vec[1] > -small[1] &&
vec[2] < small[2] && vec[2] > -small[2]) {
return -1;
}
// within margin between small and big box
return 0;
}
// print matrix to screen
void mju_printMat(const mjtNum* mat, int nr, int nc) {
for (int r=0; r < nr; r++) {
+6
View File
@@ -78,6 +78,12 @@ MJAPI void mju_decodePyramid(mjtNum* force, const mjtNum* pyramid,
// integrate spring-damper analytically, return pos(dt)
MJAPI mjtNum mju_springDamper(mjtNum pos0, mjtNum vel0, mjtNum Kp, mjtNum Kv, mjtNum dt);
// return 1 if point is outside box given by pos, mat, size * inflate
// return -1 if point is inside box given by pos, mat, size / inflate
// return 0 if point is between the inflated and deflated boxes
MJAPI int mju_outsideBox(const mjtNum point[3], const mjtNum pos[3], const mjtNum mat[9],
const mjtNum size[3], mjtNum inflate);
// print matrix
MJAPI void mju_printMat(const mjtNum* mat, int nr, int nc);
+16
View File
@@ -86,6 +86,11 @@ void mjv_makeSceneState(const mjModel* m, const mjData* d, mjvSceneState* scnsta
#undef XMJV
#undef X
// create an arena in the scnstate, to allow visualization code to use the stack.
// TODO: Consider allocating way less than narena, since stack allocations in
// visualization code are much smaller than the arena space required by the model,
// typically.
scnstate->nbuffer += roundUpToCacheLine(m->narena);
// buffer space required for contacts
int condimmax = mj_isPyramidal(m) ? 10 : 6;
scnstate->nbuffer += roundUpToCacheLine(sizeof(*d->contact) * maxgeom);
@@ -118,6 +123,10 @@ void mjv_makeSceneState(const mjModel* m, const mjData* d, mjvSceneState* scnsta
#undef XMJV
#undef X
scnstate->model.narena = m->narena;
scnstate->data.arena = (void*)ptr;
ptr += roundUpToCacheLine(m->narena);
scnstate->data.contact = (mjContact*)ptr;
ptr += roundUpToCacheLine(sizeof(*scnstate->data.contact) * scnstate->maxgeom);
@@ -177,6 +186,7 @@ void mjv_assignFromSceneState(const mjvSceneState* scnstate, mjModel* m, mjData*
m->opt = scnstate->model.opt;
m->vis = scnstate->model.vis;
m->stat = scnstate->model.stat;
m->narena = scnstate->model.narena;
#define X(dtype, var, dim0, dim1)
#define XMJV(dtype, var, dim0, dim1) m->var = scnstate->model.var;
@@ -194,10 +204,16 @@ void mjv_assignFromSceneState(const mjvSceneState* scnstate, mjModel* m, mjData*
#endif
memcpy(d->warning, scnstate->data.warning, sizeof(d->warning));
d->threadpool = 0;
d->nefc = scnstate->data.nefc;
d->ncon = scnstate->data.ncon;
d->nisland = scnstate->data.nisland;
d->time = scnstate->data.time;
d->narena = scnstate->model.narena;
d->arena = scnstate->data.arena;
d->parena = 0;
d->pbase = 0;
d->pstack = 0;
#define X(dtype, var, dim0, dim1)
#define XMJV(dtype, var, dim0, dim1) d->var = scnstate->data.var;
+34 -64
View File
@@ -62,7 +62,7 @@ static void makeLabel(const mjModel* m, mjtObj type, int id, char* label) {
}
// copy result into label
strncpy(label, txt, 99);
strncpy(label, txt, 100);
label[99] = '\0';
}
@@ -509,56 +509,6 @@ static int bodycategory(const mjModel* m, int bodyid) {
// draw bounding box
static void drawBoundingBox(mjData* d, mjvScene* scn,
const mjtNum aabb[6], const mjtNum xpos[3],
const mjtNum xmat[9], const float rgba[4]) {
mjtNum x[3];
mjtNum dist[3][3];
mjvGeom* thisgeom;
int category = mjCAT_DECOR;
int objtype = mjOBJ_UNKNOWN;
int i = -1;
if (xmat != NULL) {
mju_rotVecMat(x, aabb, xmat);
mju_addTo3(x, xpos);
for (int j=0; j < 3; j++) {
for (int k=0; k < 3; k++) {
dist[k][j] = aabb[k+3] * xmat[3*j+k];
}
}
} else {
mju_copy3(x, aabb);
mju_addTo3(x, xpos);
for (int j=0; j < 3; j++) {
mju_zero3(dist[j]);
dist[j][j] = aabb[j+3];
}
}
int split[3] = {1, 2, 4};
for (int v=0; v < 8; v++) {
mjtNum from[3] = {x[0], x[1], x[2]};
for (int k=0; k < 3; k++) {
mju_addToScl3(from, dist[k], v&split[k] ? 1 : -1);
}
mjtNum to[3];
for (int k=0; k < 3; k++) {
mju_addScl3(to, from, dist[k], 2);
if (!(v&split[k])) {
START
mjv_connector(thisgeom, mjGEOM_LINE, 2, from, to);
f2f(thisgeom->rgba, rgba, 4);
FINISH
}
}
}
}
// computes the camera frustum
static void getFrustum(float zver[2], float zhor[2], float znear,
const float K[4], const float sensorsize[2]) {
@@ -683,6 +633,8 @@ void mjv_addGeoms(const mjModel* m, mjData* d, const mjvOption* vopt,
}
// body BVH
category = mjCAT_DECOR;
objtype = mjOBJ_UNKNOWN;
if (vopt->flags[mjVIS_BODYBVH]) {
float rgba[] = {1, 0, 0, 1};
for (int i = 0; i < m->nbvhstatic; i++) {
@@ -708,20 +660,30 @@ void mjv_addGeoms(const mjModel* m, mjData* d, const mjvOption* vopt,
break;
}
// compute transformation
mjtNum *aabb = isleaf ? m->geom_aabb + 6*geomid : m->bvh_aabb + 6*i;
// get xpos, xmat, size
const mjtNum* xpos = isleaf ? d->geom_xpos + 3 * geomid : d->xipos + 3 * bodyid;
const mjtNum* xmat = isleaf ? d->geom_xmat + 9 * geomid : d->ximat + 9 * bodyid;
const mjtNum *size = isleaf ? m->geom_aabb + 6*geomid + 3 : m->bvh_aabb + 6*i + 3;
// offset xpos with aabb center (not always at frame origin)
const mjtNum *center = isleaf ? m->geom_aabb + 6*geomid : m->bvh_aabb + 6*i;
mjtNum pos[3];
mju_rotVecMat(pos, center, xmat);
mju_addTo3(pos, xpos);
rgba[0] = d->bvh_active[i] ? 1 : 0;
rgba[1] = d->bvh_active[i] ? 0 : 1;
drawBoundingBox(d, scn, aabb, xpos, xmat, rgba);
START
mjv_initGeom(thisgeom, mjGEOM_LINEBOX, size, pos, xmat, rgba);
FINISH
}
}
// flex BVH
category = mjCAT_DECOR;
objtype = mjOBJ_UNKNOWN;
if (vopt->flags[mjVIS_FLEXBVH]) {
float rgba[] = {1, 0, 0, 0.1};
for (int f=0; f < m->nflex; f++) {
@@ -741,10 +703,8 @@ void mjv_addGeoms(const mjModel* m, mjData* d, const mjvOption* vopt,
rgba[0] = d->bvh_active[i] ? 1 : 0;
rgba[1] = d->bvh_active[i] ? 0 : 1;
// b/304453879 : add LINEBOX geom for bounding box visualization
START
mjv_initGeom(thisgeom, mjGEOM_BOX, aabb+3, aabb, NULL, rgba);
mjv_initGeom(thisgeom, mjGEOM_LINEBOX, aabb+3, aabb, NULL, rgba);
FINISH
}
}
@@ -752,6 +712,8 @@ void mjv_addGeoms(const mjModel* m, mjData* d, const mjvOption* vopt,
}
// mesh BVH
category = mjCAT_DECOR;
objtype = mjOBJ_UNKNOWN;
if (vopt->flags[mjVIS_MESHBVH]) {
float rgba[] = {1, 0, 0, 1};
for (int geomid = 0; geomid < m->ngeom; geomid++) {
@@ -770,11 +732,6 @@ void mjv_addGeoms(const mjModel* m, mjData* d, const mjvOption* vopt,
}
}
// compute transformation
const mjtNum *aabb = m->bvh_aabb + 6*i;
const mjtNum* xpos = d->geom_xpos + 3 * geomid;
const mjtNum* xmat = d->geom_xmat + 9 * geomid;
if (!d->bvh_active[i]) {
continue;
}
@@ -782,7 +739,20 @@ void mjv_addGeoms(const mjModel* m, mjData* d, const mjvOption* vopt,
rgba[0] = d->bvh_active[i] ? 1 : 0;
rgba[1] = d->bvh_active[i] ? 0 : 1;
drawBoundingBox(d, scn, aabb, xpos, xmat, rgba);
// get xpos, xmat, size
const mjtNum* xpos = d->geom_xpos + 3 * geomid;
const mjtNum* xmat = d->geom_xmat + 9 * geomid;
const mjtNum *size = m->bvh_aabb + 6*i + 3;
// offset xpos with aabb center (not always at geom origin)
const mjtNum *center = m->bvh_aabb + 6*i;
mjtNum pos[3];
mju_rotVecMat(pos, center, xmat);
mju_addTo3(pos, xpos);
START
mjv_initGeom(thisgeom, mjGEOM_LINEBOX, size, pos, xmat, rgba);
FINISH
}
}
}
+11 -2
View File
@@ -1675,8 +1675,17 @@ void mjr_addAux(int index, int width, int height, int samples, mjrContext* con)
// check max size
int maxSize = 0;
glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &maxSize);
if (width > maxSize || height > maxSize) {
mju_error("Auxiliary buffer size exceeds maximum allowed by OpenGL implementation");
if (width > maxSize) {
mju_error(
"Auxiliary buffer width exceeds maximum allowed by OpenGL "
"implementation: %d > %d",
width, maxSize);
}
if (height > maxSize) {
mju_error(
"Auxiliary buffer height exceeds maximum allowed by OpenGL "
"implementation: %d > %d",
height, maxSize);
}
// clamp samples request
+35
View File
@@ -404,6 +404,41 @@ static void renderGeom(const mjvGeom* geom, int mode, const float* headpos,
}
break;
case mjGEOM_LINEBOX: // box with line edges
glLineWidth(1.5*con->lineWidth);
lighting = glIsEnabled(GL_LIGHTING);
glDisable(GL_LIGHTING);
// bottom face
glBegin(GL_LINE_LOOP);
glVertex3f(-size[0], -size[1], -size[2]);
glVertex3f( size[0], -size[1], -size[2]);
glVertex3f( size[0], size[1], -size[2]);
glVertex3f(-size[0], size[1], -size[2]);
glEnd();
// top face
glBegin(GL_LINE_LOOP);
glVertex3f(-size[0], -size[1], size[2]);
glVertex3f( size[0], -size[1], size[2]);
glVertex3f( size[0], size[1], size[2]);
glVertex3f(-size[0], size[1], size[2]);
glEnd();
// vertical edges
glBegin(GL_LINES);
glVertex3f(-size[0], -size[1], -size[2]);
glVertex3f(-size[0], -size[1], size[2]);
glVertex3f( size[0], -size[1], -size[2]);
glVertex3f( size[0], -size[1], size[2]);
glVertex3f( size[0], size[1], -size[2]);
glVertex3f( size[0], size[1], size[2]);
glVertex3f(-size[0], size[1], -size[2]);
glVertex3f(-size[0], size[1], size[2]);
glEnd();
glLineWidth(con->lineWidth);
if (lighting) {
glEnable(GL_LIGHTING);
}
break;
case mjGEOM_TRIANGLE: // triangle
glBegin(GL_TRIANGLES);
glVertex3f(0, 0, 0);
+5
View File
@@ -296,6 +296,11 @@ void mjCMesh::LoadSDF() {
name.c_str(), id);
}
if (scale_[0] != 1 || scale_[1] != 1 || scale_[2] != 1) {
throw mjCError(this, "attribute scale is not compatible with SDFs in mesh '%s', (id = %d)",
name.c_str(), id);
}
model->ResolvePlugin(this, plugin_name, plugin_instance_name, &plugin_instance);
const mjpPlugin* plugin = mjp_getPluginAtSlot(plugin_instance->plugin_slot);
if (!(plugin->capabilityflags & mjPLUGIN_SDF)) {
+147 -242
View File
@@ -19,7 +19,9 @@
#include <cstddef>
#include <cstdlib>
#include <cstring>
#include <map>
#include <string>
#include <string_view>
#include <vector>
#include <mujoco/mjdata.h>
@@ -112,6 +114,16 @@ mjCModel::mjCModel() {
center[0] = mjNAN;
center[1] = center[2] = 0;
//------------------------ auto-computed statistics
#ifndef MEMORY_SANITIZER
// initializing as best practice, but want MSAN to catch unintialized use
meaninertia_auto = 0;
meanmass_auto = 0;
meansize_auto = 0;
extent_auto = 0;
center_auto[0] = center_auto[1] = center_auto[2] = 0;
#endif
//------------------------ engine data
modelname = "MuJoCo Model";
mj_defaultOption(&option);
@@ -170,6 +182,35 @@ mjCModel::mjCModel() {
world->name = "world";
world->def = defaults[0];
bodies.push_back(world);
for (int i = 0; i < mjNOBJECT; ++i) {
object_lists[i] = nullptr;
}
object_lists[mjOBJ_BODY] = (std::vector<mjCBase*>*) &bodies;
object_lists[mjOBJ_XBODY] = (std::vector<mjCBase*>*) &bodies;
object_lists[mjOBJ_JOINT] = (std::vector<mjCBase*>*) &joints;
object_lists[mjOBJ_GEOM] = (std::vector<mjCBase*>*) &geoms;
object_lists[mjOBJ_SITE] = (std::vector<mjCBase*>*) &sites;
object_lists[mjOBJ_CAMERA] = (std::vector<mjCBase*>*) &cameras;
object_lists[mjOBJ_LIGHT] = (std::vector<mjCBase*>*) &lights;
object_lists[mjOBJ_FLEX] = (std::vector<mjCBase*>*) &flexes;
object_lists[mjOBJ_MESH] = (std::vector<mjCBase*>*) &meshes;
object_lists[mjOBJ_SKIN] = (std::vector<mjCBase*>*) &skins;
object_lists[mjOBJ_HFIELD] = (std::vector<mjCBase*>*) &hfields;
object_lists[mjOBJ_TEXTURE] = (std::vector<mjCBase*>*) &textures;
object_lists[mjOBJ_MATERIAL] = (std::vector<mjCBase*>*) &materials;
object_lists[mjOBJ_PAIR] = (std::vector<mjCBase*>*) &pairs;
object_lists[mjOBJ_EXCLUDE] = (std::vector<mjCBase*>*) &excludes;
object_lists[mjOBJ_EQUALITY] = (std::vector<mjCBase*>*) &equalities;
object_lists[mjOBJ_TENDON] = (std::vector<mjCBase*>*) &tendons;
object_lists[mjOBJ_ACTUATOR] = (std::vector<mjCBase*>*) &actuators;
object_lists[mjOBJ_SENSOR] = (std::vector<mjCBase*>*) &sensors;
object_lists[mjOBJ_NUMERIC] = (std::vector<mjCBase*>*) &numerics;
object_lists[mjOBJ_TEXT] = (std::vector<mjCBase*>*) &texts;
object_lists[mjOBJ_TUPLE] = (std::vector<mjCBase*>*) &tuples;
object_lists[mjOBJ_KEY] = (std::vector<mjCBase*>*) &keys;
object_lists[mjOBJ_PLUGIN] = (std::vector<mjCBase*>*) &plugins;
}
@@ -442,118 +483,20 @@ mjCPlugin* mjCModel::AddPlugin(void) {
// get number of objects of specified type
int mjCModel::NumObjects(mjtObj type) {
switch (type) {
case mjOBJ_BODY:
case mjOBJ_XBODY:
return (int)bodies.size();
case mjOBJ_JOINT:
return (int)joints.size();
case mjOBJ_GEOM:
return (int)geoms.size();
case mjOBJ_SITE:
return (int)sites.size();
case mjOBJ_CAMERA:
return (int)cameras.size();
case mjOBJ_LIGHT:
return (int)lights.size();
case mjOBJ_FLEX:
return (int)flexes.size();
case mjOBJ_MESH:
return (int)meshes.size();
case mjOBJ_SKIN:
return (int)skins.size();
case mjOBJ_HFIELD:
return (int)hfields.size();
case mjOBJ_TEXTURE:
return (int)textures.size();
case mjOBJ_MATERIAL:
return (int)materials.size();
case mjOBJ_PAIR:
return (int)pairs.size();
case mjOBJ_EXCLUDE:
return (int)excludes.size();
case mjOBJ_EQUALITY:
return (int)equalities.size();
case mjOBJ_TENDON:
return (int)tendons.size();
case mjOBJ_ACTUATOR:
return (int)actuators.size();
case mjOBJ_SENSOR:
return (int)sensors.size();
case mjOBJ_NUMERIC:
return (int)numerics.size();
case mjOBJ_TEXT:
return (int)texts.size();
case mjOBJ_TUPLE:
return (int)tuples.size();
case mjOBJ_KEY:
return (int)keys.size();
case mjOBJ_PLUGIN:
return (int)plugins.size();
default:
if (!object_lists[type]) {
return 0;
}
return (int) object_lists[type]->size();
}
// get pointer to specified object
mjCBase* mjCModel::GetObject(mjtObj type, int id) {
if (id>=0 && id<NumObjects(type)) {
switch (type) {
case mjOBJ_BODY:
case mjOBJ_XBODY:
return bodies[id];
case mjOBJ_JOINT:
return joints[id];
case mjOBJ_GEOM:
return geoms[id];
case mjOBJ_SITE:
return sites[id];
case mjOBJ_CAMERA:
return cameras[id];
case mjOBJ_LIGHT:
return lights[id];
case mjOBJ_FLEX:
return flexes[id];
case mjOBJ_MESH:
return meshes[id];
case mjOBJ_SKIN:
return skins[id];
case mjOBJ_HFIELD:
return hfields[id];
case mjOBJ_TEXTURE:
return textures[id];
case mjOBJ_MATERIAL:
return materials[id];
case mjOBJ_PAIR:
return pairs[id];
case mjOBJ_EXCLUDE:
return excludes[id];
case mjOBJ_EQUALITY:
return equalities[id];
case mjOBJ_TENDON:
return tendons[id];
case mjOBJ_ACTUATOR:
return actuators[id];
case mjOBJ_SENSOR:
return sensors[id];
case mjOBJ_NUMERIC:
return numerics[id];
case mjOBJ_TEXT:
return texts[id];
case mjOBJ_TUPLE:
return tuples[id];
case mjOBJ_KEY:
return keys[id];
case mjOBJ_PLUGIN:
return plugins[id];
default:
return 0;
}
if (id < 0 || id >= NumObjects(type)) {
return nullptr;
}
return 0;
return (*object_lists[type])[id];
}
@@ -631,67 +574,31 @@ mjCDef* mjCModel::AddDef(string name, int parentid) {
// find object by name in given list
template <class T>
static T* findobject(string name, vector<T*>& list) {
for (unsigned int i=0; i<list.size(); i++) {
if (list[i]->name == name) {
return list[i];
static T* findobject(std::string_view name, const vector<T*>& list, const mjKeyMap& ids) {
// this can occur in the URDF parser
if (ids.empty()) {
for (unsigned int i=0; i<list.size(); i++) {
if (list[i]->name == name) {
return list[i];
}
}
return nullptr;
}
return 0;
// during model compilation
auto id = ids.find(name);
if (id == ids.end()) {
return nullptr;
}
return list[id->second];
}
// find object in global lists given string type and name
mjCBase* mjCModel::FindObject(mjtObj type, string name) {
switch (type) {
case mjOBJ_BODY:
case mjOBJ_XBODY:
return findobject(name, bodies);
case mjOBJ_JOINT:
return findobject(name, joints);
case mjOBJ_GEOM:
return findobject(name, geoms);
case mjOBJ_SITE:
return findobject(name, sites);
case mjOBJ_CAMERA:
return findobject(name, cameras);
case mjOBJ_LIGHT:
return findobject(name, lights);
case mjOBJ_FLEX:
return findobject(name, flexes);
case mjOBJ_MESH:
return findobject(name, meshes);
case mjOBJ_SKIN:
return findobject(name, skins);
case mjOBJ_HFIELD:
return findobject(name, hfields);
case mjOBJ_TEXTURE:
return findobject(name, textures);
case mjOBJ_MATERIAL:
return findobject(name, materials);
case mjOBJ_PAIR:
return findobject(name, pairs);
case mjOBJ_EXCLUDE:
return findobject(name, excludes);
case mjOBJ_EQUALITY:
return findobject(name, equalities);
case mjOBJ_TENDON:
return findobject(name, tendons);
case mjOBJ_ACTUATOR:
return findobject(name, actuators);
case mjOBJ_SENSOR:
return findobject(name, sensors);
case mjOBJ_NUMERIC:
return findobject(name, numerics);
case mjOBJ_TEXT:
return findobject(name, texts);
case mjOBJ_TUPLE:
return findobject(name, tuples);
case mjOBJ_PLUGIN:
return findobject(name, plugins);
default:
return 0;
if (!object_lists[type]) {
return nullptr;
}
return findobject(name, *object_lists[type], ids[type]);
}
@@ -842,57 +749,56 @@ void mjCModel::IndexAssets(void) {
// if asset name is missing, set to filename
void mjCModel::SetDefaultNames(void) {
template <typename T>
void mjCModel::SetDefaultNames(std::vector<T*>& assets) {
string stripped;
std::map<string, std::vector<int>> names;
// meshes
for (int i=0; i<meshes.size(); i++) {
if (meshes[i]->name.empty()) {
stripped = mjuu_strippath(meshes[i]->file());
meshes[i]->name = mjuu_stripext(stripped);
// name cannot be empty
if (meshes[i]->name.empty()) {
throw mjCError(meshes[i], "empty name in mesh");
}
// use filename if name is missing
for (int i=0; i<assets.size(); i++) {
if (assets[i]->name.empty()) {
stripped = mjuu_strippath(assets[i]->get_file());
assets[i]->name = mjuu_stripext(stripped);
names[assets[i]->name].push_back(i);
}
}
// skins
for (int i=0; i<skins.size(); i++) {
if (skins[i]->name.empty()) {
stripped = mjuu_strippath(skins[i]->file);
skins[i]->name = mjuu_stripext(stripped);
// add suffix if duplicates
for (auto const& [name, indices] : names) {
if (indices.size() > 1) {
for (int i=0; i<indices.size(); i++) {
assets[indices[i]]->name += "_" + std::to_string(i);
}
}
}
}
// throw error if a name is missing
void mjCModel::CheckEmptyNames(void) {
// meshes
for (int i=0; i<meshes.size(); i++) {
if (meshes[i]->name.empty()) {
throw mjCError(meshes[i], "empty name in mesh");
}
}
// hfields
for (int i=0; i<hfields.size(); i++) {
if (hfields[i]->name.empty()) {
stripped = mjuu_strippath(hfields[i]->file);
hfields[i]->name = mjuu_stripext(stripped);
// name cannot be empty
if (hfields[i]->name.empty()) {
throw mjCError(hfields[i], "empty name in height field");
}
throw mjCError(hfields[i], "empty name in height field");
}
}
// textures
for (int i=0; i<textures.size(); i++) {
if (textures[i]->name.empty()) {
stripped = mjuu_strippath(textures[i]->file);
textures[i]->name = mjuu_stripext(stripped);
// name cannot be empty, except for skybox
if (textures[i]->name.empty() && textures[i]->type!=mjTEXTURE_SKYBOX) {
throw mjCError(textures[i], "empty name in texture");
}
if (textures[i]->name.empty() && textures[i]->type!=mjTEXTURE_SKYBOX) {
throw mjCError(textures[i], "empty name in texture");
}
}
// materials: name check only
// materials
for (int i=0; i<materials.size(); i++) {
if (materials[i]->name.empty()) {
throw mjCError(materials[i], "empty name in material");
@@ -2357,6 +2263,14 @@ void mjCModel::CopyObjects(mjModel* m) {
//------------------------------- FUSE STATIC ------------------------------------------------------
template <class T>
static void makelistid(std::vector<T*>& dest, std::vector<T*>& source) {
for (int i=0; i<source.size(); i++) {
source[i]->id = (int)dest.size();
dest.push_back(source[i]);
}
}
// change frame to parent body
static void changeframe(double childpos[3], double childquat[4],
const double bodypos[3], const double bodyquat[4]) {
@@ -2379,23 +2293,9 @@ void mjCModel::FuseReindex(mjCBody* body) {
body->bodies[i]->id : body->weldid);
}
// joints
for (int i=0; i<body->joints.size(); i++) {
body->joints[i]->id = (int)joints.size();
joints.push_back(body->joints[i]);
}
// geoms
for (int i=0; i<body->geoms.size(); i++) {
body->geoms[i]->id = (int)geoms.size();
geoms.push_back(body->geoms[i]);
}
// sites
for (int i=0; i<body->sites.size(); i++) {
body->sites[i]->id = (int)sites.size();
sites.push_back(body->sites[i]);
}
makelistid(joints, body->joints);
makelistid(geoms, body->geoms);
makelistid(sites, body->sites);
// process children recursively
for (int i=0; i<body->bodies.size(); i++) {
@@ -2632,34 +2532,38 @@ static void reassignid(vector<T*>& list) {
// set ids, check for repeated names
template <class T>
static void processlist(vector<T*>& list, string defname, bool checkrepeat=true) {
static void processlist(mjListKeyMap& ids, vector<T*>& list,
mjtObj type, bool checkrepeat = true) {
// loop over list elements
for (int i=0; i<(int)list.size(); i++) {
for (size_t i=0; i < list.size(); i++) {
// check for incompatible id setting; SHOULD NOT OCCUR
if (list[i]->id!=-1 && list[i]->id!=i) {
throw mjCError(list[i], "incompatible id in %s array, position %d", defname.c_str(), i);
throw mjCError(list[i], "incompatible id in %s array, position %d", mju_type2Str(type), i);
}
// id equals position in array
list[i]->id = i;
// add to ids map
ids[type][list[i]->name] = i;
}
// check for repeated names
if (checkrepeat) {
// created vectors with all names
vector<string> allnames;
for (int i=0; i<(int)list.size(); i++) {
for (size_t i=0; i < list.size(); i++) {
if (!list[i]->name.empty()) {
allnames.push_back(list[i]->name);
}
}
// sort and check for duplicates
if (allnames.size()>1) {
if (allnames.size() > 1) {
std::sort(allnames.begin(), allnames.end());
auto adjacent = std::adjacent_find(allnames.begin(), allnames.end());
if (adjacent!=allnames.end()) {
string msg = "repeated name '" + *adjacent + "' in " + defname;
if (adjacent != allnames.end()) {
string msg = "repeated name '" + *adjacent + "' in " + mju_type2Str(type);
throw mjCError(NULL, msg.c_str());
}
}
@@ -2785,33 +2689,21 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs) {
// make lists of objects created in kinematic tree
MakeLists(bodies[0]);
// set object ids and default names, check for repeated names
processlist(bodies, "body");
processlist(joints, "joint");
processlist(geoms, "geom");
processlist(sites, "site");
processlist(cameras, "camera");
processlist(lights, "light");
processlist(flexes, "flex");
processlist(meshes, "mesh");
processlist(skins, "skin");
processlist(hfields, "hfield");
processlist(textures, "texture");
processlist(materials, "material");
processlist(pairs, "pair");
processlist(excludes, "exclude");
processlist(equalities, "equality");
processlist(tendons, "tendon");
processlist(actuators, "actuator");
processlist(sensors, "sensor");
processlist(numerics, "numeric");
processlist(texts, "text");
processlist(tuples, "tuple");
processlist(keys, "key");
processlist(plugins, "plugin");
// fill missing names and check that they are all filled
SetDefaultNames(meshes);
SetDefaultNames(skins);
SetDefaultNames(hfields);
SetDefaultNames(textures);
CheckEmptyNames();
// set default names, convert names into indices
SetDefaultNames();
// set object ids, check for repeated names
for (int i = 0; i < mjNOBJECT; i++) {
if (i != mjOBJ_XBODY && object_lists[i]) {
processlist(ids, *object_lists[i], (mjtObj) i);
}
}
// convert names into indices
IndexAssets();
// mark meshes that need convex hull
@@ -3141,6 +3033,13 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, const mjVFS* vfs) {
// actuator lengthrange computation
LengthRange(m, d);
// save automatically-computed statistics, to disambiguate when saving
extent_auto = m->stat.extent;
meaninertia_auto = m->stat.meaninertia;
meanmass_auto = m->stat.meanmass;
meansize_auto = m->stat.meansize;
copyvec(center_auto, m->stat.center, 3);
// override model statistics if defined by user
if (mjuu_defined(extent)) m->stat.extent = (mjtNum)extent;
if (mjuu_defined(meaninertia)) m->stat.meaninertia = (mjtNum)meaninertia;
@@ -3217,10 +3116,16 @@ bool mjCModel::CopyBack(const mjModel* m) {
option = m->opt;
visual = m->vis;
// runtime-modifiable members of mjStatistic
meansize = m->stat.meansize;
extent = m->stat.extent;
mju_copy3(center, m->stat.center);
// runtime-modifiable members of mjStatistic, if different from computed values
if (m->stat.meaninertia != meaninertia_auto) meaninertia = m->stat.meaninertia;
if (m->stat.meanmass != meanmass_auto) meanmass = m->stat.meanmass;
if (m->stat.meansize != meansize_auto) meansize = m->stat.meansize;
if (m->stat.extent != extent_auto) extent = m->stat.extent;
if (m->stat.center[0] != center_auto[0] ||
m->stat.center[1] != center_auto[1] ||
m->stat.center[2] != center_auto[2]) {
mju_copy3(center, m->stat.center);
}
// qpos0, qpos_spring
for (int i=0; i<njnt; i++) {
+23 -1
View File
@@ -15,6 +15,8 @@
#ifndef MUJOCO_SRC_USER_USER_MODEL_H_
#define MUJOCO_SRC_USER_USER_MODEL_H_
#include <functional>
#include <map>
#include <string>
#include <utility>
#include <vector>
@@ -30,6 +32,9 @@ typedef enum _mjtInertiaFromGeom {
mjINERTIAFROMGEOM_AUTO // use only if inertial element is missing
} mjtInertiaFromGeom;
typedef std::map<std::string, int, std::less<> > mjKeyMap;
typedef std::array<mjKeyMap, mjNOBJECT> mjListKeyMap;
//---------------------------------- class mjCModel ------------------------------------------------
@@ -176,10 +181,13 @@ class mjCModel {
template <class T> // add object of any type, with def parameter
T* AddObjectDef(std::vector<T*>& list, std::string type, mjCDef* def);
template<class T> // if asset name is missing, set to filename
void SetDefaultNames(std::vector<T*>& assets);
//------------------------ compile phases
void MakeLists(mjCBody* body); // make lists of bodies, geoms, joints, sites
void IndexAssets(void); // convert asset names into indices
void SetDefaultNames(void); // if mesh or hfield name is missing, set to filename
void CheckEmptyNames(void); // check empty names
void SetSizes(void); // compute sizes
void AutoSpringDamper(mjModel*);// automatic stiffness and damping computation
void LengthRange(mjModel*, mjData*); // compute actuator lengthrange
@@ -284,6 +292,20 @@ class mjCModel {
std::vector<mjCLight*> lights; // list of lights
//------------------------ internal variables
// array of pointers to each object list (enumerated by type)
std::array<std::vector<mjCBase*>*, mjNOBJECT> object_lists;
// statistics, as computed by mj_setConst
double meaninertia_auto; // mean diagonal inertia, as computed by mj_setConst
double meanmass_auto; // mean body mass, as computed by mj_setConst
double meansize_auto; // mean body size, as computed by mj_setConst
double extent_auto; // spatial extent, as computed by mj_setConst
double center_auto[3]; // center of model, as computed by mj_setConst
// map from object names to ids
mjListKeyMap ids;
bool hasImplicitPluginElem; // already encountered an implicit plugin sensor/actuator
bool compiled; // already compiled flag (cannot be compiled again)
mjCError errInfo; // last error info
+7 -7
View File
@@ -993,7 +993,7 @@ void mjCBody::Compile(void) {
// frame
if (frame) {
mjuu_frameaccum(pos, quat, frame->pos, frame->quat);
mjuu_frameaccumChild(frame->pos, frame->quat, pos, quat);
}
// accumulate rbound, contype, conaffinity over geoms
@@ -1101,7 +1101,7 @@ void mjCFrame::Compile() {
// compile parents and accumulate result
if (frame) {
frame->Compile();
mjuu_frameaccum(pos, quat, frame->pos, frame->quat);
mjuu_frameaccumChild(frame->pos, frame->quat, pos, quat);
}
mjuu_normvec(quat, 4);
@@ -1263,7 +1263,7 @@ int mjCJoint::Compile(void) {
mjuu_zerovec(pos, 3);
} else if (frame) {
double qunit[4] = {1, 0, 0, 0};
mjuu_frameaccum(pos, qunit, frame->pos, frame->quat);
mjuu_frameaccumChild(frame->pos, frame->quat, pos, qunit);
}
// convert reference angles to radians for hinge joints
@@ -1878,7 +1878,7 @@ void mjCGeom::Compile(void) {
// frame
if (frame) {
mjuu_frameaccum(pos, quat, frame->pos, frame->quat);
mjuu_frameaccumChild(frame->pos, frame->quat, pos, quat);
}
}
@@ -1990,7 +1990,7 @@ void mjCSite::Compile(void) {
// frame
if (frame) {
mjuu_frameaccum(pos, quat, frame->pos, frame->quat);
mjuu_frameaccumChild(frame->pos, frame->quat, pos, quat);
}
// normalize quaternion
@@ -2055,7 +2055,7 @@ void mjCCamera::Compile(void) {
// frame
if (frame) {
mjuu_frameaccum(pos, quat, frame->pos, frame->quat);
mjuu_frameaccumChild(frame->pos, frame->quat, pos, quat);
}
// normalize quaternion
@@ -2159,7 +2159,7 @@ void mjCLight::Compile(void) {
// frame
if (frame) {
mjuu_frameaccum(pos, quat, frame->pos, frame->quat);
mjuu_frameaccumChild(frame->pos, frame->quat, pos, quat);
}
// normalize direction, make sure it is not zero
+21 -10
View File
@@ -367,8 +367,8 @@ void mjuu_frame2quat(double* quat, const double* x, const double* y, const doubl
// invert frame transformation
void mjuu_frameinvert(double* newpos, double* newquat,
const double* oldpos, const double* oldquat) {
void mjuu_frameinvert(double newpos[3], double newquat[4],
const double oldpos[3], const double oldquat[4]) {
// position
mjuu_localaxis(newpos, oldpos, oldquat);
newpos[0] = -newpos[0];
@@ -384,28 +384,39 @@ void mjuu_frameinvert(double* newpos, double* newquat,
// accumulate frame transformations (forward kinematics)
void mjuu_frameaccum(double* pos, double* quat,
const double* addpos, const double* addquat) {
void mjuu_frameaccum(double pos[3], double quat[4],
const double childpos[3], const double childquat[4]) {
double mat[9], vec[3], qtmp[4];
mjuu_quat2mat(mat, quat);
mjuu_mulvecmat(vec, addpos, mat);
mjuu_mulvecmat(vec, childpos, mat);
pos[0] += vec[0];
pos[1] += vec[1];
pos[2] += vec[2];
mjuu_mulquat(qtmp, quat, addquat);
mjuu_mulquat(qtmp, quat, childquat);
mjuu_copyvec(quat, qtmp, 4);
}
// accumulate frame transformation in second frame
void mjuu_frameaccumChild(const double pos[3], const double quat[4],
double childpos[3], double childquat[4]) {
double p[] = {pos[0], pos[1], pos[2]};
double q[] = {quat[0], quat[1], quat[2], quat[3]};
mjuu_frameaccum(p, q, childpos, childquat);
mjuu_copyvec(childpos, p, 3);
mjuu_copyvec(childquat, q, 4);
}
// invert frame accumulation
void mjuu_frameaccuminv(double* pos, double* quat,
const double* addpos, const double* addquat) {
void mjuu_frameaccuminv(double pos[3], double quat[4],
const double childpos[3], const double childquat[4]) {
double mat[9], vec[3], qtmp[4];
double qneg[4] = {addquat[0], -addquat[1], -addquat[2], -addquat[3]};
double qneg[4] = {childquat[0], -childquat[1], -childquat[2], -childquat[3]};
mjuu_mulquat(qtmp, quat, qneg);
mjuu_copyvec(quat, qtmp, 4);
mjuu_quat2mat(mat, quat);
mjuu_mulvecmat(vec, addpos, mat);
mjuu_mulvecmat(vec, childpos, mat);
pos[0] -= vec[0];
pos[1] -= vec[1];
pos[2] -= vec[2];
+11 -7
View File
@@ -104,16 +104,20 @@ void mjuu_z2quat(double* quat, const double* vec);
void mjuu_frame2quat(double* quat, const double* x, const double* y, const double* z);
// invert frame transformation
void mjuu_frameinvert(double* newpos, double* newquat,
const double* oldpos, const double* oldquat);
void mjuu_frameinvert(double newpos[3], double newquat[4],
const double oldpos[3], const double oldquat[4]);
// accumulate frame transformations
void mjuu_frameaccum(double* pos, double* quat,
const double* addpos, const double* addquat);
// accumulate frame transformation into parent frame
void mjuu_frameaccum(double pos[3], double quat[4],
const double childpos[3], const double childquat[4]);
// accumulate frame transformation into child frame
void mjuu_frameaccumChild(const double pos[3], const double quat[4],
double childpos[3], double childquat[4]);
// invert frame accumulation
void mjuu_frameaccuminv(double* pos, double* quat,
const double* addpos, const double* addquat);
void mjuu_frameaccuminv(double pos[3], double quat[4],
const double childpos[3], const double childquat[4]);
// convert local_inertia[3] to global_inertia[6]
void mjuu_globalinertia(double* global, const double* local, const double* quat);
+3 -1
View File
@@ -14,6 +14,7 @@
#include <algorithm>
#include <array>
#include <climits>
#include <cmath>
#include <cstddef>
#include <cstdio>
@@ -1004,7 +1005,8 @@ void mjXUtil::WriteAttr(XMLElement* elem, string name, int n, const T* data, con
}
// append number
if (isint(data[i])) {
double doubledata = static_cast<double>(data[i]);
if (doubledata < INT_MAX && doubledata > -INT_MAX && isint(data[i])) {
stream << Round(data[i]);
} else {
stream << data[i];
+9 -1
View File
@@ -12,6 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
mujoco_test(engine_collision_box_test)
target_link_libraries(engine_collision_box_test fixture gmock)
mujoco_test(engine_collision_convex_test)
target_link_libraries(engine_collision_convex_test fixture gmock)
@@ -96,5 +99,10 @@ target_link_libraries(engine_util_spatial_test fixture gmock)
mujoco_test(engine_vfs_test)
target_link_libraries(engine_vfs_test fixture gmock)
mujoco_test(engine_vis_state_test)
mujoco_test(
engine_vis_state_test
PROPERTIES
ENVIRONMENT
"MUJOCO_PLUGIN_DIR=$<TARGET_FILE_DIR:elasticity>"
)
target_link_libraries(engine_vis_state_test fixture gmock)
+248
View File
@@ -0,0 +1,248 @@
// 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 engine/engine_collision_box.c.
#include <string>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mujoco.h>
#include "test/fixture.h"
#include "src/engine/engine_collision_primitive.h"
#include "src/engine/engine_util_misc.h"
namespace mujoco {
namespace {
using MjCollisionBoxTest = MujocoTest;
using ::testing::NotNull;
static const char* const kBad0FilePath =
"engine/testdata/collision_box/boxbox_bad0.xml";
static const char* const kBad1FilePath =
"engine/testdata/collision_box/boxbox_bad1.xml";
TEST_F(MjCollisionBoxTest, BadContacts) {
for (const char* local_path : {kBad0FilePath, kBad1FilePath}) {
const std::string xml_path = GetTestDataFilePath(local_path);
mjModel* model = mj_loadXML(xml_path.c_str(), nullptr, 0, 0);
ASSERT_THAT(model, NotNull());
mjData* data = mj_makeData(model);
mj_forward(model, data);
// allocate contact array and matching arrays
mj_markStack(data);
mjContact* con_raw = (mjContact*) mj_stackAllocByte(
data, mjMAXCONPAIR * sizeof(mjContact), alignof(mjContact));
int* match_raw = mj_stackAllocInt(data, mjMAXCONPAIR);
int* match = mj_stackAllocInt(data, data->ncon);
int g1 = -1;
int g2 = -1;
for (int c = 0; c < data->ncon; c++) {
mjContact* con = data->contact + c;
int g1new = con->geom[0];
int g2new = con->geom[1];
// not box-box: skip
if (model->geom_type[g1new] != mjGEOM_BOX ||
model->geom_type[g2new] != mjGEOM_BOX) {
continue;
}
// same geom pair: skip
if (g1 == g1new && g2 == g2new) {
continue;
}
g1 = g1new;
g2 = g2new;
// call low-level box-box collider
int num = mjc_BoxBox(model, data, con_raw, g1, g2, con->includemargin);
// allocate and clear arrays marking already matched contacts
mju_zeroInt(match_raw, num);
mju_zeroInt(match, data->ncon);
// loop over raw contacts, match with contact array using pos
int nmatched = 0;
for (int i = 0; i < num; i++) {
for (int j = 0; j < data->ncon; j++) {
if (!match[j] &&
con_raw[i].pos[0] == data->contact[j].pos[0] &&
con_raw[i].pos[1] == data->contact[j].pos[1] &&
con_raw[i].pos[2] == data->contact[j].pos[2]) {
match_raw[i] = match[j] = 1;
nmatched++;
}
}
}
// expect some contacts to have been removed
EXPECT_LT(nmatched, num) << local_path;
// get box info
const mjtNum* pos1 = data->geom_xpos + 3 * g1;
const mjtNum* mat1 = data->geom_xmat + 9 * g1;
const mjtNum* size1 = model->geom_size + 3 * g1;
const mjtNum* pos2 = data->geom_xpos + 3 * g2;
const mjtNum* mat2 = data->geom_xmat + 9 * g2;
const mjtNum* size2 = model->geom_size + 3 * g2;
mjtNum margin = mju_max(model->geom_margin[g1], model->geom_margin[g2]);
// loop over raw contacts, find removed
for (int i = 0; i < num; i++) {
if (!match_raw[i]) {
// === check if outside
mjtNum sz1[3] = {size1[0]+margin, size1[1]+margin, size1[2]+margin};
mjtNum sz2[3] = {size2[0]+margin, size2[1]+margin, size2[2]+margin};
// relative distance (1%) outside of which contacts are removed
static mjtNum kRatio = 1.01;
// is the contact outside: 1, inside: -1, within the removal width: 0
int out1 = mju_outsideBox(con_raw[i].pos, pos1, mat1, sz1, kRatio);
int out2 = mju_outsideBox(con_raw[i].pos, pos2, mat2, sz2, kRatio);
// mark as bad if outside one box and not inside the other box
bool outside = (out1 == 1 && out2 != -1) || (out2 == 1 && out1 != -1);
// expect that removed contact was outside
EXPECT_TRUE(outside);
}
}
}
mj_freeStack(data);
mj_deleteData(data);
mj_deleteModel(model);
}
}
static const char* const kDuplicateFilePath =
"engine/testdata/collision_box/boxbox_duplicate.xml";
TEST_F(MjCollisionBoxTest, DuplicateContacts) {
const std::string xml_path = GetTestDataFilePath(kDuplicateFilePath);
mjModel* model = mj_loadXML(xml_path.c_str(), nullptr, 0, 0);
ASSERT_THAT(model, NotNull());
mjData* data = mj_makeData(model);
mj_forward(model, data);
// allocate contact array and matching arrays
mj_markStack(data);
mjContact* con_raw = (mjContact*) mj_stackAllocByte(
data, mjMAXCONPAIR * sizeof(mjContact), alignof(mjContact));
int* match_raw = mj_stackAllocInt(data, mjMAXCONPAIR);
int* match = mj_stackAllocInt(data, data->ncon);
int g1 = -1;
int g2 = -1;
for (int c = 0; c < data->ncon; c++) {
mjContact* con = data->contact + c;
int g1new = con->geom[0];
int g2new = con->geom[1];
// not box-box: skip
if (model->geom_type[g1new] != mjGEOM_BOX ||
model->geom_type[g2new] != mjGEOM_BOX) {
continue;
}
// same geom pair: skip
if (g1 == g1new && g2 == g2new) {
continue;
}
g1 = g1new;
g2 = g2new;
// call low-level box-box collider
int num = mjc_BoxBox(model, data, con_raw, g1, g2, con->includemargin);
// allocate and clear arrays marking already matched contacts
mju_zeroInt(match_raw, num);
mju_zeroInt(match, data->ncon);
// loop over raw contacts, match with contact array using pos
int nmatched = 0;
for (int i = 0; i < num; i++) {
for (int j = 0; j < data->ncon; j++) {
if (!match[j] &&
con_raw[i].pos[0] == data->contact[j].pos[0] &&
con_raw[i].pos[1] == data->contact[j].pos[1] &&
con_raw[i].pos[2] == data->contact[j].pos[2]) {
match_raw[i] = match[j] = 1;
nmatched++;
}
}
}
// expect some contacts to have been removed
EXPECT_LT(nmatched, num);
// loop over raw contacts, find removed
for (int i = 0; i < num; i++) {
if (!match_raw[i]) {
// === check if duplicate
bool duplicate = false;
for (int j = 0; j < num; j++) {
if (duplicate || i == j) {
continue;
}
if (con_raw[i].pos[0] == con_raw[j].pos[0] &&
con_raw[i].pos[1] == con_raw[j].pos[1] &&
con_raw[i].pos[2] == con_raw[j].pos[2]) {
duplicate = true;
}
}
// expect that removed contact was duplicated
EXPECT_TRUE(duplicate);
}
}
}
mj_freeStack(data);
mj_deleteData(data);
mj_deleteModel(model);
}
static const char* const kDeepFilePath =
"engine/testdata/collision_box/boxbox_deep.xml";
TEST_F(MjCollisionBoxTest, DeepPenetration) {
const std::string xml_path = GetTestDataFilePath(kDeepFilePath);
mjModel* model = mj_loadXML(xml_path.c_str(), nullptr, 0, 0);
ASSERT_THAT(model, NotNull());
mjData* data = mj_makeData(model);
mj_forward(model, data);
// expect 4 contact
EXPECT_EQ(data->ncon ,4);
mj_deleteData(data);
mj_deleteModel(model);
}
} // namespace
} // namespace mujoco
+4 -4
View File
@@ -35,17 +35,17 @@ static const char* const kTendonPath =
"engine/testdata/island/tendon_wrap.xml";
static const char* const kFrustumPath =
"engine/testdata/vis_visualize/frustum.xml";
static const char* const kModelPath =
"testdata/model.xml";
static const char* const kFlex = "testdata/flex.xml";
static const char* const kModelPath = "testdata/model.xml";
#define EXPECT_ZERO(exp) EXPECT_EQ(0, exp);
TEST_F(MjvSceneStateTest, CanUpdateFromState) {
for (const char* path :
{kHammockPath, kTendonPath, kModelPath, kFrustumPath}) {
{kHammockPath, kTendonPath, kModelPath, kFrustumPath, kFlex}) {
const std::string xml_path = GetTestDataFilePath(path);
mjModel* model = mj_loadXML(xml_path.c_str(), nullptr, 0, 0);
ASSERT_THAT(model, NotNull());
ASSERT_THAT(model, NotNull()) << "Failed to load model from " << path;
mjData* data = mj_makeData(model);
while (data->time < 2) {
+36
View File
@@ -0,0 +1,36 @@
<mujoco>
<!-- This model leads to bad contacts from mjc_BoxBox -->
<default>
<geom rgba="1 1 1 1" margin="1e-3" gap="1e-3"/>
<site type="sphere" rgba="0 0 0 0" size="0.001"/>
</default>
<worldbody>
<light diffuse="1 1 1" pos="-5 -17.5 15" dir="0.24 0.84 -0.48"/>
<camera name="fixed" pos="0 -25 8" euler="90 0 0"/>
<geom name="ground" pos="0 0 0" quat="1 0 0 0" rgba="0.770 0.670 0.490 1" size="50 50 1" type="plane"/>
<body name="block01" pos="-0.9 0 1.5" quat="1 0 0 0">
<freejoint/>
<geom mass="1" name="geom01" rgba="0.5 0 0 1" size="0.5 0.5 1.5" type="box"/>
</body>
<body name="block02" pos="-0.7 0 3.5" quat="1 0 0 0">
<freejoint/>
<geom mass="1" name="geom02" rgba="0.5 0 0.350 1" size="1.5 0.5 0.5" type="box"/>
</body>
<body name="block03" pos="0.3 0 5.5" quat="1 0 0 0">
<freejoint/>
<geom mass="1" name="geom03" rgba="0 0.5 0.450 1" size="0.5 0.5 1.5" type="box"/>
</body>
<body name="block04" pos="0.200 1e-9 7.5" quat="1 0 0 0">
<freejoint/>
<geom mass="1" name="geom04" rgba="0 0.5 0.099 1" size="1.5 0.5 0.5" type="box"/>
</body>
</worldbody>
<contact>
<exclude body1="world" body2="block01"/>
<exclude body1="block03" body2="block02"/>
</contact>
</mujoco>
+24
View File
@@ -0,0 +1,24 @@
<mujoco>
<!-- This model leads to bad contacts from mjc_BoxBox -->
<statistic meansize=".01" center="-.3 -.14 .055" extent=".2"/>
<worldbody>
<light pos="-.3 -.14 1"/>
<geom type="plane" size="3 3 .01" pos="-0.025 -0.295 0"/>
<body pos="-.23 -.1 0" euler="0 0 30">
<geom type="cylinder" size=".01 .0175" pos="-.09 0 .0175"/>
<body pos="0 0 .03">
<joint axis="0 1 0"/>
<geom type="cylinder" size=".005 .039" zaxis="0 1 0" rgba=".84 .15 .33 1"/>
<geom type="box" size=".1 .02 .005" pos="0 0 .01" rgba=".84 .15 .33 1"/>
</body>
</body>
<body pos="-.3 -.14 .055" euler="0 0 -30">
<freejoint/>
<geom type="box" size=".01 .01 .01" rgba=".0 .7 .79 1"/>
</body>
</worldbody>
</mujoco>
+11
View File
@@ -0,0 +1,11 @@
<mujoco>
<!-- Box deeply penetrating another box -->
<worldbody>
<geom type="box" size="1 1 1" rgba=".5 .5 .5 .5"/>
<body pos=".1 .2 .3">
<freejoint/>
<geom type="box" size=".2 .2 .2"/>
</body>
</worldbody>
</mujoco>
+11
View File
@@ -0,0 +1,11 @@
<mujoco>
<!-- This model leads to duplicate contacts from mjc_BoxBox -->
<worldbody>
<geom type="box" size="1 1 1"/>
<body pos="0 0 2">
<freejoint/>
<geom type="box" size="1 1 1"/>
</body>
</worldbody>
</mujoco>
+11 -2
View File
@@ -26,8 +26,17 @@ test_model() {
echo "Testing $model" >&2
local iterations=10
if [[ "$model" == */composite/particle.xml && ${TESTSPEED_ASAN:-0} != 0 ]]; then
iterations=2
# for particularly slow models, only run 2 steps under ASAN, or skip.
if [[ ${TESTSPEED_ASAN:-0} != 0 ]]; then
if [[ "$model" == */composite/particle.xml ]]; then
# this test can take several minutes under ASAN
return 0
fi
if [[ "$model" == */benchmark/testdata/humanoid200.xml
|| "$model" == */engine/testdata/collision_convex/stacked_boxes.xml
]]; then
iterations=2
fi
fi
# run testspeed, writing its output to stderr.
+10
View File
@@ -0,0 +1,10 @@
<mujoco>
<asset>
<mesh file="cube.obj"/>
<mesh file="cube.obj"/>
</asset>
<worldbody>
<geom type="mesh" mesh="cube_0"/>
<geom type="mesh" mesh="cube_1"/>
</worldbody>
</mujoco>
+48 -1
View File
@@ -14,6 +14,7 @@
// Tests for user/user_model.cc.
#include <array>
#include <string>
#include <gmock/gmock.h>
@@ -21,6 +22,7 @@
#include <absl/strings/str_format.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mujoco.h>
#include "src/cc/array_safety.h"
#include "test/fixture.h"
namespace mujoco {
@@ -28,16 +30,41 @@ namespace {
using ::testing::DoubleNear;
using ::testing::ElementsAre;
using ::testing::HasSubstr;
using ::testing::IsNull;
using ::testing::NotNull;
using UserDataTest = MujocoTest;
static std::vector<mjtNum> GetRow(const mjtNum* array, int ncolumn, int row) {
return std::vector<mjtNum>(array + ncolumn * row,
array + ncolumn * (row + 1));
}
// ----------------------------- test mjCModel --------------------------------
using UserCModelTest = MujocoTest;
TEST_F(UserCModelTest, RepeatedNames) {
static constexpr char xml[] = R"(
<mujoco>
<worldbody>
<body name="body1">
<joint axis="0 1 0" name="joint1"/>
<geom size="1" name="geom1"/>
<geom size="1" name="geom1"/>
</body>
</worldbody>
</mujoco>)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(model, IsNull());
EXPECT_THAT(error.data(), HasSubstr("repeated name 'geom1' in geom"));
}
// ------------- test automatic inference of nuser_xxx -------------------------
using UserDataTest = MujocoTest;
TEST_F(UserDataTest, AutoNUserBody) {
static constexpr char xml[] = R"(
<mujoco>
@@ -190,6 +217,26 @@ TEST_F(UserDataTest, AutoNUserSensor) {
mj_deleteModel(m);
}
// ------------- test duplicate names ------------------------------------------
TEST_F(UserDataTest, DuplicateNames) {
static const char* const kFilePath = "user/testdata/load_twice.xml";
const std::string xml_path = GetTestDataFilePath(kFilePath);
std::array<char, 1024> error;
mjModel* m = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size());
EXPECT_THAT(m, NotNull()) << error.data();
EXPECT_THAT(m->nmesh, 2);
for (int i = 0; i < m->nmesh; i++) {
char mesh_name[mjMAXUINAME] = "";
util::strcat_arr(mesh_name, m->names + m->name_meshadr[i]);
EXPECT_THAT(std::string(mesh_name), "cube_" + std::to_string(i));
}
mj_deleteModel(m);
}
// ------------- test fusestatic -----------------------------------------------
using FuseStaticTest = MujocoTest;
+43 -21
View File
@@ -1404,38 +1404,47 @@ TEST_F(MujocoTest, Frame) {
<mujoco>
<worldbody>
<frame euler="0 0 30">
<geom size=".1" euler="0 0 20"/>
<geom name="0" size=".1" euler="0 0 20"/>
</frame>
<frame axisangle="0 0 1 90">
<frame axisangle="0 1 0 90">
<geom size=".1"/>
<frame axisangle="0 1 0 90">
<frame axisangle="0 0 1 90">
<geom name="1" size=".1"/>
</frame>
</frame>
<frame pos="0 1 0" euler="0 20 0">
<geom name="2" pos=".5 .6 .7" size=".1" euler="30 0 0"/>
</frame>
<body>
<frame pos="0 1 0">
<geom size=".1" pos="0 1 0"/>
<geom name="3" size=".1" pos="0 1 0"/>
<body pos="1 0 0">
<geom size=".1" pos="0 0 1"/>
<geom name="4" size=".1" pos="0 0 1"/>
</body>
</frame>
</body>
<body>
<geom size=".1"/>
<geom name="5" size=".1"/>
<frame euler="90 0 0">
<joint type="hinge" axis="0 0 1"/>
</frame>
</body>
<body pos="0 1 0" euler="0 20 0">
<geom name="6" pos=".5 .6 .7" size=".1" euler="30 0 0"/>
</body>
</worldbody>
</mujoco>
)";
constexpr mjtNum eps = 1e-14;
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m, testing::NotNull()) << error.data();
EXPECT_EQ(m->nbody, 4);
EXPECT_EQ(m->nbody, 5);
// geom quat transformed to euler = 0 0 50
EXPECT_NEAR(m->geom_quat[0], mju_cos(25. * mjPI / 180.), 1e-3);
@@ -1444,15 +1453,15 @@ TEST_F(MujocoTest, Frame) {
EXPECT_NEAR(m->geom_quat[3], mju_sin(25. * mjPI / 180.), 1e-3);
// geom transformed to frame 0 1 0, 0 0 1, 1 0 0
EXPECT_NEAR(m->geom_quat[4], .5, 1e-6);
EXPECT_NEAR(m->geom_quat[5], .5, 1e-6);
EXPECT_NEAR(m->geom_quat[6], .5, 1e-6);
EXPECT_NEAR(m->geom_quat[7], .5, 1e-6);
EXPECT_NEAR(m->geom_quat[4], .5, eps);
EXPECT_NEAR(m->geom_quat[5], .5, eps);
EXPECT_NEAR(m->geom_quat[6], .5, eps);
EXPECT_NEAR(m->geom_quat[7], .5, eps);
// geom pos transformed from 0 1 0 to 0 2 0
EXPECT_EQ(m->geom_pos[6], 0);
EXPECT_EQ(m->geom_pos[7], 2);
EXPECT_EQ(m->geom_pos[8], 0);
EXPECT_EQ(m->geom_pos[ 9], 0);
EXPECT_EQ(m->geom_pos[10], 2);
EXPECT_EQ(m->geom_pos[11], 0);
// body pos transformed from 1 0 0 to 1 1 0
EXPECT_EQ(m->body_pos[6], 1);
@@ -1460,16 +1469,29 @@ TEST_F(MujocoTest, Frame) {
EXPECT_EQ(m->body_pos[8], 0);
// nested geom pos not transformed
EXPECT_EQ(m->geom_pos[ 9], 0);
EXPECT_EQ(m->geom_pos[10], 0);
EXPECT_EQ(m->geom_pos[11], 1);
EXPECT_EQ(m->geom_pos[12], 0);
EXPECT_EQ(m->geom_pos[13], 0);
EXPECT_EQ(m->geom_pos[14], 1);
// joint axis transformed to 0 -1 0
EXPECT_NEAR(m->jnt_axis[0], 0, 1e-6);
EXPECT_NEAR(m->jnt_axis[1], -1, 1e-6);
EXPECT_NEAR(m->jnt_axis[2], 0, 1e-6);
EXPECT_NEAR(m->jnt_axis[0], 0, eps);
EXPECT_NEAR(m->jnt_axis[1], -1, eps);
EXPECT_NEAR(m->jnt_axis[2], 0, eps);
mjData* d = mj_makeData(m);
mj_kinematics(m, d);
// body and frame equivalence geom 2 vs 6
EXPECT_NEAR(d->geom_xpos[6], d->geom_xpos[18], eps);
EXPECT_NEAR(d->geom_xpos[7], d->geom_xpos[19], eps);
EXPECT_NEAR(d->geom_xpos[8], d->geom_xpos[20], eps);
EXPECT_NEAR(d->geom_xmat[18], d->geom_xmat[54], eps);
EXPECT_NEAR(d->geom_xmat[19], d->geom_xmat[55], eps);
EXPECT_NEAR(d->geom_xmat[20], d->geom_xmat[56], eps);
EXPECT_NEAR(d->geom_xmat[21], d->geom_xmat[57], eps);
mj_deleteModel(m);
mj_deleteData(d);
}
} // namespace
+51 -1
View File
@@ -1135,7 +1135,7 @@ using DecompilerTest = MujocoTest;
TEST_F(DecompilerTest, SavesStatitics) {
static constexpr char xml[] = R"(
<mujoco>
<statistic meansize="2" extent="3" center="4 5 6"/>
<statistic meansize="2" extent="3" center="4 5 6" meanmass="7" meaninertia="8"/>
</mujoco>
)";
mjModel* model = LoadModelFromString(xml);
@@ -1145,10 +1145,60 @@ TEST_F(DecompilerTest, SavesStatitics) {
model->stat.center[0] = 9;
model->stat.center[1] = 10;
model->stat.center[2] = 11;
model->stat.meanmass = 12;
model->stat.meaninertia = 13;
std::string saved_xml = SaveAndReadXml(model);
EXPECT_THAT(saved_xml, HasSubstr("meansize=\"7\""));
EXPECT_THAT(saved_xml, HasSubstr("extent=\"8\""));
EXPECT_THAT(saved_xml, HasSubstr("center=\"9 10 11\""));
EXPECT_THAT(saved_xml, HasSubstr("meanmass=\"12\""));
EXPECT_THAT(saved_xml, HasSubstr("meaninertia=\"13\""));
mj_deleteModel(model);
}
TEST_F(DecompilerTest, DoesntSaveInferredStatitics) {
static constexpr char xml[] = R"(
<mujoco>
<worldbody>
<body>
<geom size="0.2"/>
</body>
</worldbody>
</mujoco>
)";
mjModel* model = LoadModelFromString(xml);
ASSERT_THAT(model, NotNull());
std::string saved_xml = SaveAndReadXml(model);
EXPECT_THAT(saved_xml, Not(HasSubstr("meansize")));
EXPECT_THAT(saved_xml, Not(HasSubstr("meanmass")));
EXPECT_THAT(saved_xml, Not(HasSubstr("meaninertia")));
EXPECT_THAT(saved_xml, Not(HasSubstr("center")));
EXPECT_THAT(saved_xml, Not(HasSubstr("extent")));
EXPECT_THAT(saved_xml, Not(HasSubstr("statistic")));
mj_deleteModel(model);
}
TEST_F(DecompilerTest, VeryLargeNumbers) {
static constexpr char xml[] = R"(
<mujoco>
<compiler angle="radian"/>
<worldbody>
<camera focal="16777217 1" sensorsize="1 1" resolution="100 100"/>
<body pos="1e+20 0 0">
<geom size="1"/>
<joint axis="1 0 0" range="-1e+10 1e+10"/>
</body>
</worldbody>
</mujoco>
)";
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
ASSERT_THAT(model, NotNull()) << error.data();
std::string saved_xml = SaveAndReadXml(model);
// note, focal is float and loses precision 16777217 -> 16777216
EXPECT_THAT(saved_xml, HasSubstr("focal=\"16777216 1\""));
EXPECT_THAT(saved_xml, HasSubstr("pos=\"1e+20 0 0\""));
EXPECT_THAT(saved_xml, HasSubstr("range=\"-1e+10 1e+10\""));
mj_deleteModel(model);
}
@@ -37,7 +37,7 @@ public class MujocoBinaryRetriever {
if (AssetDatabase.LoadMainAssetAtPath(mujocoPath + "/mujoco.dylib") == null) {
File.Copy(
"/Applications/MuJoCo.app/Contents/Frameworks" +
"/mujoco.framework/Versions/Current/libmujoco.3.0.2.dylib",
"/mujoco.framework/Versions/Current/libmujoco.3.1.2.dylib",
mujocoPath + "/mujoco.dylib");
AssetDatabase.Refresh();
}
@@ -45,7 +45,7 @@ public class MujocoBinaryRetriever {
if (AssetDatabase.LoadMainAssetAtPath(mujocoPath + "/libmujoco.so") == null) {
File.Copy(
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) +
"/.mujoco/mujoco-3.0.2/lib/libmujoco.so.3.0.2",
"/.mujoco/mujoco-3.1.2/lib/libmujoco.so.3.1.2",
mujocoPath + "/libmujoco.so");
AssetDatabase.Refresh();
}
+6 -4
View File
@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#if UNITY_EDITOR
using System;
using UnityEditor;
using UnityEngine;
@@ -23,7 +22,7 @@ namespace Mujoco {
// and left mouse drag will apply a force on the body. Holding shift down will change the applied
// force direction between World XZ plane and Y[camera-up].
[CustomEditor(typeof(MjBody))]
[CustomEditor(typeof(MjComponent), true)]
public class MjMouseSpring : Editor {
private bool _lastShiftKeyState = false;
@@ -102,7 +101,11 @@ namespace Mujoco {
return;
}
MjBody body = target as MjBody;
var targetObject = target as MjComponent;
MjBody body = targetObject.GetComponentInParent<MjBody>();
if(!body)
return;
Vector3 bodyPosition =
body != null ? body.transform.position : Vector3.zero;
var scene = MjScene.Instance;
@@ -188,4 +191,3 @@ namespace Mujoco {
}
}
}
#endif
@@ -22,7 +22,7 @@ namespace Mujoco {
[CustomEditor(typeof(MjShapeComponent), true)]
[CanEditMultipleObjects]
public class MjShapeComponentEditor : Editor {
public class MjShapeComponentEditor : MjMouseSpring {
public override void OnInspectorGUI() {
+10 -5
View File
@@ -108,7 +108,7 @@ public const int mjMAXLINEPNT = 1000;
public const int mjMAXPLANEGRID = 200;
public const bool THIRD_PARTY_MUJOCO_MJXMACRO_H_ = true;
public const bool THIRD_PARTY_MUJOCO_MUJOCO_H_ = true;
public const int mjVERSION_HEADER = 302;
public const int mjVERSION_HEADER = 312;
// ------------------------------------Enums------------------------------------
@@ -191,10 +191,11 @@ public enum mjtGeom : int{
mjGEOM_ARROW1 = 101,
mjGEOM_ARROW2 = 102,
mjGEOM_LINE = 103,
mjGEOM_FLEX = 104,
mjGEOM_SKIN = 105,
mjGEOM_LABEL = 106,
mjGEOM_TRIANGLE = 107,
mjGEOM_LINEBOX = 104,
mjGEOM_FLEX = 105,
mjGEOM_SKIN = 106,
mjGEOM_LABEL = 107,
mjGEOM_TRIANGLE = 108,
mjGEOM_NONE = 1001,
}
public enum mjtCamLight : int{
@@ -301,6 +302,7 @@ public enum mjtObj : int{
mjOBJ_TUPLE = 23,
mjOBJ_KEY = 24,
mjOBJ_PLUGIN = 25,
mjNOBJECT = 26,
}
public enum mjtConstraint : int{
mjCNSTR_EQUALITY = 0,
@@ -6093,6 +6095,7 @@ public unsafe struct model {
public int nnames;
public int npaths;
public int nsensordata;
public int narena;
public mjOption_ opt;
public mjVisual_ vis;
public mjStatistic_ stat;
@@ -6285,6 +6288,7 @@ public unsafe struct data {
public int* wrap_obj;
public double* ten_length;
public double* wrap_xpos;
public double* bvh_aabb_dyn;
public byte* bvh_active;
public int* island_dofadr;
public int* island_dofind;
@@ -6294,6 +6298,7 @@ public unsafe struct data {
public double* flexvert_xpos;
public mjContact_* contact;
public double* efc_force;
public void* arena;
}
[StructLayout(LayoutKind.Sequential)]
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "org.mujoco",
"displayName": "MuJoCo",
"version": "3.0.2",
"version": "3.1.2",
"description": "MuJoCo importer and runtime plug-in",
"dependencies": {},
"author": {