Add Signed Distance Field to collision geometries.

PiperOrigin-RevId: 557507088
Change-Id: I358a642407aee1ba8dfc9d405eb9a4f609435fe9
This commit is contained in:
Alessio Quaglino
2023-08-16 09:14:29 -07:00
committed by Copybara-Service
parent 6245edae28
commit fdb041580c
57 changed files with 3229 additions and 81 deletions
+2 -1
View File
@@ -83,13 +83,14 @@ target_include_directories(
add_subdirectory(plugin/elasticity)
add_subdirectory(plugin/sensor)
add_subdirectory(plugin/sdf)
add_subdirectory(src/engine)
add_subdirectory(src/user)
add_subdirectory(src/xml)
add_subdirectory(src/render)
add_subdirectory(src/ui)
target_compile_definitions(mujoco PRIVATE _GNU_SOURCE CCD_STATIC_DEFINE MUJOCO_DLL_EXPORTS)
target_compile_definitions(mujoco PRIVATE _GNU_SOURCE CCD_STATIC_DEFINE MUJOCO_DLL_EXPORTS -DMC_IMPLEM_ENABLE)
if(MUJOCO_ENABLE_AVX_INTRINSICS)
target_compile_definitions(mujoco PUBLIC mjUSEPLATFORMSIMD)
endif()
+19
View File
@@ -26,6 +26,10 @@ set(MUJOCO_DEP_VERSION_tinyobjloader
1421a10d6ed9742f5b2c1766d22faa6cfbc56248
CACHE STRING "Version of `tinyobjloader` to be fetched."
)
set(MUJOCO_DEP_VERSION_MarchingCubeCpp
5b79e5d6bded086a0abe276a4b5a69fc17ae9bf1
CACHE STRING "Version of `MarchingCubeCpp` to be fetched."
)
set(MUJOCO_DEP_VERSION_ccd
7931e764a19ef6b21b443376c699bbc9c6d4fba8 # v2.1
CACHE STRING "Version of `ccd` to be fetched."
@@ -55,6 +59,7 @@ set(MUJOCO_DEP_VERSION_benchmark
)
mark_as_advanced(MUJOCO_DEP_VERSION_lodepng)
mark_as_advanced(MUJOCO_DEP_VERSION_MarchingCubeCpp)
mark_as_advanced(MUJOCO_DEP_VERSION_tinyxml2)
mark_as_advanced(MUJOCO_DEP_VERSION_tinyobjloader)
mark_as_advanced(MUJOCO_DEP_VERSION_ccd)
@@ -99,6 +104,20 @@ if(NOT TARGET lodepng)
endif()
endif()
if(NOT TARGET marchingcubecpp)
FetchContent_Declare(
marchingcubecpp
GIT_REPOSITORY https://github.com/aparis69/MarchingCubeCpp.git
GIT_TAG ${MUJOCO_DEP_VERSION_MarchingCubeCpp}
)
FetchContent_GetProperties(marchingcubecpp)
if(NOT marchingcubecpp_POPULATED)
FetchContent_Populate(marchingcubecpp)
include_directories(${marchingcubecpp_SOURCE_DIR})
endif()
endif()
set(QHULL_ENABLE_TESTING OFF)
findorfetch(
+51 -1
View File
@@ -1558,6 +1558,24 @@ The full list of processing steps applied by the compiler to each mesh is as fol
Reference orientation relative to which the 3D vertex coordinates and normals are defined. The conjugate of this
quaternion is used to rotate the positions and normals. The model compiler normalizes the quaternion automatically.
.. _mesh-plugin:
:el-prefix:`mesh/` |-| **plugin** (?)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Associate this mesh with an :ref:`engine plugin<exPlugin>`. Either :at:`plugin` or :at:`instance` are required.
.. _mesh-plugin-plugin:
:at:`plugin`: :at-val:`string, optional`
Plugin identifier, used for implicit plugin instantiation.
.. _mesh-plugin-instance:
:at:`instance`: :at-val:`string, optional`
Instance name, used for explicit plugin instantiation.
.. _asset-skin:
@@ -1977,6 +1995,16 @@ adjust it properly through the XML.
:at:`mpr_tolerance`: :at-val:`real, "1e-6"`
Tolerance threshold used for early termination of the MPR algorithm.
.. _option-sdf_iterations:
:at:`sdf_iterations`: :at-val:`int, "10"`
Number of iterations used for Signed Distance Field collisions (per initial point).
.. _option-sdf_initpoints:
:at:`sdf_initpoints`: :at-val:`int, "40"`
Number of starting points used for fining contacts with Signed Distance Field collisions.
.. _option-flag:
@@ -2513,7 +2541,7 @@ helps clarify the role of bodies and geoms in MuJoCo.
.. _body-geom-type:
:at:`type`: :at-val:`[plane, hfield, sphere, capsule, ellipsoid, cylinder, box, mesh], "sphere"`
:at:`type`: :at-val:`[plane, hfield, sphere, capsule, ellipsoid, cylinder, box, mesh, sdf], "sphere"`
Type of geometric shape. The keywords have the following meaning: The **plane** type defines a plane which is
infinite for collision detection purposes. It can only be attached to the world body or static children of the world.
The plane passes through a point specified via the pos attribute. It is normal to the Z axis of the geom's local
@@ -2576,6 +2604,11 @@ helps clarify the role of bodies and geoms in MuJoCo.
they are offset by the translation and rotation that were needed to center and align the mesh asset in its own
coordinate frame. Recall the discussion of centering and alignment in the :ref:`mesh <asset-mesh>` element.
The **sdf** type defines a signed distance field (SDF, also referred to as signed distance function). In order to
visualize the SDF, a custom mesh must be specified using the :ref:`mesh/plugin <mesh-plugin>` attribute. See the
`model/plugin/sdf/ <https://github.com/deepmind/mujoco/tree/main/model/plugin/sdf>`__ directory for example models
with SDF geometries. For more details regarding SDF plugins, see the :ref:`Extensions chapter<exWriting>`.
.. _body-geom-contype:
:at:`contype`: :at-val:`int, "1"`
@@ -2832,6 +2865,23 @@ helps clarify the role of bodies and geoms in MuJoCo.
:at:`user`: :at-val:`real(nuser_geom), "0 0 ..."`
See :ref:`CUser`.
.. _geom-plugin:
:el-prefix:`geom/` |-| **plugin** (?)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Associate this geom with an :ref:`engine plugin<exPlugin>`. Either :at:`plugin` or :at:`instance` are required.
.. _geom-plugin-plugin:
:at:`plugin`: :at-val:`string, optional`
Plugin identifier, used for implicit plugin instantiation.
.. _geom-plugin-instance:
:at:`instance`: :at-val:`string, optional`
Instance name, used for explicit plugin instantiation.
.. _body-site:
+29 -1
View File
@@ -167,6 +167,20 @@
| | | | :ref:`refpos<asset-mesh-refpos>` | :ref:`refquat<asset-mesh-refquat>` | :ref:`scale<asset-mesh-scale>` | :ref:`smoothnormal<asset-mesh-smoothnormal>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_2| mesh |br| |_2| |L| | | .. table:: |
| :ref:`plugin | \* | :class: mjcf-attributes |
| <mesh-plugin>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`plugin<mesh-plugin-plugin>` | :ref:`instance<mesh-plugin-instance>` | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_3| plugin |br| |_3| |L| | | .. table:: |
| :ref:`config | \* | :class: mjcf-attributes |
| <plugin-config>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`key<plugin-config-key>` | :ref:`value<plugin-config-value>` | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| asset |br| |_| |L| | | .. table:: |
| :ref:`skin | \* | :class: mjcf-attributes |
| <asset-skin>` | | |
@@ -212,7 +226,7 @@
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`cone<option-cone>` | :ref:`jacobian<option-jacobian>` | :ref:`solver<option-solver>` | :ref:`iterations<option-iterations>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`noslip_iterations<option-noslip_iterations>` | :ref:`mpr_iterations<option-mpr_iterations>` | | | |
| | | | :ref:`noslip_iterations<option-noslip_iterations>` | :ref:`mpr_iterations<option-mpr_iterations>` | :ref:`sdf_iterations<option-sdf_iterations>` | :ref:`sdf_initpoints<option-sdf_initpoints>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| option |br| |_| |L| | | .. table:: |
@@ -313,6 +327,20 @@
| | | | :ref:`user<body-geom-user>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_2| geom |br| |_2| |L| | | .. table:: |
| :ref:`plugin | \* | :class: mjcf-attributes |
| <geom-plugin>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`plugin<geom-plugin-plugin>` | :ref:`instance<geom-plugin-instance>` | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_3| plugin |br| |_3| |L| | | .. table:: |
| :ref:`config | \* | :class: mjcf-attributes |
| <plugin-config>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`key<plugin-config-key>` | :ref:`value<plugin-config-value>` | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| body |br| |_| |L| | | .. table:: |
| :ref:`site | \* | :class: mjcf-attributes |
| <body-site>` | | |
+14 -3
View File
@@ -8,15 +8,26 @@ Upcoming version (not yet released)
General
^^^^^^^
.. youtube:: Vc1tq0fFvQA
.. youtube:: QewlEqIZi1o
:align: right
:width: 240px
1. Added constraint island discovery in :ref:`mj_island`. Constraint islands are disjoint sets of constraints
1. Added new signed distance field (SDF) collision primitive. SDFs can take any shape and are not constrained to be
convex. Collision points are found by minimizing the maximum of the two colliding SDFs via gradient descent.
- Added new SDF plugin for defining implicit geometries. The plugin must define methods computing an SDF and its
gradient at query points See the :ref:`documentation<exWriting>` for more details.
- Increased ``mjMAXCONPAIR`` to 100.
.. youtube:: Vc1tq0fFvQA
:align: right
:width: 240px
#. Added constraint island discovery in :ref:`mj_island`. Constraint islands are disjoint sets of constraints
and degrees-of-freedom that do not interact. In a future release the constraint solver will be refactored to
exploit the disjoint structure. Island discovery can be activated using a new :ref:`enable flag<option-flag-island>`
which will be removed after the refactor. If island discovery is enabled, geoms, contacts and
tendons will be colored according to the corresponding island, see video:
tendons will be colored according to the corresponding island, see video.
#. Added a new :ref:`dyntype<actuator-general-dyntype>`, ``filterexact``, which updates first-order filter states with
the exact formula rather than with Euler integration.
#. Added an actuator attribute, :ref:`actearly<actuator-general-actearly>`, which uses semi-implicit integration for
+22
View File
@@ -416,6 +416,7 @@ typedef enum mjtGeom_ { // type of geometric shape
mjGEOM_CYLINDER, // cylinder
mjGEOM_BOX, // box
mjGEOM_MESH, // mesh
mjGEOM_SDF, // signed distance field
mjNGEOMTYPES, // number of regular geom types
@@ -703,6 +704,10 @@ struct mjOption_ { // physics options
int mpr_iterations; // maximum number of MPR solver iterations
int disableflags; // bit flags for disabling standard features
int enableflags; // bit flags for enabling optional features
// sdf collision settings
int sdf_initpoints; // number of starting points for gradient descent
int sdf_iterations; // max number of iterations for gradient descent
};
typedef struct mjOption_ mjOption;
struct mjVisual_ { // visualization options
@@ -969,6 +974,7 @@ struct mjModel_ {
int* geom_matid; // material id for rendering; -1: none (ngeom x 1)
int* geom_group; // group for visibility (ngeom x 1)
int* geom_priority; // geom contact priority (ngeom x 1)
int* geom_plugin; // plugin instance id; -1: not in use (ngeom x 1)
mjtByte* geom_sameframe; // same as body frame (1) or iframe (2) (ngeom x 1)
mjtNum* geom_solmix; // mixing coef for solref/imp in geom pair (ngeom x 1)
mjtNum* geom_solref; // constraint solver reference: contact (ngeom x mjNREF)
@@ -1262,6 +1268,7 @@ typedef enum mjtPluginCapabilityBit_ {
mjPLUGIN_ACTUATOR = 1<<0, // actuator forces
mjPLUGIN_SENSOR = 1<<1, // sensor measurements
mjPLUGIN_PASSIVE = 1<<2, // passive forces
mjPLUGIN_SDF = 1<<3, // signed distance fields
} mjtPluginCapabilityBit;
struct mjpPlugin_ {
const char* name; // globally unique name identifying the plugin
@@ -1298,6 +1305,20 @@ struct mjpPlugin_ {
// called by mjv_updateScene (optional)
void (*visualize)(const mjModel*m, mjData* d, const mjvOption* opt, mjvScene* scn, int instance);
// methods specific to signed distance fields (optional)
// signed distance from the surface
mjtNum (*sdf_distance)(const mjtNum point[3], const mjData* d, int instance);
// gradient of distance with respect to local coordinates
void (*sdf_gradient)(mjtNum gradient[3], const mjtNum point[3], const mjData* d, int instance);
// called during compilation for marching cubes
mjtNum (*sdf_staticdistance)(const mjtNum point[3], const mjtNum* attributes);
// bounding box of implicit surface
void (*sdf_aabb)(mjtNum aabb[6], const mjtNum* attributes);
};
typedef struct mjpPlugin_ mjpPlugin;
typedef enum mjtGridPos_ { // grid position for overlay
@@ -1719,6 +1740,7 @@ typedef enum mjtVisFlag_ { // flags enabling model element visualization
mjVIS_SKIN, // skin
mjVIS_MIDPHASE, // mid-phase bounding volume hierarchy
mjVIS_MESHBVH, // mesh bounding volume hierarchy
mjVIS_SDFITER, // iterations of SDF gradient descent
mjNVISFLAG // number of visualization flags
} mjtVisFlag;
+51 -1
View File
@@ -84,6 +84,7 @@ supported plugin capabilities are:
* Actuator plugin
* Sensor plugin
* Passive force plugin
* Signed distance field plugin
Additional capabilities will be added in the future as required.
@@ -234,7 +235,7 @@ provided for this scan-and-load use case.
Writing plugins
^^^^^^^^^^^^^^^
This section, targeted at developers, is not yet written. We encourage people who wish to write their own plugins
This section, targeted at developers, is incomplete. We encourage people who wish to write their own plugins
to contact the MuJoCo development team for help. A good starting point for experienced developers is the
`associated tests <https://github.com/deepmind/mujoco/blob/main/test/engine/engine_plugin_test.cc>`_ and the first-party
plugins in the `first-party plugin directory <https://github.com/deepmind/mujoco/tree/main/plugin>`_.
@@ -247,6 +248,55 @@ A future version of this section will include:
* Things that developers need to keep in mind in order to ensure that plugins function correctly when :ref:`mjData` is
copied, stepped, or reset.
Currently, there are three directories of first-party plugins:
* **elasticity:** The plugins in the `elasticity/ <https://github.com/deepmind/mujoco/tree/main/plugin/elasticity>`__
directory are passive forces based on continuum mechanics for 1-dimensional and 3-dimensional bodies. The
1D model is invariant under rotations and captures the large deformation of elastic cables, decoupling twisting and
bending strains. The 3D solid is a
`Saint Venant-Kirchhoff <https://en.wikipedia.org/wiki/Hyperelastic_material#Saint_Venant%E2%80%93Kirchhoff_model>`__
model discretized with piecewise linear finite elements, which is suitable for large deformations with small strains.
* **sensor:** The plugins in the `sensor/ <https://github.com/deepmind/mujoco/tree/main/plugin/sensor>`__
directory implement custom sensors. Currently the sole sensor plugin is the touch grid sensor, see the
`README <https://github.com/deepmind/mujoco/blob/main/plugin/sensor/README.md>`__ for details.
* **sdf:** The plugins in the `sdf/ <https://github.com/deepmind/mujoco/tree/main/plugin/sdf>`__
directory specify custom shapes in a mesh-free manner, by defining methods computing a signed distance field and its
gradient at query points. This shape then acts as a new geom type in the collision table at the top of
`engine_collision_driver.c <https://github.com/deepmind/mujoco/blob/main/src/engine/engine_collision_driver.c>`__.
Collision points are found by minimizing the maximum of the two colliding SDFs via gradient descent.
Because SDFs are non-convex, multiple starting points are required in order to converge to multiple local minima.
The number of starting points is set using :ref:`sdf_initpoints<option-sdf_initpoints>`, and are
initialized using the Halton sequence inside the intersection of the axis-aligned bounding boxes.
The number of gradient descent iterations is set using :ref:`sdf_iterations<option-sdf_iterations>`.
While *exact* SDFs---encoding the precise signed distance to the surface---are preferred, collisions are possible with
any function whose value vanishes at the surface and grows monotonically away from it, with a negative sign in the
interior. For such functions, it is still possible to find collisons, albeit with a possibly
increased number of starting points.
The ``sdf_distance`` method is called by the compiler to produce a visual mesh for rendering using the marching cubes
algorithm implemented by `MarchingCubeCpp <https://github.com/aparis69/MarchingCubeCpp>`__.
Future improvement to the gradient descent algorithm, such as a line search which takes advantage of the properties of
SDFs, might reduce the number of iterations and/or starting points.
For the sdf plugin, the following methods need to be specified
``sdf_distance``:
Returns the signed distance of the query point given in local coordinates.
``sdf_staticdistance``:
This is the static version of the previous function, taking config attributes as additional inputs. This function is
required because mesh creation occurs during model compilation before the plugin object has been instantiated.
``sdf_gradient``:
Computes the gradient in local coodinates of the SDF at the query point.
``sdf_aabb``:
Computes the axis-aligned bounding box in local coordinates. This volume is voxelized uniformly before the call to
the marching cubes algorithm.
.. _exProvider:
Resource providers
+7 -1
View File
@@ -23,7 +23,7 @@
#define mjMINMU 1E-5 // minimum friction coefficient
#define mjMINIMP 0.0001 // minimum constraint impedance
#define mjMAXIMP 0.9999 // maximum constraint impedance
#define mjMAXCONPAIR 50 // maximum number of contacts per geom pair
#define mjMAXCONPAIR 100 // maximum number of contacts per geom pair
#define mjMAXTREEDEPTH 50 // maximum bounding volume hierarchy depth
#define mjMAXVFS 2000 // maximum number of files in virtual file system
#define mjMAXVFSNAME 1000 // maximum filename size in virtual file system
@@ -94,6 +94,7 @@ typedef enum mjtGeom_ { // type of geometric shape
mjGEOM_CYLINDER, // cylinder
mjGEOM_BOX, // box
mjGEOM_MESH, // mesh
mjGEOM_SDF, // signed distance field
mjNGEOMTYPES, // number of regular geom types
@@ -437,6 +438,10 @@ struct mjOption_ { // physics options
int mpr_iterations; // maximum number of MPR solver iterations
int disableflags; // bit flags for disabling standard features
int enableflags; // bit flags for enabling optional features
// sdf collision settings
int sdf_initpoints; // number of starting points for gradient descent
int sdf_iterations; // max number of iterations for gradient descent
};
typedef struct mjOption_ mjOption;
@@ -715,6 +720,7 @@ struct mjModel_ {
int* geom_matid; // material id for rendering; -1: none (ngeom x 1)
int* geom_group; // group for visibility (ngeom x 1)
int* geom_priority; // geom contact priority (ngeom x 1)
int* geom_plugin; // plugin instance id; -1: not in use (ngeom x 1)
mjtByte* geom_sameframe; // same as body frame (1) or iframe (2) (ngeom x 1)
mjtNum* geom_solmix; // mixing coef for solref/imp in geom pair (ngeom x 1)
mjtNum* geom_solref; // constraint solver reference: contact (ngeom x mjNREF)
+15
View File
@@ -56,6 +56,7 @@ typedef enum mjtPluginCapabilityBit_ {
mjPLUGIN_ACTUATOR = 1<<0, // actuator forces
mjPLUGIN_SENSOR = 1<<1, // sensor measurements
mjPLUGIN_PASSIVE = 1<<2, // passive forces
mjPLUGIN_SDF = 1<<3, // signed distance fields
} mjtPluginCapabilityBit;
struct mjpPlugin_ {
@@ -93,6 +94,20 @@ struct mjpPlugin_ {
// called by mjv_updateScene (optional)
void (*visualize)(const mjModel*m, mjData* d, const mjvOption* opt, mjvScene* scn, int instance);
// methods specific to signed distance fields (optional)
// signed distance from the surface
mjtNum (*sdf_distance)(const mjtNum point[3], const mjData* d, int instance);
// gradient of distance with respect to local coordinates
void (*sdf_gradient)(mjtNum gradient[3], const mjtNum point[3], const mjData* d, int instance);
// called during compilation for marching cubes
mjtNum (*sdf_staticdistance)(const mjtNum point[3], const mjtNum* attributes);
// bounding box of implicit surface
void (*sdf_aabb)(mjtNum aabb[6], const mjtNum* attributes);
};
typedef struct mjpPlugin_ mjpPlugin;
+1
View File
@@ -126,6 +126,7 @@ typedef enum mjtVisFlag_ { // flags enabling model element visualization
mjVIS_SKIN, // skin
mjVIS_MIDPHASE, // mid-phase bounding volume hierarchy
mjVIS_MESHBVH, // mesh bounding volume hierarchy
mjVIS_SDFITER, // iterations of SDF gradient descent
mjNVISFLAG // number of visualization flags
} mjtVisFlag;
+4 -1
View File
@@ -41,7 +41,9 @@
X( int, noslip_iterations ) \
X( int, mpr_iterations ) \
X( int, disableflags ) \
X( int, enableflags )
X( int, enableflags ) \
X( int, sdf_initpoints ) \
X( int, sdf_iterations )
#define MJOPTION_SCALARS \
@@ -225,6 +227,7 @@
XMJV( int, geom_matid, ngeom, 1 ) \
XMJV( int, geom_group, ngeom, 1 ) \
X ( int, geom_priority, ngeom, 1 ) \
X ( int, geom_plugin, ngeom, 1 ) \
X ( mjtByte, geom_sameframe, ngeom, 1 ) \
X ( mjtNum, geom_solmix, ngeom, 1 ) \
X ( mjtNum, geom_solref, ngeom, mjNREF ) \
+5 -2
View File
@@ -82,7 +82,8 @@ ENUMS: Mapping[str, EnumDecl] = dict([
('mjGEOM_CYLINDER', 5),
('mjGEOM_BOX', 6),
('mjGEOM_MESH', 7),
('mjNGEOMTYPES', 8),
('mjGEOM_SDF', 8),
('mjNGEOMTYPES', 9),
('mjGEOM_ARROW', 100),
('mjGEOM_ARROW1', 101),
('mjGEOM_ARROW2', 102),
@@ -553,7 +554,8 @@ ENUMS: Mapping[str, EnumDecl] = dict([
('mjVIS_SKIN', 23),
('mjVIS_MIDPHASE', 24),
('mjVIS_MESHBVH', 25),
('mjNVISFLAG', 26),
('mjVIS_SDFITER', 26),
('mjNVISFLAG', 27),
]),
)),
('mjtRndFlag',
@@ -592,6 +594,7 @@ ENUMS: Mapping[str, EnumDecl] = dict([
('mjPLUGIN_ACTUATOR', 1),
('mjPLUGIN_SENSOR', 2),
('mjPLUGIN_PASSIVE', 4),
('mjPLUGIN_SDF', 8),
]),
)),
('mjtGridPos',
+18 -1
View File
@@ -263,6 +263,16 @@ STRUCTS: Mapping[str, StructDecl] = dict([
type=ValueType(name='int'),
doc='bit flags for enabling optional features',
),
StructFieldDecl(
name='sdf_initpoints',
type=ValueType(name='int'),
doc='number of starting points for gradient descent',
),
StructFieldDecl(
name='sdf_iterations',
type=ValueType(name='int'),
doc='max number of iterations for gradient descent',
),
),
)),
('mjVisual',
@@ -1627,6 +1637,13 @@ STRUCTS: Mapping[str, StructDecl] = dict([
),
doc='geom contact priority (ngeom x 1)',
),
StructFieldDecl(
name='geom_plugin',
type=PointerType(
inner_type=ValueType(name='int'),
),
doc='plugin instance id; -1: not in use (ngeom x 1)',
),
StructFieldDecl(
name='geom_sameframe',
type=PointerType(
@@ -4874,7 +4891,7 @@ STRUCTS: Mapping[str, StructDecl] = dict([
name='flags',
type=ArrayType(
inner_type=ValueType(name='mjtByte'),
extents=(26,),
extents=(27,),
),
doc='visualization flags (indexed by mjtVisFlag)',
),
+50
View File
@@ -0,0 +1,50 @@
<mujoco>
<extension>
<plugin plugin="mujoco.sdf.bowl">
<instance name="bowl">
<config key="height" value="0.4"/>
<config key="radius" value="1.0"/>
<config key="thickness" value="0.02"/>
</instance>
</plugin>
</extension>
<asset>
<mesh name="bowl">
<plugin instance="bowl"/>
</mesh>
</asset>
<option sdf_iterations="5" sdf_initpoints="20"/>
<default>
<geom solref="0.01 1" solimp=".95 .99 .0001" condim="1"/>
</default>
<statistic meansize=".1"/>
<include file="scene.xml"/>
<worldbody>
<light name="left" pos="0 0 4"/>
<light name="right" pos="-1 -1 2" dir="1 1 -2"/>
<body euler="45 -15 0" pos="0 0 1">
<geom type="sdf" name="bowl" mesh="bowl" rgba=".2 .8 .8 1">
<plugin instance="bowl"/>
</geom>
</body>
<body pos=".6 -.4 2.5">
<freejoint/>
<geom type="sphere" size=".15" rgba=".8 .17 .15 1"/>
</body>
<body pos="0 -.4 2.5">
<freejoint/>
<geom type="sphere" size=".15" rgba=".8 .17 .15 1"/>
</body>
<body pos=".3 -.4 2.5">
<freejoint/>
<geom type="sphere" size=".15" rgba=".8 .17 .15 1"/>
</body>
</worldbody>
</mujoco>
+55
View File
@@ -0,0 +1,55 @@
<mujoco>
<extension>
<plugin plugin="mujoco.sdf.gear">
<instance name="gear1">
<config key="alpha" value="0"/>
</instance>
</plugin>
<plugin plugin="mujoco.sdf.gear">
<instance name="gear2">
<config key="alpha" value="15"/>
</instance>
</plugin>
</extension>
<asset>
<mesh name="gear1">
<plugin instance="gear1"/>
</mesh>
<mesh name="gear2">
<plugin instance="gear2"/>
</mesh>
</asset>
<option sdf_iterations="5" sdf_initpoints="20"/>
<default>
<geom solref="0.01 1" solimp=".95 .99 .0001" friction="0.2"/>
</default>
<statistic meansize=".1"/>
<include file="scene.xml"/>
<worldbody>
<body pos="0 0 .2">
<joint name="freewheel" type="hinge" damping="1000" axis="0 0 1"/>
<geom type="sdf" name="gear1" mesh="gear1" rgba="0.4 0.4 0.4 1">
<plugin instance="gear1"/>
</geom>
</body>
<body pos="2.85 0 .2">
<joint name="drive" type="hinge" damping=".1" axis="0 0 1"/>
<geom type="sdf" name="gear2" mesh="gear2" rgba="0.7 0.7 0.7 1">
<plugin instance="gear2"/>
</geom>
</body>
<light name="left" pos="0 0 1"/>
<light name="right" pos="1 0 1"/>
</worldbody>
<actuator>
<motor name="drive" joint="drive" ctrlrange="-1 1" gear="1500" ctrllimited="true"/>
</actuator>
</mujoco>
+35
View File
@@ -0,0 +1,35 @@
<mujoco>
<extension>
<plugin plugin="mujoco.sdf.torus">
<instance name="torus">
<config key="radius1" value="0.35"/>
<config key="radius2" value="0.15"/>
</instance>
</plugin>
</extension>
<option gravity="0 0 -9.81"/>
<asset>
<mesh name="torus">
<plugin instance="torus"/>
</mesh>
<mesh file="../../mug/mug.obj" scale=".2 .2 .2"/>
</asset>
<include file="scene.xml"/>
<worldbody>
<body pos="0 .05 2.5" euler="90 0 0">
<freejoint/>
<geom type="sdf" mesh="torus" rgba=".8 .17 .15 1" group="1">
<plugin instance="torus"/>
</geom>
</body>
<body>
<geom type="mesh" mesh="mug" euler="90 0 0" rgba="0 0 1 .2"/>
</body>
<light pos="1 0 7" dir="0 0 -1" castshadow="false"/>
<light pos="-1 0 7" dir="0 0 -1" castshadow="false"/>
</worldbody>
</mujoco>
+56
View File
@@ -0,0 +1,56 @@
<mujoco>
<extension>
<plugin plugin="mujoco.sdf.nut">
<instance name="nut">
<config key="radius" value="0.26"/>
</instance>
</plugin>
<plugin plugin="mujoco.sdf.bolt">
<instance name="bolt">
<config key="radius" value="0.26"/>
</instance>
</plugin>
</extension>
<compiler autolimits="true"/>
<include file="scene.xml"/>
<visual>
<map force="0.05"/>
</visual>
<asset>
<mesh name="nut">
<plugin instance="nut"/>
</mesh>
<mesh name="bolt">
<plugin instance="bolt"/>
</mesh>
</asset>
<option sdf_iterations="15" sdf_initpoints="60"/>
<default>
<geom solref="0.01 1" solimp=".95 .99 .0001" friction="0.01"/>
</default>
<statistic meansize=".1"/>
<worldbody>
<body pos="-0.0012496 0.00329058 0.830362" quat="-0.000212626 0.999996 -0.00200453 0.00185878">
<joint type="free" damping="30"/>
<geom type="sdf" name="nut" mesh="nut" rgba="0.83 0.68 0.4 1">
<plugin instance="nut"/>
</geom>
</body>
<body euler="180 0 0">
<geom type="sdf" name="bolt" mesh="bolt" rgba="0.7 0.7 0.7 1">
<plugin instance="bolt"/>
</geom>
</body>
<light name="left" pos="-1 0 2" cutoff="80"/>
<light name="right" pos="1 0 2" cutoff="80"/>
</worldbody>
</mujoco>
+68
View File
@@ -0,0 +1,68 @@
<mujoco>
<extension>
<plugin plugin="mujoco.sdf.torus">
<instance name="torus">
<config key="radius1" value="0.35"/>
<config key="radius2" value="0.15"/>
</instance>
</plugin>
</extension>
<asset>
<mesh name="torus">
<plugin instance="torus"/>
</mesh>
</asset>
<option sdf_iterations="10" sdf_initpoints="20"/>
<include file="scene.xml"/>
<default>
<geom solref="0.01 1" solimp=".95 .99 .0001"/>
</default>
<worldbody>
<body pos="0 0 .175">
<freejoint/>
<geom type="sdf" mesh="torus" rgba=".8 .2 .2 1">
<plugin instance="torus"/>
</geom>
</body>
<body pos=".1 .1 1.2">
<freejoint/>
<geom type="sdf" mesh="torus" rgba=".8 .2 .2 1">
<plugin instance="torus"/>
</geom>
</body>
<body pos=".1 .1 2.6">
<freejoint/>
<geom type="sdf" mesh="torus" rgba=".8 .2 .2 1">
<plugin instance="torus"/>
</geom>
</body>
<body pos="-.1 .2 2">
<freejoint/>
<geom type="sphere" size="0.3"/>
</body>
<body pos=".3 -.1 2" euler="0 0 45">
<freejoint/>
<geom type="ellipsoid" size=".4 .2 .2"/>
</body>
<body pos="-.1 .1 .5">
<freejoint/>
<geom type="capsule" size=".1" fromto="-.2 -.2 .1 .2 .2 .1"/>
</body>
<body pos=".2 -.2 .3">
<freejoint/>
<geom type="cylinder" size=".1" fromto="-.2 -.2 .1 .2 .2 .1"/>
</body>
<body pos=".1 .1 3.5">
<freejoint/>
<geom type="box" size=".2 .2 .2"/>
</body>
<light name="left" pos="0 0 1"/>
<light name="right" pos="1 0 1"/>
</worldbody>
</mujoco>
+38
View File
@@ -0,0 +1,38 @@
<!-- Copyright 2021 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.
-->
<mujoco>
<compiler texturedir="asset"/>
<statistic meansize=".05"/>
<visual>
<rgba haze="0.15 0.25 0.35 1"/>
<map stiffness="700" shadowscale="0.5" fogstart="1" fogend="15" zfar="40" haze="1" shadowclip="3"/>
</visual>
<asset>
<texture type="skybox" builtin="gradient" rgb1="0.3 0.5 0.7" rgb2="0 0 0" width="512" height="512"/>
<texture name="texplane" type="2d" builtin="checker" rgb1=".2 .3 .4" rgb2=".1 0.15 0.2"
width="512" height="512" mark="cross" markrgb=".8 .8 .8"/>
<material name="matplane" reflectance="0.3" texture="texplane" texrepeat="1 1" texuniform="true"/>
</asset>
<worldbody>
<light directional="true" diffuse=".8 .8 .8" specular="0.2 0.2 0.2" pos="0 0 4" dir="0 0 -1"/>
<geom name="ground" type="plane" pos="0 0 -.02" size="10 10 .01" material="matplane" condim="1"/>
</worldbody>
</mujoco>
+54
View File
@@ -0,0 +1,54 @@
<mujoco>
<extension>
<plugin plugin="mujoco.sdf.torus">
<instance name="torus">
<config key="radius1" value="0.35"/>
<config key="radius2" value="0.15"/>
</instance>
</plugin>
</extension>
<asset>
<mesh name="torus">
<plugin instance="torus"/>
</mesh>
</asset>
<option sdf_iterations="10" sdf_initpoints="40"/>
<include file="scene.xml"/>
<default>
<geom solref="0.01 1" solimp=".95 .99 .0001" friction="0.1"/>
</default>
<worldbody>
<body pos="-1 0 3.8">
<freejoint/>
<geom type="sdf" mesh="torus" rgba=".2 .2 .8 1">
<plugin instance="torus"/>
</geom>
</body>
<body pos="-1 0 3.4">
<freejoint/>
<geom type="sdf" mesh="torus" rgba=".2 .8 .2 1">
<plugin instance="torus"/>
</geom>
</body>
<body pos="-1 0 3">
<freejoint/>
<geom type="sdf" mesh="torus" rgba=".8 .2 .2 1">
<plugin instance="torus"/>
</geom>
</body>
<body pos="0 0 2">
<geom type="cylinder" size=".1" fromto="1 0 -1 -1 0 1"/>
</body>
<body pos="0 0 2">
<geom type="cylinder" size=".5" fromto="1.2 0 -1.2 1 0 -1"/>
</body>
<light name="left" pos="-2 0 7" cutoff="80"/>
<light name="right" pos="2 0 7" cutoff="80"/>
</worldbody>
</mujoco>
+51
View File
@@ -0,0 +1,51 @@
# Copyright 2022 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
#
# https://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.
set(MUJOCO_SDF_INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/../..
${CMAKE_CURRENT_SOURCE_DIR}/../../src
)
set(MUJOCO_SDF_SRCS
sdf.cc
sdf.h
bolt.cc
bolt.h
bowl.cc
bowl.h
gear.cc
gear.h
register.cc
nut.cc
nut.h
torus.cc
torus.h
)
add_library(sdf SHARED)
target_sources(sdf PRIVATE ${MUJOCO_SDF_SRCS})
target_include_directories(sdf PRIVATE ${MUJOCO_SDF_INCLUDE})
target_link_libraries(sdf PRIVATE mujoco)
target_compile_options(
sdf
PRIVATE ${AVX_COMPILE_OPTIONS}
${MUJOCO_MACOS_COMPILE_OPTIONS}
${EXTRA_COMPILE_OPTIONS}
${MUJOCO_CXX_FLAGS}
)
target_link_options(
sdf
PRIVATE
${MUJOCO_MACOS_LINK_OPTIONS}
${EXTRA_LINK_OPTIONS}
)
+184
View File
@@ -0,0 +1,184 @@
// Copyright 2022 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.
#include <cmath>
#include <cstdlib>
#include <optional>
#include <mujoco/mjplugin.h>
#include <mujoco/mjtnum.h>
#include <mujoco/mujoco.h>
#include "sdf.h"
#include "bolt.h"
namespace mujoco::plugin::sdf {
namespace {
static mjtNum distance(const mjtNum p[3], const mjtNum attributes[1]) {
// see https://www.shadertoy.com/view/XtffzX
mjtNum screw = 12;
mjtNum radius = mju_sqrt(p[0]*p[0]+p[1]*p[1]) - attributes[0];
mjtNum sqrt12 = mju_sqrt(2.)/2.;
// a triangle wave spun around Oy, offset by the angle between x and z
mjtNum azimuth = mju_atan2(p[1], p[0]);
mjtNum triangle = abs(Fract(p[2] * screw - azimuth / mjPI / 2.) - .5);
mjtNum thread = (radius - triangle / screw) * sqrt12;
// clip the top and bottom
mjtNum bolt = Subtraction(thread, .5 - abs(p[2] + .5));
mjtNum cone = (p[2] - radius) * sqrt12;
// add a diagonal clipping for more realism
bolt = Subtraction(bolt, cone + 1. * sqrt12);
// create the hexagonal geometry for the head
mjtNum point2D[2] = {p[0], p[1]};
mjtNum res[2];
mjtNum k = 6. / mjPI / 2.;
mjtNum angle = -floor((mju_atan2(point2D[1], point2D[0])) * k + .5) / k;
mjtNum s[2] = {mju_sin(angle), mju_sin(angle + mjPI * .5)};
mjtNum mat[4] = {s[1], -s[0], s[0], s[1]};
mju_mulMatVec(res, mat, point2D, 2, 2);
mjtNum point3D[3] = {res[0], res[1], p[2]};
mjtNum head = point3D[0] - .5;
// the top is also rounded down with a cone
head = Intersection(head, abs(point3D[2] + .25) - .25);
head = Intersection(head, (point3D[2] + radius - .22) * sqrt12);
return Union(bolt, head);
}
} // namespace
// factory function
std::optional<Bolt> Bolt::Create(
const mjModel* m, mjData* d, int instance) {
if (CheckAttr("radius", m, instance)) {
return Bolt(m, d, instance);
} else {
mju_warning("Invalid parameter specification in Bolt plugin");
return std::nullopt;
}
}
// plugin constructor
Bolt::Bolt(const mjModel* m, mjData* d, int instance) {
radius = strtod(mj_getPluginConfig(m, instance, "radius"), nullptr);
}
// add new element in the vector storing iteration counts
void Bolt::Compute(const mjModel* m, mjData* d, int instance) {
visualizer_.Next();
}
// reset visualization counter
void Bolt::Reset() {
visualizer_.Reset();
}
// plugin visualization
void Bolt::Visualize(const mjModel* m, mjData* d, const mjvOption* opt,
mjvScene* scn, int instance) {
visualizer_.Visualize(m, d, opt, scn, instance);
}
// sdf
mjtNum Bolt::Distance(const mjtNum point[3]) const {
return distance(point, &radius);
}
// gradient of sdf
void Bolt::Gradient(mjtNum grad[3], const mjtNum point[3]) const {
mjtNum eps = 1e-8;
mjtNum dist0 = distance(point, &radius);
mjtNum pointX[3] = {point[0]+eps, point[1], point[2]};
mjtNum distX = distance(pointX, &radius);
mjtNum pointY[3] = {point[0], point[1]+eps, point[2]};
mjtNum distY = distance(pointY, &radius);
mjtNum pointZ[3] = {point[0], point[1], point[2]+eps};
mjtNum distZ = distance(pointZ, &radius);
grad[0] = (distX - dist0) / eps;
grad[1] = (distY - dist0) / eps;
grad[2] = (distZ - dist0) / eps;
}
// plugin registration
void Bolt::RegisterPlugin() {
mjpPlugin plugin;
mjp_defaultPlugin(&plugin);
plugin.name = "mujoco.sdf.bolt";
plugin.capabilityflags |= mjPLUGIN_SDF;
const char* attributes[] = {"radius"};
plugin.nattribute = sizeof(attributes) / sizeof(attributes[0]);
plugin.attributes = attributes;
plugin.nstate = +[](const mjModel* m, int instance) { return 0; };
plugin.init = +[](const mjModel* m, mjData* d, int instance) {
auto sdf_or_null = Bolt::Create(m, d, instance);
if (!sdf_or_null.has_value()) {
return -1;
}
d->plugin_data[instance] = reinterpret_cast<uintptr_t>(
new Bolt(std::move(*sdf_or_null)));
return 0;
};
plugin.destroy = +[](mjData* d, int instance) {
delete reinterpret_cast<Bolt*>(d->plugin_data[instance]);
d->plugin_data[instance] = 0;
};
plugin.reset = +[](const mjModel* m, double* plugin_state, void* plugin_data,
int instance) {
auto sdf = reinterpret_cast<Bolt*>(plugin_data);
sdf->Reset();
};
plugin.visualize = +[](const mjModel* m, mjData* d, const mjvOption* opt,
mjvScene* scn, int instance) {
auto* sdf = reinterpret_cast<Bolt*>(d->plugin_data[instance]);
sdf->Visualize(m, d, opt, scn, instance);
};
plugin.compute =
+[](const mjModel* m, mjData* d, int instance, int capability_bit) {
auto* sdf = reinterpret_cast<Bolt*>(d->plugin_data[instance]);
sdf->Compute(m, d, instance);
};
plugin.sdf_distance =
+[](const mjtNum point[3], const mjData* d, int instance) {
auto* sdf = reinterpret_cast<Bolt*>(d->plugin_data[instance]);
sdf->visualizer_.AddPoint(point);
return sdf->Distance(point);
};
plugin.sdf_gradient = +[](mjtNum gradient[3], const mjtNum point[3],
const mjData* d, int instance) {
auto* sdf = reinterpret_cast<Bolt*>(d->plugin_data[instance]);
sdf->Gradient(gradient, point);
};
plugin.sdf_staticdistance =
+[](const mjtNum point[3], const mjtNum* attributes) {
return distance(point, attributes);
};
plugin.sdf_aabb =
+[](mjtNum aabb[6], const mjtNum* attributes) {
aabb[0] = aabb[1] = aabb[2] = 0;
aabb[3] = aabb[4] = .6;
aabb[5] = 1;
};
mjp_registerPlugin(&plugin);
}
} // namespace mujoco::plugin::sdf
+58
View File
@@ -0,0 +1,58 @@
// Copyright 2022 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.
#ifndef MUJOCO_PLUGIN_SDF_BOLT_H_
#define MUJOCO_PLUGIN_SDF_BOLT_H_
#include <optional>
#include <mujoco/mjdata.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mjtnum.h>
#include <mujoco/mjvisualize.h>
#include "sdf.h"
namespace mujoco::plugin::sdf {
// this plugin implements a modification of the signed distance function
// from https://www.shadertoy.com/view/XtffzX of a bolt with a hexagonal head
class Bolt {
public:
// Creates a new Bolt instance (allocated with `new`) or
// returns null on failure.
static std::optional<Bolt> Create(const mjModel* m, mjData* d, int instance);
Bolt(Bolt&&) = default;
~Bolt() = default;
void Reset();
void Visualize(const mjModel* m, mjData* d, const mjvOption* opt,
mjvScene* scn, int instance);
void Compute(const mjModel* m, mjData* d, int instance);
mjtNum Distance(const mjtNum point[3]) const;
void Gradient(mjtNum grad[3], const mjtNum point[3]) const;
static void RegisterPlugin();
mjtNum radius;
private:
Bolt(const mjModel* m, mjData* d, int instance);
SdfVisualizer visualizer_;
};
} // namespace mujoco::plugin::sdf
#endif // MUJOCO_PLUGIN_SDF_BOLT_H_
+184
View File
@@ -0,0 +1,184 @@
// Copyright 2022 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.
#include <sstream>
#include <optional>
#include <mujoco/mjplugin.h>
#include <mujoco/mjtnum.h>
#include <mujoco/mujoco.h>
#include "bowl.h"
namespace mujoco::plugin::sdf {
namespace {
static mjtNum distance(const mjtNum point[3], const mjtNum attributes[3]) {
mjtNum height = attributes[0];
mjtNum radius = attributes[1];
mjtNum thick = attributes[2];
mjtNum width = mju_sqrt(radius*radius - height*height);
// see https://iquilezles.org/articles/distfunctions/
mjtNum q[2] = { mju_norm(point, 2), point[2] };
mjtNum qdiff[2] = { q[0] - width, q[1] - height };
return ((height*q[0] < width*q[1]) ? mju_norm(qdiff, 2)
: mju_abs(mju_norm(q, 2)-radius))-thick;
}
} // namespace
// factory function
std::optional<Bowl> Bowl::Create(
const mjModel* m, mjData* d, int instance) {
if (CheckAttr("radius", m, instance) && CheckAttr("height", m, instance) &&
CheckAttr("thickness", m, instance)) {
return Bowl(m, d, instance);
} else {
mju_warning("Invalid parameter specification in Bowl plugin");
return std::nullopt;
}
}
// plugin constructor
Bowl::Bowl(const mjModel* m, mjData* d, int instance) {
radius = strtod(mj_getPluginConfig(m, instance, "radius"), nullptr);
height = strtod(mj_getPluginConfig(m, instance, "height"), nullptr);
thick = strtod(mj_getPluginConfig(m, instance, "thickness"), nullptr);
width = mju_sqrt(radius*radius - height*height);
}
// add new element in the vector storing iteration counts
void Bowl::Compute(const mjModel* m, mjData* d, int instance) {
visualizer_.Next();
}
// reset visualization counter
void Bowl::Reset() {
visualizer_.Reset();
}
// plugin visualization
void Bowl::Visualize(const mjModel* m, mjData* d, const mjvOption* opt, mjvScene* scn,
int instance) {
visualizer_.Visualize(m, d, opt, scn, instance);
}
// sdf
mjtNum Bowl::Distance(const mjtNum point[3]) const {
mjtNum attributes[3]= {height, radius, thick};
return distance(point, attributes);
}
// gradient of sdf
void Bowl::Gradient(mjtNum grad[3], const mjtNum point[3]) const {
// mjtNum q[2] = { mju_norm(point, 2), point[2] };
// if (height*q[0] < width*q[1]) {
// mjtNum qdiff[2] = { q[0] - width, q[1] - height };
// mjtNum qdiffnorm = mju_norm(qdiff, 2);
// mjtNum grad_qdiff[3] = {qdiff[0] * point[0] / q[0],
// qdiff[0] * point[1] / q[0],
// qdiff[1]};
// grad[0] = - grad_qdiff[0] / qdiffnorm;
// grad[1] = - grad_qdiff[1] / qdiffnorm;
// grad[2] = - grad_qdiff[2] / qdiffnorm;
// } else {
// mjtNum pnorm = mju_norm3(point);
// mjtNum grad_dist = (pnorm - radius) / mju_abs(pnorm - radius);
// grad[0] = - grad_dist * point[0] / pnorm;
// grad[1] = - grad_dist * point[1] / pnorm;
// grad[2] = - grad_dist * point[2] / pnorm;
// }
mjtNum attributes[3]= {height, radius, thick};
mjtNum eps = 1e-8;
mjtNum dist0 = distance(point, attributes);
mjtNum pointX[3] = {point[0]+eps, point[1], point[2]};
mjtNum distX = distance(pointX, attributes);
mjtNum pointY[3] = {point[0], point[1]+eps, point[2]};
mjtNum distY = distance(pointY, attributes);
mjtNum pointZ[3] = {point[0], point[1], point[2]+eps};
mjtNum distZ = distance(pointZ, attributes);
grad[0] = (distX - dist0) / eps;
grad[1] = (distY - dist0) / eps;
grad[2] = (distZ - dist0) / eps;
}
// plugin registration
void Bowl::RegisterPlugin() {
mjpPlugin plugin;
mjp_defaultPlugin(&plugin);
plugin.name = "mujoco.sdf.bowl";
plugin.capabilityflags |= mjPLUGIN_SDF;
const char* attributes[] = {"radius", "height", "thickness"};
plugin.nattribute = sizeof(attributes) / sizeof(attributes[0]);
plugin.attributes = attributes;
plugin.nstate = +[](const mjModel* m, int instance) { return 0; };
plugin.init = +[](const mjModel* m, mjData* d, int instance) {
auto sdf_or_null = Bowl::Create(m, d, instance);
if (!sdf_or_null.has_value()) {
return -1;
}
d->plugin_data[instance] = reinterpret_cast<uintptr_t>(
new Bowl(std::move(*sdf_or_null)));
return 0;
};
plugin.destroy = +[](mjData* d, int instance) {
delete reinterpret_cast<Bowl*>(d->plugin_data[instance]);
d->plugin_data[instance] = 0;
};
plugin.reset = +[](const mjModel* m, double* plugin_state, void* plugin_data,
int instance) {
auto sdf = reinterpret_cast<Bowl*>(plugin_data);
sdf->Reset();
};
plugin.visualize = +[](const mjModel* m, mjData* d, const mjvOption* opt,
mjvScene* scn, int instance) {
auto* sdf = reinterpret_cast<Bowl*>(d->plugin_data[instance]);
sdf->Visualize(m, d, opt, scn, instance);
};
plugin.compute =
+[](const mjModel* m, mjData* d, int instance, int capability_bit) {
auto* sdf = reinterpret_cast<Bowl*>(d->plugin_data[instance]);
sdf->Compute(m, d, instance);
};
plugin.sdf_distance =
+[](const mjtNum point[3], const mjData* d, int instance) {
auto* sdf = reinterpret_cast<Bowl*>(d->plugin_data[instance]);
sdf->visualizer_.AddPoint(point);
return sdf->Distance(point);
};
plugin.sdf_gradient = +[](mjtNum gradient[3], const mjtNum point[3],
const mjData* d, int instance) {
auto* sdf = reinterpret_cast<Bowl*>(d->plugin_data[instance]);
sdf->Gradient(gradient, point);
};
plugin.sdf_staticdistance =
+[](const mjtNum point[3], const mjtNum* attributes) {
return distance(point, attributes);
};
plugin.sdf_aabb =
+[](mjtNum aabb[6], const mjtNum* attributes) {
mjtNum radius = attributes[1];
mjtNum thick = attributes[2];
aabb[0] = aabb[1] = aabb[2] = 0;
aabb[3] = aabb[4] = aabb[5] = radius + thick;
};
mjp_registerPlugin(&plugin);
}
} // namespace mujoco::plugin::sdf
+58
View File
@@ -0,0 +1,58 @@
// Copyright 2022 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.
#ifndef MUJOCO_PLUGIN_SDF_BOWL_H_
#define MUJOCO_PLUGIN_SDF_BOWL_H_
#include <optional>
#include <mujoco/mjdata.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mjtnum.h>
#include <mujoco/mjvisualize.h>
#include "sdf.h"
namespace mujoco::plugin::sdf {
class Bowl {
public:
// Creates a new Bowl instance (allocated with `new`) or
// returns null on failure.
static std::optional<Bowl> Create(const mjModel* m, mjData* d, int instance);
Bowl(Bowl&&) = default;
~Bowl() = default;
void Reset();
void Visualize(const mjModel* m, mjData* d, const mjvOption* opt,
mjvScene* scn, int instance);
void Compute(const mjModel* m, mjData* d, int instance);
mjtNum Distance(const mjtNum point[3]) const;
void Gradient(mjtNum grad[3], const mjtNum point[3]) const;
static void RegisterPlugin();
mjtNum radius;
mjtNum height;
mjtNum thick;
mjtNum width;
private:
Bowl(const mjModel* m, mjData* d, int instance);
SdfVisualizer visualizer_;
};
} // namespace mujoco::plugin::sdf
#endif // MUJOCO_PLUGIN_SDF_BOWL_H_
+263
View File
@@ -0,0 +1,263 @@
// Copyright 2022 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.
#include <cstdio>
#include <sstream>
#include <optional>
#include <mujoco/mjplugin.h>
#include <mujoco/mjtnum.h>
#include <mujoco/mjvisualize.h>
#include <mujoco/mujoco.h>
#include "gear.h"
namespace mujoco::plugin::sdf {
namespace {
static mjtNum circle(mjtNum rho, mjtNum r) {
return rho - r;
}
static mjtNum smoothUnion(mjtNum a, mjtNum b, mjtNum k) {
mjtNum h = mju_clip(0.5 + 0.5*(b - a) / k, 0.0, 1.0);
return b * (1. - h) + a * h - k * h * (1. - h);
}
static mjtNum smoothIntersection(mjtNum a, mjtNum b, mjtNum k) {
return Subtraction(
Intersection(a, b),
smoothUnion(Subtraction(a, b), Subtraction(b, a), k));
}
static mjtNum extrusion(const mjtNum p[3], mjtNum sdf_2d, mjtNum h) {
mjtNum w[2] = { sdf_2d, abs(p[2]) - h };
mjtNum w_abs[2] = { mju_max(w[0], 0), mju_max(w[1], 0) };
return mju_min(mju_max(w[0], w[1]), 0.) + mju_norm(w_abs, 2);
}
static mjtNum mod(mjtNum x, mjtNum y) {
return x - y * floor(x/y);
}
static mjtNum distance2D(const mjtNum p[3], const mjtNum attributes[3]) {
// see https://www.shadertoy.com/view/3lG3WR
mjtNum D = 2.8; // should be an attribute
mjtNum N = 25; // should be an attribute
mjtNum psi = 3.096e-5 * N * N -6.557e-3 * N + 0.551; // pressure angle
mjtNum alpha = attributes[0];
mjtNum R = D / 2.0;
/* The Pitch Circle Diameter is the diameter of a circle which by a pure
* rolling action would transmit the same motion as the actual gear wheel. It
* should be noted that in the case of wheels which connect non-parallel
* shafts, the pitch circle diameter is different for each cross section of
* the wheel normal to the axis of rotation.
*/
mjtNum rho = mju_norm(p, 2);
mjtNum Pd = N / D; // Diametral Pitch: teeth per unit length of diameter
mjtNum P =
mjPI / Pd; // Circular Pitch: the length of arc round the pitch circle
// between corresponding points on adjacent teeth.
mjtNum a = 1.0 / Pd; // Addendum: radial length of a tooth from the pitch
// circle to the tip of the tooth.
mjtNum Do = D + 2.0 * a; // Outside Diameter
mjtNum Ro = Do / 2.0;
mjtNum h = 2.2 / Pd;
mjtNum innerR = Ro - h - 0.4;
// Early exit
if (innerR - rho > 0.0)
return innerR - rho;
// Early exit
if (Ro - rho < -0.2)
return rho - Ro;
mjtNum Db = D * mju_cos(psi); // Base Diameter
mjtNum Rb = Db / 2.0;
mjtNum fi = mju_atan2(p[1], p[0]) + alpha;
mjtNum alphaStride = P / R;
mjtNum invAlpha = mju_acos(Rb / R);
mjtNum invPhi = mju_tan(invAlpha) - invAlpha;
mjtNum shift = alphaStride / 2.0 - 2.0 * invPhi;
mjtNum fia = mod(fi + shift / 2.0, alphaStride) - shift / 2.0;
mjtNum fib = mod(-fi - shift + shift / 2.0, alphaStride) - shift / 2.0;
mjtNum dista = -1.0e6;
mjtNum distb = -1.0e6;
if (Rb < rho) {
mjtNum acos_rbRho = mju_acos(Rb/rho);
mjtNum thetaa = fia + acos_rbRho;
mjtNum thetab = fib + acos_rbRho;
mjtNum ta = mju_sqrt(rho * rho - Rb * Rb);
// https://math.stackexchange.com/questions/1266689/distance-from-a-point-to-the-involute-of-a-circle
dista = ta - Rb * thetaa;
distb = ta - Rb * thetab;
}
mjtNum gearOuter = circle(rho, Ro);
mjtNum gearLowBase = circle(rho, Ro - h);
mjtNum crownBase = circle(rho, innerR);
mjtNum cogs = Intersection(dista, distb);
mjtNum baseWalls = Intersection(fia - (alphaStride - shift),
fib - (alphaStride - shift));
cogs = Intersection(baseWalls, cogs);
cogs = smoothIntersection(gearOuter, cogs, 0.01);
cogs = smoothUnion(gearLowBase, cogs, Rb - Ro + h);
cogs = Subtraction(cogs, crownBase);
return extrusion(p, cogs, .1);
}
static mjtNum distance(const mjtNum p[3], const mjtNum attributes[3]) {
return extrusion(p, distance2D(p, attributes), .1) - .005;
}
} // namespace
// factory function
std::optional<Gear> Gear::Create(
const mjModel* m, mjData* d, int instance) {
if (CheckAttr("alpha", m, instance)) {
return Gear(m, d, instance);
} else {
mju_warning("Invalid parameter specification in Gear plugin");
return std::nullopt;
}
}
// plugin constructor
Gear::Gear(const mjModel* m, mjData* d, int instance) {
alpha = strtod(mj_getPluginConfig(m, instance, "alpha"), nullptr);
}
// plugin computation
void Gear::Compute(const mjModel* m, mjData* d, int instance) {
visualizer_.Next();
}
// plugin reset
void Gear::Reset() {
visualizer_.Reset();
}
// plugin visualization
void Gear::Visualize(const mjModel* m, mjData* d, const mjvOption* opt,
mjvScene* scn, int instance) {
visualizer_.Visualize(m, d, opt, scn, instance);
}
// sdf
mjtNum Gear::Distance(const mjtNum point[3]) const {
return distance(point, &alpha);
}
// gradient of sdf
void Gear::Gradient(mjtNum grad[3], const mjtNum point[3]) const {
mjtNum attributes[1]= {alpha};
mjtNum eps = 1e-8;
mjtNum dist0 = distance(point, attributes);
mjtNum pointX[3] = {point[0]+eps, point[1], point[2]};
mjtNum distX = distance(pointX, attributes);
mjtNum pointY[3] = {point[0], point[1]+eps, point[2]};
mjtNum distY = distance(pointY, attributes);
mjtNum pointZ[3] = {point[0], point[1], point[2]+eps};
mjtNum distZ = distance(pointZ, attributes);
grad[0] = (distX - dist0) / eps;
grad[1] = (distY - dist0) / eps;
grad[2] = (distZ - dist0) / eps;
}
// plugin registration
void Gear::RegisterPlugin() {
mjpPlugin plugin;
mjp_defaultPlugin(&plugin);
plugin.name = "mujoco.sdf.gear";
plugin.capabilityflags |= mjPLUGIN_SDF;
const char* attributes[] = {"alpha"};
plugin.nattribute = sizeof(attributes) / sizeof(attributes[0]);
plugin.attributes = attributes;
plugin.nstate = +[](const mjModel* m, int instance) { return 0; };
plugin.init = +[](const mjModel* m, mjData* d, int instance) {
auto sdf_or_null = Gear::Create(m, d, instance);
if (!sdf_or_null.has_value()) {
return -1;
}
d->plugin_data[instance] = reinterpret_cast<uintptr_t>(
new Gear(std::move(*sdf_or_null)));
return 0;
};
plugin.destroy = +[](mjData* d, int instance) {
delete reinterpret_cast<Gear*>(d->plugin_data[instance]);
d->plugin_data[instance] = 0;
};
plugin.reset = +[](const mjModel* m, double* plugin_state, void* plugin_data,
int instance) {
auto sdf = reinterpret_cast<Gear*>(plugin_data);
sdf->Reset();
};
plugin.visualize = +[](const mjModel* m, mjData* d, const mjvOption* opt,
mjvScene* scn, int instance) {
auto* sdf = reinterpret_cast<Gear*>(d->plugin_data[instance]);
sdf->Visualize(m, d, opt, scn, instance);
};
plugin.compute =
+[](const mjModel* m, mjData* d, int instance, int capability_bit) {
auto* sdf = reinterpret_cast<Gear*>(d->plugin_data[instance]);
sdf->Compute(m, d, instance);
};
plugin.sdf_distance =
+[](const mjtNum point[3], const mjData* d, int instance) {
auto* sdf = reinterpret_cast<Gear*>(d->plugin_data[instance]);
sdf->visualizer_.AddPoint(point);
return sdf->Distance(point);
};
plugin.sdf_gradient = +[](mjtNum gradient[3], const mjtNum point[3],
const mjData* d, int instance) {
auto* sdf = reinterpret_cast<Gear*>(d->plugin_data[instance]);
sdf->Gradient(gradient, point);
};
plugin.sdf_staticdistance =
+[](const mjtNum point[3], const mjtNum* attributes) {
return distance(point, attributes);
};
plugin.sdf_aabb =
+[](mjtNum aabb[6], const mjtNum* attributes) {
aabb[0] = aabb[1] = aabb[2] = 0;
aabb[3] = aabb[4] = 1.7;
aabb[5] = .11;
};
mjp_registerPlugin(&plugin);
}
} // namespace mujoco::plugin::sdf
+56
View File
@@ -0,0 +1,56 @@
// Copyright 2022 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.
#ifndef MUJOCO_PLUGIN_SDF_GEAR_H_
#define MUJOCO_PLUGIN_SDF_GEAR_H_
#include <optional>
#include <vector>
#include <mujoco/mjdata.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mjtnum.h>
#include <mujoco/mjvisualize.h>
#include "sdf.h"
namespace mujoco::plugin::sdf {
class Gear {
public:
// Creates a new Gear instance (allocated with `new`) or
// returns null on failure.
static std::optional<Gear> Create(const mjModel* m, mjData* d, int instance);
Gear(Gear&&) = default;
~Gear() = default;
void Reset();
void Visualize(const mjModel* m, mjData* d, const mjvOption* opt,
mjvScene* scn, int instance);
void Compute(const mjModel* m, mjData* d, int instance);
mjtNum Distance(const mjtNum point[3]) const;
void Gradient(mjtNum grad[3], const mjtNum point[3]) const;
static void RegisterPlugin();
mjtNum alpha;
private:
Gear(const mjModel* m, mjData* d, int instance);
SdfVisualizer visualizer_;
};
} // namespace mujoco::plugin::sdf
#endif // MUJOCO_PLUGIN_SDF_GEAR_H_
+183
View File
@@ -0,0 +1,183 @@
// Copyright 2022 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.
#include <cmath>
#include <sstream>
#include <optional>
#include <mujoco/mjplugin.h>
#include <mujoco/mjtnum.h>
#include <mujoco/mujoco.h>
#include "nut.h"
namespace mujoco::plugin::sdf {
namespace {
static mjtNum distance(const mjtNum p[3], const mjtNum attributes[1]) {
// see https://www.shadertoy.com/view/XtffzX
mjtNum screw = 12;
mjtNum radius2 = mju_sqrt(p[0]*p[0]+p[1]*p[1]) - attributes[0];
mjtNum sqrt12 = mju_sqrt(2.)/2.;
// a triangle wave spun around Oy, offset by the angle between x and z
mjtNum azimuth = mju_atan2(p[1], p[0]);
mjtNum triangle = abs(Fract(p[2] * screw - azimuth / mjPI / 2.) - .5);
mjtNum thread2 = (radius2 - triangle / screw) * sqrt12;
// clip the top
mjtNum cone2 = (p[2] - radius2) * sqrt12;
// the hole is the same thing, but substracted from the whole thing
mjtNum hole = Subtraction(thread2, cone2 + .5 * sqrt12);
hole = Union(hole, -cone2 - .05 * sqrt12);
// create the hexagonal geometry for the head
mjtNum point2D[2] = {p[0], p[1]};
mjtNum res[2];
mjtNum k = 6. / mjPI / 2.;
mjtNum angle = -floor((mju_atan2(point2D[1], point2D[0])) * k + .5) / k;
mjtNum s[2] = {mju_sin(angle), mju_sin(angle + mjPI * .5)};
mjtNum mat[4] = {s[1], -s[0], s[0], s[1]};
mju_mulMatVec(res, mat, point2D, 2, 2);
mjtNum point3D[3] = {res[0], res[1], p[2]};
mjtNum head = point3D[0] - .5;
// the top is also rounded down with a cone
head = Intersection(head, abs(point3D[2] + .25) - .25);
head = Intersection(head, (point3D[2] + radius2 - .22) * sqrt12);
return Subtraction(head, hole);
}
} // namespace
// factory function
std::optional<Nut> Nut::Create(
const mjModel* m, mjData* d, int instance) {
if (CheckAttr("radius", m, instance)) {
return Nut(m, d, instance);
} else {
mju_warning("Invalid parameter specification in Nut plugin");
return std::nullopt;
}
}
// plugin constructor
Nut::Nut(const mjModel* m, mjData* d, int instance) {
radius = strtod(mj_getPluginConfig(m, instance, "radius"), nullptr);
}
// plugin computation
void Nut::Compute(const mjModel* m, mjData* d, int instance) {
visualizer_.Next();
}
// plugin reset
void Nut::Reset() {
visualizer_.Reset();
}
// plugin visualization
void Nut::Visualize(const mjModel* m, mjData* d, const mjvOption* opt,
mjvScene* scn, int instance) {
visualizer_.Visualize(m, d, opt, scn, instance);
}
// sdf
mjtNum Nut::Distance(const mjtNum point[3]) const {
return distance(point, &radius);
}
// gradient of sdf
void Nut::Gradient(mjtNum grad[3], const mjtNum point[3]) const {
mjtNum eps = 1e-8;
mjtNum dist0 = distance(point, &radius);
mjtNum pointX[3] = {point[0]+eps, point[1], point[2]};
mjtNum distX = distance(pointX, &radius);
mjtNum pointY[3] = {point[0], point[1]+eps, point[2]};
mjtNum distY = distance(pointY, &radius);
mjtNum pointZ[3] = {point[0], point[1], point[2]+eps};
mjtNum distZ = distance(pointZ, &radius);
grad[0] = (distX - dist0) / eps;
grad[1] = (distY - dist0) / eps;
grad[2] = (distZ - dist0) / eps;
}
// plugin registration
void Nut::RegisterPlugin() {
mjpPlugin plugin;
mjp_defaultPlugin(&plugin);
plugin.name = "mujoco.sdf.nut";
plugin.capabilityflags |= mjPLUGIN_SDF;
const char* attributes[] = {"radius"};
plugin.nattribute = sizeof(attributes) / sizeof(attributes[0]);
plugin.attributes = attributes;
plugin.nstate = +[](const mjModel* m, int instance) { return 0; };
plugin.init = +[](const mjModel* m, mjData* d, int instance) {
auto sdf_or_null = Nut::Create(m, d, instance);
if (!sdf_or_null.has_value()) {
return -1;
}
d->plugin_data[instance] = reinterpret_cast<uintptr_t>(
new Nut(std::move(*sdf_or_null)));
return 0;
};
plugin.destroy = +[](mjData* d, int instance) {
delete reinterpret_cast<Nut*>(d->plugin_data[instance]);
d->plugin_data[instance] = 0;
};
plugin.reset = +[](const mjModel* m, double* plugin_state, void* plugin_data,
int instance) {
auto sdf = reinterpret_cast<Nut*>(plugin_data);
sdf->Reset();
};
plugin.visualize = +[](const mjModel* m, mjData* d, const mjvOption* opt,
mjvScene* scn, int instance) {
auto* sdf = reinterpret_cast<Nut*>(d->plugin_data[instance]);
sdf->Visualize(m, d, opt, scn, instance);
};
plugin.compute =
+[](const mjModel* m, mjData* d, int instance, int capability_bit) {
auto* sdf = reinterpret_cast<Nut*>(d->plugin_data[instance]);
sdf->Compute(m, d, instance);
};
plugin.sdf_distance =
+[](const mjtNum point[3], const mjData* d, int instance) {
auto* sdf = reinterpret_cast<Nut*>(d->plugin_data[instance]);
sdf->visualizer_.AddPoint(point);
return sdf->Distance(point);
};
plugin.sdf_gradient = +[](mjtNum gradient[3], const mjtNum point[3],
const mjData* d, int instance) {
auto* sdf = reinterpret_cast<Nut*>(d->plugin_data[instance]);
sdf->Gradient(gradient, point);
};
plugin.sdf_staticdistance =
+[](const mjtNum point[3], const mjtNum* attributes) {
return distance(point, attributes);
};
plugin.sdf_aabb =
+[](mjtNum aabb[6], const mjtNum* attributes) {
aabb[0] = aabb[1] = aabb[2] = 0;
aabb[3] = aabb[4] = .6;
aabb[5] = 1;
};
mjp_registerPlugin(&plugin);
}
} // namespace mujoco::plugin::sdf
+58
View File
@@ -0,0 +1,58 @@
// Copyright 2022 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.
#ifndef MUJOCO_PLUGIN_SDF_NUT_H_
#define MUJOCO_PLUGIN_SDF_NUT_H_
#include <optional>
#include <mujoco/mjdata.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mjtnum.h>
#include <mujoco/mjvisualize.h>
#include "sdf.h"
namespace mujoco::plugin::sdf {
// this plugin implements a modification of the signed distance function
// from https://www.shadertoy.com/view/XtffzX of hexagonal nut
class Nut {
public:
// Creates a new Nut instance (allocated with `new`) or
// returns null on failure.
static std::optional<Nut> Create(const mjModel* m, mjData* d, int instance);
Nut(Nut&&) = default;
~Nut() = default;
void Reset();
void Visualize(const mjModel* m, mjData* d, const mjvOption* opt,
mjvScene* scn, int instance);
void Compute(const mjModel* m, mjData* d, int instance);
mjtNum Distance(const mjtNum point[3]) const;
void Gradient(mjtNum grad[3], const mjtNum point[3]) const;
static void RegisterPlugin();
mjtNum radius;
private:
Nut(const mjModel* m, mjData* d, int instance);
SdfVisualizer visualizer_;
};
} // namespace mujoco::plugin::sdf
#endif // MUJOCO_PLUGIN_SDF_NUT_H_
+31
View File
@@ -0,0 +1,31 @@
// Copyright 2022 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.
#include "bolt.h"
#include "bowl.h"
#include "gear.h"
#include "nut.h"
#include "torus.h"
namespace mujoco::plugin::sdf {
mjPLUGIN_LIB_INIT {
Bolt::RegisterPlugin();
Bowl::RegisterPlugin();
Gear::RegisterPlugin();
Nut::RegisterPlugin();
Torus::RegisterPlugin();
}
} // namespace mujoco::plugin::sdf
+128
View File
@@ -0,0 +1,128 @@
// Copyright 2022 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.
#include <cstddef>
#include <string>
#include <mujoco/mjplugin.h>
#include <mujoco/mujoco.h>
#include "sdf.h"
namespace mujoco::plugin::sdf {
bool CheckAttr(const char* name, const mjModel* m, int instance) {
char *end;
std::string value = mj_getPluginConfig(m, instance, name);
value.erase(std::remove_if(value.begin(), value.end(), isspace), value.end());
strtod(value.c_str(), &end);
return end == value.data() + value.size();
}
SdfVisualizer::SdfVisualizer() {
points_.assign(10*(mjMAXCONPAIR+1)*(mjMAXCONPAIR+1)*3, 0);
npoints_.clear();
}
void SdfVisualizer::AddPoint(const mjtNum point[3]) {
if (!npoints_.empty()) {
points_[3*npoints_.back()+0] = point[0];
points_[3*npoints_.back()+1] = point[1];
points_[3*npoints_.back()+2] = point[2];
npoints_.back()++;
}
}
void SdfVisualizer::Next() {
npoints_.push_back(npoints_.empty() ? 0 : npoints_.back());
}
void SdfVisualizer::Reset() {
npoints_.clear();
}
void SdfVisualizer::Visualize(const mjModel* m, const mjData* d,
const mjvOption* opt, mjvScene* scn,
int instance) {
if (!opt->flags[mjVIS_SDFITER]) {
return;
}
if (npoints_.empty()) {
return;
}
int tot = 0, n = 0, g = 0;
for (int i = 0; i < m->ngeom; i++) {
if (m->geom_plugin[i] == instance) {
g = i;
break;
}
}
mjtNum* points = points_.data();
int* npoints = npoints_.data();
int niter = npoints_.size();
mjtNum geom_mat[9], offset[3], rotation[9], from[3], to[3];
mjtNum* geom_xpos = d->geom_xpos + 3*g;
mjtNum* geom_xmat = d->geom_xmat + 9*g;
mjtNum* geom_pos = m->geom_pos + 3*g;
mjtNum* geom_quat = m->geom_quat + 4*g;
mju_quat2Mat(geom_mat, geom_quat);
mju_mulMatMatT(rotation, geom_xmat, geom_mat, 3, 3, 3);
mju_rotVecMat(offset, geom_pos, rotation);
mju_sub3(offset, geom_xpos, offset);
for (int i = 0; i < niter; i++) {
n = npoints[i] - tot;
if (!n) {
continue;
}
for (int k = 0; k < 2; k++) {
for (int j = 0; j < (k == 0 ? 2 : n-1); j++) {
if (scn->ngeom >= scn->maxgeom) {
mj_warning((mjData*)d, mjWARN_VGEOMFULL, scn->maxgeom);
return;
}
mjvGeom* thisgeom = scn->geoms + scn->ngeom;
mjtNum* p1 = points + (tot + (k == 0 ? (n-1) * j : j))*3;
mjtNum* p2 = points + (tot + j + 1)*3;
mju_rotVecMat(from, p1, rotation);
mju_addTo3(from, offset);
mju_rotVecMat(to, p2, rotation);
mju_addTo3(to, offset);
if (k == 0) {
float rgba[4] = {static_cast<float>(j > 0), 0,
static_cast<float>(j == 0), 1};
mjtNum size[] = {.2*m->stat.meansize};
mjv_initGeom(thisgeom, mjGEOM_SPHERE, size, from, geom_xmat, rgba);
} else {
mjv_initGeom(thisgeom, mjGEOM_NONE, NULL, NULL, NULL, NULL);
thisgeom->objtype = mjOBJ_UNKNOWN;
thisgeom->objid = i;
thisgeom->category = mjCAT_DECOR;
thisgeom->segid = scn->ngeom;
to[0] = from[0] + .95*(to[0]-from[0]);
to[1] = from[1] + .95*(to[1]-from[1]);
to[2] = from[2] + .95*(to[2]-from[2]);
mjv_connector(thisgeom, mjGEOM_LINE, 2, from, to);
thisgeom->rgba[0] = (j+1.)/(n-1.);
thisgeom->rgba[1] = 0;
thisgeom->rgba[2] = 1 - (j+1.)/(n-1.);
thisgeom->rgba[3] = 1;
}
scn->ngeom++;
}
}
tot += n;
}
}
} // namespace mujoco::plugin::sdf
+61
View File
@@ -0,0 +1,61 @@
// Copyright 2022 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.
#ifndef MUJOCO_PLUGIN_SDF_SDF_H_
#define MUJOCO_PLUGIN_SDF_SDF_H_
#include <vector>
#include <mujoco/mujoco.h>
namespace mujoco::plugin::sdf {
inline mjtNum Union(mjtNum a, mjtNum b) {
return mju_min(a, b);
}
inline mjtNum Intersection(mjtNum a, mjtNum b) {
return mju_max(a, b);
}
inline mjtNum Subtraction(mjtNum a, mjtNum b) {
return mju_max(a, -b);
}
inline mjtNum Fract(mjtNum x) {
return x - floor(x);
}
// reads numeric attributes
bool CheckAttr(const char* name, const mjModel* m, int instance);
class SdfVisualizer {
public:
SdfVisualizer();
void Visualize(const mjModel* m, const mjData* d, const mjvOption* opt,
mjvScene* scn, int instance);
void AddPoint(const mjtNum point[3]);
void Reset();
void Next(); // adds a new gradient descent trajectory to be visualized
private:
std::vector<mjtNum> points_; // query points
std::vector<int> npoints_; // number of iterations from the starting point
};
} // namespace mujoco::plugin::sdf
#endif // MUJOCO_PLUGIN_SDF_SDF_H_
+124
View File
@@ -0,0 +1,124 @@
// Copyright 2022 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.
#include <sstream>
#include <optional>
#include <mujoco/mjplugin.h>
#include <mujoco/mjtnum.h>
#include <mujoco/mujoco.h>
#include "torus.h"
namespace mujoco::plugin::sdf {
namespace {
static mjtNum distance(const mjtNum p[3], const mjtNum radius[2]) {
mjtNum q = mju_sqrt(p[0]*p[0] + p[1]*p[1]) - radius[0];
return mju_sqrt(q*q + p[2]*p[2]) - radius[1];
}
} // namespace
// factory function
std::optional<Torus> Torus::Create(
const mjModel* m, mjData* d, int instance) {
if (CheckAttr("radius1", m, instance) && CheckAttr("radius2", m, instance)) {
return Torus(m, d, instance);
} else {
mju_warning("Invalid radius1 or radius2 parameters in Torus plugin");
return std::nullopt;
}
}
// plugin constructor
Torus::Torus(const mjModel* m, mjData* d, int instance) {
radius[0] = strtod(mj_getPluginConfig(m, instance, "radius1"), nullptr);
radius[1] = strtod(mj_getPluginConfig(m, instance, "radius2"), nullptr);
}
// sdf
mjtNum Torus::Distance(const mjtNum point[3]) const {
return distance(point, radius);
}
// gradient of sdf
void Torus::Gradient(mjtNum grad[3], const mjtNum p[3]) const {
mjtNum len_xy = mju_sqrt(p[0]*p[0] + p[1]*p[1]);
mjtNum q = len_xy - radius[0];
mjtNum grad_q[2] = { p[0] / len_xy, p[1] / len_xy };
mjtNum len_qz = mju_sqrt(q*q + p[2]*p[2]);
grad[0] = q*grad_q[0] / mjMAX(len_qz, mjMINVAL);
grad[1] = q*grad_q[1] / mjMAX(len_qz, mjMINVAL);
grad[2] = p[2] / mjMAX(len_qz, mjMINVAL);
}
// plugin registration
void Torus::RegisterPlugin() {
mjpPlugin plugin;
mjp_defaultPlugin(&plugin);
plugin.name = "mujoco.sdf.torus";
plugin.capabilityflags |= mjPLUGIN_SDF;
const char* attributes[] = {"radius1", "radius2", "axis"};
plugin.nattribute = sizeof(attributes) / sizeof(attributes[0]);
plugin.attributes = attributes;
plugin.nstate = +[](const mjModel* m, int instance) { return 0; };
plugin.init = +[](const mjModel* m, mjData* d, int instance) {
auto sdf_or_null = Torus::Create(m, d, instance);
if (!sdf_or_null.has_value()) {
return -1;
}
d->plugin_data[instance] = reinterpret_cast<uintptr_t>(
new Torus(std::move(*sdf_or_null)));
return 0;
};
plugin.destroy = +[](mjData* d, int instance) {
delete reinterpret_cast<Torus*>(d->plugin_data[instance]);
d->plugin_data[instance] = 0;
};
plugin.reset = +[](const mjModel* m, double* plugin_state, void* plugin_data,
int instance) {
// do nothing
};
plugin.compute =
+[](const mjModel* m, mjData* d, int instance, int capability_bit) {
// do nothing;
};
plugin.sdf_distance =
+[](const mjtNum point[3], const mjData* d, int instance) {
auto* sdf = reinterpret_cast<Torus*>(d->plugin_data[instance]);
return sdf->Distance(point);
};
plugin.sdf_gradient = +[](mjtNum gradient[3], const mjtNum point[3],
const mjData* d, int instance) {
auto* sdf = reinterpret_cast<Torus*>(d->plugin_data[instance]);
sdf->Gradient(gradient, point);
};
plugin.sdf_staticdistance =
+[](const mjtNum point[3], const mjtNum* attributes) {
return distance(point, attributes);
};
plugin.sdf_aabb =
+[](mjtNum aabb[6], const mjtNum* attributes) {
aabb[0] = aabb[1] = aabb[2] = 0;
aabb[3] = aabb[4] = attributes[0] + attributes[1];
aabb[5] = attributes[1];
};
mjp_registerPlugin(&plugin);
}
} // namespace mujoco::plugin::sdf
+47
View File
@@ -0,0 +1,47 @@
// Copyright 2022 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.
#ifndef MUJOCO_PLUGIN_SDF_TORUS_H_
#define MUJOCO_PLUGIN_SDF_TORUS_H_
#include <optional>
#include <mujoco/mjdata.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mjtnum.h>
#include "sdf.h"
namespace mujoco::plugin::sdf {
class Torus {
public:
// Creates a new Torus instance or returns null on failure.
static std::optional<Torus> Create(const mjModel* m, mjData* d, int instance);
Torus(Torus&&) = default;
~Torus() = default;
mjtNum Distance(const mjtNum point[3]) const;
void Gradient(mjtNum grad[3], const mjtNum point[3]) const;
static void RegisterPlugin();
mjtNum radius[2];
private:
Torus(const mjModel* m, mjData* d, int instance);
};
} // namespace mujoco::plugin::sdf
#endif // MUJOCO_PLUGIN_SDF_TORUS_H_
+6 -1
View File
@@ -27,6 +27,7 @@
#include "lodepng.h"
#include <mujoco/mjdata.h>
#include <mujoco/mjui.h>
#include <mujoco/mjvisualize.h>
#include <mujoco/mjxmacro.h>
#include <mujoco/mujoco.h>
@@ -605,6 +606,8 @@ void MakePhysicsSection(mj::Simulate* sim, int oldstate) {
{mjITEM_EDITINT, "MRR Iter", 2, &(opt->mpr_iterations), "1 0 1000"},
{mjITEM_EDITNUM, "MPR Tol", 2, &(opt->mpr_tolerance), "1 0 1"},
{mjITEM_EDITNUM, "API Rate", 2, &(opt->apirate), "1 0 1000"},
{mjITEM_EDITINT, "SDF Iter", 2, &(opt->sdf_iterations), "1 1 20"},
{mjITEM_EDITINT, "SDF Init", 2, &(opt->sdf_initpoints), "1 1 100"},
{mjITEM_SEPARATOR, "Physical Parameters", 1},
{mjITEM_EDITNUM, "Gravity", 2, opt->gravity, "3"},
{mjITEM_EDITNUM, "Wind", 2, opt->wind, "3"},
@@ -756,7 +759,7 @@ void MakeRenderingSection(mj::Simulate* sim, const mjModel* m, int oldstate) {
// create tree slider
mjuiDef defTree[] = {
{mjITEM_SLIDERINT, "Tree depth", 2, &sim->opt.bvh_depth, "0 15"},
{mjITEM_SLIDERINT, "Tree depth", 2, &sim->opt.bvh_depth, "0 20"},
{mjITEM_END}
};
mjui_add(&sim->ui0, defTree);
@@ -1713,6 +1716,8 @@ void Simulate::Sync() {
X(mpr_iterations);
X(disableflags);
X(enableflags);
X(sdf_initpoints);
X(sdf_iterations);
#undef X
+2
View File
@@ -23,6 +23,8 @@ set(MUJOCO_ENGINE_SRCS
engine_collision_driver.h
engine_collision_primitive.c
engine_collision_primitive.h
engine_collision_sdf.c
engine_collision_sdf.h
engine_core_constraint.c
engine_core_constraint.h
engine_core_smooth.c
+11 -9
View File
@@ -23,6 +23,7 @@
#include "engine/engine_callback.h"
#include "engine/engine_collision_convex.h"
#include "engine/engine_collision_primitive.h"
#include "engine/engine_collision_sdf.h"
#include "engine/engine_core_constraint.h"
#include "engine/engine_crossplatform.h"
#include "engine/engine_io.h"
@@ -34,15 +35,16 @@
// table of pair-wise collision functions
mjfCollision mjCOLLISIONFUNC[mjNGEOMTYPES][mjNGEOMTYPES] = {
/* PLANE HFIELD SPHERE CAPSULE ELLIPSOID CYLINDER BOX MESH */
/*PLANE */ {0, 0, mjc_PlaneSphere, mjc_PlaneCapsule, mjc_PlaneConvex, mjc_PlaneCylinder, mjc_PlaneBox, mjc_PlaneConvex},
/*HFIELD */ {0, 0, mjc_ConvexHField, mjc_ConvexHField, mjc_ConvexHField, mjc_ConvexHField, mjc_ConvexHField, mjc_ConvexHField},
/*SHPERE */ {0, 0, mjc_SphereSphere, mjc_SphereCapsule, mjc_Convex, mjc_SphereCylinder, mjc_SphereBox, mjc_Convex},
/*CAPSULE */ {0, 0, 0, mjc_CapsuleCapsule, mjc_Convex, mjc_Convex, mjc_CapsuleBox, mjc_Convex},
/*ELLIPSOID */ {0, 0, 0, 0, mjc_Convex, mjc_Convex, mjc_Convex, mjc_Convex},
/*CYLINDER */ {0, 0, 0, 0, 0, mjc_Convex, mjc_Convex, mjc_Convex},
/*BOX */ {0, 0, 0, 0, 0, 0, mjc_BoxBox, mjc_Convex},
/*MESH */ {0, 0, 0, 0, 0, 0, 0, mjc_Convex}
/* PLANE HFIELD SPHERE CAPSULE ELLIPSOID CYLINDER BOX MESH SDF */
/*PLANE */ {0, 0, mjc_PlaneSphere, mjc_PlaneCapsule, mjc_PlaneConvex, mjc_PlaneCylinder, mjc_PlaneBox, mjc_PlaneConvex, mjc_SDF},
/*HFIELD */ {0, 0, mjc_ConvexHField, mjc_ConvexHField, mjc_ConvexHField, mjc_ConvexHField, mjc_ConvexHField, mjc_ConvexHField, mjc_HFieldSDF},
/*SPHERE */ {0, 0, mjc_SphereSphere, mjc_SphereCapsule, mjc_Convex, mjc_SphereCylinder, mjc_SphereBox, mjc_Convex, mjc_SDF},
/*CAPSULE */ {0, 0, 0, mjc_CapsuleCapsule, mjc_Convex, mjc_Convex, mjc_CapsuleBox, mjc_Convex, mjc_SDF},
/*ELLIPSOID */ {0, 0, 0, 0, mjc_Convex, mjc_Convex, mjc_Convex, mjc_Convex, mjc_SDF},
/*CYLINDER */ {0, 0, 0, 0, 0, mjc_Convex, mjc_Convex, mjc_Convex, mjc_SDF},
/*BOX */ {0, 0, 0, 0, 0, 0, mjc_BoxBox, mjc_Convex, mjc_SDF},
/*MESH */ {0, 0, 0, 0, 0, 0, 0, mjc_Convex, mjc_MeshSDF},
/*SDF */ {0, 0, 0, 0, 0, 0, 0, 0, mjc_SDF}
};
+695
View File
@@ -0,0 +1,695 @@
// Copyright 2021 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.
#include "engine/engine_collision_sdf.h"
#include <stdio.h>
#include <mujoco/mjdata.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mjtnum.h>
#include "engine/engine_collision_primitive.h"
#include "engine/engine_io.h"
#include "engine/engine_plugin.h"
#include "engine/engine_util_blas.h"
#include "engine/engine_util_errmem.h"
#include "engine/engine_util_misc.h"
#include "engine/engine_util_spatial.h"
#define MAXSDFFACE 1300
//---------------------------- primitives sdf ---------------------------------------------
static mjtNum geomDistance(const mjModel* m, const mjData* d, const mjpPlugin* p,
int i, const mjtNum x[3], mjtGeom type) {
mjtNum a[3], b[3];
const mjtNum* size = m->geom_size+3*i;
// see https://iquilezles.org/articles/distfunctions/
switch (type) {
case mjGEOM_PLANE:
return x[2];
case mjGEOM_SPHERE:
return mju_norm3(x) - size[0];
case mjGEOM_BOX:
a[0] = mju_abs(x[0]) - size[0];
a[1] = mju_abs(x[1]) - size[1];
a[2] = mju_abs(x[2]) - size[2];
b[0] = mju_max(a[0], 0);
b[1] = mju_max(a[1], 0);
b[2] = mju_max(a[2], 0);
return mju_norm3(b) + mju_min(mju_max(a[0], mju_max(a[1], a[2])), 0);
case mjGEOM_CAPSULE:
a[0] = x[0];
a[1] = x[1];
a[2] = x[2] - mju_clip(x[2], -size[1], size[1]);
return mju_norm3(a) - size[0];
case mjGEOM_ELLIPSOID:
a[0] = x[0] / size[0];
a[1] = x[1] / size[1];
a[2] = x[2] / size[2];
b[0] = a[0] / size[0];
b[1] = a[1] / size[1];
b[2] = a[2] / size[2];
mjtNum k0 = mju_norm3(a);
mjtNum k1 = mju_norm3(b);
return k0 * (k0 - 1.0) / k1;
case mjGEOM_CYLINDER:
a[0] = mju_sqrt(x[0]*x[0]+x[1]*x[1]) - size[0];
a[1] = mju_abs(x[2]) - size[1];
b[0] = mju_max(a[0], 0);
b[1] = mju_max(a[1], 0);
return mju_min(mju_max(a[0], a[1]), 0) + mju_norm(b, 2);
case mjGEOM_SDF:
return p->sdf_distance(x, d, i);
default:
mju_error("sdf collisions not available for geom type %d", type);
return 0;
}
}
static void geomGradient(mjtNum gradient[3], const mjModel* m, const mjData* d,
const mjpPlugin* p, int i, const mjtNum x[3],
mjtGeom type) {
mjtNum a[3], b[3], c, e;
const mjtNum* size = m->geom_size+3*i;
// see https://iquilezles.org/articles/distfunctions/
switch (type) {
case mjGEOM_PLANE:
mju_zero3(gradient);
gradient[2] = 1;
break;
case mjGEOM_SPHERE:
mju_copy3(gradient, x);
c = mju_norm3(x);
gradient[0] *= 1. / c;
gradient[1] *= 1. / c;
gradient[2] *= 1. / c;
break;
case mjGEOM_BOX:
mju_zero3(gradient);
a[0] = mju_abs(x[0]) - size[0];
a[1] = mju_abs(x[1]) - size[1];
a[2] = mju_abs(x[2]) - size[2];
int k = a[0] > a[1] ? 0 : 1;
int l = a[2] > a[k] ? 2 : k;
if (a[l]<0) {
gradient[l] = x[l] / mju_abs(x[l]);
} else {
b[0] = mju_max(a[0], 0);
b[1] = mju_max(a[1], 0);
b[2] = mju_max(a[2], 0);
c = mju_norm3(b);
gradient[0] = a[0]>0 ? b[0] / c * x[0] / mju_abs(x[0]) : 0;
gradient[1] = a[1]>0 ? b[1] / c * x[1] / mju_abs(x[1]) : 0;
gradient[2] = a[2]>0 ? b[2] / c * x[2] / mju_abs(x[2]) : 0;
}
break;
case mjGEOM_CAPSULE:
a[0] = x[0];
a[1] = x[1];
a[2] = x[2] - mju_clip(x[2], -size[1], size[1]);
c = mju_norm3(a);
gradient[0] = a[0] / c;
gradient[1] = a[1] / c;
gradient[2] = a[2] / c;
break;
case mjGEOM_ELLIPSOID:
a[0] = x[0] / size[0];
a[1] = x[1] / size[1];
a[2] = x[2] / size[2];
b[0] = a[0] / size[0];
b[1] = a[1] / size[1];
b[2] = a[2] / size[2];
mjtNum k0 = mju_norm3(a);
mjtNum k1 = mju_norm3(b);
gradient[0] = a[0]*(2.*k0 - 1.) / k1 + k0*(k0 - 1.) * b[0]/(k1*k1);
gradient[1] = a[1]*(2.*k0 - 1.) / k1 + k0*(k0 - 1.) * b[1]/(k1*k1);
gradient[2] = a[2]*(2.*k0 - 1.) / k1 + k0*(k0 - 1.) * b[2]/(k1*k1);
break;
case mjGEOM_CYLINDER:
c = mju_sqrt(x[0]*x[0]+x[1]*x[1]);
e = mju_abs(x[2]);
a[0] = c - size[0];
a[1] = e - size[1];
mjtNum grada[3] = {x[0] / c, x[1] / c, x[2] / e};
int j = a[0] > a[1] ? 0 : 1;
if (a[j] < 0) {
gradient[0] = j==0 ? grada[0] : 0;
gradient[1] = j==0 ? grada[1] : 0;
gradient[2] = j==1 ? grada[2] : 0;
} else {
b[0] = mju_max(a[0], 0);
b[1] = mju_max(a[1], 0);
mjtNum bnorm = mju_norm(b, 2);
gradient[0] = grada[0] * b[0] / bnorm;
gradient[1] = grada[1] * b[0] / bnorm;
gradient[2] = grada[2] * b[1] / bnorm;
}
break;
case mjGEOM_SDF:
p->sdf_gradient(gradient, x, d, i);
break;
default:
mju_error("sdf collisions not available for geom type %d", type);
}
}
//---------------------------- helper functions -------------------------------------------
// signed distance function
mjtNum mjc_distance(const mjModel* m, const mjData* d, const mjSDF* s, const mjtNum x[3]) {
mjtNum y[3];
switch (s->type) {
case mjSDFTYPE_SINGLE:
return geomDistance(m, d, s->plugin[0], s->id[0], x, s->geomtype[0]);
case mjSDFTYPE_INTERSECTION:
mju_rotVecMat(y, x, s->relmat);
mju_addTo3(y, s->relpos);
return mju_max(geomDistance(m, d, s->plugin[0], s->id[0], x, s->geomtype[0]),
geomDistance(m, d, s->plugin[1], s->id[1], y, s->geomtype[1]));
default:
mju_error("SDF type not available");
return 0;
}
}
// gradient of sdf
void mjc_gradient(const mjModel* m, const mjData* d, const mjSDF* s,
mjtNum gradient[3], const mjtNum x[3]) {
mjtNum y[3];
const mjtNum* point[2] = {x, y};
switch (s->type) {
case mjSDFTYPE_INTERSECTION:
mju_rotVecMat(y, x, s->relmat);
mju_addTo3(y, s->relpos);
int i = geomDistance(m, d, s->plugin[0], s->id[0], x, s->geomtype[0]) >
geomDistance(m, d, s->plugin[1], s->id[1], y, s->geomtype[1]) ? 0 : 1;
geomGradient(gradient, m, d, s->plugin[i], s->id[i], point[i], s->geomtype[i]);
if (i==1) {
mju_rotVecMatT(gradient, gradient, s->relmat);
}
break;
case mjSDFTYPE_AVERAGE:
mju_rotVecMat(y, x, s->relmat);
mju_addTo3(y, s->relpos);
mjtNum grad1[3], grad2[3];
geomGradient(grad1, m, d, s->plugin[0], s->id[0], x, s->geomtype[0]);
mju_normalize3(grad1);
geomGradient(grad2, m, d, s->plugin[1], s->id[1], y, s->geomtype[1]);
mju_rotVecMatT(grad2, grad2, s->relmat);
mju_normalize3(grad2);
mju_sub3(gradient, grad1, grad2);
mju_normalize3(gradient);
break;
case mjSDFTYPE_SINGLE:
geomGradient(gradient, m, d, s->plugin[0], s->id[0], point[0], s->geomtype[0]);
break;
default:
mju_error("SDF type not available");
}
}
// get sdf from geom id
static const mjpPlugin* getSDF(const mjModel* m, int id) {
int instance = m->geom_plugin[id];
const int nslot = mjp_pluginCount();
const int slot = m->plugin[instance];
const mjpPlugin* sdf = mjp_getPluginAtSlotUnsafe(slot, nslot);
if (!sdf) mju_error("invalid plugin slot: %d", slot);
if (!(sdf->capabilityflags & mjPLUGIN_SDF)) {
mju_error("Plugin is not a signed distance field at slot %d", slot);
}
return sdf;
}
// map (pos12, mat12) as (xpos2, xmat2)^-1 \circ (xpos1, xmat1)
static void mapPose(const mjtNum xpos1[3], const mjtNum xquat1[4],
const mjtNum xpos2[3], const mjtNum xquat2[4],
mjtNum pos12[3], mjtNum mat12[9]) {
mjtNum negpos[3], negquat[4], quat12[4];
mju_negPose(negpos, negquat, xpos2, xquat2);
mju_mulPose(pos12, quat12, negpos, negquat, xpos1, xquat1);
mju_quat2Mat(mat12, quat12);
}
// subtract mesh position from sdf transformation
static void undoTransformation(const mjModel* m, const mjData* d, int g,
mjtNum sdf_xpos[3], mjtNum sdf_quat[4]) {
mjtNum* xpos = d->geom_xpos + 3 * g;
mjtNum* xmat = d->geom_xmat + 9 * g;
if (m->geom_type[g]==mjGEOM_MESH || m->geom_type[g]==mjGEOM_SDF) {
mjtNum negpos[3], negquat[4], xquat[4];
mjtNum* pos = m->geom_pos + 3 * g;
mjtNum* quat = m->geom_quat + 4 * g;
mju_mat2Quat(xquat, xmat);
mju_negPose(negpos, negquat, pos, quat);
mju_mulPose(sdf_xpos, sdf_quat, xpos, xquat, negpos, negquat);
} else {
mju_copy3(sdf_xpos, xpos);
mju_mat2Quat(sdf_quat, xmat);
}
}
//---------------------------- narrow phase -----------------------------------------------
// check if the collision point already exists
static int isknown(const mjtNum* points, const mjtNum x[3], int cnt) {
for (int i = 0; i < cnt; i++) {
if (mju_dist3(x, points + 3*i) < mjMINVAL) {
return 1;
}
}
return 0;
}
// adds candidate point to result
static int addContact(mjtNum* points, mjContact* con, const mjtNum x[3],
const mjtNum pos2[3], const mjtNum quat2[4], mjtNum dist,
int cnt, const mjModel* m, const mjSDF* s, mjData* d) {
// check if there is a collision
if (dist > 0 || isknown(points, x, cnt)) {
return cnt;
} else {
mju_copy3(points+3*cnt, x);
}
// compute normal in local coordinates
mjtNum norm[3], vec[3];
mjc_gradient(m, d, s, norm, x);
mju_scl3(norm, norm, -1);
// construct contact
con[cnt].dist = dist;
mju_rotVecQuat(con[cnt].frame, norm, quat2);
mju_zero3(con[cnt].frame+3);
mju_makeFrame(con[cnt].frame);
mju_scl3(vec, con[cnt].frame, -con[cnt].dist/2);
mju_rotVecQuat(con[cnt].pos, x, quat2);
mju_addTo3(con[cnt].pos, pos2);
mju_addTo3(con[cnt].pos, vec);
return cnt+1;
}
// finds minimum of Frank-Wolfe objective
static mjtNum stepFrankWolfe(mjtNum x[3], const mjtNum* corners, int ncorners,
const mjModel* m, const mjSDF* sdf, mjData* d) {
for (int step=0; step<m->opt.sdf_iterations; step++) {
mjtNum best = 1e10, fun, s[3], grad[3];
// evaluate gradient
mjc_gradient(m, d, sdf, grad, x);
// evaluate all corners
for (int i=0; i<ncorners; i++) {
// compute sdf
fun = mju_dot3(corners + 3*i, grad);
// save argmin
if (fun<best) {
best = fun;
mju_copy3(s, corners + 3*i);
}
}
// update collision point
mju_subFrom3(s, x);
mju_addToScl3(x, s, 2. / (step+2.));
}
// compute distance
return mjc_distance(m, d, sdf, x);
}
// finds minimum using gradient descent
static mjtNum stepGradient(mjtNum x[3], const mjModel* m, const mjSDF* s,
mjData* d) {
mjtNum alpha = 0.2; // step along the gradient direction
for (int step=0; step<m->opt.sdf_iterations; step++) {
mjtNum grad[3];
// evaluate gradient
mjc_gradient(m, d, s, grad, x);
// sanity check
if (isnan(grad[0]) || isnan(grad[1]) || isnan(grad[2])) {
return mjMAXVAL;
}
// update solution
mju_addToScl3(x, grad, -alpha/(mjtNum)(step+1));
}
// compute distance
return mjc_distance(m, d, s, x);
}
//---------------------------- bounding box vs sdf -------------------------------------------------
// stricter triangle collision
static int triangleIntersect(const mjtNum triangle[9], const mjModel* m,
const mjSDF* sdf, mjData* d) {
mjtNum edges[6];
mjtNum normal[3], center[3];
mjtNum v[9], cross[9], p[3];
mjtNum kDistanceScl = 10.;
// triangle normal
mju_sub3(edges+0, triangle+3, triangle);
mju_sub3(edges+3, triangle+6, triangle);
mju_cross(normal, edges, edges+3);
mju_normalize3(normal);
// fourth point
mju_scl3(p, triangle, 1./3.);
mju_addToScl3(p, triangle+3, 1./3.);
mju_addToScl3(p, triangle+6, 1./3.);
mjtNum h = -mjc_distance(m, d, sdf, p)/kDistanceScl;
mju_addToScl3(p, normal, -h);
// circumsphere center
mju_sub3(v+0, triangle+0, p);
mju_sub3(v+3, triangle+3, p);
mju_sub3(v+6, triangle+6, p);
mju_cross(cross+0, v+3, v+6);
mju_cross(cross+3, v+6, v+0);
mju_cross(cross+6, v+0, v+3);
mju_scl3(center, cross, mju_dot3(v, v));
mju_addToScl3(center, cross+3, mju_dot3(v+3, v+3));
mju_addToScl3(center, cross+6, mju_dot3(v+6, v+6));
mju_scl3(center, center, 1./(2.*mju_dot3(v, cross)));
// circumsphere radius
mjtNum r = mju_sqrt(mju_dot3(center, center));
// coordinate change
mju_addTo3(center, p);
return mjc_distance(m, d, sdf, center)<r;
}
// intersect with circumsphere of bounding box
static int boxIntersect(const mjtNum bvh[6], const mjtNum offset[3],
const mjtNum rotation[9], const mjModel* m,
const mjSDF* s, mjData* d) {
mjtNum candidate[3];
mjtNum r = mju_norm3(bvh+3);
mju_rotVecMat(candidate, bvh, rotation);
mju_addTo3(candidate, offset);
// check if inside the bounding box
return mjc_distance(m, d, s, candidate)<r;
}
//---------------------------- mesh vs sdf broad phase --------------------------------------------
// tree vs sdf binary search
static void collideBVH(const mjModel* m, mjData* d, int g,
const mjtNum offset[3], const mjtNum rotation[9],
int* faces, int* npoints, int* n0,
const mjSDF* sdf) {
const int bvhadr = m->mesh_bvhadr[m->geom_dataid[g]];
const int* faceid = m->bvh_geomid + bvhadr;
const mjtNum* bvh = m->bvh_aabb + 6*bvhadr;
const int* child = m->bvh_child + 2*bvhadr;
mjtByte* visited = d->bvh_active + bvhadr;
mjMARKSTACK;
// TODO(quaglino): Store bvh max depths to make this bound tighter.
int max_stack = m->mesh_bvhnum[m->geom_dataid[g]];
struct CollideTreeArgs_ {
int node;
};
typedef struct CollideTreeArgs_ CollideTreeArgs;
CollideTreeArgs* stack = (CollideTreeArgs*)mj_stackAlloc(
d, max_stack * sizeof(CollideTreeArgs*) / sizeof(mjtNum));
int nstack = 0;
stack[nstack].node = 0;
nstack++;
while (nstack) {
(*n0)++;
// pop from stack
nstack--;
int node = stack[nstack].node;
// node1 is a leaf
if (faceid[node] != -1) {
if (visited[node]) {
continue;
}
if (boxIntersect(bvh+6*node, offset, rotation, m, sdf, d)) {
faces[*npoints] = faceid[node];
if (++(*npoints)==MAXSDFFACE) {
mju_warning("mjc_MeshSDF: too many bounding volumes, some contacts may be missed");
mjFREESTACK;
return;
}
visited[node] = 1;
}
continue;
}
// if no intersection at intermediate levels, stop
if (!boxIntersect(bvh+6*node, offset, rotation, m, sdf, d)) {
continue;
}
visited[node] = 1;
// recursive call
for (int i=0; i<2; i++) {
if (child[2*node+i] != -1) {
if (nstack >= max_stack) mju_error("BVH stack depth exceeded.");
stack[nstack].node = child[2*node+i];
nstack++;
}
}
}
mjFREESTACK;
}
//------------------------------ collision functions -----------------------------------------------
// collision between a height field and a signed distance field
int mjc_HFieldSDF(const mjModel* m, const mjData* d, mjContact* con, int g1, int g2, mjtNum margin) {
mju_warning("HField vs SDF collision not yet supported!");
return 0;
}
// collision between a mesh and a signed distance field
int mjc_MeshSDF(const mjModel* m, const mjData* d, mjContact* con, int g1, int g2, mjtNum margin) {
mjtNum* pos1 = d->geom_xpos + 3 * g1;
mjtNum* mat1 = d->geom_xmat + 9 * g1;
mjtNum offset[3], rotation[9];
mjtNum corners[9], points[3*MAXSDFFACE], x[3], dist;
int vertadr = m->mesh_vertadr[m->geom_dataid[g1]];
int faceadr = m->mesh_faceadr[m->geom_dataid[g1]];
int cnt=0, npoints=0, n0=0, faces[MAXSDFFACE]={-1};
// get sdf plugin
int instance = m->geom_plugin[g2];
const mjpPlugin* sdf_ptr = getSDF(m, g2);
mjtGeom geomtype = mjGEOM_SDF;
// copy into data
mjSDF sdf;
sdf.id = &instance;
sdf.type = mjSDFTYPE_SINGLE;
sdf.plugin = &sdf_ptr;
sdf.geomtype = &geomtype;
// compute transformation from g1 to g2
mjtNum pos2true[3], sdf_quat[4], quat1[4];
mju_mat2Quat(quat1, mat1);
undoTransformation(m, d, g2, pos2true, sdf_quat);
mapPose(pos1, quat1, pos2true, sdf_quat, offset, rotation);
// binary tree search
collideBVH(m, (mjData*)d, g1, offset, rotation, faces, &npoints, &n0, &sdf);
// Frank-Wolfe algorithm
for (int i=0; i<npoints; i++) {
int face = faceadr + faces[i];
for (int v=0; v<3; v++) {
mjtNum vec[3] = {
m->mesh_vert[3*(vertadr+m->mesh_face[3*face+v])+0],
m->mesh_vert[3*(vertadr+m->mesh_face[3*face+v])+1],
m->mesh_vert[3*(vertadr+m->mesh_face[3*face+v])+2],
};
// transform local 1 (mesh) to local 2 (sdf)
mju_rotVecMat(corners+3*v, vec, rotation);
mju_addTo3(corners+3*v, offset);
}
// stricter culling
if (!triangleIntersect(corners, m, &sdf, (mjData*)d)) {
continue;
}
// starting point
x[0] = (corners[0]+corners[3]+corners[6])/3;
x[1] = (corners[1]+corners[4]+corners[7])/3;
x[2] = (corners[2]+corners[5]+corners[8])/3;
// SHOULD NOT OCCUR
if (cnt==mjMAXCONPAIR) mju_error("mjc_MeshSDF: too many contact points");
// Frank-Wolfe
dist = stepFrankWolfe(x, corners, 3, m, &sdf, (mjData*)d);
cnt = addContact(points, con, x, pos2true, sdf_quat, dist, cnt, m, &sdf, (mjData*)d);
}
return cnt;
}
// collision between two SDFs
int mjc_SDF(const mjModel* m, const mjData* d, mjContact* con, int g1, int g2, mjtNum margin) {
mjGETINFO;
size1 = m->geom_aabb + 6*g1;
size2 = m->geom_aabb + 6*g2;
int cnt = 0;
mjtNum x[3], y[3], dist, vec1[3], vec2[3];
mjtNum aabb1[6] = {mjMAXVAL, mjMAXVAL, mjMAXVAL, -mjMAXVAL, -mjMAXVAL, -mjMAXVAL};
mjtNum aabb2[6] = {mjMAXVAL, mjMAXVAL, mjMAXVAL, -mjMAXVAL, -mjMAXVAL, -mjMAXVAL};
mjtNum aabb[6] = {mjMAXVAL, mjMAXVAL, mjMAXVAL, -mjMAXVAL, -mjMAXVAL, -mjMAXVAL};
// second geom must be an SDF
if (m->geom_type[g2] != mjGEOM_SDF) {
mju_error("geom is not an SDF");
}
// compute transformations from/to g1 to/from g2
mjtNum quat1[4], quat2[4];
mjtNum pos1true[3], offset21[3], rotation21[9], rotation12[9];
mjtNum pos2true[3], offset1[3], rotation1[9], offset12[3];
mjtNum offset2[3], rotation2[9], squat1[4], squat2[4];
undoTransformation(m, d, g1, pos1true, squat1);
undoTransformation(m, d, g2, pos2true, squat2);
mju_mat2Quat(quat1, mat1);
mju_mat2Quat(quat2, mat2);
mapPose(pos2, quat2, pos1, quat1, offset1, rotation1);
mapPose(pos1, quat1, pos1true, squat1, offset2, rotation2);
mapPose(pos2true, squat2, pos1true, squat1, offset21, rotation21);
mapPose(pos1true, squat1, pos2true, squat2, offset12, rotation12);
// axis-aligned bounding boxes in g1 frame
for (int i=0; i<8; i++) {
vec1[0] = (i&1 ? size1[0]+size1[3] : size1[0]-size1[3]);
vec1[1] = (i&2 ? size1[1]+size1[4] : size1[1]-size1[4]);
vec1[2] = (i&4 ? size1[2]+size1[5] : size1[2]-size1[5]);
vec2[0] = (i&1 ? size2[0]+size2[3] : size2[0]-size2[3]);
vec2[1] = (i&2 ? size2[1]+size2[4] : size2[1]-size2[4]);
vec2[2] = (i&4 ? size2[2]+size2[5] : size2[2]-size2[5]);
mju_rotVecMat(vec2, vec2, rotation1);
mju_addTo3(vec2, offset1);
for (int k=0; k<3; k++) {
aabb1[0+k] = mju_min(aabb1[0+k], vec1[k]);
aabb1[3+k] = mju_max(aabb1[3+k], vec1[k]);
aabb2[0+k] = mju_min(aabb2[0+k], vec2[k]);
aabb2[3+k] = mju_max(aabb2[3+k], vec2[k]);
}
}
// intersection of aabbs
for (int k=0; k<3; k++) {
aabb[0+k] = mju_max(aabb1[0+k], aabb2[0+k]);
aabb[3+k] = mju_min(aabb1[3+k], aabb2[3+k]);
}
// no intersection if max < min
if (aabb[3]<aabb[0] || aabb[4]<aabb[1] || aabb[5]<aabb[2]) {
return cnt;
}
// create sdf pointers
int instance[2];
const mjpPlugin* sdf_ptr[2];
mjtGeom geomtypes[2] = {m->geom_type[g2], m->geom_type[g1]};
instance[0] = m->geom_plugin[g2];
sdf_ptr[0] = getSDF(m, g2);
// get sdf plugins
if (m->geom_type[g1] == mjGEOM_SDF) {
instance[1] = m->geom_plugin[g1];
sdf_ptr[1] = getSDF(m, g1);
} else {
instance[1] = g1;
sdf_ptr[1] = NULL;
}
// reset visualization count
sdf_ptr[0]->reset(m, NULL, (void*)(d->plugin_data[instance[0]]), instance[0]);
// copy into sdf
mjSDF sdf;
sdf.id = instance;
sdf.relpos = offset21;
sdf.relmat = rotation21;
sdf.plugin = sdf_ptr;
sdf.geomtype = geomtypes;
// minimize sdf intersection
mjtNum contacts[3*mjMAXCONPAIR];
int i = 0, j = 0;
while (i<m->opt.sdf_initpoints) {
x[0] = aabb[0] + (aabb[3]-aabb[0]) * mju_Halton(j, 2);
x[1] = aabb[1] + (aabb[4]-aabb[1]) * mju_Halton(j, 3);
x[2] = aabb[2] + (aabb[5]-aabb[2]) * mju_Halton(j, 5);
mju_rotVecMat(y, x, rotation2);
mju_addTo3(y, offset2);
mju_rotVecMat(x, y, rotation12);
mju_addTo3(x, offset12);
j++;
// here a criterion for rejecting points could be inserted
i++;
// start counters
sdf_ptr[0]->compute(m, (mjData*)d, instance[0], mjPLUGIN_SDF);
// gradient descent
sdf.type = mjSDFTYPE_INTERSECTION;
dist = stepGradient(x, m, &sdf, (mjData*)d);
sdf.type = mjSDFTYPE_AVERAGE;
cnt = addContact(contacts, con, x, pos2true, squat2, dist, cnt, m, &sdf, (mjData*)d);
}
return cnt;
}
+62
View File
@@ -0,0 +1,62 @@
// Copyright 2021 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.
#ifndef MUJOCO_SRC_ENGINE_ENGINE_COLLISION_SDF_H_
#define MUJOCO_SRC_ENGINE_ENGINE_COLLISION_SDF_H_
#include <mujoco/mjdata.h>
#include <mujoco/mjexport.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mjplugin.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef enum mjtSDFType_ {
mjSDFTYPE_SINGLE = 0,
mjSDFTYPE_INTERSECTION,
mjSDFTYPE_AVERAGE,
} mjtSDFType;
struct mjSDF_ {
const mjpPlugin** plugin;
int* id;
mjtSDFType type;
mjtNum* relpos;
mjtNum* relmat;
mjtGeom* geomtype;
};
typedef struct mjSDF_ mjSDF;
// signed distance function
MJAPI mjtNum mjc_distance(const mjModel* m, const mjData* d, const mjSDF* s, const mjtNum x[3]);
// gradient of sdf
MJAPI void mjc_gradient(const mjModel* m, const mjData* d, const mjSDF* s, mjtNum gradient[3],
const mjtNum x[3]);
// collision between a height field and a signed distance field
int mjc_HFieldSDF(const mjModel* m, const mjData* d, mjContact* con, int g1, int g2, mjtNum margin);
// collision between a mesh and a signed distance field
int mjc_MeshSDF(const mjModel* m, const mjData* d, mjContact* con, int g1, int g2, mjtNum margin);
// collision between two signed distance fields
int mjc_SDF(const mjModel* m, const mjData* d, mjContact* con, int g1, int g2, mjtNum margin);
#ifdef __cplusplus
}
#endif
#endif // MUJOCO_SRC_ENGINE_ENGINE_COLLISION_SDF_H_
+5 -1
View File
@@ -123,6 +123,10 @@ void mj_defaultOption(mjOption* opt) {
opt->mpr_iterations = 50;
opt->disableflags = 0;
opt->enableflags = 0;
// sdf collisions
opt->sdf_initpoints = 40;
opt->sdf_iterations = 10;
}
@@ -1716,7 +1720,7 @@ const char* mj_validateReferences(const mjModel* m) {
if (m->geom_dataid[i] >= m->nhfield || m->geom_dataid[i] < -1) {
return "Invalid model: geom_dataid out of bounds.";
}
} else if (m->geom_type[i] == mjGEOM_MESH) {
} else if ((m->geom_type[i]==mjGEOM_MESH) || (m->geom_type[i]==mjGEOM_SDF)) {
if (m->geom_dataid[i] >= m->nmesh || m->geom_dataid[i] < -1) {
return "Invalid model: geom_dataid out of bounds.";
}
+1 -1
View File
@@ -55,7 +55,7 @@ namespace {
constexpr int kMaxNameLength = 1024;
constexpr int kMaxAttributes = 255;
constexpr int kCacheLine = 64;
constexpr int kCacheLine = 256;
// vfs prefix
constexpr const char* kVfsPrefix = mjVFS_PREFIX;
+66
View File
@@ -22,6 +22,7 @@
#include <mujoco/mjmodel.h>
#include <mujoco/mjvisualize.h>
#include "engine/engine_io.h"
#include "engine/engine_plugin.h"
#include "engine/engine_util_blas.h"
#include "engine/engine_util_errmem.h"
#include "engine/engine_util_misc.h"
@@ -721,6 +722,67 @@ mjtNum mju_rayTree(const mjModel* m, const mjData* d, int id, const mjtNum* pnt,
return x;
}
// intersect ray with signed distance field
mjtNum ray_sdf(const mjModel* m, const mjData* d, int g,
const mjtNum* pnt, const mjtNum* vec) {
mjtNum distance_total = 0;
mjtNum p[3];
// exclude using bounding box
if (ray_box(d->geom_xpos+3*g, d->geom_xmat+9*g, m->geom_size+3*g, pnt, vec, NULL) < 0) {
return -1;
}
// get sdf
int instance = m->geom_plugin[g];
const int nslot = mjp_pluginCount();
const int slot = m->plugin[instance];
const mjpPlugin* sdf = mjp_getPluginAtSlotUnsafe(slot, nslot);
if (!sdf) mju_error("invalid plugin slot: %d", slot);
if (!(sdf->capabilityflags & mjPLUGIN_SDF)) {
mju_error("Plugin is not a sign distance field at slot %d", slot);
}
// reset counter
sdf->reset(m, NULL, (void*)(d->plugin_data[instance]), instance);
// compute transformation
mjtNum sdf_quat[4], sdf_xmat[9], sdf_xpos[9];
mjtNum negpos[3], negquat[4], xquat[4];
mjtNum* xpos = d->geom_xpos + 3*g;
mjtNum* pos = m->geom_pos + 3*g;
mjtNum* quat = m->geom_quat + 4*g;
mju_mat2Quat(xquat, d->geom_xmat + 9*g);
mju_negPose(negpos, negquat, pos, quat);
mju_mulPose(sdf_xpos, sdf_quat, xpos, xquat, negpos, negquat);
mju_quat2Mat(sdf_xmat, sdf_quat);
// unit direction
mjtNum dir[3] = {vec[0], vec[1], vec[2]};
mju_normalize3(dir);
// ray marching
for (int i=0; i < 40; i++) {
mju_addScl3(p, pnt, dir, distance_total);
mju_subFrom3(p, sdf_xpos);
mju_rotVecMatT(p, p, sdf_xmat);
mjtNum distance = sdf->sdf_distance(p, (mjData*)d, instance);
distance_total += distance;
if (distance < 1e-8) {
return distance_total;
}
}
// reset counter
sdf->reset(m, NULL, (void*)(d->plugin_data[instance]), instance);
return -1;
}
// intersect ray with mesh
mjtNum mj_rayMesh(const mjModel* m, const mjData* d, int id,
const mjtNum* pnt, const mjtNum* vec) {
@@ -911,6 +973,8 @@ mjtNum mj_ray(const mjModel* m, const mjData* d, const mjtNum* pnt, const mjtNum
newdist = mj_rayMesh(m, d, i, pnt, vec);
} else if (m->geom_type[i] == mjGEOM_HFIELD) {
newdist = mj_rayHfield(m, d, i, pnt, vec);
} else if (m->geom_type[i] == mjGEOM_SDF) {
newdist = ray_sdf(m, d, i, pnt, vec);
}
// otherwise general dispatch
@@ -1068,6 +1132,8 @@ static mjtNum mju_singleRay(const mjModel* m, mjData* d, const mjtNum pnt[3], co
newdist = mj_rayMesh(m, d, i, pnt, vec);
} else if (m->geom_type[i] == mjGEOM_HFIELD) {
newdist = mj_rayHfield(m, d, i, pnt, vec);
} else if (m->geom_type[i] == mjGEOM_SDF) {
newdist = ray_sdf(m, d, i, pnt, vec);
}
// otherwise general dispatch
+2 -1
View File
@@ -92,7 +92,8 @@ const char* mjVISSTRING[mjNVISFLAG][3] = {
{"Static Bo&dy", "1", "D"},
{"Skin", "1", ";"},
{"Body Tree", "0", "`"},
{"Mesh Tree", "0", ""}
{"Mesh Tree", "0", ""},
{"SDF iters", "0", ""}
};
+8 -3
View File
@@ -647,6 +647,10 @@ void mjv_addGeoms(const mjModel* m, mjData* d, const mjvOption* vopt,
const mjtNum* xpos = d->geom_xpos + 3 * geomid;
const mjtNum* xmat = d->geom_xmat + 9 * geomid;
if (!d->bvh_active[i]) {
continue;
}
rgba[0] = d->bvh_active[i] ? 1 : 0;
rgba[1] = d->bvh_active[i] ? 0 : 1;
@@ -1053,7 +1057,8 @@ void mjv_addGeoms(const mjModel* m, mjData* d, const mjvOption* vopt,
for (int k=geomadr; k < geomadr+geomnum; k++) {
int geomtype = m->geom_type[k];
// add inflated geom if it is a regular primitive
if (geomtype != mjGEOM_PLANE && geomtype != mjGEOM_HFIELD && geomtype != mjGEOM_MESH) {
if (geomtype != mjGEOM_PLANE && geomtype != mjGEOM_HFIELD &&
geomtype != mjGEOM_MESH && geomtype != mjGEOM_SDF) {
START
// inflate sizes by 5%
mju_scl3(sz, m->geom_size+3*k, 1.05);
@@ -1183,7 +1188,7 @@ void mjv_addGeoms(const mjModel* m, mjData* d, const mjvOption* vopt,
setMaterial(m, thisgeom, geom_matid, rgba, vopt->flags);
// set texcoord
if (m->geom_type[i] == mjGEOM_MESH &&
if ((m->geom_type[i] == mjGEOM_MESH || m->geom_type[i] == mjGEOM_SDF) &&
m->geom_dataid[i] >= 0 &&
m->mesh_texcoordadr[m->geom_dataid[i]] >= 0) {
thisgeom->texcoord = 1;
@@ -1205,7 +1210,7 @@ void mjv_addGeoms(const mjModel* m, mjData* d, const mjvOption* vopt,
}
// mesh: 2*i is original, 2*i+1 is convex hull
if (m->geom_type[i] == mjGEOM_MESH) {
if (m->geom_type[i] == mjGEOM_MESH || m->geom_type[i] == mjGEOM_SDF) {
thisgeom->dataid *= 2;
if (m->mesh_graphadr[m->geom_dataid[i]] >= 0 && vopt->flags[mjVIS_CONVEXHULL] &&
(m->geom_contype[i] || m->geom_conaffinity[i])) {
+1
View File
@@ -356,6 +356,7 @@ static void renderGeom(const mjvGeom* geom, int mode, const float* headpos,
break;
case mjGEOM_MESH: // mesh
case mjGEOM_SDF:
if (geom->dataid >= 0) {
glCallList(con->baseMesh + geom->dataid);
}
+78 -1
View File
@@ -27,7 +27,7 @@
#define TINYOBJLOADER_IMPLEMENTATION
#endif
#include <mujoco/mjmacro.h>
#include <MC.h>
#include <mujoco/mjmodel.h>
#include "cc/array_safety.h"
#include "engine/engine_crossplatform.h"
@@ -256,6 +256,78 @@ void mjCMesh::set_needhull(bool needhull) {
// generate mesh using marching cubes
void mjCMesh::LoadSDF() {
if (plugin_name.empty() && plugin_instance_name.empty()) {
throw mjCError(
this, "neither 'plugin' nor 'instance' is specified for 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)) {
throw mjCError(this, "plugin '%s' does not support signed distance fields", plugin->name);
}
int i=0;
mjtNum attributes[10] = {0};
for (auto const& pair : plugin_instance->config_attribs) {
attributes[i++] = std::stod(pair.second);
}
mjtNum aabb[6] = {0};
plugin->sdf_aabb(aabb, attributes);
mjtNum total = aabb[3] + aabb[4] + aabb[5];
const mjtNum n = 300;
int nx, ny, nz;
nx = floor(n / total * aabb[3]) + 1;
ny = floor(n / total * aabb[4]) + 1;
nz = floor(n / total * aabb[5]) + 1;
MC::MC_FLOAT* field = new MC::MC_FLOAT[nx * ny * nz];
for (int i = 0; i < nx; i++) {
for (int j = 0; j < ny; j++) {
for (int k = 0; k < nz; k++) {
mjtNum point[] = {aabb[0]-aabb[3] + 2 * aabb[3] * i / (nx-1),
aabb[1]-aabb[4] + 2 * aabb[4] * j / (ny-1),
aabb[2]-aabb[5] + 2 * aabb[5] * k / (nz-1)};
field[(k * ny + j) * nx + i] = plugin->sdf_staticdistance(point, attributes);
}
}
}
MC::mcMesh mesh;
MC::marching_cube(field, nx, ny, nz, mesh);
std::vector<float> uservert;
std::vector<float> usernormal;
std::vector<int> userface;
for (size_t i = 0; i < mesh.vertices.size(); i++) {
uservert.push_back(2*aabb[3]*mesh.vertices.at(i).x/(nx-1) + aabb[0]-aabb[3]);
uservert.push_back(2*aabb[4]*mesh.vertices.at(i).y/(ny-1) + aabb[1]-aabb[4]);
uservert.push_back(2*aabb[5]*mesh.vertices.at(i).z/(nz-1) + aabb[2]-aabb[5]);
}
for (size_t i = 0; i < mesh.normals.size(); i++) {
usernormal.push_back(mesh.normals.at(i).x);
usernormal.push_back(mesh.normals.at(i).y);
usernormal.push_back(mesh.normals.at(i).z);
}
for (size_t i = 0; i < mesh.indices.size(); i++) {
userface.push_back(mesh.indices.at(i));
}
set_uservert(uservert);
set_usernormal(usernormal);
set_userface(userface);
delete[] field;
}
// compiler
void mjCMesh::Compile(int vfs_provider) {
// load file
@@ -293,6 +365,11 @@ void mjCMesh::Compile(int vfs_provider) {
}
}
// create using marching cubes
if (is_plugin) {
LoadSDF();
}
// copy user vertex
if (!uservert_.empty()) {
// check repeated
+8
View File
@@ -2743,6 +2743,14 @@ void mjCModel::TryCompile(mjModel*& m, mjData*& d, int vfs_provider) {
}
}
for (int i = 0; i < ngeom; ++i) {
if (geoms[i]->is_plugin) {
m->geom_plugin[i] = geoms[i]->plugin_instance->id;
} else {
m->geom_plugin[i] = -1;
}
}
std::vector<std::vector<int>> plugin_to_sensors(nplugin);
for (int i = 0; i < nsensor; ++i) {
if (sensors[i]->type == mjSENS_PLUGIN) {
+34 -11
View File
@@ -484,6 +484,12 @@ mjCBase::mjCBase() {
xmlpos[0] = xmlpos[1] = -1;
model = 0;
def = 0;
// plugin variables
is_plugin = false;
plugin_instance = nullptr;
plugin_name = "";
plugin_instance_name = "";
}
@@ -555,12 +561,6 @@ mjCBody::mjCBody(mjCModel* _model) {
gravcomp = 0;
userdata.clear();
// plugin variables
is_plugin = false;
plugin_instance = nullptr;
plugin_name = "";
plugin_instance_name = "";
// clear object lists
bodies.clear();
geoms.clear();
@@ -1284,6 +1284,12 @@ mjCGeom::mjCGeom(mjCModel* _model, mjCDef* _def) {
// set model, def
model = _model;
def = (_def ? _def : (_model ? _model->defaults[0] : 0));
// plugin variables
is_plugin = false;
plugin_instance = nullptr;
plugin_name = "";
plugin_instance_name = "";
}
@@ -1293,7 +1299,7 @@ double mjCGeom::GetVolume(void) {
double height;
// get from mesh
if (type==mjGEOM_MESH) {
if (type==mjGEOM_MESH || type==mjGEOM_SDF) {
if (meshid<0 || meshid>=(int)model->meshes.size()) {
throw mjCError(this, "invalid meshid in mesh geom '%s' (id = %d)", name.c_str(), id);
}
@@ -1353,7 +1359,7 @@ void mjCGeom::SetInertia(void) {
double height;
// get from mesh
if (type==mjGEOM_MESH) {
if (type==mjGEOM_MESH || type==mjGEOM_SDF) {
if (meshid<0 || meshid>=(int)model->meshes.size()) {
throw mjCError(this, "invalid meshid in mesh geom '%s' (id = %d)", name.c_str(), id);
}
@@ -1440,6 +1446,7 @@ double mjCGeom::GetRBound(void) {
return sqrt(size[0]*size[0]+size[1]*size[1]+size[2]*size[2]);
case mjGEOM_MESH:
case mjGEOM_SDF:
aabb = model->meshes[meshid]->aabb();
haabb[0] = mjMAX(fabs(aabb[0]), fabs(aabb[3]));
haabb[1] = mjMAX(fabs(aabb[1]), fabs(aabb[4]));
@@ -1586,6 +1593,7 @@ void mjCGeom::ComputeAABB() {
break;
case mjGEOM_MESH:
case mjGEOM_SDF:
mjuu_copyvec(aabb, model->meshes[meshid]->aabb(), 6);
break;
@@ -1647,7 +1655,7 @@ void mjCGeom::Compile(void) {
}
// check mesh
if (type==mjGEOM_MESH && meshid<0) {
if ((type==mjGEOM_MESH || type==mjGEOM_SDF) && meshid<0) {
throw mjCError(this, "mesh geom '%s' (id = %d) must have valid meshid", name.c_str(), id);
}
@@ -1730,7 +1738,7 @@ void mjCGeom::Compile(void) {
// fit geom if type is not mjGEOM_MESH
double meshpos[3];
if (type!=mjGEOM_MESH) {
if (type!=mjGEOM_MESH && type!=mjGEOM_SDF) {
pmesh->FitGeom(this, meshpos);
// remove reference to mesh
@@ -1752,7 +1760,7 @@ void mjCGeom::Compile(void) {
size[0] = model->hfields[hfieldid]->size[0];
size[1] = model->hfields[hfieldid]->size[1];
size[2] = 0.5*(model->hfields[hfieldid]->size[2]+model->hfields[hfieldid]->size[3]);
} else if (type==mjGEOM_MESH) {
} else if (type==mjGEOM_MESH || type==mjGEOM_SDF) {
const double* aabb = model->meshes[meshid]->aabb();
size[0] = mjMAX(fabs(aabb[0]), fabs(aabb[3]));
size[1] = mjMAX(fabs(aabb[1]), fabs(aabb[4]));
@@ -1793,6 +1801,21 @@ void mjCGeom::Compile(void) {
if (fluid_switch > 0) {
SetFluidCoefs();
}
// plugin
if (is_plugin) {
if (plugin_name.empty() && plugin_instance_name.empty()) {
throw mjCError(
this, "neither 'plugin' nor 'instance' is specified for geom '%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)) {
throw mjCError(this, "plugin '%s' does not support sign distance fields", plugin->name);
}
}
}
+1
View File
@@ -610,6 +610,7 @@ class mjCMesh: public mjCBase {
void LoadOBJ(mjResource* resource); // load mesh in wavefront OBJ format
void LoadSTL(mjResource* resource); // load mesh in STL BIN format
void LoadMSH(mjResource* resource); // load mesh in MSH BIN format
void LoadSDF(); // generate mesh using marching cubes
void MakeGraph(void); // make graph of convex hull
void CopyGraph(void); // copy graph into face data
void MakeNormal(void); // compute vertex normals
+32 -5
View File
@@ -80,7 +80,7 @@ void ReadPluginConfigs(tinyxml2::XMLElement* elem, mjCPlugin* pp) {
//---------------------------------- MJCF schema ---------------------------------------------------
static const int nMJCF = 191;
static const int nMJCF = 203;
static const char* MJCF[nMJCF][mjXATTRNUM] = {
{"mujoco", "!", "1", "model"},
{"<"},
@@ -95,12 +95,13 @@ static const char* MJCF[nMJCF][mjXATTRNUM] = {
"inttotal", "interval", "tolrange"},
{">"},
{"option", "*", "22",
{"option", "*", "24",
"timestep", "apirate", "impratio", "tolerance", "noslip_tolerance", "mpr_tolerance",
"gravity", "wind", "magnetic", "density", "viscosity",
"o_margin", "o_solref", "o_solimp",
"integrator", "collision", "cone", "jacobian",
"solver", "iterations", "noslip_iterations", "mpr_iterations"},
"solver", "iterations", "noslip_iterations", "mpr_iterations",
"sdf_iterations", "sdf_initpoints"},
{"<"},
{"flag", "?", "19", "constraint", "equality", "frictionloss", "limit", "contact",
"passive", "gravity", "clampctrl", "warmstart",
@@ -220,6 +221,12 @@ static const char* MJCF[nMJCF][mjXATTRNUM] = {
{"hfield", "*", "6", "name", "content_type", "file", "nrow", "ncol", "size"},
{"mesh", "*", "12", "name", "class", "content_type", "file", "vertex", "normal",
"texcoord", "face", "refpos", "refquat", "scale", "smoothnormal"},
{"<"},
{"plugin", "*", "2", "plugin", "instance"},
{"<"},
{"config", "*", "2", "key", "value"},
{">"},
{">"},
{"skin", "*", "9", "name", "file", "material", "rgba", "inflate",
"vertex", "texcoord", "face", "group"},
{"<"},
@@ -249,6 +256,12 @@ static const char* MJCF[nMJCF][mjXATTRNUM] = {
"shellinertia", "solmix", "solref", "solimp",
"margin", "gap", "fromto", "pos", "quat", "axisangle", "xyaxes", "zaxis", "euler",
"hfield", "mesh", "fitscale", "rgba", "fluidshape", "fluidcoef", "user"},
{"<"},
{"plugin", "*", "2", "plugin", "instance"},
{"<"},
{"config", "*", "2", "key", "value"},
{">"},
{">"},
{"site", "*", "15", "name", "class", "type", "group", "pos", "quat",
"material", "size", "fromto", "axisangle", "xyaxes", "zaxis", "euler", "rgba", "user"},
{"camera", "*", "13", "name", "class", "fovy", "ipd",
@@ -497,7 +510,8 @@ const mjMap geom_map[mjNGEOMTYPES] = {
{"ellipsoid", mjGEOM_ELLIPSOID},
{"cylinder", mjGEOM_CYLINDER},
{"box", mjGEOM_BOX},
{"mesh", mjGEOM_MESH}
{"mesh", mjGEOM_MESH},
{"sdf", mjGEOM_SDF}
};
@@ -964,6 +978,8 @@ void mjXReader::Option(XMLElement* section, mjOption* opt) {
ReadAttrInt(section, "iterations", &opt->iterations);
ReadAttrInt(section, "noslip_iterations", &opt->noslip_iterations);
ReadAttrInt(section, "mpr_iterations", &opt->mpr_iterations);
ReadAttrInt(section, "sdf_iterations", &opt->sdf_iterations);
ReadAttrInt(section, "sdf_initpoints", &opt->sdf_initpoints);
// read disable sub-element
XMLElement* elem = FindSubElem(section, "flag");
@@ -1196,6 +1212,11 @@ void mjXReader::OneMesh(XMLElement* elem, mjCMesh* pmesh) {
pmesh->set_refquat(ReadAttrArr<double, 4>(elem, "refquat"));
pmesh->set_scale(ReadAttrArr<double, 3>(elem, "scale"));
XMLElement* eplugin = elem->FirstChildElement("plugin");
if (eplugin) {
OnePlugin(eplugin, pmesh);
}
if (MapValue(elem, "smoothnormal", &n, bool_map, 2)) {
pmesh->set_smoothnormal((n==1));
}
@@ -1382,6 +1403,12 @@ void mjXReader::OneGeom(XMLElement* elem, mjCGeom* pgeom) {
// read userdata
ReadVector(elem, "user", pgeom->userdata, text);
// plugin sub-element
XMLElement* eplugin = elem->FirstChildElement("plugin");
if (eplugin) {
OnePlugin(eplugin, pgeom);
}
// remaining attributes
ReadAttr(elem, "mass", 1, &pgeom->_mass, text);
ReadAttr(elem, "density", 1, &pgeom->density, text);
@@ -2101,10 +2128,10 @@ void mjXReader::OnePlugin(XMLElement* elem, mjCBase* object) {
ReadAttrTxt(elem, "instance", object->plugin_instance_name);
if (object->plugin_instance_name.empty()) {
object->plugin_instance = model->AddPlugin();
ReadPluginConfigs(elem, object->plugin_instance);
} else {
model->hasImplicitPluginElem = true;
}
ReadPluginConfigs(elem, object->plugin_instance);
}
+17 -4
View File
@@ -278,7 +278,7 @@ void mjXWriter::OneGeom(XMLElement* elem, mjCGeom* pgeom, mjCDef* def) {
}
// mesh geom
if (pgeom->type==mjGEOM_MESH) {
if (pgeom->type==mjGEOM_MESH || pgeom->type==mjGEOM_SDF) {
mjCMesh* pmesh = model->meshes[pgeom->meshid];
// write pos/quat if there is a difference
@@ -337,7 +337,7 @@ void mjXWriter::OneGeom(XMLElement* elem, mjCGeom* pgeom, mjCDef* def) {
if (pgeom->type==mjGEOM_HFIELD) {
WriteAttrTxt(elem, "hfield", pgeom->hfield);
}
if (pgeom->type==mjGEOM_MESH) {
if (pgeom->type==mjGEOM_MESH || pgeom->type==mjGEOM_SDF) {
WriteAttrTxt(elem, "mesh", pgeom->mesh);
}
@@ -347,6 +347,11 @@ void mjXWriter::OneGeom(XMLElement* elem, mjCGeom* pgeom, mjCDef* def) {
} else {
WriteVector(elem, "user", pgeom->userdata, def->geom.userdata);
}
// write plugin
if (pgeom->is_plugin) {
OnePlugin(InsertEnd(elem, "plugin"), pgeom);
}
}
@@ -807,6 +812,8 @@ void mjXWriter::Option(XMLElement* root) {
WriteAttrInt(section, "iterations", model->option.iterations, opt.iterations);
WriteAttrInt(section, "noslip_iterations", model->option.noslip_iterations, opt.noslip_iterations);
WriteAttrInt(section, "mpr_iterations", model->option.mpr_iterations, opt.mpr_iterations);
WriteAttrInt(section, "sdf_iterations", model->option.sdf_iterations, opt.sdf_iterations);
WriteAttrInt(section, "sdf_initpoints", model->option.sdf_initpoints, opt.sdf_initpoints);
// write disable/enable flags if any of them are set; invert while writing
if (model->option.disableflags || model->option.enableflags) {
@@ -1307,8 +1314,14 @@ void mjXWriter::Asset(XMLElement* root) {
for (int i=0; i<nmesh; i++) {
// create element and write
mjCMesh* pmesh = (mjCMesh*)model->GetObject(mjOBJ_MESH, i);
elem = InsertEnd(section, "mesh");
OneMesh(elem, pmesh, pmesh->def);
if (pmesh->is_plugin) {
elem = InsertEnd(section, "mesh");
WriteAttrTxt(elem, "name", pmesh->name);
OnePlugin(InsertEnd(elem, "plugin"), pmesh);
} else{
elem = InsertEnd(section, "mesh");
OneMesh(elem, pmesh, pmesh->def);
}
}
// write skins
+6 -1
View File
@@ -41,7 +41,12 @@ target_link_libraries(
absl::str_format
)
mujoco_test(engine_plugin_test)
mujoco_test(
engine_plugin_test
PROPERTIES
ENVIRONMENT
"MUJOCO_PLUGIN_DIR=$<TARGET_FILE_DIR:elasticity>"
)
target_link_libraries(
engine_plugin_test
fixture
+84
View File
@@ -0,0 +1,84 @@
// Copyright 2022 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 sdf collisions.
#include <cstdio>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <mujoco/mjdata.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mjtnum.h>
#include <mujoco/mujoco.h>
#include "src/engine/engine_collision_sdf.h"
#include "test/fixture.h"
namespace mujoco {
namespace {
using ::testing::NotNull;
using SdfTest = MujocoTest;
static constexpr int kpoints = 6;
static constexpr int kgeoms = 5;
static constexpr char kSdfModel[] = R"(
<mujoco>
<worldbody>
<geom type="plane" size="5 5 .1" pos="0 0 -1"/>
<body pos="-.1 .2 2" euler="0 0 45">
<geom type="sphere" size="1"/>
<geom type="capsule" size=".1" fromto="-2 -2 1 2 2 1"/>
<geom type="cylinder" size="1" fromto="0 0 0 2 0 0"/>
<geom type="box" size="1 1 1"/>
</body>
</worldbody>
</mujoco>
)";
TEST_F(SdfTest, SdfPrimitive) {
mjModel* model = LoadModelFromString(kSdfModel);
ASSERT_THAT(model, NotNull());
mjData* data = mj_makeData(model);
ASSERT_THAT(data, NotNull());
ASSERT_THAT(model->ngeom, kgeoms);
mjSDF sdf;
const mjpPlugin* null_plugin = NULL;
mjtNum gradient[3], dist[kgeoms][kpoints] = {
{0, 0, 0, 0, 1, 1}, // plane
{-1, 0, 0, mju_sqrt(2)-1, mju_sqrt(2)-1, mju_sqrt(3)-1}, // sphere
{-.1, .9, .9, mju_sqrt(2)-.1, .9, mju_sqrt(2)-.1}, // capsule
{-1, 0, 0, mju_sqrt(2)-1, 0, mju_sqrt(2)-1}, // cylinder
{-1, 0, 0, 0, 0, 0}, // box
};
mjtNum points[kpoints][3] = {{0, 0, 0}, {1, 0, 0}, {0, 1, 0},
{1, 1, 0}, {0, 1, 1}, {1, 1, 1}};
for (int i = 0; i < kgeoms; i++) {
sdf.plugin = &null_plugin;
sdf.id = &i;
sdf.type = mjSDFTYPE_SINGLE;
sdf.geomtype = (mjtGeom*)(model->geom_type+i);
for (int j = 0; j < kpoints; j++) {
ASSERT_THAT(mjc_distance(model, data, &sdf, points[j]), dist[i][j]);
mjc_gradient(model, data, &sdf, gradient, points[j]);
}
}
mj_deleteData(data);
mj_deleteModel(model);
}
} // namespace
} // namespace mujoco
+12 -8
View File
@@ -32,9 +32,9 @@ namespace {
using ::testing::HasSubstr;
using ::testing::NotNull;
constexpr int kNumTruePlugins = 8;
constexpr int kNumFakePlugins = 30;
constexpr int kNumTestPlugins = 3;
const int kNumTruePlugins = mjp_pluginCount();
class BaseTestPlugin {
public:
@@ -319,10 +319,10 @@ int RegisterPassivePlugin() {
return mjp_registerPlugin(&plugin);
}
class PluginTest : public MujocoTest {
class EnginePluginTest : public PluginTest {
public:
// register all plugins
PluginTest() : MujocoTest() {
EnginePluginTest() : PluginTest() {
RegisterSensorPlugin();
for (int i = 1; i <= kNumFakePlugins; ++i) {
@@ -388,7 +388,11 @@ constexpr char xml[] = R"(
</mujoco>
)";
TEST_F(PluginTest, MultiplePluginTableBlocks) {
TEST_F(PluginTest, FirstPartyPlugins) {
EXPECT_THAT(mjp_pluginCount(), kNumTruePlugins);
}
TEST_F(EnginePluginTest, MultiplePluginTableBlocks) {
EXPECT_EQ(mjp_pluginCount(), kNumTruePlugins + kNumFakePlugins + kNumTestPlugins);
const mjpPlugin* last_plugin = nullptr;
@@ -412,14 +416,14 @@ TEST_F(PluginTest, MultiplePluginTableBlocks) {
EXPECT_LT(table_count, kNumFakePlugins);
}
TEST_F(PluginTest, RegisterIdenticalPlugin) {
TEST_F(EnginePluginTest, RegisterIdenticalPlugin) {
EXPECT_EQ(RegisterSensorPlugin(), kNumTruePlugins);
EXPECT_EQ(RegisterActuatorPlugin(), kNumTruePlugins + kNumFakePlugins + 1);
EXPECT_EQ(RegisterPassivePlugin(), kNumTruePlugins + kNumFakePlugins + 2);
EXPECT_EQ(mjp_pluginCount(), kNumTruePlugins + kNumFakePlugins + kNumTestPlugins);
}
TEST_F(PluginTest, SaveXml) {
TEST_F(EnginePluginTest, SaveXml) {
char error[1024] = {0};
mjModel* m = LoadModelFromString(xml, error, sizeof(error));
@@ -471,7 +475,7 @@ TEST_F(PluginTest, SaveXml) {
mj_deleteModel(m2);
}
TEST_F(PluginTest, SensorPlugin) {
TEST_F(EnginePluginTest, SensorPlugin) {
int expected_init_count = TestSensor::InitCount();
int expected_destroy_count = TestSensor::DestroyCount();
EXPECT_EQ(expected_init_count, expected_destroy_count);
@@ -529,7 +533,7 @@ TEST_F(PluginTest, SensorPlugin) {
EXPECT_EQ(TestSensor::DestroyCount(), expected_destroy_count);
}
TEST_F(PluginTest, ActuatorPlugin) {
TEST_F(EnginePluginTest, ActuatorPlugin) {
int expected_init_count = TestActuator::InitCount();
int expected_destroy_count = TestActuator::DestroyCount();
EXPECT_EQ(expected_init_count, expected_destroy_count);
+8 -17
View File
@@ -66,27 +66,18 @@ const std::string SaveAndReadXml(const mjModel* model);
std::vector<mjtNum> GetCtrlNoise(const mjModel* m, int nsteps,
mjtNum ctrlnoise = 0.01);
// Installs elasticity plugins
// TODO(quaglino): load all plugins with a macro
// Installs all plugins
class PluginTest : public MujocoTest {
public:
// load plugin library
PluginTest() : MujocoTest() {
#if defined(_WIN32) || defined(__CYGWIN__)
mj_loadPluginLibrary((
std::string(std::getenv("MUJOCO_PLUGIN_DIR")) +
std::string("\\elasticity.dll")).c_str());
#else
#if defined(__APPLE__)
mj_loadPluginLibrary((
std::string(std::getenv("MUJOCO_PLUGIN_DIR")) +
std::string("/libelasticity.dylib")).c_str());
#else
mj_loadPluginLibrary((
std::string(std::getenv("MUJOCO_PLUGIN_DIR")) +
std::string("/libelasticity.so")).c_str());
#endif
#endif
mj_loadAllPluginLibraries(
std::string(std::getenv("MUJOCO_PLUGIN_DIR")).c_str(), +[](const char* filename, int first, int count) {
std::printf("Plugins registered by library '%s':\n", filename);
for (int i = first; i < first + count; ++i) {
std::printf(" %s\n", mjp_getPluginAtSlot(i)->name);
}
});
}
};
@@ -56,8 +56,6 @@ TEST_F(PluginTest, ElasticEnergy) {
mjModel* m = LoadModelFromString(cantilever_xml, error, sizeof(error));
ASSERT_THAT(m, testing::NotNull()) << error;
mjData* d = mj_makeData(m);
EXPECT_THAT(mjp_pluginCount(), 2);
auto* solid = reinterpret_cast<plugin::elasticity::Solid*>(d->plugin_data[0]);
// check that if the entire geometry is rescaled by a factor "scale", then
+10 -4
View File
@@ -39,7 +39,7 @@ public const double mjMAXVAL = 10000000000.0;
public const double mjMINMU = 1e-05;
public const double mjMINIMP = 0.0001;
public const double mjMAXIMP = 0.9999;
public const int mjMAXCONPAIR = 50;
public const int mjMAXCONPAIR = 100;
public const int mjMAXTREEDEPTH = 50;
public const int mjMAXVFS = 2000;
public const int mjMAXVFSNAME = 1000;
@@ -177,7 +177,8 @@ public enum mjtGeom : int{
mjGEOM_CYLINDER = 5,
mjGEOM_BOX = 6,
mjGEOM_MESH = 7,
mjNGEOMTYPES = 8,
mjGEOM_SDF = 8,
mjNGEOMTYPES = 9,
mjGEOM_ARROW = 100,
mjGEOM_ARROW1 = 101,
mjGEOM_ARROW2 = 102,
@@ -374,6 +375,7 @@ public enum mjtPluginCapabilityBit : int{
mjPLUGIN_ACTUATOR = 1,
mjPLUGIN_SENSOR = 2,
mjPLUGIN_PASSIVE = 4,
mjPLUGIN_SDF = 8,
}
public enum mjtGridPos : int{
mjGRID_TOPLEFT = 0,
@@ -497,7 +499,8 @@ public enum mjtVisFlag : int{
mjVIS_SKIN = 23,
mjVIS_MIDPHASE = 24,
mjVIS_MESHBVH = 25,
mjNVISFLAG = 26,
mjVIS_SDFITER = 26,
mjNVISFLAG = 27,
}
public enum mjtRndFlag : int{
mjRND_SHADOW = 0,
@@ -1776,6 +1779,8 @@ public unsafe struct mjOption_ {
public int mpr_iterations;
public int disableflags;
public int enableflags;
public int sdf_initpoints;
public int sdf_iterations;
}
[StructLayout(LayoutKind.Sequential)]
@@ -2033,6 +2038,7 @@ public unsafe struct mjModel_ {
public int* geom_matid;
public int* geom_group;
public int* geom_priority;
public int* geom_plugin;
public byte* geom_sameframe;
public double* geom_solmix;
public double* geom_solref;
@@ -2601,7 +2607,7 @@ public unsafe struct mjvOption_ {
public fixed byte tendongroup[6];
public fixed byte actuatorgroup[6];
public fixed byte skingroup[6];
public fixed byte flags[26];
public fixed byte flags[27];
public int bvh_depth;
}