Fix bug in `mjx.ray` and speed up ray-mesh by ~30% for large meshes.

PiperOrigin-RevId: 629209303
Change-Id: I39466e9a2dc55ec48dcca1780e8abef903d1c754
This commit is contained in:
Erik Frey
2024-04-29 15:46:44 -07:00
committed by Copybara-Service
parent 618d879904
commit 719476c274
3 changed files with 32 additions and 42 deletions
+5 -4
View File
@@ -41,14 +41,15 @@ MJX
13. Added support for all :ref:`condim <coContact>`: 1, 3, 4, 6. 13. Added support for all :ref:`condim <coContact>`: 1, 3, 4, 6.
14. Add support functions for ``id2name`` and ``name2id``, MJX versions of :ref:`mj_id2name` and :ref:`mj_name2id`. 14. Add support functions for ``id2name`` and ``name2id``, MJX versions of :ref:`mj_id2name` and :ref:`mj_name2id`.
15. Added support for :ref:`gravcomp<body-gravcomp>` and :ref:`actuatorgravcomp<body-joint-actuatorgravcomp>`. 15. Added support for :ref:`gravcomp<body-gravcomp>` and :ref:`actuatorgravcomp<body-joint-actuatorgravcomp>`.
16. Fixed a bug in ``mjx.ray`` for sometimes allowed negative distances for ray-mesh tests.
Bug fixes Bug fixes
^^^^^^^^^ ^^^^^^^^^
16. Defaults of lights were not being saved, now fixed. 17. Defaults of lights were not being saved, now fixed.
17. Prevent overwriting of frame names by body names when saving an XML. Bug introduced in 3.1.4. 18. Prevent overwriting of frame names by body names when saving an XML. Bug introduced in 3.1.4.
18. Fixed bug in Python binding of :ref:`mj_saveModel`: ``buffer`` argument was documented as optional but was actually 19. Fixed bug in Python binding of :ref:`mj_saveModel`: ``buffer`` argument was documented as optional but was actually
not optional. not optional.
19. Fixed bug that prevented memory allocations larger than 2.15 GB. 20. Fixed bug that prevented memory allocations larger than 2.15 GB.
Version 3.1.4 (April 10th, 2024) Version 3.1.4 (April 10th, 2024)
+19 -28
View File
@@ -134,32 +134,27 @@ def _ray_triangle(
vert: jax.Array, vert: jax.Array,
pnt: jax.Array, pnt: jax.Array,
vec: jax.Array, vec: jax.Array,
b0: jax.Array, basis: jax.Array,
b1: jax.Array,
) -> jax.Array: ) -> jax.Array:
"""Returns the distance at which a ray intersects with a triangle.""" """Returns the distance at which a ray intersects with a triangle."""
# project difference vectors in ray normal plane # project difference vectors in ray normal plane
planar = jp.dot(jp.array([b0, b1]), (vert - pnt).T) planar = jp.dot(vert - pnt, basis)
# determine if origin is inside planar projection of triangle # determine if origin is inside planar projection of triangle
# A = (p0-p2, p1-p2), b = -p2, solve A*t = b # A = (p0-p2, p1-p2), b = -p2, solve A*t = b
A = jp.array( # pylint: disable=invalid-name A = planar[0:2] - planar[2] # pylint: disable=invalid-name
[planar[:, 0] - planar[:, 2], planar[:, 1] - planar[:, 2]] b = -planar[2]
).T.flatten() det = A[0, 0] * A[1, 1] - A[1, 0] * A[0, 1]
b = -planar[:, 2]
det = A[0] * A[3] - A[1] * A[2]
valid = jp.abs(det) >= mujoco.mjMINVAL
t0 = (A[3] * b[0] - A[1] * b[1]) / det t0 = (A[1, 1] * b[0] - A[1, 0] * b[1]) / det
t1 = (-A[2] * b[0] + A[0] * b[1]) / det t1 = (-A[0, 1] * b[0] + A[0, 0] * b[1]) / det
valid &= (t0 >= 0) & (t1 >= 0) & (t0 + t1 <= 1) valid = (t0 >= 0) & (t1 >= 0) & (t0 + t1 <= 1)
# intersect ray with plane of triangle # intersect ray with plane of triangle
nrm = jp.cross(vert[0] - vert[2], vert[1] - vert[2]) nrm = jp.cross(vert[0] - vert[2], vert[1] - vert[2])
denom = jp.dot(vec, nrm) dist = jp.dot(vert[2] - pnt, nrm) / jp.dot(vec, nrm)
valid &= jp.abs(denom) >= mujoco.mjMINVAL valid &= dist >= 0
dist = jp.where(valid, dist, jp.inf)
dist = jp.where(valid, -jp.dot(pnt - vert[2], nrm) / denom, jp.inf)
return dist return dist
@@ -174,33 +169,29 @@ def _ray_mesh(
"""Returns the best distance and geom_id for ray mesh intersections.""" """Returns the best distance and geom_id for ray mesh intersections."""
data_id = m.geom_dataid[geom_id] data_id = m.geom_dataid[geom_id]
ray_basis = lambda x: math.orthogonals(math.normalize(x)) ray_basis = lambda x: jp.array(math.orthogonals(math.normalize(x))).T
b0, b1 = jax.vmap(ray_basis)(vec) basis = jax.vmap(ray_basis)(vec)
faceadr = np.append(m.mesh_faceadr, m.nmeshface) faceadr = np.append(m.mesh_faceadr, m.nmeshface)
vertadr = np.append(m.mesh_vertadr, m.nmeshvert) vertadr = np.append(m.mesh_vertadr, m.nmeshvert)
dists = [] dists, geom_ids = [], []
for i, id_ in enumerate(data_id): for i, id_ in enumerate(data_id):
face = m.mesh_face[faceadr[id_] : faceadr[id_ + 1]] face = m.mesh_face[faceadr[id_] : faceadr[id_ + 1]]
vert = m.mesh_vert[vertadr[id_] : vertadr[id_ + 1]] vert = m.mesh_vert[vertadr[id_] : vertadr[id_ + 1]]
dist = jax.vmap(_ray_triangle, in_axes=(0, None, None, None, None))( vert = jp.array(vert[face])
vert[face], pnt[i], vec[i], b0[i], b1[i] dist = jax.vmap(_ray_triangle, in_axes=(0, None, None, None))(
vert, pnt[i], vec[i], basis[i]
) )
dists.append(dist) dists.append(dist)
geom_ids.append(np.repeat(geom_id[i], dist.size))
# map the triangle id to data id
tri_id = np.append(0, (faceadr[data_id + 1] - faceadr[data_id]).cumsum())
tri_data_id = np.zeros(tri_id[-1], dtype=np.int32)
tri_data_id[tri_id[:-1]] = 1
tri_data_id = tri_data_id.cumsum() - 1
dists = jp.concatenate(dists) dists = jp.concatenate(dists)
min_id = jp.argmin(dists) min_id = jp.argmin(dists)
# Grab the best distance amongst all meshes, bypassing the argmin in `ray`. # Grab the best distance amongst all meshes, bypassing the argmin in `ray`.
# This avoids having to compute the best distance per mesh. # This avoids having to compute the best distance per mesh.
dist = dists[min_id, None] dist = dists[min_id, None]
id_ = jp.array(geom_id)[jp.array(tri_data_id)[min_id], None] id_ = jp.array(np.concatenate(geom_ids))[min_id, None]
return dist, id_ return dist, id_
+8 -10
View File
@@ -152,11 +152,7 @@ class RayTest(absltest.TestCase):
mx, dx = mjx.put_model(m), mjx.put_data(m, d) mx, dx = mjx.put_model(m), mjx.put_data(m, d)
# look at the tetrahedron # look at the tetrahedron
pnt, vec = jp.array([2.0, 2.0, 2.0]), -jp.array([ pnt, vec = jp.array([2.0, 2.0, 2.0]), -jp.array([1.0, 1.0, 1.0])
1.0,
1.0,
1.0,
])
vec /= jp.linalg.norm(vec) vec /= jp.linalg.norm(vec)
dist, geomid = jax.jit(mjx.ray)(mx, dx, pnt, vec) dist, geomid = jax.jit(mjx.ray)(mx, dx, pnt, vec)
_assert_eq(geomid, 4, 'geom_id') _assert_eq(geomid, 4, 'geom_id')
@@ -166,12 +162,14 @@ class RayTest(absltest.TestCase):
_assert_eq(geomid, 4, 'geom_id') _assert_eq(geomid, 4, 'geom_id')
_assert_eq(dist, mj_dist, 'dist-tetrahedron') _assert_eq(dist, mj_dist, 'dist-tetrahedron')
# look away from the dodecahedron
pnt, vec = jp.array([4.0, 2.0, 2.0]), jp.array([2.0, 1.0, 1.0])
vec /= jp.linalg.norm(vec)
_, geomid = jax.jit(mjx.ray)(mx, dx, pnt, vec)
_assert_eq(geomid, -1, 'geom_id')
# look at the dodecahedron # look at the dodecahedron
pnt, vec = jp.array([4.0, 2.0, 2.0]), -jp.array([ pnt, vec = jp.array([4.0, 2.0, 2.0]), -jp.array([2.0, 1.0, 1.0])
2.0,
1.0,
1.0,
])
vec /= jp.linalg.norm(vec) vec /= jp.linalg.norm(vec)
dist, geomid = jax.jit(mjx.ray)(mx, dx, pnt, vec) dist, geomid = jax.jit(mjx.ray)(mx, dx, pnt, vec)
_assert_eq(geomid, 5, 'geom_id') _assert_eq(geomid, 5, 'geom_id')