From 2f5e5d3da120f80152522df945cb908f7d1e39ae Mon Sep 17 00:00:00 2001 From: Michael Moss Date: Fri, 24 Apr 2026 08:37:43 -0700 Subject: [PATCH] Add documentation for mjpDecoder. PiperOrigin-RevId: 905048609 Change-Id: I6342673cdc53b80406ad77fc95cb8bb5790a5df6 --- doc/changelog.rst | 5 ++ doc/programming/extension.rst | 137 +++++++++++++++++++++++++++++++++- 2 files changed, 140 insertions(+), 2 deletions(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index 93a4312d..af200ca1 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -25,6 +25,11 @@ General **Migration:** The flag :ref:`multiccd` must be explicitly disabled. +Documentation +^^^^^^^^^^^^^ + +* Added :ref:`documentation` for :ref:`mjpDecoder` plugins. + Bug fixes ^^^^^^^^^ diff --git a/doc/programming/extension.rst b/doc/programming/extension.rst index 720f5829..bd2df7e9 100644 --- a/doc/programming/extension.rst +++ b/doc/programming/extension.rst @@ -3,8 +3,8 @@ Extensions ---------- -This section describes MuJoCo's mechanisms for user-authored extensions. At present, extensibility is provided -via :ref:`engine plugins` and :ref:`resource providers`. +This section describes MuJoCo's mechanisms for user-authored extensions. At present, extensibility is provided by +via :ref:`engine plugins`, :ref:`decoders`, and :ref:`resource providers`. .. _exPlugin: @@ -337,6 +337,139 @@ For the sdf plugin, the following methods need to be specified Computes the axis-aligned bounding box in local coordinates. This volume is voxelized uniformly before the call to the marching cubes algorithm. +.. _exDecoder: + +Decoders +~~~~~~~~ + +Decoder plugins extend asset loading capabilities beyond MJCF and URDF. They are :ref:`registered` +similarly to other MuJoCo plugins. + +MuJoCo ships with two built-in decoders for common mesh formats: + +- **OBJ decoder** (``plugin/obj_decoder``) -- `Wavefront OBJ `_. +- **STL decoder** (``plugin/stl_decoder``) -- `STL `_. + +Additionally, we provide the following optional decoder plugins: + +- **USD decoder** (``plugin/usd_decoder``) -- `Universal Scene Description `_. + +These plugins also serve as examples for how to write custom decoders. The obj decoder is perhaps the simplest to +understand, while the USD decoder is more complex due to its support for entire scenes. + +.. _exDecoderInterface: + +Decoder interface +^^^^^^^^^^^^^^^^^ + +A decoder is described by the :ref:`mjpDecoder` struct, which has the following fields: + +``content_type`` + A MIME-like content type string identifying the format. For example, ``"model/obj"``, or ``"model/stl"``. + When a mesh asset specifies a ``content-type`` attribute in MJCF, this string is used + to find the appropriate decoder. + +``extension`` + A file extension string (including the dot) used for matching when no content type is specified. Multiple + extensions can be separated by pipes (`|`) for formats with multiple extensions such as ``.usd|.usda|.usdc|.usdz``. + +``can_decode`` + A callback of type :ref:`mjfCanDecode` that determines whether the decoder can handle a given resource. This is + typically implemented by checking the file extension but may also check the file contents to differentiate between + formats. For example, URDF and MJCF files both have a ``.xml`` extension. Returns nonzero if the decoder can handle + the resource. + +``decode`` + A callback of type :ref:`mjfDecode` that performs the actual decoding. It receives an :ref:`mjResource` and + returns a newly allocated :ref:`mjSpec` containing the decoded asset data. The caller takes + ownership of the returned spec and is responsible for freeing it with :ref:`mj_deleteSpec`. Returns ``NULL`` on + failure. + +When a decoder is invoked for a mesh asset, the compiler will reference the first mesh element in the spec returned +by the ``decode`` callback. + +When a decoder is invoked for a model asset, the spec returned by the ``decode`` callback may contain any number of +elements of any type. + +.. _exDecoderRegistration: + +Registration +^^^^^^^^^^^^ + +Decoders must be registered before they can be used. Registration is performed via +:ref:`mjp_registerDecoder`. The :ref:`mjp_defaultDecoder` function initializes an :ref:`mjpDecoder` struct with +default values. The :ref:`mjPLUGIN_LIB_INIT` macro is used to define the initialization function that registers the +decoder when the library is loaded. + +.. code-block:: C + + mjPLUGIN_LIB_INIT(my_format_decoder) { + mjpDecoder decoder; + mjp_defaultDecoder(&decoder); + decoder.content_type = "model/my-format"; + decoder.extension = ".myf|.myfa|.myfc"; + decoder.decode = MyDecode; + decoder.can_decode = MyCanDecode; + mjp_registerDecoder(&decoder); + } + + +.. _exDecoderExample: + +Example +^^^^^^^ + +Below is a minimal decoder that reads a hypothetical binary mesh format: + +.. code-block:: C + + #include + + static mjSpec* MyDecode(mjResource* resource, const mjVFS* vfs) { + const void* bytes = NULL; + int nbytes = mju_readResource(resource, &bytes); + if (nbytes < 0) { + mju_warning("failed to read resource '%s'", resource->name); + return NULL; + } + + /* ... parse bytes into vertex/face arrays ... */ + + mjSpec* spec = mj_makeSpec(); + mjsMesh* mesh = mjs_addMesh(spec, NULL); + mjs_setString(mesh->file, resource->name); + mjs_setFloat(mesh->uservert, vertices, nvert * 3); + mjs_setInt(mesh->userface, faces, nface * 3); + return spec; + } + + static int MyCanDecode(const mjResource* resource) { + /* check file extension */ + const char* name = resource->name; + int len = strlen(name); + return len > 4 && strcmp(name + len - 4, ".myf") == 0; + } + + mjPLUGIN_LIB_INIT(my_format_decoder) { + mjpDecoder decoder; + mjp_defaultDecoder(&decoder); + decoder.content_type = "model/my-format"; + decoder.extension = ".myf"; + decoder.decode = MyDecode; + decoder.can_decode = MyCanDecode; + mjp_registerDecoder(&decoder); + } + +Once registered, the decoder is used automatically when MuJoCo encounters an asset with a matching file extension +or content type: + +.. code-block:: xml + + + + + + .. _exProvider: Resource providers