Merge branch 'main' into open3d-removal

This commit is contained in:
Abhishek Joshi
2024-07-22 18:25:34 -05:00
28 changed files with 702 additions and 173 deletions
+121 -1
View File
@@ -1636,6 +1636,11 @@ still be specified here but this functionality is now deprecated and will be rem
:at:`vflip`: :at-val:`[false, true], "false"` :at:`vflip`: :at-val:`[false, true], "false"`
If true, images loaded from file are flipped in the vertical direction. Does not affect procedural textures. If true, images loaded from file are flipped in the vertical direction. Does not affect procedural textures.
.. _asset-texture-nchannel:
:at:`nchannel`: :at-val:`int, "3"`
The number of channels in the texture image file. This allows loading 4-channel textures (RGBA) or single-channel
textures (e.g., for Physics-Based Rendering properties such as roughness or metallic).
.. _asset-material: .. _asset-material:
@@ -1666,7 +1671,12 @@ properties are grouped together.
If this attribute is specified, the material has a texture associated with it. Referencing the material from a model If this attribute is specified, the material has a texture associated with it. Referencing the material from a model
element will cause the texture to be applied to that element. Note that the value of this attribute is the name of a element will cause the texture to be applied to that element. Note that the value of this attribute is the name of a
texture asset, not a texture file name. Textures cannot be loaded in the material definition; instead they must be texture asset, not a texture file name. Textures cannot be loaded in the material definition; instead they must be
loaded explicitly via the :ref:`texture <asset-texture>` element and then referenced here. loaded explicitly via the :ref:`texture <asset-texture>` element and then referenced here. The texture referenced
here is used for specifying the RGB values. For advanced rendering (e.g., Physics-Based Rendering), more texture
types need to be specified (e.g., roughness, metallic). In this case, this texture attribute should be omitted, and
the texture types should be specified explicitly via the specific role child elements, e.g.,
:ref:`texture <material-orm>`. Note however that the built-in renderer does not support PBR properties, so these
advanced rendering features are only available when using an external renderer.
.. _asset-material-texrepeat: .. _asset-material-texrepeat:
@@ -1738,6 +1748,116 @@ properties are grouped together.
model element which defines its own local rgba attribute, the local definition has precedence. Note that this "local" model element which defines its own local rgba attribute, the local definition has precedence. Note that this "local"
definition could in fact come from a defaults class. The remaining material properties always apply. definition could in fact come from a defaults class. The remaining material properties always apply.
.. _material-rgb:
:el-prefix:`material/` |-| **rgb** (?)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This element references a texture asset used to specify base color / albedo values.
.. _material-rgb-texture:
:at:`texture`: :at-val:`string, required`
Name of the texture, expected to have exactly 3 channels.
.. _material-normal:
:el-prefix:`material/` |-| **normal** (?)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This element references a texture asset used to specify the bump map (surface normals).
.. _material-normal-texture:
:at:`texture`: :at-val:`string, required`
Name of the texture, expected to have exactly 3 channels.
.. _material-occlusion:
:el-prefix:`material/` |-| **occlusion** (?)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This element references a texture asset used to specify ambient occlusion.
.. _material-occlusion-texture:
:at:`texture`: :at-val:`string, required`
Name of the texture, expected to have exactly one channel.
.. _material-roughness:
:el-prefix:`material/` |-| **roughness** (?)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This element references a texture asset used to specify the roughness map.
.. _material-roughness-texture:
:at:`texture`: :at-val:`string, required`
Name of the texture, expected to have exactly one channel.
.. _material-metallic:
:el-prefix:`material/` |-| **metallic** (?)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This element references a texture asset used to specify the metallic map.
.. _material-metallic-texture:
:at:`texture`: :at-val:`string, required`
Name of the texture, expected to have exactly one channel.
.. _material-opacity:
:el-prefix:`material/` |-| **opacity** (?)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This element references a texture asset used to specify the opacity map (alpha channel, transparency).
.. _material-opacity-texture:
:at:`texture`: :at-val:`string, required`
Name of the texture, expected to have exactly one channel.
.. _material-emissive:
:el-prefix:`material/` |-| **emissive** (?)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This element references a texture asset used to specify light emission.
.. _material-emissive-texture:
:at:`texture`: :at-val:`string, required`
Name of the texture, expected to have exactly 4 channels.
.. _material-orm:
:el-prefix:`material/` |-| **orm** (?)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This element references a texture asset used to specify a packed ORM map, where occlusion, roughness, and metallic
are joined into the corresponding RGB values of a single texture.
.. _material-orm-texture:
:at:`texture`: :at-val:`string, required`
Name of the texture, expected to have exactly 3 channels.
.. _material-rgba:
:el-prefix:`material/` |-| **rgba** (?)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This element references a texture asset used to specify a packed map where albedo and opacity are joined into the same
4-channel texture.
.. _material-rgba-texture:
:at:`texture`: :at-val:`string, required`
Name of the texture, expected to have exactly 4 channels.
.. _asset-model: .. _asset-model:
+64 -1
View File
@@ -163,7 +163,7 @@
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`markrgb<asset-texture-markrgb>` | :ref:`random<asset-texture-random>` | :ref:`width<asset-texture-width>` | :ref:`height<asset-texture-height>` | | | | | | :ref:`markrgb<asset-texture-markrgb>` | :ref:`random<asset-texture-random>` | :ref:`width<asset-texture-width>` | :ref:`height<asset-texture-height>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`hflip<asset-texture-hflip>` | :ref:`vflip<asset-texture-vflip>` | | | | | | | | :ref:`hflip<asset-texture-hflip>` | :ref:`vflip<asset-texture-vflip>` | :ref:`nchannel<asset-texture-nchannel>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| asset |br| |_| |L| | | .. table:: | | |_| asset |br| |_| |L| | | .. table:: |
@@ -177,6 +177,69 @@
| | | | :ref:`reflectance<asset-material-reflectance>` | :ref:`metallic<asset-material-metallic>` | :ref:`roughness<asset-material-roughness>` | :ref:`rgba<asset-material-rgba>` | | | | | | :ref:`reflectance<asset-material-reflectance>` | :ref:`metallic<asset-material-metallic>` | :ref:`roughness<asset-material-roughness>` | :ref:`rgba<asset-material-rgba>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_2| material |br| |_2| |L| | | .. table:: |
| :ref:`rgb | ? | :class: mjcf-attributes |
| <material-rgb>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`texture<material-rgb-texture>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_2| material |br| |_2| |L| | | .. table:: |
| :ref:`occlusion | ? | :class: mjcf-attributes |
| <material-occlusion>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`texture<material-occlusion-texture>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_2| material |br| |_2| |L| | | .. table:: |
| :ref:`roughness | ? | :class: mjcf-attributes |
| <material-roughness>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`texture<material-roughness-texture>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_2| material |br| |_2| |L| | | .. table:: |
| :ref:`metallic | ? | :class: mjcf-attributes |
| <material-metallic>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`texture<material-metallic-texture>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_2| material |br| |_2| |L| | | .. table:: |
| :ref:`normal | ? | :class: mjcf-attributes |
| <material-normal>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`texture<material-normal-texture>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_2| material |br| |_2| |L| | | .. table:: |
| :ref:`opacity | ? | :class: mjcf-attributes |
| <material-opacity>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`texture<material-opacity-texture>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_2| material |br| |_2| |L| | | .. table:: |
| :ref:`emissive | ? | :class: mjcf-attributes |
| <material-emissive>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`texture<material-emissive-texture>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_2| material |br| |_2| |L| | | .. table:: |
| :ref:`rgba | ? | :class: mjcf-attributes |
| <material-rgba>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`texture<material-rgba-texture>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_2| material |br| |_2| |L| | | .. table:: |
| :ref:`orm | ? | :class: mjcf-attributes |
| <material-orm>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`texture<material-orm-texture>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| asset |br| |_| |L| | | .. table:: | | |_| asset |br| |_| |L| | | .. table:: |
| :ref:`model | \* | :class: mjcf-attributes | | :ref:`model | \* | :class: mjcf-attributes |
| <asset-model>` | | | | <asset-model>` | | |
+9 -2
View File
@@ -10,15 +10,22 @@ General
1. Renamed ``mjModel.tex_rbg`` to ``mjModel.tex_data``. 1. Renamed ``mjModel.tex_rbg`` to ``mjModel.tex_data``.
2. Added a new disable flag ``mjDSBL_AUTORESETNAN`` to disable automatic reset when NaNs or infinities are produced. 2. Added a new disable flag ``mjDSBL_AUTORESETNAN`` to disable automatic reset when NaNs or infinities are produced.
3. Added sub-elements to the MJCF :ref:`material<asset-material>` element, to allow specification of multiple textures
for rendering (e.g., :ref:`occlusion-roughness-metallic<material-orm>`). Note that the MuJoCo renderer doesn't support
these new features, and they are made available for use with external renderers.
MJX MJX
^^^ ^^^
3. Added more fields to ``mjx.Model`` and ``mjx.Data`` for further compatibility with the corresponding MuJoCo structs. 4. Added more fields to ``mjx.Model`` and ``mjx.Data`` for further compatibility with the corresponding MuJoCo structs.
5. Added support for :ref:`fixed tendons <tendon-fixed>`.
6. Added support for tendon length limits (``mjCNSTR_LIMIT_TENDON`` in :ref:`mjtConstraint`).
7. Added support for tendon equality constraints (``mjEQ_TENDON`` in :ref:`mjtEq`).
8. Added support for tendon actuator transmission (``mjTRN_TENDON`` in :ref:`mjtTrn`).
Python bindings Python bindings
^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^
4. Added support for asset dictionary argument in ``mujoco.spec.from_file``, ``mujoco.spec.from_string`` and 9. Added support for asset dictionary argument in ``mujoco.spec.from_file``, ``mujoco.spec.from_string`` and
``mujoco.spec.compile``. ``mujoco.spec.compile``.
Version 3.2.0 (Jul 15, 2024) Version 3.2.0 (Jul 15, 2024)
+2 -1
View File
@@ -2015,6 +2015,7 @@ typedef struct mjsTexture_ { // texture specification
double random; // probability of random dots double random; // probability of random dots
int height; // height in pixels (square for cube and skybox) int height; // height in pixels (square for cube and skybox)
int width; // width in pixels int width; // width in pixels
int nchannel; // number of channels
// method 2: single file // method 2: single file
mjString* content_type; // content type of file mjString* content_type; // content type of file
@@ -2035,7 +2036,7 @@ typedef struct mjsTexture_ { // texture specification
typedef struct mjsMaterial_ { // material specification typedef struct mjsMaterial_ { // material specification
mjsElement* element; // element type mjsElement* element; // element type
mjString* name; // name mjString* name; // name
mjString* texture; // name of texture (empty: none) mjStringVec* textures; // names of textures (empty: none)
mjtByte texuniform; // make texture cube uniform mjtByte texuniform; // make texture cube uniform
float texrepeat[2]; // texture repetition for 2D mapping float texrepeat[2]; // texture repetition for 2D mapping
float emission; // emission float emission; // emission
+5 -7
View File
@@ -188,7 +188,7 @@ The following features are **fully supported** in MJX:
* - :ref:`Joint <mjtJoint>` * - :ref:`Joint <mjtJoint>`
- ``FREE``, ``BALL``, ``SLIDE``, ``HINGE`` - ``FREE``, ``BALL``, ``SLIDE``, ``HINGE``
* - :ref:`Transmission <mjtTrn>` * - :ref:`Transmission <mjtTrn>`
- ``TRN_JOINT``, ``TRN_SITE`` - ``TRN_JOINT``, ``TRN_SITE``, ``TRN_TENDON``
* - :ref:`Actuator Dynamics <mjtDyn>` * - :ref:`Actuator Dynamics <mjtDyn>`
- ``NONE``, ``INTEGRATOR``, ``FILTER``, ``FILTEREXACT`` - ``NONE``, ``INTEGRATOR``, ``FILTER``, ``FILTEREXACT``
* - :ref:`Actuator Gain <mjtGain>` * - :ref:`Actuator Gain <mjtGain>`
@@ -200,7 +200,7 @@ The following features are **fully supported** in MJX:
* - :ref:`Constraint <mjtConstraint>` * - :ref:`Constraint <mjtConstraint>`
- ``EQUALITY``, ``LIMIT_JOINT``, ``CONTACT_FRICTIONLESS``, ``CONTACT_PYRAMIDAL``, ``CONTACT_ELLIPTIC`` - ``EQUALITY``, ``LIMIT_JOINT``, ``CONTACT_FRICTIONLESS``, ``CONTACT_PYRAMIDAL``, ``CONTACT_ELLIPTIC``
* - :ref:`Equality <mjtEq>` * - :ref:`Equality <mjtEq>`
- ``CONNECT``, ``WELD``, ``JOINT`` - ``CONNECT``, ``WELD``, ``JOINT``, ``TENDON``
* - :ref:`Integrator <mjtIntegrator>` * - :ref:`Integrator <mjtIntegrator>`
- ``EULER``, ``RK4`` - ``EULER``, ``RK4``
* - :ref:`Cone <mjtCone>` * - :ref:`Cone <mjtCone>`
@@ -211,6 +211,8 @@ The following features are **fully supported** in MJX:
- ``CG``, ``NEWTON`` - ``CG``, ``NEWTON``
* - Fluid Model * - Fluid Model
- :ref:`flInertia` - :ref:`flInertia`
* - :ref:`Tendons <tendon>`
- :ref:`Fixed <tendon-fixed>`
The following features are **in development** and coming soon: The following features are **in development** and coming soon:
@@ -230,8 +232,6 @@ The following features are **in development** and coming soon:
- ``IMPLICIT``, ``IMPLICITFAST`` - ``IMPLICIT``, ``IMPLICITFAST``
* - Dynamics * - Dynamics
- :ref:`Inverse <mj_inverse>` - :ref:`Inverse <mj_inverse>`
* - :ref:`Transmission <mjtTrn>`
- ``TRN_TENDON``
* - :ref:`Actuator Dynamics <mjtDyn>` * - :ref:`Actuator Dynamics <mjtDyn>`
- ``MUSCLE`` - ``MUSCLE``
* - :ref:`Actuator Gain <mjtGain>` * - :ref:`Actuator Gain <mjtGain>`
@@ -243,9 +243,7 @@ The following features are **in development** and coming soon:
* - Fluid Model * - Fluid Model
- :ref:`flEllipsoid` - :ref:`flEllipsoid`
* - :ref:`Tendons <tendon>` * - :ref:`Tendons <tendon>`
- :ref:`Spatial <tendon-spatial>`, :ref:`Fixed <tendon-fixed>` - :ref:`Spatial <tendon-spatial>`
* - :ref:`Equality <mjtEq>`
- ``TENDON``
* - :ref:`Sensors <mjtSensor>` * - :ref:`Sensors <mjtSensor>`
- All except ``PLUGIN``, ``USER`` - All except ``PLUGIN``, ``USER``
* - Lights * - Lights
+2 -1
View File
@@ -500,6 +500,7 @@ typedef struct mjsTexture_ { // texture specification
double random; // probability of random dots double random; // probability of random dots
int height; // height in pixels (square for cube and skybox) int height; // height in pixels (square for cube and skybox)
int width; // width in pixels int width; // width in pixels
int nchannel; // number of channels
// method 2: single file // method 2: single file
mjString* content_type; // content type of file mjString* content_type; // content type of file
@@ -522,7 +523,7 @@ typedef struct mjsTexture_ { // texture specification
typedef struct mjsMaterial_ { // material specification typedef struct mjsMaterial_ { // material specification
mjsElement* element; // element type mjsElement* element; // element type
mjString* name; // name mjString* name; // name
mjString* texture; // name of texture (empty: none) mjStringVec* textures; // names of textures (empty: none)
mjtByte texuniform; // make texture cube uniform mjtByte texuniform; // make texture cube uniform
float texrepeat[2]; // texture repetition for 2D mapping float texrepeat[2]; // texture repetition for 2D mapping
float emission; // emission float emission; // emission
+8 -3
View File
@@ -9969,6 +9969,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([
type=ValueType(name='int'), type=ValueType(name='int'),
doc='width in pixels', doc='width in pixels',
), ),
StructFieldDecl(
name='nchannel',
type=ValueType(name='int'),
doc='number of channels',
),
StructFieldDecl( StructFieldDecl(
name='content_type', name='content_type',
type=PointerType( type=PointerType(
@@ -10045,11 +10050,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([
doc='name', doc='name',
), ),
StructFieldDecl( StructFieldDecl(
name='texture', name='textures',
type=PointerType( type=PointerType(
inner_type=ValueType(name='mjString'), inner_type=ValueType(name='mjStringVec'),
), ),
doc='name of texture (empty: none)', doc='names of textures (empty: none)',
), ),
StructFieldDecl( StructFieldDecl(
name='texuniform', name='texuniform',
+1
View File
@@ -39,6 +39,7 @@ from mujoco.mjx._src.smooth import crb
from mujoco.mjx._src.smooth import factor_m from mujoco.mjx._src.smooth import factor_m
from mujoco.mjx._src.smooth import kinematics from mujoco.mjx._src.smooth import kinematics
from mujoco.mjx._src.smooth import rne from mujoco.mjx._src.smooth import rne
from mujoco.mjx._src.smooth import tendon
from mujoco.mjx._src.smooth import transmission from mujoco.mjx._src.smooth import transmission
from mujoco.mjx._src.solver import solve from mujoco.mjx._src.solver import solve
from mujoco.mjx._src.support import full_m from mujoco.mjx._src.support import full_m
+73
View File
@@ -205,6 +205,44 @@ def _efc_equality_joint(m: Model, d: Data) -> Optional[_Efc]:
return rows(*args) return rows(*args)
def _efc_equality_tendon(m: Model, d: Data) -> Optional[_Efc]:
"""Calculates constraint rows for tendon equality constraints."""
eq_id = np.nonzero(m.eq_type == EqType.TENDON)[0]
if (m.opt.disableflags & DisableBit.EQUALITY) or eq_id.size == 0:
return None
obj1id, obj2id, data, solref, solimp = jax.tree_util.tree_map(
lambda x: x[eq_id],
(
m.eq_obj1id,
m.eq_obj2id,
m.eq_data,
m.eq_solref,
m.eq_solimp,
),
)
@jax.vmap
def rows(obj2id, data, solref, solimp, invweight, jac1, jac2, pos1, pos2):
dif = pos2 * (obj2id > -1)
dif_power = jp.power(dif, jp.arange(0, 5))
pos = pos1 - jp.dot(data[:5], dif_power)
deriv = jp.dot(data[1:5], dif_power[:4] * jp.arange(1, 5)) * (obj2id > -1)
j = jac1 + jac2 * -deriv
return _row(j, pos, pos, invweight, solref, solimp)
inv1, inv2 = m.tendon_invweight0[obj1id], m.tendon_invweight0[obj2id]
jac1, jac2 = d.ten_J[obj1id], d.ten_J[obj2id]
pos1 = d.ten_length[obj1id] - m.tendon_length0[obj1id]
pos2 = d.ten_length[obj2id] - m.tendon_length0[obj2id]
invweight = inv1 + inv2 * (obj2id > -1)
return rows(obj2id, data, solref, solimp, invweight, jac1, jac2, pos1, pos2)
def _efc_friction(m: Model, d: Data) -> Optional[_Efc]: def _efc_friction(m: Model, d: Data) -> Optional[_Efc]:
# TODO(robotics-team): implement _instantiate_friction # TODO(robotics-team): implement _instantiate_friction
del m, d del m, d
@@ -222,6 +260,8 @@ def _efc_limit_ball(m: Model, d: Data) -> Optional[_Efc]:
@jax.vmap @jax.vmap
def rows(qposadr, dofadr, jnt_range, jnt_margin, solref, solimp): def rows(qposadr, dofadr, jnt_range, jnt_margin, solref, solimp):
axis, angle = math.quat_to_axis_angle(d.qpos[jp.arange(4) + qposadr]) axis, angle = math.quat_to_axis_angle(d.qpos[jp.arange(4) + qposadr])
# ball rotation angle is always positive
axis, angle = math.normalize_with_norm(axis * angle)
pos = jp.amax(jnt_range) - angle - jnt_margin pos = jp.amax(jnt_range) - angle - jnt_margin
active = pos < 0 active = pos < 0
j = jp.zeros(m.nv).at[jp.arange(3) + dofadr].set(-axis) j = jp.zeros(m.nv).at[jp.arange(3) + dofadr].set(-axis)
@@ -263,6 +303,34 @@ def _efc_limit_slide_hinge(m: Model, d: Data) -> Optional[_Efc]:
return rows(*args) return rows(*args)
def _efc_limit_tendon(m: Model, d: Data) -> Optional[_Efc]:
"""Calculates constraint rows for tendon limits."""
tendon_id = np.nonzero(m.tendon_limited)[0]
if (m.opt.disableflags & DisableBit.LIMIT) or tendon_id.size == 0:
return None
length, j, range_, margin, invweight, solref, solimp = jax.tree_util.tree_map(
lambda x: x[tendon_id],
(
d.ten_length,
d.ten_J,
m.tendon_range,
m.tendon_margin,
m.tendon_invweight0,
m.tendon_solref_lim,
m.tendon_solimp_lim,
),
)
dist_min, dist_max = length - range_[:, 0], range_[:, 1] - length
pos = jp.minimum(dist_min, dist_max) - margin
active = pos < 0
j = jax.vmap(jp.multiply)(j, ((dist_min < dist_max) * 2 - 1) * active)
return jax.vmap(_row)(j, pos * active, pos, invweight, solref, solimp)
def _efc_contact_frictionless(m: Model, d: Data) -> Optional[_Efc]: def _efc_contact_frictionless(m: Model, d: Data) -> Optional[_Efc]:
"""Calculates constraint rows for frictionless contacts.""" """Calculates constraint rows for frictionless contacts."""
@@ -365,6 +433,7 @@ def counts(efc_type: np.ndarray) -> Tuple[int, int, int, int]:
ne = (efc_type == ConstraintType.EQUALITY).sum() ne = (efc_type == ConstraintType.EQUALITY).sum()
nf = 0 # no support for friction loss yet nf = 0 # no support for friction loss yet
nl = (efc_type == ConstraintType.LIMIT_JOINT).sum() nl = (efc_type == ConstraintType.LIMIT_JOINT).sum()
nl += (efc_type == ConstraintType.LIMIT_TENDON).sum()
nc_f = (efc_type == ConstraintType.CONTACT_FRICTIONLESS).sum() nc_f = (efc_type == ConstraintType.CONTACT_FRICTIONLESS).sum()
nc_p = (efc_type == ConstraintType.CONTACT_PYRAMIDAL).sum() nc_p = (efc_type == ConstraintType.CONTACT_PYRAMIDAL).sum()
nc_e = (efc_type == ConstraintType.CONTACT_ELLIPTIC).sum() nc_e = (efc_type == ConstraintType.CONTACT_ELLIPTIC).sum()
@@ -387,10 +456,12 @@ def make_efc_type(
num_rows = (m.eq_type == EqType.CONNECT).sum() * 3 num_rows = (m.eq_type == EqType.CONNECT).sum() * 3
num_rows += (m.eq_type == EqType.WELD).sum() * 6 num_rows += (m.eq_type == EqType.WELD).sum() * 6
num_rows += (m.eq_type == EqType.JOINT).sum() num_rows += (m.eq_type == EqType.JOINT).sum()
num_rows += (m.eq_type == EqType.TENDON).sum()
efc_types += [ConstraintType.EQUALITY] * num_rows efc_types += [ConstraintType.EQUALITY] * num_rows
if not m.opt.disableflags & DisableBit.LIMIT: if not m.opt.disableflags & DisableBit.LIMIT:
efc_types += [ConstraintType.LIMIT_JOINT] * m.jnt_limited.sum() efc_types += [ConstraintType.LIMIT_JOINT] * m.jnt_limited.sum()
efc_types += [ConstraintType.LIMIT_TENDON] * m.tendon_limited.sum()
if not m.opt.disableflags & DisableBit.CONTACT: if not m.opt.disableflags & DisableBit.CONTACT:
for condim in (1, 3, 4, 6): for condim in (1, 3, 4, 6):
@@ -441,9 +512,11 @@ def make_constraint(m: Model, d: Data) -> Data:
_efc_equality_connect(m, d), _efc_equality_connect(m, d),
_efc_equality_weld(m, d), _efc_equality_weld(m, d),
_efc_equality_joint(m, d), _efc_equality_joint(m, d),
_efc_equality_tendon(m, d),
_efc_friction(m, d), _efc_friction(m, d),
_efc_limit_ball(m, d), _efc_limit_ball(m, d),
_efc_limit_slide_hinge(m, d), _efc_limit_slide_hinge(m, d),
_efc_limit_tendon(m, d),
_efc_contact_frictionless(m, d), _efc_contact_frictionless(m, d),
) )
if m.opt.cone == ConeType.ELLIPTIC: if m.opt.cone == ConeType.ELLIPTIC:
+5 -5
View File
@@ -96,21 +96,21 @@ class ConstraintTest(parameterized.TestCase):
ne, nf, nl, nc = constraint.counts(constraint.make_efc_type(m)) ne, nf, nl, nc = constraint.counts(constraint.make_efc_type(m))
self.assertEqual(ne, 0) self.assertEqual(ne, 0)
self.assertEqual(nf, 0) self.assertEqual(nf, 0)
self.assertEqual(nl, 3) self.assertEqual(nl, 5)
self.assertEqual(nc, 148) self.assertEqual(nc, 148)
dx = constraint.make_constraint(mjx.put_model(m), mjx.make_data(m)) dx = constraint.make_constraint(mjx.put_model(m), mjx.make_data(m))
self.assertEqual(dx.efc_J.shape[0], 151) # only joint range, contact self.assertEqual(dx.efc_J.shape[0], 153) # only joint/tendon limit, contact
def test_disable_contact(self): def test_disable_contact(self):
m = test_util.load_test_file('constraints.xml') m = test_util.load_test_file('constraints.xml')
m.opt.disableflags = m.opt.disableflags | mjx.DisableBit.CONTACT m.opt.disableflags = m.opt.disableflags | mjx.DisableBit.CONTACT
ne, nf, nl, nc = constraint.counts(constraint.make_efc_type(m)) ne, nf, nl, nc = constraint.counts(constraint.make_efc_type(m))
self.assertEqual(ne, 10) self.assertEqual(ne, 11)
self.assertEqual(nf, 0) self.assertEqual(nf, 0)
self.assertEqual(nl, 3) self.assertEqual(nl, 5)
self.assertEqual(nc, 0) self.assertEqual(nc, 0)
dx = constraint.make_constraint(mjx.put_model(m), mjx.make_data(m)) dx = constraint.make_constraint(mjx.put_model(m), mjx.make_data(m))
self.assertEqual(dx.efc_J.shape[0], 13) # only joint range, limit self.assertEqual(dx.efc_J.shape[0], 16) # only equality, joint/tendon limit
if __name__ == '__main__': if __name__ == '__main__':
+5 -1
View File
@@ -66,6 +66,7 @@ def fwd_position(m: Model, d: Data) -> Data:
d = smooth.kinematics(m, d) d = smooth.kinematics(m, d)
d = smooth.com_pos(m, d) d = smooth.com_pos(m, d)
d = smooth.camlight(m, d) d = smooth.camlight(m, d)
d = smooth.tendon(m, d)
d = smooth.crb(m, d) d = smooth.crb(m, d)
d = smooth.factor_m(m, d) d = smooth.factor_m(m, d)
d = collision_driver.collision(m, d) d = collision_driver.collision(m, d)
@@ -77,7 +78,10 @@ def fwd_position(m: Model, d: Data) -> Data:
@named_scope @named_scope
def fwd_velocity(m: Model, d: Data) -> Data: def fwd_velocity(m: Model, d: Data) -> Data:
"""Velocity-dependent computations.""" """Velocity-dependent computations."""
d = d.replace(actuator_velocity=d.actuator_moment @ d.qvel) d = d.replace(
actuator_velocity=d.actuator_moment @ d.qvel,
ten_velocity=d.ten_J @ d.qvel,
)
d = smooth.com_vel(m, d) d = smooth.com_vel(m, d)
d = passive.passive(m, d) d = passive.passive(m, d)
d = smooth.rne(m, d) d = smooth.rne(m, d)
+2 -4
View File
@@ -15,7 +15,7 @@
"""Functions to initialize, load, or save data.""" """Functions to initialize, load, or save data."""
import copy import copy
from typing import Any, Dict, List, Tuple, Union from typing import List, Tuple, Union
import jax import jax
from jax import numpy as jp from jax import numpy as jp
@@ -74,9 +74,6 @@ def put_model(
) -> types.Model: ) -> types.Model:
"""Puts mujoco.MjModel onto a device, resulting in mjx.Model.""" """Puts mujoco.MjModel onto a device, resulting in mjx.Model."""
if _check_unsupported and m.ntendon:
raise NotImplementedError('tendons are not supported')
mesh_geomid = set() mesh_geomid = set()
for g1, g2, ip in collision_driver.geom_pairs(m): for g1, g2, ip in collision_driver.geom_pairs(m):
t1, t2 = m.geom_type[[g1, g2]] t1, t2 = m.geom_type[[g1, g2]]
@@ -104,6 +101,7 @@ def put_model(
(m.actuator_gaintype, types.GainType, mujoco.mjtGain), (m.actuator_gaintype, types.GainType, mujoco.mjtGain),
(m.actuator_trntype, types.TrnType, mujoco.mjtTrn), (m.actuator_trntype, types.TrnType, mujoco.mjtTrn),
(m.eq_type, types.EqType, mujoco.mjtEq), (m.eq_type, types.EqType, mujoco.mjtEq),
(m.wrap_type, types.WrapType, mujoco.mjtWrap),
): ):
missing = set(enum_field) - set(enum_type) missing = set(enum_field) - set(enum_type)
if _check_unsupported and missing: if _check_unsupported and missing:
+15 -7
View File
@@ -148,20 +148,28 @@ class ModelIOTest(parameterized.TestCase):
) )
) )
def test_tendon_not_implemented(self): def test_spatial_tendon_not_implemented(self):
with self.assertRaises(NotImplementedError): with self.assertRaises(NotImplementedError):
mjx.put_model(mujoco.MjModel.from_xml_string(""" mjx.put_model(mujoco.MjModel.from_xml_string("""
<mujoco> <mujoco>
<worldbody> <worldbody>
<body> <body name="arm">
<joint name="left_hip" type="hinge"/> <joint name="arm" axis="0 1 0"/>
<geom size="0.05"/> <geom name="shoulder" type="sphere" size=".05"/>
<site name="arm" pos="-.1 0 .05"/>
</body>
<body name="slider" pos=".05 0 -.2">
<joint name="slider" type="slide" damping="1"/>
<geom name="slider" type="box" size=".01 .01 .01"/>
<site name="slider" pos="0 0 .01"/>
</body> </body>
</worldbody> </worldbody>
<tendon> <tendon>
<fixed> <spatial name="rope" range="0 .35">
<joint coef="1" joint="left_hip"/> <site site="slider"/>
</fixed> <site site="arm"/>
</spatial>
</tendon> </tendon>
</mujoco>""")) </mujoco>"""))
+7
View File
@@ -70,6 +70,13 @@ def _spring_damper(m: Model, d: Data) -> jax.Array:
# dof-level dampers # dof-level dampers
qfrc -= m.dof_damping * d.qvel qfrc -= m.dof_damping * d.qvel
# tendon-level spring-dampers
below, above = m.tendon_lengthspring.T - d.ten_length
frc_spring = jp.where(below > 0, m.tendon_stiffness * below, 0)
frc_spring = jp.where(above < 0, m.tendon_stiffness * above, frc_spring)
frc_damper = -m.tendon_damping * d.ten_velocity
qfrc += d.ten_J.T @ (frc_spring + frc_damper)
return qfrc return qfrc
+1 -1
View File
@@ -42,7 +42,7 @@ class PassiveTest(absltest.TestCase):
m = test_util.load_test_file('pendula.xml') m = test_util.load_test_file('pendula.xml')
d = mujoco.MjData(m) d = mujoco.MjData(m)
# give the system a little kick to ensure we have non-identity rotations # give the system a little kick to ensure we have non-identity rotations
d.ctrl = np.array([0.1, -0.1, 0.2, 0.3, -0.4, 0.5, -0.6, 0.1]) d.ctrl = np.array([0.1, -0.1, 0.2, 0.3, -0.4, 0.5, -0.6, 0.1, -0.2])
mujoco.mj_step(m, d, 10) # let dynamics get state significantly non-zero mujoco.mj_step(m, d, 10) # let dynamics get state significantly non-zero
mujoco.mj_forward(m, d) mujoco.mj_forward(m, d)
mx = mjx.put_model(m) mx = mjx.put_model(m)
+20 -2
View File
@@ -311,7 +311,9 @@ def factor_m(m: Model, d: Data) -> Data:
if j == -1: if j == -1:
break break
out_beg, out_end = tuple(m.dof_Madr[j : j + 2]) out_beg, out_end = tuple(m.dof_Madr[j : j + 2])
updates.setdefault(depth[j], []).append((out_beg, out_end, madr_d, madr_ij)) updates.setdefault(depth[j], []).append(
(out_beg, out_end, madr_d, madr_ij)
)
qld = d.qM qld = d.qM
@@ -463,6 +465,20 @@ def rne(m: Model, d: Data) -> Data:
return d return d
def tendon(m: Model, d: Data) -> Data:
"""Computes tendon lengths and moments."""
if not m.ntendon:
return d
ten_id = np.repeat(np.arange(m.ntendon), m.tendon_num)
length = m.wrap_prm * d.qpos[m.jnt_qposadr[m.wrap_objid]]
ten_length = jax.ops.segment_sum(length, ten_id, m.ntendon)
ten_j = jp.zeros((m.ntendon, m.nv))
ten_j = ten_j.at[ten_id, m.jnt_dofadr[m.wrap_objid]].set(m.wrap_prm)
return d.replace(ten_length=ten_length, ten_J=ten_j)
def _site_dof_mask(m: Model) -> np.ndarray: def _site_dof_mask(m: Model) -> np.ndarray:
"""Creates a dof mask for site transmissions.""" """Creates a dof mask for site transmissions."""
mask = np.ones((m.nu, m.nv)) mask = np.ones((m.nu, m.nv))
@@ -494,7 +510,6 @@ def _site_dof_mask(m: Model) -> np.ndarray:
def transmission(m: Model, d: Data) -> Data: def transmission(m: Model, d: Data) -> Data:
"""Computes actuator/transmission lengths and moments.""" """Computes actuator/transmission lengths and moments."""
# TODO: consider combining transmission calculation into fwd_actuation.
if not m.nu: if not m.nu:
return d return d
@@ -545,6 +560,9 @@ def transmission(m: Model, d: Data) -> Data:
jac = jp.concatenate((jacp, jacr), axis=1) * site_dof_mask[:, None] jac = jp.concatenate((jacp, jacr), axis=1) * site_dof_mask[:, None]
wrench = jp.concatenate((frame_xmat @ gear[:3], frame_xmat @ gear[3:])) wrench = jp.concatenate((frame_xmat @ gear[:3], frame_xmat @ gear[3:]))
moment = jac @ wrench moment = jac @ wrench
elif trntype == TrnType.TENDON:
length = d.ten_length[trnid[0]] * gear[:1]
moment = d.ten_J[trnid[0]] * gear[0]
else: else:
raise RuntimeError(f'unrecognized trntype: {TrnType(trntype)}') raise RuntimeError(f'unrecognized trntype: {TrnType(trntype)}')
+18 -6
View File
@@ -48,7 +48,7 @@ class SmoothTest(absltest.TestCase):
"""Tests MJX smooth functions match MuJoCo smooth functions.""" """Tests MJX smooth functions match MuJoCo smooth functions."""
m = test_util.load_test_file('pendula.xml') m = test_util.load_test_file('pendula.xml')
# # force MJX sparse for testing: # tell MJX to use sparse mass matrices:
m.opt.jacobian = mujoco.mjtJacobian.mjJAC_SPARSE m.opt.jacobian = mujoco.mjtJacobian.mjJAC_SPARSE
d = mujoco.MjData(m) d = mujoco.MjData(m)
# give the system a little kick to ensure we have non-identity rotations # give the system a little kick to ensure we have non-identity rotations
@@ -75,6 +75,10 @@ class SmoothTest(absltest.TestCase):
_assert_attr_eq(d, dx, 'subtree_com') _assert_attr_eq(d, dx, 'subtree_com')
_assert_attr_eq(d, dx, 'cinert') _assert_attr_eq(d, dx, 'cinert')
_assert_attr_eq(d, dx, 'cdof') _assert_attr_eq(d, dx, 'cdof')
# camlight
dx = jax.jit(mjx.camlight)(mx, mjx.put_data(m, d))
_assert_attr_eq(d, dx, 'cam_xpos')
_assert_eq(d.cam_xmat.reshape((-1, 3, 3)), dx.cam_xmat, 'cam_xmat')
# crb # crb
dx = jax.jit(mjx.crb)(mx, mjx.put_data(m, d)) dx = jax.jit(mjx.crb)(mx, mjx.put_data(m, d))
_assert_attr_eq(d, dx, 'crb') _assert_attr_eq(d, dx, 'crb')
@@ -90,14 +94,22 @@ class SmoothTest(absltest.TestCase):
# rne # rne
dx = jax.jit(mjx.rne)(mx, mjx.put_data(m, d)) dx = jax.jit(mjx.rne)(mx, mjx.put_data(m, d))
_assert_attr_eq(d, dx, 'qfrc_bias') _assert_attr_eq(d, dx, 'qfrc_bias')
# set dense jacobian for tendon:
m.opt.jacobian = mujoco.mjtJacobian.mjJAC_DENSE
d = mujoco.MjData(m)
# give the system a little kick to ensure we have non-identity rotations
d.qvel = np.random.random(m.nv)
mujoco.mj_step(m, d, 10) # let dynamics get state significantly non-zero
mujoco.mj_forward(m, d)
# tendon
dx = jax.jit(mjx.tendon)(mx, mjx.put_data(m, d))
_assert_attr_eq(d, dx, 'ten_J')
_assert_attr_eq(d, dx, 'ten_length')
# transmission # transmission
dx = jax.jit(mjx.transmission)(mx, mjx.put_data(m, d)) dx = jax.jit(mjx.transmission)(mx, dx)
_assert_attr_eq(d, dx, 'actuator_length') _assert_attr_eq(d, dx, 'actuator_length')
_assert_attr_eq(d, dx, 'actuator_moment') _assert_attr_eq(d, dx, 'actuator_moment')
# camlight
dx = jax.jit(mjx.camlight)(mx, mjx.put_data(m, d))
_assert_attr_eq(d, dx, 'cam_xpos')
_assert_eq(d.cam_xmat.reshape((-1, 3, 3)), dx.cam_xmat, 'cam_xmat')
def test_disable_gravity(self): def test_disable_gravity(self):
m = mujoco.MjModel.from_xml_string(""" m = mujoco.MjModel.from_xml_string("""
+48 -34
View File
@@ -25,7 +25,7 @@ import numpy as np
class DisableBit(enum.IntFlag): class DisableBit(enum.IntFlag):
"""Disable default feature bitflags. """Disable default feature bitflags.
Attributes: Members:
CONSTRAINT: entire constraint solver CONSTRAINT: entire constraint solver
EQUALITY: equality constraints EQUALITY: equality constraints
FRICTIONLOSS: joint and tendon frictionloss constraints FRICTIONLOSS: joint and tendon frictionloss constraints
@@ -56,7 +56,7 @@ class DisableBit(enum.IntFlag):
class JointType(enum.IntEnum): class JointType(enum.IntEnum):
"""Type of degree of freedom. """Type of degree of freedom.
Attributes: Members:
FREE: global position and orientation (quat) (7,) FREE: global position and orientation (quat) (7,)
BALL: orientation (quat) relative to parent (4,) BALL: orientation (quat) relative to parent (4,)
SLIDE: sliding distance along body-fixed axis (1,) SLIDE: sliding distance along body-fixed axis (1,)
@@ -77,7 +77,7 @@ class JointType(enum.IntEnum):
class IntegratorType(enum.IntEnum): class IntegratorType(enum.IntEnum):
"""Integrator mode. """Integrator mode.
Attributes: Members:
EULER: semi-implicit Euler EULER: semi-implicit Euler
RK4: 4th-order Runge Kutta RK4: 4th-order Runge Kutta
""" """
@@ -89,7 +89,7 @@ class IntegratorType(enum.IntEnum):
class GeomType(enum.IntEnum): class GeomType(enum.IntEnum):
"""Type of geometry. """Type of geometry.
Attributes: Members:
PLANE: plane PLANE: plane
HFIELD: height field HFIELD: height field
SPHERE: sphere SPHERE: sphere
@@ -115,7 +115,7 @@ class GeomType(enum.IntEnum):
class ConvexMesh(PyTreeNode): class ConvexMesh(PyTreeNode):
"""Geom properties for convex meshes. """Geom properties for convex meshes.
Attributes: Members:
vert: vertices of the convex mesh vert: vertices of the convex mesh
face: faces of the convex mesh face: faces of the convex mesh
face_normal: normal vectors for the faces face_normal: normal vectors for the faces
@@ -133,7 +133,7 @@ class ConvexMesh(PyTreeNode):
class ConeType(enum.IntEnum): class ConeType(enum.IntEnum):
"""Type of friction cone. """Type of friction cone.
Attributes: Members:
PYRAMIDAL: pyramidal PYRAMIDAL: pyramidal
ELLIPTIC: elliptic ELLIPTIC: elliptic
""" """
@@ -144,7 +144,7 @@ class ConeType(enum.IntEnum):
class JacobianType(enum.IntEnum): class JacobianType(enum.IntEnum):
"""Type of constraint Jacobian. """Type of constraint Jacobian.
Attributes: Members:
DENSE: dense DENSE: dense
SPARSE: sparse SPARSE: sparse
AUTO: sparse if nv>60 and device is TPU, dense otherwise AUTO: sparse if nv>60 and device is TPU, dense otherwise
@@ -157,7 +157,7 @@ class JacobianType(enum.IntEnum):
class SolverType(enum.IntEnum): class SolverType(enum.IntEnum):
"""Constraint solver algorithm. """Constraint solver algorithm.
Attributes: Members:
CG: Conjugate gradient (primal) CG: Conjugate gradient (primal)
""" """
# unsupported: PGS # unsupported: PGS
@@ -168,33 +168,46 @@ class SolverType(enum.IntEnum):
class EqType(enum.IntEnum): class EqType(enum.IntEnum):
"""Type of equality constraint. """Type of equality constraint.
Attributes: Members:
CONNECT: connect two bodies at a point (ball joint) CONNECT: connect two bodies at a point (ball joint)
WELD: fix relative position and orientation of two bodies WELD: fix relative position and orientation of two bodies
JOINT: couple the values of two scalar joints with cubic JOINT: couple the values of two scalar joints with cubic
TENDON: couple the lengths of two tendons with cubic
""" """
CONNECT = mujoco.mjtEq.mjEQ_CONNECT CONNECT = mujoco.mjtEq.mjEQ_CONNECT
WELD = mujoco.mjtEq.mjEQ_WELD WELD = mujoco.mjtEq.mjEQ_WELD
JOINT = mujoco.mjtEq.mjEQ_JOINT JOINT = mujoco.mjtEq.mjEQ_JOINT
# unsupported: TENDON, DISTANCE TENDON = mujoco.mjtEq.mjEQ_TENDON
# unsupported: DISTANCE
class WrapType(enum.IntEnum):
"""Type of tendon wrap object.
Members:
JOINT: constant moment arm
"""
JOINT = mujoco.mjtWrap.mjWRAP_JOINT
# unsupported: NONE, PULLEY, SITE, SPHERE, CYLINDER
class TrnType(enum.IntEnum): class TrnType(enum.IntEnum):
"""Type of actuator transmission. """Type of actuator transmission.
Attributes: Members:
JOINT: force on joint JOINT: force on joint
SITE: force on site SITE: force on site
""" """
JOINT = mujoco.mjtTrn.mjTRN_JOINT JOINT = mujoco.mjtTrn.mjTRN_JOINT
SITE = mujoco.mjtTrn.mjTRN_SITE SITE = mujoco.mjtTrn.mjTRN_SITE
# unsupported: JOINTINPARENT, SLIDERCRANK, TENDON, BODY TENDON = mujoco.mjtTrn.mjTRN_TENDON
# unsupported: JOINTINPARENT, SLIDERCRANK, BODY
class DynType(enum.IntEnum): class DynType(enum.IntEnum):
"""Type of actuator dynamics. """Type of actuator dynamics.
Attributes: Members:
NONE: no internal dynamics; ctrl specifies force NONE: no internal dynamics; ctrl specifies force
INTEGRATOR: integrator: da/dt = u INTEGRATOR: integrator: da/dt = u
FILTER: linear filter: da/dt = (u-a) / tau FILTER: linear filter: da/dt = (u-a) / tau
@@ -210,7 +223,7 @@ class DynType(enum.IntEnum):
class GainType(enum.IntEnum): class GainType(enum.IntEnum):
"""Type of actuator gain. """Type of actuator gain.
Attributes: Members:
FIXED: fixed gain FIXED: fixed gain
AFFINE: const + kp*length + kv*velocity AFFINE: const + kp*length + kv*velocity
""" """
@@ -222,7 +235,7 @@ class GainType(enum.IntEnum):
class BiasType(enum.IntEnum): class BiasType(enum.IntEnum):
"""Type of actuator bias. """Type of actuator bias.
Attributes: Members:
NONE: no bias NONE: no bias
AFFINE: const + kp*length + kv*velocity AFFINE: const + kp*length + kv*velocity
""" """
@@ -234,16 +247,17 @@ class BiasType(enum.IntEnum):
class ConstraintType(enum.IntEnum): class ConstraintType(enum.IntEnum):
"""Type of constraint. """Type of constraint.
Attributes: Members:
EQUALITY: equality constraint EQUALITY: equality constraint
LIMIT_JOINT: joint limit LIMIT_JOINT: joint limit
LIMIT_TENDON: tendon limit
CONTACT_FRICTIONLESS: frictionless contact CONTACT_FRICTIONLESS: frictionless contact
CONTACT_PYRAMIDAL: frictional contact, pyramidal friction cone CONTACT_PYRAMIDAL: frictional contact, pyramidal friction cone
""" """
EQUALITY = mujoco.mjtConstraint.mjCNSTR_EQUALITY EQUALITY = mujoco.mjtConstraint.mjCNSTR_EQUALITY
# unsupported: FRICTION_DOF, FRICTION_TENDON # unsupported: FRICTION_DOF, FRICTION_TENDON
LIMIT_JOINT = mujoco.mjtConstraint.mjCNSTR_LIMIT_JOINT LIMIT_JOINT = mujoco.mjtConstraint.mjCNSTR_LIMIT_JOINT
# unsupported: LIMIT_TENDON LIMIT_TENDON = mujoco.mjtConstraint.mjCNSTR_LIMIT_TENDON
CONTACT_FRICTIONLESS = mujoco.mjtConstraint.mjCNSTR_CONTACT_FRICTIONLESS CONTACT_FRICTIONLESS = mujoco.mjtConstraint.mjCNSTR_CONTACT_FRICTIONLESS
CONTACT_PYRAMIDAL = mujoco.mjtConstraint.mjCNSTR_CONTACT_PYRAMIDAL CONTACT_PYRAMIDAL = mujoco.mjtConstraint.mjCNSTR_CONTACT_PYRAMIDAL
CONTACT_ELLIPTIC = mujoco.mjtConstraint.mjCNSTR_CONTACT_ELLIPTIC CONTACT_ELLIPTIC = mujoco.mjtConstraint.mjCNSTR_CONTACT_ELLIPTIC
@@ -252,7 +266,7 @@ class ConstraintType(enum.IntEnum):
class CamLightType(enum.IntEnum): class CamLightType(enum.IntEnum):
"""Type of camera light. """Type of camera light.
Attributes: Members:
FIXED: pos and rot fixed in body FIXED: pos and rot fixed in body
TRACK: pos tracks body, rot fixed in global TRACK: pos tracks body, rot fixed in global
TRACKCOM: pos tracks subtree com, rot fixed in body TRACKCOM: pos tracks subtree com, rot fixed in body
@@ -900,18 +914,18 @@ class Model(PyTreeNode):
tendon_adr: np.ndarray tendon_adr: np.ndarray
tendon_num: np.ndarray tendon_num: np.ndarray
tendon_limited: np.ndarray tendon_limited: np.ndarray
tendon_solref_lim: np.ndarray tendon_solref_lim: jax.Array
tendon_solimp_lim: np.ndarray tendon_solimp_lim: jax.Array
tendon_solref_fri: np.ndarray tendon_solref_fri: jax.Array
tendon_solimp_fri: np.ndarray tendon_solimp_fri: jax.Array
tendon_range: np.ndarray tendon_range: jax.Array
tendon_margin: np.ndarray tendon_margin: jax.Array
tendon_stiffness: np.ndarray tendon_stiffness: jax.Array
tendon_damping: np.ndarray tendon_damping: jax.Array
tendon_frictionloss: np.ndarray tendon_frictionloss: jax.Array
tendon_lengthspring: np.ndarray tendon_lengthspring: jax.Array
tendon_length0: np.ndarray tendon_length0: jax.Array
tendon_invweight0: np.ndarray tendon_invweight0: jax.Array
wrap_type: np.ndarray wrap_type: np.ndarray
wrap_objid: np.ndarray wrap_objid: np.ndarray
wrap_prm: np.ndarray wrap_prm: np.ndarray
@@ -1193,7 +1207,7 @@ class Data(PyTreeNode):
qM: jax.Array # pylint:disable=invalid-name qM: jax.Array # pylint:disable=invalid-name
qLD: jax.Array # pylint:disable=invalid-name qLD: jax.Array # pylint:disable=invalid-name
qLDiagInv: jax.Array # pylint:disable=invalid-name qLDiagInv: jax.Array # pylint:disable=invalid-name
qLDiagSqrtInv: jax.Array qLDiagSqrtInv: jax.Array # pylint:disable=invalid-name
bvh_aabb_dyn: jax.Array bvh_aabb_dyn: jax.Array
bvh_active: jax.Array bvh_active: jax.Array
# position, velocity dependent: # position, velocity dependent:
@@ -1243,6 +1257,6 @@ class Data(PyTreeNode):
efc_force: jax.Array efc_force: jax.Array
# sparse representation of qM, qLD, qLDiagInv, for compatibility with MuJoCo # sparse representation of qM, qLD, qLDiagInv, for compatibility with MuJoCo
# when in dense mode # when in dense mode
_qM_sparse: jax.Array _qM_sparse: jax.Array # pylint:disable=invalid-name
_qLD_sparse: jax.Array _qLD_sparse: jax.Array # pylint:disable=invalid-name
_qLDiagInv_sparse: jax.Array _qLDiagInv_sparse: jax.Array # pylint:disable=invalid-name
+12
View File
@@ -64,10 +64,22 @@
</body> </body>
</worldbody> </worldbody>
<tendon>
<fixed name="tendon_1" limited="true" range="-0.3 0.1" stiffness=".1" damping=".2">
<joint joint="joint3" coef=".1"/>
<joint joint="joint4" coef="-.2"/>
</fixed>
<fixed name="tendon_2" limited="true" range="-0.3 2" solreflimit="0.03 0.9" solimplimit="0.89 0.9 0.01 2.1">
<joint joint="joint4" coef=".3"/>
<joint joint="joint5" coef="-.4"/>
</fixed>
</tendon>
<equality> <equality>
<connect name="connect" body1="anchor1" body2="beam1" anchor="1 0 -1" /> <connect name="connect" body1="anchor1" body2="beam1" anchor="1 0 -1" />
<weld name="weld" body1="anchor2" body2="beam2" relpose="0 0 0 1 -.3 0 0" torquescale="0.002" anchor="0 -2 0"/> <weld name="weld" body1="anchor2" body2="beam2" relpose="0 0 0 1 -.3 0 0" torquescale="0.002" anchor="0 -2 0"/>
<joint name="joint" joint1="joint3" joint2="joint4" polycoef="0.5 -1 0.1 0.15 0.2" /> <joint name="joint" joint1="joint3" joint2="joint4" polycoef="0.5 -1 0.1 0.15 0.2" />
<tendon name="tendon" tendon1="tendon_1" tendon2="tendon_2" polycoef="0.5 -1 0.1 0.15 0.2"/>
</equality> </equality>
<actuator> <actuator>
+12
View File
@@ -126,6 +126,17 @@
</body> </body>
</worldbody> </worldbody>
<tendon>
<fixed name="tendon_1" limited="true" range="-0.3 0.1" stiffness=".1" damping=".2">
<joint joint="joint2" coef=".1"/>
<joint joint="joint3" coef="-.2"/>
</fixed>
<fixed name="tendon_2" limited="true" range="-0.3 2" springlength="0 0.05" stiffness=".3" damping=".4">
<joint joint="joint4" coef=".3"/>
<joint joint="joint5" coef="-.4"/>
</fixed>
</tendon>
<actuator> <actuator>
<motor gear="250 0 0" joint="joint1" name="act1"/> <motor gear="250 0 0" joint="joint1" name="act1"/>
<motor gear="0 275 0" joint="joint1" name="act2"/> <motor gear="0 275 0" joint="joint1" name="act2"/>
@@ -135,5 +146,6 @@
<motor gear="150" joint="joint15" name="act6"/> <motor gear="150" joint="joint15" name="act6"/>
<motor gear="150" joint="joint16" name="act7"/> <motor gear="150" joint="joint16" name="act7"/>
<motor gear="150" joint="joint17" name="act8"/> <motor gear="150" joint="joint17" name="act8"/>
<position tendon="tendon_2" kp="100"/>
</actuator> </actuator>
</mujoco> </mujoco>
+3 -2
View File
@@ -1304,7 +1304,7 @@ static void makeMaterial(const mjModel* m, mjrContext* con) {
} }
if (m->nmat >= mjMAXMATERIAL-1) { if (m->nmat >= mjMAXMATERIAL-1) {
mju_error("Maximum number of materials is %d", mjMAXMATERIAL); mju_error("Maximum number of materials is 100, got %d", m->nmat);
} }
for (int i=0; i < m->nmat; i++) { for (int i=0; i < m->nmat; i++) {
if (m->mat_texid[i*mjNTEXROLE + mjTEXROLE_RGB] >= 0) { if (m->mat_texid[i*mjNTEXROLE + mjTEXROLE_RGB] >= 0) {
@@ -1320,7 +1320,8 @@ static void makeMaterial(const mjModel* m, mjrContext* con) {
for (int i=0; i < m->ntex; i++) { for (int i=0; i < m->ntex; i++) {
if (m->tex_type[i] == mjTEXTURE_SKYBOX) { if (m->tex_type[i] == mjTEXTURE_SKYBOX) {
if (m->nmat >= mjMAXMATERIAL-2) { if (m->nmat >= mjMAXMATERIAL-2) {
mju_error("With skybox, maximum number of materials is %d", mjMAXMATERIAL); mju_error("With skybox, maximum number of materials is 99, got %d",
m->nmat);
} }
for (int j=0; j < mjNTEXROLE; j++) { for (int j=0; j < mjNTEXROLE; j++) {
con->mat_texid[mjNTEXROLE * (mjMAXMATERIAL-1) + j] = -1; con->mat_texid[mjNTEXROLE * (mjMAXMATERIAL-1) + j] = -1;
+18 -18
View File
@@ -947,13 +947,11 @@ void mjCModel::DeleteMaterial(std::vector<T*>& list, std::string_view name) {
// delete texture with given name or all textures if the name is omitted // delete all textures
template <class T> template <class T>
static void DeleteTexture(std::vector<T*>& list, std::string_view name = "") { static void DeleteAllTextures(std::vector<T*>& list) {
for (T* plist : list) { for (T* plist : list) {
if (name.empty() || plist->get_texture() == name) { plist->del_textures();
plist->del_texture();
}
} }
} }
@@ -1046,7 +1044,7 @@ void mjCModel::DeleteAll<mjCMaterial>(std::vector<mjCMaterial*>& elements) {
template <> template <>
void mjCModel::DeleteAll<mjCTexture>(std::vector<mjCTexture*>& elements) { void mjCModel::DeleteAll<mjCTexture>(std::vector<mjCTexture*>& elements) {
DeleteTexture(materials_); DeleteAllTextures(materials_);
for (mjCTexture* element : elements) { for (mjCTexture* element : elements) {
delete element; delete element;
} }
@@ -1202,13 +1200,15 @@ void mjCModel::IndexAssets(bool discard) {
for (int i=0; i<materials_.size(); i++) { for (int i=0; i<materials_.size(); i++) {
mjCMaterial* material = materials_[i]; mjCMaterial* material = materials_[i];
// find texture by name // find textures by name
if (!material->texture_.empty()) { for (int j=0; j<mjNTEXROLE; j++) {
mjCBase* texture = FindObject(mjOBJ_TEXTURE, material->texture_); if (!material->textures_[j].empty()) {
if (texture) { mjCBase* texture = FindObject(mjOBJ_TEXTURE, material->textures_[j]);
material->texid = texture->id; if (texture) {
} else { material->texid[j] = texture->id;
throw mjCError(material, "texture '%s' not found in material %d", material->texture_.c_str(), i); } else {
throw mjCError(material, "texture '%s' not found in material %d", material->textures_[j].c_str(), i);
}
} }
} }
} }
@@ -2504,14 +2504,15 @@ void mjCModel::CopyObjects(mjModel* m) {
m->tex_type[i] = ptex->type; m->tex_type[i] = ptex->type;
m->tex_height[i] = ptex->height; m->tex_height[i] = ptex->height;
m->tex_width[i] = ptex->width; m->tex_width[i] = ptex->width;
m->tex_nchannel[i] = ptex->nchannel;
m->tex_adr[i] = data_adr; m->tex_adr[i] = data_adr;
m->tex_nchannel[i] = 3;
// copy rgb data // copy rgb data
memcpy(m->tex_data + data_adr, ptex->rgb.data(), 3*ptex->width*ptex->height); memcpy(m->tex_data + data_adr, ptex->data.data(),
ptex->nchannel * ptex->width * ptex->height);
// advance counter // advance counter
data_adr += 3*ptex->width*ptex->height; data_adr += ptex->nchannel * ptex->width * ptex->height;
} }
// materials // materials
@@ -2521,9 +2522,8 @@ void mjCModel::CopyObjects(mjModel* m) {
// set fields // set fields
for (int j=0; j<mjNTEXROLE; j++) { for (int j=0; j<mjNTEXROLE; j++) {
m->mat_texid[mjNTEXROLE*i+j] = -1; m->mat_texid[mjNTEXROLE*i+j] = pmat->texid[j];
} }
m->mat_texid[mjNTEXROLE*i+mjTEXROLE_RGB] = pmat->texid;
m->mat_texuniform[i] = pmat->texuniform; m->mat_texuniform[i] = pmat->texuniform;
mjuu_copyvec(m->mat_texrepeat+2*i, pmat->texrepeat, 2); mjuu_copyvec(m->mat_texrepeat+2*i, pmat->texrepeat, 2);
m->mat_emission[i] = pmat->emission; m->mat_emission[i] = pmat->emission;
+86 -64
View File
@@ -3217,7 +3217,7 @@ mjCTexture::mjCTexture(mjCModel* _model) {
spec_cubefiles_.assign(6, ""); spec_cubefiles_.assign(6, "");
// clear internal variables // clear internal variables
rgb.clear(); data.clear();
// point to local // point to local
PointToLocal(); PointToLocal();
@@ -3266,14 +3266,14 @@ void mjCTexture::CopyFromSpec() {
cubefiles_ = spec_cubefiles_; cubefiles_ = spec_cubefiles_;
// clear precompiled asset. TODO: use asset cache // clear precompiled asset. TODO: use asset cache
rgb.clear(); data.clear();
} }
// free data storage allocated by lodepng // free data storage allocated by lodepng
mjCTexture::~mjCTexture() { mjCTexture::~mjCTexture() {
rgb.clear(); data.clear();
} }
@@ -3366,21 +3366,21 @@ void mjCTexture::Builtin2D(void) {
double pos = 2*sqrt(x*x+y*y) - 1; double pos = 2*sqrt(x*x+y*y) - 1;
// interpolate through sigmoid // interpolate through sigmoid
interp(rgb.data() + 3*(r*width+c), rgb2, rgb1, pos); interp(data.data() + 3*(r*width+c), rgb2, rgb1, pos);
} }
} }
} }
// checker // checker
else if (builtin==mjBUILTIN_CHECKER) { else if (builtin==mjBUILTIN_CHECKER) {
checker(rgb.data(), RGB1, RGB2, width, height); checker(data.data(), RGB1, RGB2, width, height);
} }
// flat // flat
else if (builtin==mjBUILTIN_FLAT) { else if (builtin==mjBUILTIN_FLAT) {
for (int r=0; r<height; r++) { for (int r=0; r<height; r++) {
for (int c=0; c<width; c++) { for (int c=0; c<width; c++) {
memcpy(rgb.data()+3*(r*width+c), RGB1, 3); memcpy(data.data()+3*(r*width+c), RGB1, 3);
} }
} }
} }
@@ -3390,28 +3390,28 @@ void mjCTexture::Builtin2D(void) {
// edge // edge
if (mark==mjMARK_EDGE) { if (mark==mjMARK_EDGE) {
for (int r=0; r<height; r++) { for (int r=0; r<height; r++) {
memcpy(rgb.data()+3*(r*width+0), RGBm, 3); memcpy(data.data()+3*(r*width+0), RGBm, 3);
memcpy(rgb.data()+3*(r*width+width-1), RGBm, 3); memcpy(data.data()+3*(r*width+width-1), RGBm, 3);
} }
for (int c=0; c<width; c++) { for (int c=0; c<width; c++) {
memcpy(rgb.data()+3*(0*width+c), RGBm, 3); memcpy(data.data()+3*(0*width+c), RGBm, 3);
memcpy(rgb.data()+3*((height-1)*width+c), RGBm, 3); memcpy(data.data()+3*((height-1)*width+c), RGBm, 3);
} }
} }
// cross // cross
else if (mark==mjMARK_CROSS) { else if (mark==mjMARK_CROSS) {
for (int r=0; r<height; r++) { for (int r=0; r<height; r++) {
memcpy(rgb.data()+3*(r*width+width/2), RGBm, 3); memcpy(data.data()+3*(r*width+width/2), RGBm, 3);
} }
for (int c=0; c<width; c++) { for (int c=0; c<width; c++) {
memcpy(rgb.data()+3*(height/2*width+c), RGBm, 3); memcpy(data.data()+3*(height/2*width+c), RGBm, 3);
} }
} }
// random dots // random dots
else if (mark==mjMARK_RANDOM && random>0) { else if (mark==mjMARK_RANDOM && random>0) {
randomdot(rgb.data(), markrgb, width, height, random); randomdot(data.data(), markrgb, width, height, random);
} }
} }
@@ -3446,26 +3446,26 @@ void mjCTexture::BuiltinCube(void) {
// set sides // set sides
interp(RGBi, rgb1, rgb2, elside); interp(RGBi, rgb1, rgb2, elside);
memcpy(rgb.data() + 0 * 3 * ww + 3 * (r * w + c), RGBi, 3); // 0: right memcpy(data.data() + 0 * 3 * ww + 3 * (r * w + c), RGBi, 3); // 0: right
memcpy(rgb.data() + 1 * 3 * ww + 3 * (r * w + c), RGBi, 3); // 1: left memcpy(data.data() + 1 * 3 * ww + 3 * (r * w + c), RGBi, 3); // 1: left
memcpy(rgb.data() + 4 * 3 * ww + 3 * (r * w + c), RGBi, 3); // 4: front memcpy(data.data() + 4 * 3 * ww + 3 * (r * w + c), RGBi, 3); // 4: front
memcpy(rgb.data() + 5 * 3 * ww + 3 * (r * w + c), RGBi, 3); // 5: back memcpy(data.data() + 5 * 3 * ww + 3 * (r * w + c), RGBi, 3); // 5: back
// set up and down // set up and down
interp(rgb.data() + 2 * 3 * ww + 3 * (r * w + c), rgb1, rgb2, elup); // 2: up interp(data.data() + 2 * 3 * ww + 3 * (r * w + c), rgb1, rgb2, elup); // 2: up
interp(rgb.data() + 3 * 3 * ww + 3 * (r * w + c), rgb1, rgb2, -elup); // 3: down interp(data.data() + 3 * 3 * ww + 3 * (r * w + c), rgb1, rgb2, -elup); // 3: down
} }
} }
} }
// checker // checker
else if (builtin == mjBUILTIN_CHECKER) { else if (builtin == mjBUILTIN_CHECKER) {
checker(rgb.data() + 0 * 3 * ww, RGB1, RGB2, w, w); checker(data.data() + 0 * 3 * ww, RGB1, RGB2, w, w);
checker(rgb.data() + 1 * 3 * ww, RGB1, RGB2, w, w); checker(data.data() + 1 * 3 * ww, RGB1, RGB2, w, w);
checker(rgb.data() + 2 * 3 * ww, RGB1, RGB2, w, w); checker(data.data() + 2 * 3 * ww, RGB1, RGB2, w, w);
checker(rgb.data() + 3 * 3 * ww, RGB1, RGB2, w, w); checker(data.data() + 3 * 3 * ww, RGB1, RGB2, w, w);
checker(rgb.data() + 4 * 3 * ww, RGB2, RGB1, w, w); checker(data.data() + 4 * 3 * ww, RGB2, RGB1, w, w);
checker(rgb.data() + 5 * 3 * ww, RGB2, RGB1, w, w); checker(data.data() + 5 * 3 * ww, RGB2, RGB1, w, w);
} }
// flat // flat
@@ -3473,14 +3473,14 @@ void mjCTexture::BuiltinCube(void) {
for (int r = 0; r < w; r++) { for (int r = 0; r < w; r++) {
for (int c = 0; c < w; c++) { for (int c = 0; c < w; c++) {
// set sides and up // set sides and up
memcpy(rgb.data() + 0 * 3 * ww + 3 * (r * w + c), RGB1, 3); memcpy(data.data() + 0 * 3 * ww + 3 * (r * w + c), RGB1, 3);
memcpy(rgb.data() + 1 * 3 * ww + 3 * (r * w + c), RGB1, 3); memcpy(data.data() + 1 * 3 * ww + 3 * (r * w + c), RGB1, 3);
memcpy(rgb.data() + 2 * 3 * ww + 3 * (r * w + c), RGB1, 3); memcpy(data.data() + 2 * 3 * ww + 3 * (r * w + c), RGB1, 3);
memcpy(rgb.data() + 4 * 3 * ww + 3 * (r * w + c), RGB1, 3); memcpy(data.data() + 4 * 3 * ww + 3 * (r * w + c), RGB1, 3);
memcpy(rgb.data() + 5 * 3 * ww + 3 * (r * w + c), RGB1, 3); memcpy(data.data() + 5 * 3 * ww + 3 * (r * w + c), RGB1, 3);
// set down // set down
memcpy(rgb.data() + 3 * 3 * ww + 3 * (r * w + c), RGB2, 3); memcpy(data.data() + 3 * 3 * ww + 3 * (r * w + c), RGB2, 3);
} }
} }
} }
@@ -3491,12 +3491,12 @@ void mjCTexture::BuiltinCube(void) {
if (mark == mjMARK_EDGE) { if (mark == mjMARK_EDGE) {
for (int j = 0; j < 6; j++) { for (int j = 0; j < 6; j++) {
for (int r = 0; r < w; r++) { for (int r = 0; r < w; r++) {
memcpy(rgb.data() + j * 3 * ww + 3 * (r * w + 0), RGBm, 3); memcpy(data.data() + j * 3 * ww + 3 * (r * w + 0), RGBm, 3);
memcpy(rgb.data() + j * 3 * ww + 3 * (r * w + w - 1), RGBm, 3); memcpy(data.data() + j * 3 * ww + 3 * (r * w + w - 1), RGBm, 3);
} }
for (int c = 0; c < w; c++) { for (int c = 0; c < w; c++) {
memcpy(rgb.data() + j * 3 * ww + 3 * (0 * w + c), RGBm, 3); memcpy(data.data() + j * 3 * ww + 3 * (0 * w + c), RGBm, 3);
memcpy(rgb.data() + j * 3 * ww + 3 * ((w - 1) * w + c), RGBm, 3); memcpy(data.data() + j * 3 * ww + 3 * ((w - 1) * w + c), RGBm, 3);
} }
} }
} }
@@ -3505,17 +3505,17 @@ void mjCTexture::BuiltinCube(void) {
else if (mark == mjMARK_CROSS) { else if (mark == mjMARK_CROSS) {
for (int j = 0; j < 6; j++) { for (int j = 0; j < 6; j++) {
for (int r = 0; r < w; r++) { for (int r = 0; r < w; r++) {
memcpy(rgb.data() + j * 3 * ww + 3 * (r * w + w / 2), RGBm, 3); memcpy(data.data() + j * 3 * ww + 3 * (r * w + w / 2), RGBm, 3);
} }
for (int c = 0; c < w; c++) { for (int c = 0; c < w; c++) {
memcpy(rgb.data() + j * 3 * ww + 3 * (w / 2 * w + c), RGBm, 3); memcpy(data.data() + j * 3 * ww + 3 * (w / 2 * w + c), RGBm, 3);
} }
} }
} }
// random dots // random dots
else if (mark == mjMARK_RANDOM && random > 0) { else if (mark == mjMARK_RANDOM && random > 0) {
randomdot(rgb.data(), markrgb, w, height, random); randomdot(data.data(), markrgb, w, height, random);
} }
} }
@@ -3523,14 +3523,23 @@ void mjCTexture::BuiltinCube(void) {
void mjCTexture::LoadPNG(mjResource* resource, void mjCTexture::LoadPNG(mjResource* resource,
std::vector<unsigned char>& image, std::vector<unsigned char>& image,
unsigned int& w, unsigned int& h) { unsigned int& w, unsigned int& h) {
PNGImage png_image = PNGImage::Load(this, resource, LCT_RGB); LodePNGColorType color_type;
if (nchannel == 4) {
color_type = LCT_RGBA;
} else if (nchannel == 3) {
color_type = LCT_RGB;
} else if (nchannel == 1) {
color_type = LCT_GREY;
} else {
throw mjCError(this, "Unsupported number of channels: %s",
std::to_string(nchannel).c_str());
}
PNGImage png_image = PNGImage::Load(this, resource, color_type);
w = png_image.Width(); w = png_image.Width();
h = png_image.Height(); h = png_image.Height();
image = png_image.MoveData(); image = png_image.MoveData();
} }
// load custom file // load custom file
void mjCTexture::LoadCustom(mjResource* resource, void mjCTexture::LoadCustom(mjResource* resource,
std::vector<unsigned char>& image, std::vector<unsigned char>& image,
@@ -3601,6 +3610,10 @@ void mjCTexture::LoadFlip(std::string filename, const mjVFS* vfs,
// horizontal flip // horizontal flip
if (hflip) { if (hflip) {
if (nchannel != 3) {
throw mjCError(
this, "currently only 3-channel textures support horizontal flip");
}
for (int r=0; r<h; r++) { for (int r=0; r<h; r++) {
for (int c=0; c<w/2; c++) { for (int c=0; c<w/2; c++) {
int c1 = w-1-c; int c1 = w-1-c;
@@ -3623,6 +3636,10 @@ void mjCTexture::LoadFlip(std::string filename, const mjVFS* vfs,
// vertical flip // vertical flip
if (vflip) { if (vflip) {
if (nchannel != 3) {
throw mjCError(
this, "currently only 3-channel textures support vertical flip");
}
for (int r=0; r<h/2; r++) { for (int r=0; r<h/2; r++) {
for (int c=0; c<w; c++) { for (int c=0; c<w; c++) {
int r1 = h-1-r; int r1 = h-1-r;
@@ -3658,12 +3675,12 @@ void mjCTexture::Load2D(std::string filename, const mjVFS* vfs) {
height = h; height = h;
// allocate and copy data // allocate and copy data
rgb.assign(3*width*height, 0); data.assign(nchannel*width*height, 0);
if (rgb.empty()) { if (data.empty()) {
throw mjCError(this, "Could not allocate memory for texture '%s' (id %d)", throw mjCError(this, "Could not allocate memory for texture '%s' (id %d)",
(const char*)file_.c_str(), id); (const char*)file_.c_str(), id);
} }
memcpy(rgb.data(), image.data(), 3*width*height); memcpy(data.data(), image.data(), nchannel*width*height);
image.clear(); image.clear();
} }
@@ -3697,8 +3714,8 @@ void mjCTexture::LoadCubeSingle(std::string filename, const mjVFS* vfs) {
} }
// allocate data // allocate data
rgb.assign(3*width*height, 0); data.assign(3*width*height, 0);
if (rgb.empty()) { if (data.empty()) {
throw mjCError(this, throw mjCError(this,
"Could not allocate memory for texture '%s' (id %d)", "Could not allocate memory for texture '%s' (id %d)",
(const char*)file_.c_str(), id); (const char*)file_.c_str(), id);
@@ -3706,7 +3723,7 @@ void mjCTexture::LoadCubeSingle(std::string filename, const mjVFS* vfs) {
// copy: repeated // copy: repeated
if (gridsize[0]==1 && gridsize[1]==1) { if (gridsize[0]==1 && gridsize[1]==1) {
memcpy(rgb.data(), image.data(), 3*width*width); memcpy(data.data(), image.data(), 3*width*width);
} }
// copy: grid // copy: grid
@@ -3739,7 +3756,7 @@ void mjCTexture::LoadCubeSingle(std::string filename, const mjVFS* vfs) {
int rstart = width*(k/gridsize[1]); int rstart = width*(k/gridsize[1]);
int cstart = width*(k%gridsize[1]); int cstart = width*(k%gridsize[1]);
for (int j=0; j<width; j++) { for (int j=0; j<width; j++) {
memcpy(rgb.data()+i*3*width*width+j*3*width, image.data()+(j+rstart)*3*w+3*cstart, 3*width); memcpy(data.data()+i*3*width*width+j*3*width, image.data()+(j+rstart)*3*w+3*cstart, 3*width);
} }
// mark as defined // mark as defined
@@ -3753,7 +3770,7 @@ void mjCTexture::LoadCubeSingle(std::string filename, const mjVFS* vfs) {
for (int k=0; k<width; k++) { for (int k=0; k<width; k++) {
for (int s=0; s<width; s++) { for (int s=0; s<width; s++) {
for (int j=0; j<3; j++) { for (int j=0; j<3; j++) {
rgb[i*3*width*width + 3*(k*width+s) + j] = (mjtByte)(255*rgb1[j]); data[i*3*width*width + 3*(k*width+s) + j] = (mjtByte)(255*rgb1[j]);
} }
} }
} }
@@ -3795,11 +3812,11 @@ void mjCTexture::LoadCubeSeparate(const mjVFS* vfs) {
} }
// first file: set size and allocate data // first file: set size and allocate data
if (rgb.empty()) { if (data.empty()) {
width = w; width = w;
height = 6*width; height = 6*width;
rgb.assign(3*width*height, 0); data.assign(3*width*height, 0);
if (rgb.empty()) { if (data.empty()) {
throw mjCError(this, "Could not allocate memory for texture"); throw mjCError(this, "Could not allocate memory for texture");
} }
} }
@@ -3812,7 +3829,7 @@ void mjCTexture::LoadCubeSeparate(const mjVFS* vfs) {
} }
// copy data // copy data
memcpy(rgb.data()+i*3*width*width, image.data(), 3*width*width); memcpy(data.data()+i*3*width*width, image.data(), 3*width*width);
image.clear(); image.clear();
// mark as defined // mark as defined
@@ -3826,7 +3843,7 @@ void mjCTexture::LoadCubeSeparate(const mjVFS* vfs) {
for (int k=0; k<width; k++) { for (int k=0; k<width; k++) {
for (int s=0; s<width; s++) { for (int s=0; s<width; s++) {
for (int j=0; j<3; j++) { for (int j=0; j<3; j++) {
rgb[i*3*width*width + 3*(k*width+s) + j] = (mjtByte)(255*rgb1[j]); data[i*3*width*width + 3*(k*width+s) + j] = (mjtByte)(255*rgb1[j]);
} }
} }
} }
@@ -3857,8 +3874,8 @@ void mjCTexture::Compile(const mjVFS* vfs) {
} }
// allocate data // allocate data
rgb.assign(3*width*height, 0); data.assign(nchannel*width*height, 0);
if (rgb.empty()) { if (data.empty()) {
throw mjCError(this, "Could not allocate memory for texture"); throw mjCError(this, "Could not allocate memory for texture");
} }
@@ -3914,7 +3931,7 @@ void mjCTexture::Compile(const mjVFS* vfs) {
} }
// make sure someone allocated data; SHOULD NOT OCCUR // make sure someone allocated data; SHOULD NOT OCCUR
if (rgb.empty()) { if (data.empty()) {
throw mjCError(this, "texture '%s' (id %d) was not specified", name.c_str(), id); throw mjCError(this, "texture '%s' (id %d) was not specified", name.c_str(), id);
} }
} }
@@ -3927,10 +3944,13 @@ void mjCTexture::Compile(const mjVFS* vfs) {
mjCMaterial::mjCMaterial(mjCModel* _model, mjCDef* _def) { mjCMaterial::mjCMaterial(mjCModel* _model, mjCDef* _def) {
mjs_defaultMaterial(&spec); mjs_defaultMaterial(&spec);
elemtype = mjOBJ_MATERIAL; elemtype = mjOBJ_MATERIAL;
textures_.assign(mjNTEXROLE, "");
spec_textures_.assign(mjNTEXROLE, "");
// clear internal // clear internal
spec_texture_.clear(); for (int i=0; i<mjNTEXROLE; i++) {
texid = -1; texid[i] = -1;
}
// reset to default if given // reset to default if given
if (_def) { if (_def) {
@@ -3969,16 +3989,16 @@ mjCMaterial& mjCMaterial::operator=(const mjCMaterial& other) {
void mjCMaterial::PointToLocal() { void mjCMaterial::PointToLocal() {
spec.element = static_cast<mjsElement*>(this); spec.element = static_cast<mjsElement*>(this);
spec.name = &name; spec.name = &name;
spec.texture = &spec_texture_; spec.textures = &spec_textures_;
spec.info = &info; spec.info = &info;
texture = nullptr; textures = nullptr;
} }
void mjCMaterial::CopyFromSpec() { void mjCMaterial::CopyFromSpec() {
*static_cast<mjsMaterial*>(this) = spec; *static_cast<mjsMaterial*>(this) = spec;
texture_ = spec_texture_; textures_ = spec_textures_;
} }
@@ -3987,8 +4007,10 @@ void mjCMaterial::NameSpace(const mjCModel* m) {
if (!name.empty()) { if (!name.empty()) {
name = m->prefix + name + m->suffix; name = m->prefix + name + m->suffix;
} }
if (!spec_texture_.empty() && model != m) { for (int i=0; i<mjNTEXROLE; i++) {
spec_texture_ = m->prefix + spec_texture_ + m->suffix; if (!spec_textures_[i].empty() && model != m) {
spec_textures_[i] = m->prefix + spec_textures_[i] + m->suffix;
}
} }
} }
+6 -6
View File
@@ -1003,7 +1003,7 @@ class mjCHField : public mjCHField_, private mjsHField {
class mjCTexture_ : public mjCBase { class mjCTexture_ : public mjCBase {
protected: protected:
std::vector<mjtByte> rgb; // rgb data std::vector<mjtByte> data; // texture data (rgb, roughness, etc.)
std::string file_; std::string file_;
std::string content_type_; std::string content_type_;
@@ -1063,9 +1063,9 @@ class mjCTexture : public mjCTexture_, private mjsTexture {
class mjCMaterial_ : public mjCBase { class mjCMaterial_ : public mjCBase {
protected: protected:
int texid; // id of material's texture int texid[mjNTEXROLE]; // id of material's textures
std::string texture_; std::vector<std::string> textures_;
std::string spec_texture_; std::vector<std::string> spec_textures_;
}; };
class mjCMaterial : public mjCMaterial_, private mjsMaterial { class mjCMaterial : public mjCMaterial_, private mjsMaterial {
@@ -1086,8 +1086,8 @@ class mjCMaterial : public mjCMaterial_, private mjsMaterial {
void PointToLocal(); void PointToLocal();
void NameSpace(const mjCModel* m); void NameSpace(const mjCModel* m);
const std::string& get_texture() const { return texture_; } const std::string& get_texture(int i) const { return textures_[i]; }
void del_texture() { texture_.clear(); } void del_textures() { for (auto& t : textures_) t.clear(); }
private: private:
void Compile(void); // compiler void Compile(void); // compiler
+47 -3
View File
@@ -239,12 +239,23 @@ const char* MJCF[nMJCF][mjXATTRNUM] = {
{"<"}, {"<"},
{"bone", "*", "5", "body", "bindpos", "bindquat", "vertid", "vertweight"}, {"bone", "*", "5", "body", "bindpos", "bindquat", "vertid", "vertweight"},
{">"}, {">"},
{"texture", "*", "22", "name", "type", "content_type", "file", "gridsize", "gridlayout", {"texture", "*", "23", "name", "type", "content_type", "file", "gridsize", "gridlayout",
"fileright", "fileleft", "fileup", "filedown", "filefront", "fileback", "fileright", "fileleft", "fileup", "filedown", "filefront", "fileback",
"builtin", "rgb1", "rgb2", "mark", "markrgb", "random", "width", "height", "builtin", "rgb1", "rgb2", "mark", "markrgb", "random", "width", "height",
"hflip", "vflip"}, "hflip", "vflip", "nchannel"},
{"material", "*", "12", "name", "class", "texture", "texrepeat", "texuniform", {"material", "*", "12", "name", "class", "texture", "texrepeat", "texuniform",
"emission", "specular", "shininess", "reflectance", "metallic", "roughness", "rgba"}, "emission", "specular", "shininess", "reflectance", "metallic", "roughness", "rgba"},
{"<"},
{"rgb", "?", "1", "texture"},
{"occlusion", "?", "1", "texture"},
{"roughness", "?", "1", "texture"},
{"metallic", "?", "1", "texture"},
{"normal", "?", "1", "texture"},
{"opacity", "?", "1", "texture"},
{"emissive", "?", "1", "texture"},
{"rgba", "?", "1", "texture"},
{"orm", "?", "1", "texture"},
{">"},
{"model", "*", "2", "name", "file"}, {"model", "*", "2", "name", "file"},
{">"}, {">"},
@@ -577,6 +588,19 @@ const mjMap camlight_map[camlight_sz] = {
{"targetbodycom", mjCAMLIGHT_TARGETBODYCOM} {"targetbodycom", mjCAMLIGHT_TARGETBODYCOM}
}; };
// texmat role type
const int texrole_sz = mjNTEXROLE - 1;
const mjMap texrole_map[texrole_sz] = {
{"rgb", mjTEXROLE_RGB},
{"occlusion", mjTEXROLE_OCCLUSION},
{"roughness", mjTEXROLE_ROUGHNESS},
{"metallic", mjTEXROLE_METALLIC},
{"normal", mjTEXROLE_NORMAL},
{"opacity", mjTEXROLE_OPACITY},
{"emissive", mjTEXROLE_EMISSIVE},
{"rgba", mjTEXROLE_RGBA},
{"orm", mjTEXROLE_ORM},
};
// integrator type // integrator type
const int integrator_sz = 4; const int integrator_sz = 4;
@@ -1534,9 +1558,26 @@ void mjXReader::OneMaterial(XMLElement* elem, mjsMaterial* pmat) {
if (ReadAttrTxt(elem, "name", name)) { if (ReadAttrTxt(elem, "name", name)) {
mjs_setString(pmat->name, name.c_str()); mjs_setString(pmat->name, name.c_str());
} }
bool tex_attributes_found = false;
if (ReadAttrTxt(elem, "texture", texture)) { if (ReadAttrTxt(elem, "texture", texture)) {
mjs_setString(pmat->texture, texture.c_str()); mjs_setInStringVec(pmat->textures, mjTEXROLE_RGB, texture.c_str());
tex_attributes_found = true;
} }
XMLElement* tex_elem = FirstChildElement(elem);
while (tex_elem) {
if (tex_attributes_found) {
throw mjXError(tex_elem, "A material with a texture attribute cannot have texture sub-elements");
}
// texture sub-element
int role = FindKey(texrole_map, texrole_sz, tex_elem->Name());
string texmat;
ReadAttrTxt(tex_elem, "texture", texmat, true);
mjs_setInStringVec(pmat->textures, role, texmat.c_str());
tex_elem = NextSiblingElement(tex_elem);
}
if (MapValue(elem, "texuniform", &n, bool_map, 2)) { if (MapValue(elem, "texuniform", &n, bool_map, 2)) {
pmat->texuniform = (n==1); pmat->texuniform = (n==1);
} }
@@ -3095,6 +3136,9 @@ void mjXReader::Asset(XMLElement* section, const mjVFS* vfs) {
} }
ReadAttrInt(elem, "width", &ptex->width); ReadAttrInt(elem, "width", &ptex->width);
ReadAttrInt(elem, "height", &ptex->height); ReadAttrInt(elem, "height", &ptex->height);
if (!ReadAttrInt(elem, "nchannel", &ptex->nchannel)) {
ptex->nchannel = 3;
}
ReadAttr(elem, "rgb1", 3, ptex->rgb1, text); ReadAttr(elem, "rgb1", 3, ptex->rgb1, text);
ReadAttr(elem, "rgb2", 3, ptex->rgb2, text); ReadAttr(elem, "rgb2", 3, ptex->rgb2, text);
ReadAttr(elem, "markrgb", 3, ptex->markrgb, text); ReadAttr(elem, "markrgb", 3, ptex->markrgb, text);
+1 -1
View File
@@ -99,7 +99,7 @@ class mjXReader : public mjXBase {
}; };
// MJCF schema // MJCF schema
#define nMJCF 232 #define nMJCF 243
extern const char* MJCF[nMJCF][mjXATTRNUM]; extern const char* MJCF[nMJCF][mjXATTRNUM];
#endif // MUJOCO_SRC_XML_XML_NATIVE_READER_H_ #endif // MUJOCO_SRC_XML_XML_NATIVE_READER_H_
+14 -2
View File
@@ -305,8 +305,20 @@ void mjXWriter::OneMaterial(XMLElement* elem, const mjCMaterial* pmat, mjCDef* d
} }
// defaults and regular // defaults and regular
if (pmat->texture != def->Material().texture) { bool has_non_rgb = false;
WriteAttrTxt(elem, "texture", pmat->get_texture()); for (int i=1; i<mjNTEXROLE; i++) {
if (!pmat->textures_[i].empty()) {
if (i != mjTEXROLE_RGB) {
has_non_rgb = true;
}
}
if (pmat->textures_[i] != def->Material().textures_[i]) {
WriteAttrTxt(elem, "texture", pmat->get_texture(i));
}
}
if (has_non_rgb) {
// // TODO elem = InsertEnd(section, "role");
mju_error("mjXWriter: no support for non-RGB textures.");
} }
WriteAttrKey(elem, "texuniform", bool_map, 2, pmat->texuniform, def->Material().texuniform); WriteAttrKey(elem, "texuniform", bool_map, 2, pmat->texuniform, def->Material().texuniform);
WriteAttr(elem, "texrepeat", 2, pmat->texrepeat, def->Material().texrepeat); WriteAttr(elem, "texrepeat", 2, pmat->texrepeat, def->Material().texrepeat);
+97 -1
View File
@@ -782,12 +782,108 @@ TEST_F(XMLReaderTest, FallbackIncludePathTest) {
std::array<char, 1024> error; std::array<char, 1024> error;
mjModel* model = mj_loadXML(modelpath.c_str(), nullptr, mjModel* model = mj_loadXML(modelpath.c_str(), nullptr,
error.data(), error.size()); error.data(), error.size());
ASSERT_THAT(model, NotNull()); ASSERT_THAT(model, NotNull()) << error.data();
EXPECT_EQ(mj_name2id(model, mjOBJ_GEOM, "ball"), 2); EXPECT_EQ(mj_name2id(model, mjOBJ_GEOM, "ball"), 2);
EXPECT_EQ(mj_name2id(model, mjOBJ_GEOM, "another_box"), 3); EXPECT_EQ(mj_name2id(model, mjOBJ_GEOM, "another_box"), 3);
mj_deleteModel(model); mj_deleteModel(model);
} }
TEST_F(XMLReaderTest, MaterialTextureTest) {
static constexpr char xml[] = R"(
<mujoco>
<asset>
<texture file="tiny0.png" type="2d" name="tiny0"/>
<texture file="tiny1.png" type="2d" name="tiny1"/>
<material name="material">
<occlusion texture="tiny0"/>
<roughness texture="tiny0"/>
<metallic texture="tiny0"/>
<rgb texture="tiny1"/>
</material>
</asset>
<worldbody>
<geom type="plane" material="material" size="4 4 4"/>
</worldbody>
</mujoco>
)";
MockFilesystem fs("MaterialTextureTest");
fs.AddFile("tiny0.png", kTinyPng, sizeof(kTinyPng));
fs.AddFile("tiny1.png", kTinyPng, sizeof(kTinyPng));
fs.AddFile("model.xml", (const unsigned char*) xml, sizeof(xml));
std::string modelpath = fs.FullPath("model.xml");
char error[1024];
mjModel* model = mj_loadXML(modelpath.c_str(), nullptr, error, 1024);
EXPECT_THAT(model, NotNull()) << error;
EXPECT_EQ(model->mat_texid[mjTEXROLE_RGB], 1);
EXPECT_EQ(model->mat_texid[mjTEXROLE_METALLIC], 0);
EXPECT_EQ(model->mat_texid[mjTEXROLE_ROUGHNESS], 0);
EXPECT_EQ(model->mat_texid[mjTEXROLE_OCCLUSION], 0);
mj_deleteModel(model);
}
TEST_F(XMLReaderTest, LegacyMaterialTextureTest) {
static constexpr char xml[] = R"(
<mujoco>
<asset>
<texture file="tiny0.png" type="2d" name="tiny0"/>
<texture file="tiny1.png" type="2d" name="tiny1"/>
<material name="material" texture="tiny1"/>
</asset>
<worldbody>
<geom type="plane" material="material" size="4 4 4"/>
</worldbody>
</mujoco>
)";
MockFilesystem fs("LegacyMaterialTextureTest");
fs.AddFile("tiny0.png", kTinyPng, sizeof(kTinyPng));
fs.AddFile("tiny1.png", kTinyPng, sizeof(kTinyPng));
fs.AddFile("model.xml", (const unsigned char*) xml, sizeof(xml));
std::string modelpath = fs.FullPath("model.xml");
char error[1024];
mjModel* model = mj_loadXML(modelpath.c_str(), nullptr, error, 1024);
EXPECT_THAT(model, NotNull()) << error;
EXPECT_EQ(model->mat_texid[mjTEXROLE_RGB], 1);
mj_deleteModel(model);
}
TEST_F(XMLReaderTest, MaterialTextureFailTest) {
static constexpr char xml[] = R"(
<mujoco>
<asset>
<texture file="tiny0.png" type="2d" name="tiny0"/>
<texture file="tiny1.png" type="2d" name="tiny1"/>
<material name="material" texture="tiny1">
<rgb texture="tiny1"/>
<occlusion texture="tiny0"/>
</material>
</asset>
<worldbody>
<geom type="plane" material="material" size="4 4 4"/>
</worldbody>
</mujoco>
)";
MockFilesystem fs("MaterialTextureFailTest");
fs.AddFile("tiny0.png", kTinyPng, sizeof(kTinyPng));
fs.AddFile("tiny1.png", kTinyPng, sizeof(kTinyPng));
fs.AddFile("model.xml", (const unsigned char*) xml, sizeof(xml));
std::string modelpath = fs.FullPath("model.xml");
std::array<char, 1024> error;
mjModel* m = LoadModelFromString(xml, error.data(), error.size());
EXPECT_THAT(m, IsNull());
EXPECT_THAT(error.data(), HasSubstr("A material with a texture attribute "
"cannot have texture sub-elements"));
}
TEST_F(XMLReaderTest, IncludeAssetsTest) { TEST_F(XMLReaderTest, IncludeAssetsTest) {
static constexpr char xml[] = R"( static constexpr char xml[] = R"(
<mujoco> <mujoco>