Speed up convex-convex collisions.
PiperOrigin-RevId: 613450874 Change-Id: Ie66e0a078e2ff51a136b949dd9459847e1fe957d
This commit is contained in:
committed by
Copybara-Service
parent
106453fa2b
commit
22cd96fbbc
@@ -2,6 +2,14 @@
|
||||
Changelog
|
||||
=========
|
||||
|
||||
Upcoming version (not yet released)
|
||||
-----------------------------------
|
||||
|
||||
MJX
|
||||
^^^
|
||||
|
||||
1. Improved performance of SAT for convex collisions.
|
||||
|
||||
Version 3.1.3 (March 5th, 2024)
|
||||
-----------------------------------
|
||||
|
||||
|
||||
+2
-2
@@ -299,8 +299,8 @@ Collisions between large meshes
|
||||
SAT works well for smaller meshes but suffers in both runtime and memory for larger meshes.
|
||||
|
||||
For
|
||||
collisions between convex meshes and primitives (spheres, capsules, planes), use **3000 vertices or less** for your convex meshes.
|
||||
For collisions between convex meshes and other convex meshes, use **30 vertices or less**.
|
||||
collisions with convex meshes, the convex decompositon of the mesh should have
|
||||
roughly **200 vertices or less** for reasonable performance.
|
||||
With careful
|
||||
tuning, MJX can simulate scenes with mesh collisions -- see the MJX
|
||||
`shadow hand <https://github.com/google-deepmind/mujoco/tree/main/mjx/mujoco/mjx/benchmark/model/shadow_hand>`__
|
||||
|
||||
@@ -52,6 +52,8 @@ class GeomInfo(PyTreeNode):
|
||||
vert: Optional[jax.Array] = None
|
||||
edge: Optional[jax.Array] = None
|
||||
facenorm: Optional[jax.Array] = None
|
||||
face_edge: Optional[jax.Array] = None
|
||||
face_edge_normal: Optional[jax.Array] = None
|
||||
|
||||
|
||||
class SolverParams(PyTreeNode):
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
# ==============================================================================
|
||||
"""Convex collisions."""
|
||||
|
||||
import functools
|
||||
from typing import Tuple
|
||||
|
||||
import jax
|
||||
@@ -177,6 +178,174 @@ def _manifold_points(
|
||||
return jp.array([a_idx, b_idx, c_idx, d_idx])
|
||||
|
||||
|
||||
def plane_convex(plane: GeomInfo, convex: GeomInfo) -> Contact:
|
||||
"""Calculates contacts between a plane and a convex object."""
|
||||
vert = convex.vert
|
||||
|
||||
# get points in the convex frame
|
||||
plane_pos = convex.mat.T @ (plane.pos - convex.pos)
|
||||
n = convex.mat.T @ plane.mat[:, 2]
|
||||
support = (plane_pos - vert) @ n
|
||||
idx = _manifold_points(vert, support > 0, n)
|
||||
pos = vert[idx]
|
||||
|
||||
# convert to world frame
|
||||
pos = convex.pos + pos @ convex.mat.T
|
||||
n = plane.mat[:, 2]
|
||||
|
||||
frame = jp.stack([math.make_frame(n)] * 4, axis=0)
|
||||
unique = jp.tril(idx == idx[:, None]).sum(axis=1) == 1
|
||||
dist = jp.where(unique, -support[idx], 1)
|
||||
pos = pos - 0.5 * dist[:, None] * n
|
||||
return dist, pos, frame
|
||||
|
||||
|
||||
def sphere_convex(sphere: GeomInfo, convex: GeomInfo) -> Contact:
|
||||
"""Calculates contact between a sphere and a convex object."""
|
||||
faces = convex.face
|
||||
normals = convex.facenorm
|
||||
|
||||
# Put sphere in convex frame.
|
||||
sphere_pos = convex.mat.T @ (sphere.pos - convex.pos)
|
||||
|
||||
# Get support from face normals.
|
||||
@jax.vmap
|
||||
def get_support(faces, normal):
|
||||
pos = sphere_pos - normal * sphere.size[0]
|
||||
return jp.dot(pos - faces[0], normal)
|
||||
|
||||
support = get_support(faces, normals)
|
||||
|
||||
# Pick the face with minimal penetration as long as it has support.
|
||||
support = jp.where(support >= 0, -1e12, support)
|
||||
best_idx = support.argmax()
|
||||
face = faces[best_idx]
|
||||
normal = normals[best_idx]
|
||||
|
||||
# Get closest point between the polygon face and the sphere center point.
|
||||
# Project the sphere center point onto poly plane. If it's inside polygon
|
||||
# edge normals, then we're done.
|
||||
pt = _project_pt_onto_plane(sphere_pos, face[0], normal)
|
||||
edge_p0 = jp.roll(face, 1, axis=0)
|
||||
edge_p1 = face
|
||||
edge_normals = jax.vmap(jp.cross, in_axes=[0, None])(
|
||||
edge_p1 - edge_p0,
|
||||
normal,
|
||||
)
|
||||
edge_dist = jax.vmap(
|
||||
lambda plane_pt, plane_norm: (pt - plane_pt).dot(plane_norm)
|
||||
)(edge_p0, edge_normals)
|
||||
inside = jp.all(edge_dist <= 0) # lte to handle degenerate edges
|
||||
|
||||
# If the point is outside edge normals, project onto the closest edge plane
|
||||
# that the point is in front of.
|
||||
degenerate_edge = jp.all(edge_normals == 0, axis=1)
|
||||
behind = edge_dist < 0.0
|
||||
edge_dist = jp.where(degenerate_edge | behind, 1e12, edge_dist)
|
||||
idx = edge_dist.argmin()
|
||||
edge_pt = math.closest_segment_point(edge_p0[idx], edge_p1[idx], pt)
|
||||
|
||||
pt = jp.where(inside, pt, edge_pt)
|
||||
|
||||
# Get the normal, dist, and contact position.
|
||||
n, d = math.normalize_with_norm(pt - sphere_pos)
|
||||
spt = sphere_pos + n * sphere.size[0]
|
||||
dist = d - sphere.size[0]
|
||||
pos = (pt + spt) * 0.5
|
||||
|
||||
# Go back to world frame.
|
||||
n = convex.mat @ n
|
||||
pos = convex.mat @ pos + convex.pos
|
||||
|
||||
return jax.tree_map(
|
||||
lambda x: jp.expand_dims(x, axis=0), (dist, pos, math.make_frame(n))
|
||||
)
|
||||
|
||||
|
||||
def capsule_convex(cap: GeomInfo, convex: GeomInfo) -> Contact:
|
||||
"""Calculates contacts between a capsule and a convex object."""
|
||||
# Get convex transformed normals, faces, and vertices.
|
||||
faces = convex.face
|
||||
normals = convex.facenorm
|
||||
|
||||
# Put capsule in convex frame.
|
||||
cap_pos = convex.mat.T @ (cap.pos - convex.pos)
|
||||
axis, length = cap.mat[:, 2], cap.size[1]
|
||||
axis = convex.mat.T @ axis
|
||||
seg = axis * length
|
||||
cap_pts = jp.array([
|
||||
cap_pos - seg,
|
||||
cap_pos + seg,
|
||||
])
|
||||
|
||||
# Get support from face normals.
|
||||
@jax.vmap
|
||||
def get_support(face, normal):
|
||||
pts = cap_pts - normal * cap.size[0]
|
||||
sup = jax.vmap(lambda x: jp.dot(x - face[0], normal))(pts)
|
||||
return sup.min()
|
||||
|
||||
support = get_support(faces, normals)
|
||||
has_support = jp.all(support < 0)
|
||||
|
||||
# Pick the face with minimal penetration as long as it has support.
|
||||
support = jp.where(support >= 0, -1e12, support)
|
||||
best_idx = support.argmax()
|
||||
face = faces[best_idx]
|
||||
normal = normals[best_idx]
|
||||
|
||||
# Clip the edge against side planes and create two contact points against the
|
||||
# face.
|
||||
edge_p0 = jp.roll(face, 1, axis=0)
|
||||
edge_p1 = face
|
||||
edge_normals = jax.vmap(jp.cross, in_axes=[0, None])(
|
||||
edge_p1 - edge_p0,
|
||||
normal,
|
||||
)
|
||||
cap_pts_clipped, mask = _clip_edge_to_planes(
|
||||
cap_pts[0], cap_pts[1], edge_p0, edge_normals
|
||||
)
|
||||
cap_pts_clipped = cap_pts_clipped - normal * cap.size[0]
|
||||
face_pts = jax.vmap(_project_pt_onto_plane, in_axes=[0, None, None])(
|
||||
cap_pts_clipped, face[0], normal
|
||||
)
|
||||
# Create variables for the face contact.
|
||||
pos = (cap_pts_clipped + face_pts) * 0.5
|
||||
norm = jp.stack([normal] * 2, 0)
|
||||
penetration = jp.where(
|
||||
mask & has_support, jp.dot(face_pts - cap_pts_clipped, normal), -1
|
||||
)
|
||||
|
||||
# Get a potential edge contact.
|
||||
edge_closest, cap_closest = jax.vmap(
|
||||
math.closest_segment_to_segment_points, in_axes=[0, 0, None, None]
|
||||
)(edge_p0, edge_p1, cap_pts[0], cap_pts[1])
|
||||
e_idx = ((edge_closest - cap_closest) ** 2).sum(axis=1).argmin()
|
||||
cap_closest_pt, edge_closest_pt = cap_closest[e_idx], edge_closest[e_idx]
|
||||
edge_axis = cap_closest_pt - edge_closest_pt
|
||||
edge_axis, edge_dist = math.normalize_with_norm(edge_axis)
|
||||
edge_pos = (
|
||||
edge_closest_pt + (cap_closest_pt - edge_axis * cap.size[0])
|
||||
) * 0.5
|
||||
edge_norm = edge_axis
|
||||
edge_penetration = cap.size[0] - edge_dist
|
||||
has_edge_contact = edge_penetration > 0
|
||||
|
||||
# Get the contact info.
|
||||
pos = jp.where(has_edge_contact, pos.at[0].set(edge_pos), pos)
|
||||
n = -jp.where(has_edge_contact, norm.at[0].set(edge_norm), norm)
|
||||
|
||||
# Go back to world frame.
|
||||
pos = convex.pos + pos @ convex.mat.T
|
||||
n = n @ convex.mat.T
|
||||
|
||||
dist = -jp.where(
|
||||
has_edge_contact, penetration.at[0].set(edge_penetration), penetration
|
||||
)
|
||||
frame = jax.vmap(math.make_frame)(n)
|
||||
return dist, pos, frame
|
||||
|
||||
|
||||
def _project_pt_onto_plane(
|
||||
pt: jax.Array, plane_pt: jax.Array, plane_normal: jax.Array
|
||||
) -> jax.Array:
|
||||
@@ -396,7 +565,7 @@ def _create_contact_manifold(
|
||||
return dist, pos, normal
|
||||
|
||||
|
||||
def _sat_hull_hull(
|
||||
def _sat_bruteforce(
|
||||
faces_a: jax.Array,
|
||||
faces_b: jax.Array,
|
||||
vertices_a: jax.Array,
|
||||
@@ -414,13 +583,16 @@ def _sat_hull_hull(
|
||||
We return both the edge and face contacts. Valid contacts can be checked with
|
||||
dist < 0. Resulting edge contacts should be preferred over face contacts.
|
||||
|
||||
This method checks all unique edge-pairs and is thus costly to run over large
|
||||
meshes, but is more performant for smaller meshes (boxes, tetrahedra, etc.).
|
||||
|
||||
Args:
|
||||
faces_a: An ndarray of hull A's polygon faces.
|
||||
faces_b: An ndarray of hull B's polygon faces.
|
||||
faces_a: Faces for hull A.
|
||||
faces_b: Faces for hull B.
|
||||
vertices_a: Vertices for hull A.
|
||||
vertices_b: Vertices for hull B.
|
||||
normals_a: Normal vectors for hull A's polygon faces.
|
||||
normals_b: Normal vectors for hull B's polygon faces.
|
||||
normals_a: Normal vectors for hull A faces.
|
||||
normals_b: Normal vectors for hull B faces.
|
||||
unique_edges_a: Unique edges for hull A.
|
||||
unique_edges_b: Unique edges for hull B.
|
||||
|
||||
@@ -428,30 +600,36 @@ def _sat_hull_hull(
|
||||
tuple of dist, pos, and normal
|
||||
"""
|
||||
# get the separating axes
|
||||
edge_dir_a = unique_edges_a[:, 0] - unique_edges_a[:, 1]
|
||||
edge_dir_b = unique_edges_b[:, 0] - unique_edges_b[:, 1]
|
||||
v_norm = jax.vmap(math.normalize)
|
||||
edge_dir_a = v_norm(unique_edges_a[:, 0] - unique_edges_a[:, 1])
|
||||
edge_dir_b = v_norm(unique_edges_b[:, 0] - unique_edges_b[:, 1])
|
||||
edge_dir_a_r = jp.tile(edge_dir_a, reps=(unique_edges_b.shape[0], 1))
|
||||
edge_dir_b_r = jp.repeat(edge_dir_b, repeats=unique_edges_a.shape[0], axis=0)
|
||||
edge_edge_axes = jax.vmap(jp.cross)(edge_dir_a_r, edge_dir_b_r)
|
||||
edge_edge_axes = jax.vmap(lambda x: math.normalize(x, axis=0))(
|
||||
edge_edge_axes
|
||||
edge_axes = jax.vmap(jp.cross)(edge_dir_a_r, edge_dir_b_r)
|
||||
degenerate_edge_axes = (edge_axes**2).sum(axis=1) < 1e-6
|
||||
edge_axes = jax.vmap(lambda x: math.normalize(x, axis=0))(edge_axes)
|
||||
n_norm = normals_a.shape[0] + normals_b.shape[0]
|
||||
degenerate_axes = jp.concatenate(
|
||||
[jp.array([False] * n_norm), degenerate_edge_axes]
|
||||
)
|
||||
|
||||
axes = jp.concatenate([normals_a, normals_b, edge_edge_axes])
|
||||
axes = jp.concatenate([normals_a, normals_b, edge_axes])
|
||||
|
||||
# for each separating axis, get the support
|
||||
@jax.vmap
|
||||
def get_support(axis):
|
||||
support_a = jax.vmap(jp.dot, in_axes=[None, 0])(axis, vertices_a)
|
||||
support_b = jax.vmap(jp.dot, in_axes=[None, 0])(axis, vertices_b)
|
||||
def get_support(axis, is_degenerate):
|
||||
# the matmul here is more performant with vmap(dot)
|
||||
dot = functools.partial(jp.dot, precision=jax.lax.Precision.HIGH)
|
||||
support_a = jax.vmap(dot, in_axes=[None, 0])(axis, vertices_a)
|
||||
support_b = jax.vmap(dot, in_axes=[None, 0])(axis, vertices_b)
|
||||
dist1 = support_a.max() - support_b.min()
|
||||
dist2 = support_b.max() - support_a.min()
|
||||
sign = jp.where(dist1 > dist2, -1, 1)
|
||||
dist = jp.minimum(dist1, dist2)
|
||||
dist = jp.where(~jp.all(axis == 0.0), dist, 1e6) # degenerate axis
|
||||
dist = jp.where(~is_degenerate, dist, 1e6) # degenerate axis
|
||||
return dist, sign
|
||||
|
||||
support, sign = get_support(axes)
|
||||
support, sign = get_support(axes, degenerate_axes)
|
||||
|
||||
# choose the best separating axis
|
||||
best_idx = jp.argmin(support)
|
||||
@@ -460,8 +638,8 @@ def _sat_hull_hull(
|
||||
is_edge_contact = best_idx >= (normals_a.shape[0] + normals_b.shape[0])
|
||||
|
||||
# get the (reference) face most aligned with the separating axis
|
||||
dist_a = jax.vmap(jp.dot, in_axes=[None, 0])(best_axis, normals_a)
|
||||
dist_b = jax.vmap(jp.dot, in_axes=[None, 0])(best_axis, normals_b)
|
||||
dist_a = normals_a @ best_axis
|
||||
dist_b = normals_b @ best_axis
|
||||
a_max = dist_a.argmax()
|
||||
b_max = dist_b.argmax()
|
||||
a_min = dist_a.argmin()
|
||||
@@ -496,172 +674,172 @@ def _sat_hull_hull(
|
||||
return dist, pos, normal
|
||||
|
||||
|
||||
def plane_convex(plane: GeomInfo, convex: GeomInfo) -> Contact:
|
||||
"""Calculates contacts between a plane and a convex object."""
|
||||
vert = convex.vert
|
||||
|
||||
# get points in the convex frame
|
||||
plane_pos = convex.mat.T @ (plane.pos - convex.pos)
|
||||
n = convex.mat.T @ plane.mat[:, 2]
|
||||
support = (plane_pos - vert) @ n
|
||||
idx = _manifold_points(vert, support > 0, n)
|
||||
pos = vert[idx]
|
||||
|
||||
# convert to world frame
|
||||
pos = convex.pos + pos @ convex.mat.T
|
||||
n = plane.mat[:, 2]
|
||||
|
||||
frame = jp.stack([math.make_frame(n)] * 4, axis=0)
|
||||
unique = jp.tril(idx == idx[:, None]).sum(axis=1) == 1
|
||||
dist = jp.where(unique, -support[idx], 1)
|
||||
pos = pos - 0.5 * dist[:, None] * n
|
||||
return dist, pos, frame
|
||||
def _arcs_intersect(
|
||||
a: jax.Array, b: jax.Array, c: jax.Array, d: jax.Array
|
||||
) -> jax.Array:
|
||||
"""Tests if arcs AB and CD on the unit sphere intersect."""
|
||||
ba, dc = jp.cross(b, a), jp.cross(d, c)
|
||||
cba, dba = jp.dot(c, ba), jp.dot(d, ba)
|
||||
adc, bdc = jp.dot(a, dc), jp.dot(b, dc)
|
||||
return (cba * dba < 0) & (adc * bdc < 0) & (cba * bdc > 0)
|
||||
|
||||
|
||||
def sphere_convex(sphere: GeomInfo, convex: GeomInfo) -> Contact:
|
||||
"""Calculates contact between a sphere and a convex object."""
|
||||
faces = convex.face
|
||||
normals = convex.facenorm
|
||||
def _sat_approx(
|
||||
centroid_a: jax.Array,
|
||||
faces_a: jax.Array,
|
||||
faces_b: jax.Array,
|
||||
vertices_a: jax.Array,
|
||||
vertices_b: jax.Array,
|
||||
normals_a: jax.Array,
|
||||
normals_b: jax.Array,
|
||||
face_edges_a: jax.Array,
|
||||
face_edges_b: jax.Array,
|
||||
face_edge_normals_a: jax.Array,
|
||||
face_edge_normals_b: jax.Array,
|
||||
) -> Tuple[jax.Array, jax.Array, jax.Array]:
|
||||
"""Runs the Separating Axis Test for a pair of hulls.
|
||||
|
||||
# Put sphere in convex frame.
|
||||
sphere_pos = convex.mat.T @ (sphere.pos - convex.pos)
|
||||
Runs the separating axis test for all faces. After obtaining a reference
|
||||
and incident face, tests edge separating axes via edge intersections
|
||||
on gauss maps.
|
||||
|
||||
Certain meshes can have nearly parallel coplanar faces that are not merged.
|
||||
Thus reference/incident faces can be non-overlapping, and face contacts will
|
||||
not get generated. Since we only check edge separating axes on
|
||||
reference/incident faces, the correct edge separating axes may also be
|
||||
missing. We mitigate this issue by checking nearly coplanar anti-parallel
|
||||
reference/incident faces for support, hence why this method is "approximate".
|
||||
|
||||
Args:
|
||||
centroid_a: Centroid of hull A.
|
||||
faces_a: Faces for hull A.
|
||||
faces_b: Faces for hull B.
|
||||
vertices_a: Vertices for hull A.
|
||||
vertices_b: Vertices for hull B.
|
||||
normals_a: Normal vectors for hull A faces.
|
||||
normals_b: Normal vectors for hull B faces.
|
||||
face_edges_a: Edges for faces in hull A.
|
||||
face_edges_b: Edges for faces in hull B.
|
||||
face_edge_normals_a: Edge normals for faces in hull A.
|
||||
face_edge_normals_b: Edge normals for faces in hull B.
|
||||
|
||||
Returns:
|
||||
tuple of dist, pos, and normal
|
||||
"""
|
||||
# Handle face separating axes.
|
||||
axes = jp.concatenate([normals_a, -normals_b])
|
||||
|
||||
# Get support from face normals.
|
||||
@jax.vmap
|
||||
def get_support(faces, normal):
|
||||
pos = sphere_pos - normal * sphere.size[0]
|
||||
return jp.dot(pos - faces[0], normal)
|
||||
def get_support(axis):
|
||||
# the matmul here is more performant with vmap(dot)
|
||||
dot = functools.partial(jp.dot, precision=jax.lax.Precision.HIGH)
|
||||
support_a = jax.vmap(dot, in_axes=[None, 0])(axis, vertices_a)
|
||||
support_b = jax.vmap(dot, in_axes=[None, 0])(axis, vertices_b)
|
||||
dist = support_a.max() - support_b.min()
|
||||
separating = dist < 0
|
||||
dist = jp.where(dist < 0, 1e6, dist)
|
||||
return dist, separating
|
||||
|
||||
support = get_support(faces, normals)
|
||||
support, separating = get_support(axes)
|
||||
is_face_separating = separating.any()
|
||||
|
||||
# Pick the face with minimal penetration as long as it has support.
|
||||
support = jp.where(support >= 0, -1e12, support)
|
||||
best_idx = support.argmax()
|
||||
face = faces[best_idx]
|
||||
normal = normals[best_idx]
|
||||
# choose the best separating axis
|
||||
best_idx = jp.argmin(support)
|
||||
best_axis = axes[best_idx]
|
||||
|
||||
# Get closest point between the polygon face and the sphere center point.
|
||||
# Project the sphere center point onto poly plane. If it's inside polygon
|
||||
# edge normals, then we're done.
|
||||
pt = _project_pt_onto_plane(sphere_pos, face[0], normal)
|
||||
edge_p0 = jp.roll(face, 1, axis=0)
|
||||
edge_p1 = face
|
||||
edge_normals = jax.vmap(jp.cross, in_axes=[0, None])(
|
||||
edge_p1 - edge_p0,
|
||||
normal,
|
||||
)
|
||||
edge_dist = jax.vmap(
|
||||
lambda plane_pt, plane_norm: (pt - plane_pt).dot(plane_norm)
|
||||
)(edge_p0, edge_normals)
|
||||
inside = jp.all(edge_dist <= 0) # lte to handle degenerate edges
|
||||
# get the (reference) face most aligned with the separating axis
|
||||
dist_a = normals_a @ best_axis
|
||||
dist_b = normals_b @ -best_axis
|
||||
face_a_idx = dist_a.argmax()
|
||||
face_b_idx = dist_b.argmax()
|
||||
|
||||
# If the point is outside edge normals, project onto the closest edge plane
|
||||
# that the point is in front of.
|
||||
degenerate_edge = jp.all(edge_normals == 0, axis=1)
|
||||
behind = edge_dist < 0.0
|
||||
edge_dist = jp.where(degenerate_edge | behind, 1e12, edge_dist)
|
||||
idx = edge_dist.argmin()
|
||||
edge_pt = math.closest_segment_point(edge_p0[idx], edge_p1[idx], pt)
|
||||
|
||||
pt = jp.where(inside, pt, edge_pt)
|
||||
|
||||
# Get the normal, dist, and contact position.
|
||||
n, d = math.normalize_with_norm(pt - sphere_pos)
|
||||
spt = sphere_pos + n * sphere.size[0]
|
||||
dist = d - sphere.size[0]
|
||||
pos = (pt + spt) * 0.5
|
||||
|
||||
# Go back to world frame.
|
||||
n = convex.mat @ n
|
||||
pos = convex.mat @ pos + convex.pos
|
||||
|
||||
return jax.tree_map(
|
||||
lambda x: jp.expand_dims(x, axis=0), (dist, pos, math.make_frame(n))
|
||||
cond = best_idx < normals_a.shape[0]
|
||||
ref_face = jp.where(cond, faces_a[face_a_idx], faces_b[face_b_idx])
|
||||
incident_face = jp.where(cond, faces_b[face_b_idx], faces_a[face_a_idx])
|
||||
ref_face_norm = jp.where(cond, normals_a[face_a_idx], normals_b[face_b_idx])
|
||||
incident_face_norm = jp.where(
|
||||
cond, normals_b[face_b_idx], normals_a[face_a_idx]
|
||||
)
|
||||
|
||||
dist, pos, normal = _create_contact_manifold(
|
||||
ref_face,
|
||||
incident_face,
|
||||
ref_face_norm,
|
||||
incident_face_norm,
|
||||
-best_axis,
|
||||
)
|
||||
|
||||
def capsule_convex(cap: GeomInfo, convex: GeomInfo) -> Contact:
|
||||
"""Calculates contacts between a capsule and a convex object."""
|
||||
# Get convex transformed normals, faces, and vertices.
|
||||
faces = convex.face
|
||||
normals = convex.facenorm
|
||||
# Handle edge separating axes by checking edge pairs on the reference and
|
||||
# incident faces. In principle, the correct edge-edge separating axis can be
|
||||
# created from edge pairs on the reference and incident faces.
|
||||
# first get the edge directions and face edge normals
|
||||
face_edges_a = face_edges_a[face_a_idx]
|
||||
v_norm = jax.vmap(math.normalize)
|
||||
face_edges_dir_a = v_norm(face_edges_a[:, 0] - face_edges_a[:, 1])
|
||||
face_edges_b = face_edges_b[face_b_idx]
|
||||
face_edges_dir_b = v_norm(face_edges_b[:, 0] - face_edges_b[:, 1])
|
||||
face_edge_normals_a = face_edge_normals_a[face_a_idx]
|
||||
face_edge_normals_b = face_edge_normals_b[face_b_idx]
|
||||
|
||||
# Put capsule in convex frame.
|
||||
cap_pos = convex.mat.T @ (cap.pos - convex.pos)
|
||||
axis, length = cap.mat[:, 2], cap.size[1]
|
||||
axis = convex.mat.T @ axis
|
||||
seg = axis * length
|
||||
cap_pts = jp.array([
|
||||
cap_pos - seg,
|
||||
cap_pos + seg,
|
||||
])
|
||||
# scatter to all edge pairs
|
||||
edge_idx_a = jp.repeat(
|
||||
jp.arange(face_edges_a.shape[0]), face_edges_b.shape[0]
|
||||
)
|
||||
edge_idx_b = jp.tile(jp.arange(face_edges_b.shape[0]), face_edges_a.shape[0])
|
||||
face_edges_dir_a = face_edges_dir_a[edge_idx_a]
|
||||
face_edges_dir_b = face_edges_dir_b[edge_idx_b]
|
||||
face_edges_pt_a = face_edges_a[:, 0][edge_idx_a]
|
||||
face_edges_pt_b = face_edges_b[:, 0][edge_idx_b]
|
||||
face_edge_normals_a = face_edge_normals_a[edge_idx_a]
|
||||
face_edge_normals_b = face_edge_normals_b[edge_idx_b]
|
||||
|
||||
# Get support from face normals.
|
||||
@jax.vmap
|
||||
def get_support(face, normal):
|
||||
pts = cap_pts - normal * cap.size[0]
|
||||
sup = jax.vmap(lambda x: jp.dot(x - face[0], normal))(pts)
|
||||
return sup.min()
|
||||
def get_normals(a_dir, a_pt, b_dir):
|
||||
edge_axis = math.normalize(jp.cross(a_dir, b_dir))
|
||||
# correct normal to point from a to b, object b is at the origin
|
||||
sign = jp.where(jp.dot(edge_axis, a_pt - centroid_a) > 0.0, 1.0, -1.0)
|
||||
return edge_axis * sign
|
||||
|
||||
support = get_support(faces, normals)
|
||||
has_support = jp.all(support < 0)
|
||||
edge_axes = get_normals(face_edges_dir_a, face_edges_pt_a, face_edges_dir_b)
|
||||
edge_dist = jax.vmap(jp.dot)(edge_axes, face_edges_pt_b - face_edges_pt_a)
|
||||
# handle degenerate axes
|
||||
edge_dist = jp.where((edge_axes**2).sum(axis=1) < 1e-6, 1e6, edge_dist)
|
||||
# ensure edges create a minkowski face by testing intersection on gauss maps
|
||||
is_minkowski_face = jax.vmap(_arcs_intersect)(
|
||||
face_edge_normals_a[:, 0],
|
||||
face_edge_normals_a[:, 1],
|
||||
-face_edge_normals_b[:, 0],
|
||||
-face_edge_normals_b[:, 1],
|
||||
)
|
||||
edge_dist = jp.where(is_minkowski_face, edge_dist, 1e6)
|
||||
edge_dist = jp.where(edge_dist > 0, -1e6, edge_dist)
|
||||
|
||||
# Pick the face with minimal penetration as long as it has support.
|
||||
support = jp.where(support >= 0, -1e12, support)
|
||||
best_idx = support.argmax()
|
||||
face = faces[best_idx]
|
||||
normal = normals[best_idx]
|
||||
best_edge_idx = edge_dist.argmax()
|
||||
best_edge_dist = edge_dist[best_edge_idx]
|
||||
# prefer edge over face contacts as long as we have a valid edge contact
|
||||
is_edge_contact = (best_edge_dist > dist.min() + 1e-6) & (best_edge_dist < 0)
|
||||
normal = jp.where(is_edge_contact, edge_axes[best_edge_idx], normal)
|
||||
dist = jp.where(is_edge_contact, jp.array([best_edge_dist, 1, 1, 1]), dist)
|
||||
|
||||
# Clip the edge against side planes and create two contact points against the
|
||||
# face.
|
||||
edge_p0 = jp.roll(face, 1, axis=0)
|
||||
edge_p1 = face
|
||||
edge_normals = jax.vmap(jp.cross, in_axes=[0, None])(
|
||||
edge_p1 - edge_p0,
|
||||
normal,
|
||||
)
|
||||
cap_pts_clipped, mask = _clip_edge_to_planes(
|
||||
cap_pts[0], cap_pts[1], edge_p0, edge_normals
|
||||
)
|
||||
cap_pts_clipped = cap_pts_clipped - normal * cap.size[0]
|
||||
face_pts = jax.vmap(_project_pt_onto_plane, in_axes=[0, None, None])(
|
||||
cap_pts_clipped, face[0], normal
|
||||
)
|
||||
# Create variables for the face contact.
|
||||
pos = (cap_pts_clipped + face_pts) * 0.5
|
||||
norm = jp.stack([normal] * 2, 0)
|
||||
penetration = jp.where(
|
||||
mask & has_support, jp.dot(face_pts - cap_pts_clipped, normal), -1
|
||||
# A failure mode occurs if faces are very narrow and nearly parallel
|
||||
# (i.e. faces did not get merged properly as coplanar faces). The face
|
||||
# contacts will be empty since the reference/incident faces may not overlap.
|
||||
# An edge-edge separating axis will not be found, since the reference and
|
||||
# incident faces will also not overlap or necessarily create a minkowski face.
|
||||
# Thus, we approximate the contact for anti-parallel faces with support.
|
||||
anti_parallel = incident_face_norm.dot(ref_face_norm) < -0.97
|
||||
dist = dist.at[0].set(
|
||||
jp.where(
|
||||
anti_parallel
|
||||
& ~is_face_separating
|
||||
& (dist > 0).all()
|
||||
& ~is_edge_contact,
|
||||
-support[best_idx],
|
||||
dist[0],
|
||||
)
|
||||
)
|
||||
|
||||
# Get a potential edge contact.
|
||||
edge_closest, cap_closest = jax.vmap(
|
||||
math.closest_segment_to_segment_points, in_axes=[0, 0, None, None]
|
||||
)(edge_p0, edge_p1, cap_pts[0], cap_pts[1])
|
||||
e_idx = ((edge_closest - cap_closest) ** 2).sum(axis=1).argmin()
|
||||
cap_closest_pt, edge_closest_pt = cap_closest[e_idx], edge_closest[e_idx]
|
||||
edge_axis = cap_closest_pt - edge_closest_pt
|
||||
edge_axis, edge_dist = math.normalize_with_norm(edge_axis)
|
||||
edge_pos = (
|
||||
edge_closest_pt + (cap_closest_pt - edge_axis * cap.size[0])
|
||||
) * 0.5
|
||||
edge_norm = edge_axis
|
||||
edge_penetration = cap.size[0] - edge_dist
|
||||
has_edge_contact = edge_penetration > 0
|
||||
|
||||
# Get the contact info.
|
||||
pos = jp.where(has_edge_contact, pos.at[0].set(edge_pos), pos)
|
||||
n = -jp.where(has_edge_contact, norm.at[0].set(edge_norm), norm)
|
||||
|
||||
# Go back to world frame.
|
||||
pos = convex.pos + pos @ convex.mat.T
|
||||
n = n @ convex.mat.T
|
||||
|
||||
dist = -jp.where(
|
||||
has_edge_contact, penetration.at[0].set(edge_penetration), penetration
|
||||
)
|
||||
frame = jax.vmap(math.make_frame)(n)
|
||||
return dist, pos, frame
|
||||
return dist, pos, normal
|
||||
|
||||
|
||||
def convex_convex(c1: GeomInfo, c2: GeomInfo) -> Contact:
|
||||
@@ -699,16 +877,41 @@ def convex_convex(c1: GeomInfo, c2: GeomInfo) -> Contact:
|
||||
unique_edges1 = jp.take(vertices1, c1.edge, axis=0)
|
||||
unique_edges2 = jp.take(vertices2, c2.edge, axis=0)
|
||||
|
||||
dist, pos, normal = _sat_hull_hull(
|
||||
faces1,
|
||||
faces2,
|
||||
vertices1,
|
||||
vertices2,
|
||||
normals1,
|
||||
normals2,
|
||||
unique_edges1,
|
||||
unique_edges2,
|
||||
face_edges1 = jp.take(vertices1, c1.face_edge, axis=0)
|
||||
face_edges2 = jp.take(vertices2, c2.face_edge, axis=0)
|
||||
|
||||
face_edge_normals1 = c1.face_edge_normal @ to_local_mat.T
|
||||
face_edge_normals2 = c2.face_edge_normal
|
||||
|
||||
enable_bruteforce = (
|
||||
unique_edges1.shape[0] * unique_edges2.shape[0]
|
||||
< face_edges1[0].shape[0] * face_edges2[0].shape[0]
|
||||
)
|
||||
if enable_bruteforce:
|
||||
dist, pos, normal = _sat_bruteforce(
|
||||
faces1,
|
||||
faces2,
|
||||
vertices1,
|
||||
vertices2,
|
||||
normals1,
|
||||
normals2,
|
||||
unique_edges1,
|
||||
unique_edges2,
|
||||
)
|
||||
else:
|
||||
dist, pos, normal = _sat_approx(
|
||||
to_local_pos,
|
||||
faces1,
|
||||
faces2,
|
||||
vertices1,
|
||||
vertices2,
|
||||
normals1,
|
||||
normals2,
|
||||
face_edges1,
|
||||
face_edges2,
|
||||
face_edge_normals1,
|
||||
face_edge_normals2,
|
||||
)
|
||||
|
||||
# Go back to world frame.
|
||||
pos = c2.pos + pos @ c2.mat.T
|
||||
|
||||
@@ -20,8 +20,8 @@ import jax
|
||||
from jax import numpy as jp
|
||||
import mujoco
|
||||
from mujoco.mjx._src import collision_base
|
||||
from mujoco.mjx._src import mesh
|
||||
from mujoco.mjx._src import support
|
||||
from mujoco.mjx._src import math
|
||||
# pylint: disable=g-importing-member
|
||||
from mujoco.mjx._src.collision_base import Candidate
|
||||
from mujoco.mjx._src.collision_base import CandidateSet
|
||||
@@ -43,7 +43,6 @@ from mujoco.mjx._src.types import GeomType
|
||||
from mujoco.mjx._src.types import Model
|
||||
# pylint: enable=g-importing-member
|
||||
|
||||
|
||||
# pair-wise collision functions
|
||||
_COLLISION_FUNC = {
|
||||
(GeomType.PLANE, GeomType.SPHERE): plane_sphere,
|
||||
@@ -91,7 +90,18 @@ def _add_candidate(
|
||||
def mesh_key(i):
|
||||
convex_data = [[None] * m.ngeom] * 3
|
||||
if isinstance(m, Model):
|
||||
convex_data = [m.geom_convex_face, m.geom_convex_vert, m.geom_convex_edge]
|
||||
convex_data = [
|
||||
m.geom_convex_face,
|
||||
m.geom_convex_vert,
|
||||
m.geom_convex_edge_dir,
|
||||
]
|
||||
elif isinstance(m, mujoco.MjModel):
|
||||
kwargs = mesh.get(m)
|
||||
convex_data = [
|
||||
kwargs['geom_convex_face'],
|
||||
kwargs['geom_convex_vert'],
|
||||
kwargs['geom_convex_edge_dir'],
|
||||
]
|
||||
key = tuple((-1,) if v[i] is None else v[i].shape for v in convex_data)
|
||||
return key
|
||||
|
||||
@@ -181,36 +191,39 @@ def _pair_info(
|
||||
m: Model, d: Data, geom1: Sequence[int], geom2: Sequence[int]
|
||||
) -> Tuple[GeomInfo, GeomInfo, Sequence[Dict[str, Optional[int]]]]:
|
||||
"""Returns geom pair info for calculating collision."""
|
||||
g1, g2 = jp.array(geom1), jp.array(geom2)
|
||||
info1 = GeomInfo(
|
||||
g1,
|
||||
d.geom_xpos[g1],
|
||||
d.geom_xmat[g1],
|
||||
m.geom_size[g1],
|
||||
)
|
||||
info2 = GeomInfo(
|
||||
g2,
|
||||
d.geom_xpos[g2],
|
||||
d.geom_xmat[g2],
|
||||
m.geom_size[g2],
|
||||
)
|
||||
in_axes1 = in_axes2 = jax.tree_map(lambda x: 0, info1)
|
||||
if m.geom_convex_face[geom1[0]] is not None:
|
||||
info1 = info1.replace(
|
||||
face=jp.stack([m.geom_convex_face[i] for i in geom1]),
|
||||
vert=jp.stack([m.geom_convex_vert[i] for i in geom1]),
|
||||
edge=jp.stack([m.geom_convex_edge[i] for i in geom1]),
|
||||
facenorm=jp.stack([m.geom_convex_facenormal[i] for i in geom1]),
|
||||
def mesh_info(geom):
|
||||
g = jp.array(geom)
|
||||
info = GeomInfo(
|
||||
g,
|
||||
d.geom_xpos[g],
|
||||
d.geom_xmat[g],
|
||||
m.geom_size[g],
|
||||
)
|
||||
in_axes1 = in_axes1.replace(face=0, vert=0, edge=0, facenorm=0)
|
||||
if m.geom_convex_face[geom2[0]] is not None:
|
||||
info2 = info2.replace(
|
||||
face=jp.stack([m.geom_convex_face[i] for i in geom2]),
|
||||
vert=jp.stack([m.geom_convex_vert[i] for i in geom2]),
|
||||
edge=jp.stack([m.geom_convex_edge[i] for i in geom2]),
|
||||
facenorm=jp.stack([m.geom_convex_facenormal[i] for i in geom2]),
|
||||
)
|
||||
in_axes2 = in_axes2.replace(face=0, vert=0, edge=0, facenorm=0)
|
||||
in_axes = jax.tree_map(lambda x: 0, info)
|
||||
is_mesh = m.geom_convex_face[geom[0]] is not None
|
||||
if is_mesh:
|
||||
info = info.replace(
|
||||
face=jp.stack([m.geom_convex_face[i] for i in geom]),
|
||||
vert=jp.stack([m.geom_convex_vert[i] for i in geom]),
|
||||
edge=jp.stack([m.geom_convex_edge_dir[i] for i in geom]),
|
||||
facenorm=jp.stack([m.geom_convex_facenormal[i] for i in geom]),
|
||||
face_edge_normal=jp.stack(
|
||||
[m.geom_convex_face_edge_normal[i] for i in geom]
|
||||
),
|
||||
face_edge=jp.stack([m.geom_convex_face_edge[i] for i in geom]),
|
||||
)
|
||||
in_axes = in_axes.replace(
|
||||
face=0,
|
||||
vert=0,
|
||||
edge=0,
|
||||
facenorm=0,
|
||||
face_edge=0,
|
||||
face_edge_normal=0,
|
||||
)
|
||||
return info, in_axes
|
||||
|
||||
info1, in_axes1 = mesh_info(geom1)
|
||||
info2, in_axes2 = mesh_info(geom2)
|
||||
return info1, info2, [in_axes1, in_axes2]
|
||||
|
||||
|
||||
@@ -289,9 +302,8 @@ def _collide_geoms(
|
||||
n_pairs = max_pairs if run_broadphase else len(geom1)
|
||||
if run_broadphase:
|
||||
# broadphase over geom pairs, using bounding spheres
|
||||
size1 = jp.max(m.geom_size[jp.array(geom1)], axis=-1)
|
||||
size2 = jp.max(m.geom_size[jp.array(geom2)], axis=-1)
|
||||
# TODO(btaba): consider re-using collision info for (sphere, sphere)
|
||||
size1 = jp.max(m.geom_size[g1.geom_id], axis=-1)
|
||||
size2 = jp.max(m.geom_size[g2.geom_id], axis=-1)
|
||||
dists = jax.vmap(jp.linalg.norm)(g2.pos - g1.pos) - (size1 + size2)
|
||||
_, idx = jax.lax.top_k(-dists, k=n_pairs)
|
||||
g1, g2, params = jax.tree_map(
|
||||
|
||||
@@ -371,15 +371,14 @@ class ConvexTest(absltest.TestCase):
|
||||
_CONVEX_CONVEX = """
|
||||
<mujoco>
|
||||
<asset>
|
||||
<mesh name="tetrahedron" file="meshes/tetrahedron.stl" scale="0.1 0.1 0.1" />
|
||||
<mesh name="dodecahedron" file="meshes/dodecahedron.stl" scale="0.01 0.01 0.01" />
|
||||
</asset>
|
||||
<worldbody>
|
||||
<body pos="0.0 2.0 0.096">
|
||||
<joint axis="1 0 0" type="free"/>
|
||||
<geom size="0.2 0.2 0.2" type="mesh" mesh="tetrahedron"/>
|
||||
<geom size="0.2 0.2 0.2" type="mesh" mesh="dodecahedron"/>
|
||||
</body>
|
||||
<body pos="0.0 2.0 0.289" euler="0.1 -0.1 45">
|
||||
<body pos="0.0 2.0 0.281" euler="0.1 -0.1 45">
|
||||
<joint axis="1 0 0" type="free"/>
|
||||
<geom size="0.1 0.1 0.1" type="mesh" mesh="dodecahedron"/>
|
||||
</body>
|
||||
@@ -388,12 +387,9 @@ class ConvexTest(absltest.TestCase):
|
||||
"""
|
||||
|
||||
def test_convex_convex(self):
|
||||
"""Tests generic convex-convex collision."""
|
||||
"""Tests generic convex-convex collision via _sat_approx."""
|
||||
directory = epath.resource_path('mujoco.mjx')
|
||||
assets = {
|
||||
'meshes/tetrahedron.stl': (
|
||||
directory / 'test_data' / 'meshes/tetrahedron.stl'
|
||||
).read_bytes(),
|
||||
'meshes/dodecahedron.stl': (
|
||||
directory / 'test_data' / 'meshes/dodecahedron.stl'
|
||||
).read_bytes(),
|
||||
@@ -492,6 +488,31 @@ class NconTest(parameterized.TestCase):
|
||||
ncon = collision_driver.ncon(m)
|
||||
self.assertEqual(ncon, 0)
|
||||
|
||||
def test_ncon_meshes(self):
|
||||
m = test_util.load_test_file('shadow_hand/scene_right.xml')
|
||||
|
||||
ncon = collision_driver.ncon(m)
|
||||
self.assertEqual(ncon, 15)
|
||||
|
||||
mx = mjx.put_model(m)
|
||||
ncon = collision_driver.ncon(mx)
|
||||
self.assertEqual(ncon, 15)
|
||||
|
||||
# get rid of max_contact_points, test only max_geom_pairs
|
||||
for i in range(m.nnumeric):
|
||||
name_ = (
|
||||
m.names[m.name_numericadr[i] :].decode('utf-8').split('\x00', 1)[0]
|
||||
)
|
||||
if name_ == 'max_contact_points':
|
||||
m.numeric_data[m.numeric_adr[i]] = -1
|
||||
|
||||
ncon = collision_driver.ncon(m)
|
||||
self.assertEqual(ncon, 307)
|
||||
|
||||
mx = mjx.put_model(m)
|
||||
ncon = collision_driver.ncon(mx)
|
||||
self.assertEqual(ncon, 307)
|
||||
|
||||
|
||||
class TopKContactTest(absltest.TestCase):
|
||||
"""Tests top-k contacts."""
|
||||
|
||||
@@ -110,7 +110,7 @@ class ModelIOTest(parameterized.TestCase):
|
||||
np.testing.assert_almost_equal(mx.geom_pos, m.geom_pos)
|
||||
self.assertLen(mx.geom_convex_face, 6)
|
||||
self.assertLen(mx.geom_convex_vert, 6)
|
||||
self.assertLen(mx.geom_convex_edge, 6)
|
||||
self.assertLen(mx.geom_convex_edge_dir, 6)
|
||||
self.assertLen(mx.geom_convex_facenormal, 6)
|
||||
|
||||
np.testing.assert_allclose(mx.jnt_type, m.jnt_type)
|
||||
|
||||
+131
-23
@@ -14,8 +14,11 @@
|
||||
# ==============================================================================
|
||||
"""Mesh processing."""
|
||||
|
||||
import collections
|
||||
import dataclasses
|
||||
import itertools
|
||||
from typing import Dict, Optional, Sequence, Tuple
|
||||
from typing import Dict, List, Optional, Sequence, Tuple
|
||||
import warnings
|
||||
|
||||
import mujoco
|
||||
# pylint: disable=g-importing-member
|
||||
@@ -44,8 +47,10 @@ _CONVEX_CACHE: Dict[Tuple[int, int], Dict[str, np.ndarray]] = {}
|
||||
_DERIVED_ARGS = [
|
||||
'geom_convex_face',
|
||||
'geom_convex_vert',
|
||||
'geom_convex_edge',
|
||||
'geom_convex_edge_dir',
|
||||
'geom_convex_facenormal',
|
||||
'geom_convex_face_edge',
|
||||
'geom_convex_face_edge_normal',
|
||||
]
|
||||
DERIVED = {(Model, d) for d in _DERIVED_ARGS}
|
||||
|
||||
@@ -73,8 +78,8 @@ def _get_face_norm(vert: np.ndarray, face: np.ndarray) -> np.ndarray:
|
||||
return face_norm
|
||||
|
||||
|
||||
def _get_unique_edges(vert: np.ndarray, face: np.ndarray) -> np.ndarray:
|
||||
"""Returns unique edges.
|
||||
def _get_unique_edge_dir(vert: np.ndarray, face: np.ndarray) -> np.ndarray:
|
||||
"""Returns unique edge directions.
|
||||
|
||||
Args:
|
||||
vert: (n_vert, 3) vertices
|
||||
@@ -109,6 +114,52 @@ def _get_unique_edges(vert: np.ndarray, face: np.ndarray) -> np.ndarray:
|
||||
return edges[unique_edge_idx]
|
||||
|
||||
|
||||
def _get_face_edge_normals(
|
||||
face: np.ndarray, face_norm: np.ndarray
|
||||
) -> Tuple[np.ndarray, np.ndarray]:
|
||||
"""Returns face edges and face edge normals."""
|
||||
# get face edges and scatter the face norms
|
||||
r_face = np.roll(face, 1, axis=1)
|
||||
face_edge = np.array([face, r_face]).transpose((1, 2, 0))
|
||||
face_edge.sort(axis=2)
|
||||
face_edge_flat = np.concatenate(face_edge)
|
||||
edge_face_idx = np.repeat(np.arange(face.shape[0]), face.shape[1])
|
||||
edge_face_norm = face_norm[edge_face_idx]
|
||||
|
||||
# get the edge normals associated with each edge
|
||||
edge_map_list = collections.defaultdict(list)
|
||||
for i in range(face_edge_flat.shape[0]):
|
||||
if face_edge_flat[i][0] == face_edge_flat[i][1]:
|
||||
continue
|
||||
edge_map_list[tuple(face_edge_flat[i])].append(edge_face_norm[i])
|
||||
|
||||
edge_map = {}
|
||||
for k, v in edge_map_list.items():
|
||||
v = np.array(v)
|
||||
if len(v) > 2:
|
||||
# Meshes can be of poor quality and contain edges adjacent to more than
|
||||
# two faces. We take the first two unique face normals.
|
||||
v = np.unique(v, axis=0)[:2]
|
||||
elif len(v) == 1:
|
||||
# Some edges are either degenerate or _MAX_HULL_FACE_VERTICES was hit
|
||||
# and face vertices were down sampled. In either case, we ignore these
|
||||
# edges.
|
||||
continue
|
||||
edge_map[k] = v
|
||||
|
||||
# for each face, list the edge normals
|
||||
face_edge_normal = []
|
||||
for face_idx in range(face_edge.shape[0]):
|
||||
normals = []
|
||||
for edge in face_edge[face_idx]:
|
||||
k = tuple(edge)
|
||||
normals.append(edge_map.get(k, np.zeros((2, 3))))
|
||||
face_edge_normal.append(np.array(normals))
|
||||
face_edge_normal = np.array(face_edge_normal)
|
||||
|
||||
return face_edge, face_edge_normal
|
||||
|
||||
|
||||
def _convex_hull_2d(points: np.ndarray, normal: np.ndarray) -> np.ndarray:
|
||||
"""Calculates the convex hull for a set of points on a plane."""
|
||||
# project points onto the closest axis plane
|
||||
@@ -128,7 +179,16 @@ def _convex_hull_2d(points: np.ndarray, normal: np.ndarray) -> np.ndarray:
|
||||
return hull_point_idx
|
||||
|
||||
|
||||
def _merge_coplanar(tm: trimesh.Trimesh) -> np.ndarray:
|
||||
@dataclasses.dataclass
|
||||
class MeshInfo:
|
||||
name: str
|
||||
vert: np.ndarray
|
||||
face: np.ndarray
|
||||
convex_vert: Optional[np.ndarray]
|
||||
convex_face: Optional[np.ndarray]
|
||||
|
||||
|
||||
def _merge_coplanar(tm: trimesh.Trimesh, mesh_info: MeshInfo) -> np.ndarray:
|
||||
"""Merges coplanar facets."""
|
||||
if not tm.facets:
|
||||
return tm.faces.copy() # no facets
|
||||
@@ -152,6 +212,13 @@ def _merge_coplanar(tm: trimesh.Trimesh) -> np.ndarray:
|
||||
face = point_idx[hull_point_idx]
|
||||
|
||||
# resize faces that exceed max polygon vertices
|
||||
if face.shape[0] > _MAX_HULL_FACE_VERTICES:
|
||||
warnings.warn(
|
||||
f'Mesh "{mesh_info.name}" has a coplanar face with more than'
|
||||
f' {_MAX_HULL_FACE_VERTICES} vertices. This may lead to performance '
|
||||
'issues and inaccuracies in collision detection. Consider '
|
||||
'decimating the mesh.'
|
||||
)
|
||||
every = face.shape[0] // _MAX_HULL_FACE_VERTICES + 1
|
||||
face = face[::every]
|
||||
facets.append(face)
|
||||
@@ -173,48 +240,82 @@ def _merge_coplanar(tm: trimesh.Trimesh) -> np.ndarray:
|
||||
return np.concatenate([faces, facets])
|
||||
|
||||
|
||||
def _get_faces_verts(
|
||||
def _mesh_info(
|
||||
m: mujoco.MjModel,
|
||||
) -> Tuple[Sequence[np.ndarray], Sequence[np.ndarray]]:
|
||||
"""Extracts mesh faces and vertices from MjModel."""
|
||||
verts, faces = [], []
|
||||
) -> List[MeshInfo]:
|
||||
"""Extracts mesh info from MjModel."""
|
||||
mesh_infos = []
|
||||
for i in range(m.nmesh):
|
||||
name = mujoco.mj_id2name(m, mujoco.mjtObj.mjOBJ_MESH.value, i)
|
||||
|
||||
last = (i + 1) >= m.nmesh
|
||||
face_start = m.mesh_faceadr[i]
|
||||
face_end = m.mesh_faceadr[i + 1] if not last else m.mesh_face.shape[0]
|
||||
face = m.mesh_face[face_start:face_end]
|
||||
faces.append(face)
|
||||
|
||||
vert_start = m.mesh_vertadr[i]
|
||||
vert_end = m.mesh_vertadr[i + 1] if not last else m.mesh_vert.shape[0]
|
||||
vert = m.mesh_vert[vert_start:vert_end]
|
||||
verts.append(vert)
|
||||
return verts, faces
|
||||
|
||||
graphadr = m.mesh_graphadr[i]
|
||||
if graphadr < 0:
|
||||
mesh_infos.append(MeshInfo(name, vert, face, None, None))
|
||||
continue
|
||||
|
||||
graph = m.mesh_graph[graphadr:]
|
||||
numvert, numface = graph[0], graph[1]
|
||||
|
||||
# unused vert_edgeadr
|
||||
# vert_edgeadr = graph[2 : numvert + 2]
|
||||
last_idx = numvert + 2
|
||||
|
||||
vert_globalid = graph[last_idx : last_idx + numvert]
|
||||
last_idx += numvert
|
||||
|
||||
# unused edge_localid
|
||||
# edge_localid = graph[last_idx : last_idx + numvert + 3 * numface]
|
||||
last_idx += numvert + 3 * numface
|
||||
|
||||
face_globalid = graph[last_idx : last_idx + 3 * numface]
|
||||
face_globalid = face_globalid.reshape((numface, 3))
|
||||
|
||||
convex_vert = vert[vert_globalid]
|
||||
vertex_map = dict(zip(vert_globalid, np.arange(vert_globalid.shape[0])))
|
||||
convex_face = np.vectorize(vertex_map.get)(face_globalid)
|
||||
mesh_infos.append(MeshInfo(name, vert, face, convex_vert, convex_face))
|
||||
|
||||
return mesh_infos
|
||||
|
||||
|
||||
def _geom_mesh_kwargs(
|
||||
vert: np.ndarray, face: np.ndarray
|
||||
mesh_info: MeshInfo,
|
||||
) -> Dict[str, np.ndarray]:
|
||||
"""Generates convex mesh attributes for mjx.Model."""
|
||||
tm = trimesh.Trimesh(vertices=vert, faces=face)
|
||||
tm_convex = trimesh.convex.convex_hull(tm)
|
||||
tm_convex = trimesh.Trimesh(
|
||||
vertices=mesh_info.convex_vert, faces=mesh_info.convex_face
|
||||
)
|
||||
vert = np.array(tm_convex.vertices)
|
||||
face = _merge_coplanar(tm_convex)
|
||||
face = _merge_coplanar(tm_convex, mesh_info)
|
||||
facenormal = _get_face_norm(vert, face)
|
||||
face_edge, face_edge_normal = _get_face_edge_normals(face, facenormal)
|
||||
return {
|
||||
'geom_convex_face': vert[face],
|
||||
'geom_convex_face_vert_idx': face,
|
||||
'geom_convex_vert': vert,
|
||||
'geom_convex_edge': _get_unique_edges(vert, face),
|
||||
'geom_convex_facenormal': _get_face_norm(vert, face),
|
||||
'geom_convex_edge_dir': _get_unique_edge_dir(vert, face),
|
||||
'geom_convex_facenormal': facenormal,
|
||||
'geom_convex_face_edge': face_edge,
|
||||
'geom_convex_face_edge_normal': face_edge_normal,
|
||||
}
|
||||
|
||||
|
||||
def get(m: mujoco.MjModel) -> Dict[str, Sequence[Optional[np.ndarray]]]:
|
||||
"""Derives geom mesh attributes for mjx.Model from MjModel."""
|
||||
kwargs = {k: [] for k in _DERIVED_ARGS}
|
||||
verts, faces = _get_faces_verts(m)
|
||||
mesh_infos = _mesh_info(m)
|
||||
geom_con = m.geom_conaffinity | m.geom_contype
|
||||
for geomid in range(m.ngeom):
|
||||
mesh_info = None
|
||||
dataid = m.geom_dataid[geomid]
|
||||
if not geom_con[geomid]:
|
||||
# ignore visual-only meshes
|
||||
@@ -222,15 +323,22 @@ def get(m: mujoco.MjModel) -> Dict[str, Sequence[Optional[np.ndarray]]]:
|
||||
continue
|
||||
elif m.geom_type[geomid] == GeomType.BOX:
|
||||
vert, face = _box(m.geom_size[geomid])
|
||||
elif dataid >= 0:
|
||||
vert, face = verts[dataid], faces[dataid]
|
||||
else:
|
||||
mesh_info = MeshInfo(
|
||||
name='box',
|
||||
vert=vert,
|
||||
face=face,
|
||||
convex_vert=vert,
|
||||
convex_face=face,
|
||||
)
|
||||
elif dataid < 0:
|
||||
kwargs = {k: kwargs[k] + [None] for k in _DERIVED_ARGS}
|
||||
continue
|
||||
|
||||
mesh_info = mesh_info or mesh_infos[dataid]
|
||||
vert, face = mesh_info.vert, mesh_info.face
|
||||
key = (hash(vert.data.tobytes()), hash(face.data.tobytes()))
|
||||
if key not in _CONVEX_CACHE:
|
||||
_CONVEX_CACHE[key] = _geom_mesh_kwargs(vert, face)
|
||||
_CONVEX_CACHE[key] = _geom_mesh_kwargs(mesh_info)
|
||||
|
||||
kwargs = {k: kwargs[k] + [_CONVEX_CACHE[key][k]] for k in _DERIVED_ARGS}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
from absl.testing import absltest
|
||||
from mujoco.mjx._src import mesh
|
||||
import numpy as np
|
||||
import trimesh
|
||||
|
||||
|
||||
class GeomMeshKwargsTest(absltest.TestCase):
|
||||
@@ -33,7 +34,18 @@ class GeomMeshKwargsTest(absltest.TestCase):
|
||||
face = np.array(
|
||||
[[0, 1, 2], [0, 3, 1], [0, 4, 3], [0, 2, 4], [2, 1, 4], [1, 3, 4]]
|
||||
)
|
||||
h = mesh._geom_mesh_kwargs(vert, face)
|
||||
tm = trimesh.Trimesh(vertices=vert, faces=face)
|
||||
tm_convex = trimesh.convex.convex_hull(tm)
|
||||
convex_vert = np.array(tm_convex.vertices)
|
||||
convex_face = np.array(tm_convex.faces)
|
||||
mesh_info = mesh.MeshInfo(
|
||||
name='test',
|
||||
vert=vert,
|
||||
face=face,
|
||||
convex_vert=convex_vert,
|
||||
convex_face=convex_face,
|
||||
)
|
||||
h = mesh._geom_mesh_kwargs(mesh_info)
|
||||
|
||||
# get index of vertices in h['geom_convex_vert'] for vertices in vert
|
||||
dist = np.repeat(vert, vert.shape[0], axis=0) - np.tile(
|
||||
@@ -57,7 +69,7 @@ class GeomMeshKwargsTest(absltest.TestCase):
|
||||
)
|
||||
|
||||
# check edges
|
||||
unique_edge = np.vectorize(map_.get)(h['geom_convex_edge'])
|
||||
unique_edge = np.vectorize(map_.get)(h['geom_convex_edge_dir'])
|
||||
unique_edge = np.array(sorted(unique_edge.tolist()))
|
||||
np.testing.assert_array_equal(
|
||||
unique_edge,
|
||||
@@ -67,6 +79,51 @@ class GeomMeshKwargsTest(absltest.TestCase):
|
||||
# face normals
|
||||
self.assertEqual(h['geom_convex_facenormal'].shape, (5, 3))
|
||||
|
||||
# face edges
|
||||
edges = np.concatenate(h['geom_convex_face_edge'])
|
||||
edges = np.vectorize(map_.get)(edges)
|
||||
mask = edges[:, 0] != edges[:, 1]
|
||||
edges = edges[mask]
|
||||
sort_col_idx = np.argsort(edges, axis=1)
|
||||
edges = np.take_along_axis(edges, sort_col_idx, axis=1)
|
||||
sort_row_idx = np.lexsort((edges[:, 1], edges[:, 0]))
|
||||
edges = edges[sort_row_idx]
|
||||
np.testing.assert_array_equal(
|
||||
edges,
|
||||
np.array([
|
||||
[0, 2],
|
||||
[0, 2],
|
||||
[0, 3],
|
||||
[0, 3],
|
||||
[0, 4],
|
||||
[0, 4],
|
||||
[1, 2],
|
||||
[1, 2],
|
||||
[1, 3],
|
||||
[1, 3],
|
||||
[1, 4],
|
||||
[1, 4],
|
||||
[2, 4],
|
||||
[2, 4],
|
||||
[3, 4],
|
||||
[3, 4],
|
||||
]),
|
||||
)
|
||||
|
||||
# face edge normals
|
||||
edge_normal = h['geom_convex_face_edge_normal']
|
||||
edge_normal = np.concatenate(edge_normal)
|
||||
edge_normal = edge_normal[mask]
|
||||
edge_normal = np.take_along_axis(
|
||||
edge_normal, sort_col_idx[..., None], axis=1
|
||||
)
|
||||
edge_normal = edge_normal[sort_row_idx]
|
||||
edge_normal_02 = np.array([[0.4472136, -0.0, 0.89442719], [-1.0, 0.0, 0.0]])
|
||||
np.testing.assert_array_almost_equal(
|
||||
edge_normal[:2],
|
||||
np.array([edge_normal_02, edge_normal_02]),
|
||||
)
|
||||
|
||||
|
||||
class ConvexHull2DTest(absltest.TestCase):
|
||||
|
||||
@@ -109,7 +166,7 @@ class UniqueEdgesTest(absltest.TestCase):
|
||||
[[-0.1, 0.0, -0.1], [0.0, 0.1, 0.1], [0.1, 0.0, -0.1], [0.0, -0.1, 0.1]]
|
||||
)
|
||||
face = np.array([[0, 1, 2], [0, 2, 3], [0, 3, 1], [2, 1, 3]])
|
||||
idx = mesh._get_unique_edges(vert, face)
|
||||
idx = mesh._get_unique_edge_dir(vert, face)
|
||||
np.testing.assert_array_equal(
|
||||
idx, np.array([[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3]])
|
||||
)
|
||||
|
||||
@@ -394,8 +394,10 @@ class Model(PyTreeNode):
|
||||
mesh_face: vertex face data (nmeshface, 3)
|
||||
geom_convex_face: vertex face data, MJX only (ngeom,)
|
||||
geom_convex_vert: vertex data, MJX only (ngeom,)
|
||||
geom_convex_edge: unique edge data, MJX only (ngeom,)
|
||||
geom_convex_edge_dir: unique edge direction, MJX only (ngeom,)
|
||||
geom_convex_facenormal: normal face data, MJX only (ngeom,)
|
||||
geom_convex_face_edge: edges for each face (ngeom,)
|
||||
geom_convex_face_edge_normal: edge normals for each face (ngeom,)
|
||||
pair_dim: contact dimensionality (npair,)
|
||||
pair_geom1: id of geom1 (npair,)
|
||||
pair_geom2: id of geom2 (npair,)
|
||||
@@ -539,8 +541,10 @@ class Model(PyTreeNode):
|
||||
pair_geom2: np.ndarray
|
||||
geom_convex_face: List[Optional[jax.Array]]
|
||||
geom_convex_vert: List[Optional[jax.Array]]
|
||||
geom_convex_edge: List[Optional[jax.Array]]
|
||||
geom_convex_edge_dir: List[Optional[jax.Array]]
|
||||
geom_convex_facenormal: List[Optional[jax.Array]]
|
||||
geom_convex_face_edge: List[Optional[jax.Array]]
|
||||
geom_convex_face_edge_normal: List[Optional[jax.Array]]
|
||||
pair_solref: jax.Array
|
||||
pair_solreffriction: jax.Array
|
||||
pair_solimp: jax.Array
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
# https://github.com/mikedh/trimesh
|
||||
v 6.50390586 0.64350290 9.24793311
|
||||
v 5.30661760 -0.31024313 9.73751972
|
||||
v 4.94829845 3.15382325 9.11860457
|
||||
v 6.33721589 4.12708360 8.09188972
|
||||
v 4.89932115 1.87489667 8.51418670
|
||||
v 4.70769594 6.29147507 5.98527472
|
||||
v -3.34707744 6.97764180 19.92969765
|
||||
v 3.37781185 6.95828391 19.93485428
|
||||
v 3.86552899 6.99640846 4.70996617
|
||||
v -3.86635821 6.99562104 4.70428710
|
||||
v 7.87627282 -4.43532761 11.05140915
|
||||
v 5.15111169 -2.50918618 29.19889183
|
||||
v 5.21323338 -5.31973481 21.52245554
|
||||
v 7.23443379 -5.57507698 7.08196652
|
||||
v 3.55631959 -7.25167066 13.72878573
|
||||
v 3.07783375 -6.06397783 22.26222936
|
||||
v 6.79721197 0.78554179 28.07666368
|
||||
v 7.34199933 -2.39018025 21.29567778
|
||||
v 8.46922360 -2.75276442 10.83537109
|
||||
v 5.70007033 -6.65083634 12.11134503
|
||||
v 6.65417384 -4.34381538 20.30772765
|
||||
v 2.40619519 -4.04642312 29.14441797
|
||||
v -3.14414705 -7.26638904 13.85199291
|
||||
v -2.63835983 -6.00383154 22.58289427
|
||||
v -3.00959200 -4.30725036 28.45014698
|
||||
v -8.67648316 -1.39026155 6.96999792
|
||||
v -4.06466345 -7.30215421 7.07949375
|
||||
v -6.28665038 -6.40156394 7.07810144
|
||||
v -6.54392136 -6.18002794 10.50626076
|
||||
v -8.30555380 -3.95818407 7.11847429
|
||||
v 3.99831418 -7.31980111 7.07815211
|
||||
v 8.55120535 -1.75383243 6.99438213
|
||||
v 8.56780262 0.94971194 7.19002689
|
||||
v 8.43784203 -0.11071443 11.68166065
|
||||
v 5.95701350 -2.93345893 8.95484023
|
||||
v 5.48357694 -5.40361293 7.09865875
|
||||
v 2.51696542 -1.82616354 31.30618217
|
||||
v 5.23352468 -0.09383099 30.37120968
|
||||
v 2.54165736 -0.08445142 31.87502021
|
||||
v -2.07591824 -2.80877817 30.61779698
|
||||
v -2.68516470 -0.30738259 31.87551034
|
||||
v -5.88866912 -2.35051278 27.97823005
|
||||
v -5.61433545 -0.28582589 30.06682621
|
||||
v -6.88747168 -0.60927090 26.91366651
|
||||
v -4.52763552 -2.99362370 29.28895007
|
||||
v -5.12471544 -6.68446634 14.74402446
|
||||
v -7.19221015 -4.66949421 16.33102903
|
||||
v -8.04709503 -2.58759069 16.47269914
|
||||
v -5.09882985 -6.05347300 19.08687005
|
||||
v -8.46397644 -0.09998855 12.28854257
|
||||
v -5.96767427 -3.59595155 8.64092197
|
||||
v -5.81328100 -5.28758722 7.07790139
|
||||
v -6.27336927 1.27569430 9.39815533
|
||||
v 5.02206109 4.76332516 29.37171859
|
||||
v 4.15314519 5.23168516 21.95940541
|
||||
v 5.17313368 5.59165002 26.74885525
|
||||
v 8.06735094 4.13382332 5.94027701
|
||||
v 1.84103550 0.11588066 9.57800292
|
||||
v -5.10987421 -1.53844923 9.52260374
|
||||
v -4.00466120 5.32536649 21.27395753
|
||||
v -5.14488595 5.62968259 26.54913621
|
||||
v -4.93618877 4.82608602 29.39774940
|
||||
v 0.13861477 5.60451047 30.84654114
|
||||
v 5.17690385 4.16565385 29.34090177
|
||||
v -2.33512516 5.71259775 31.51929105
|
||||
v 3.02924934 5.52483519 31.19856340
|
||||
v 0.33599790 6.02786602 31.69972194
|
||||
v 8.40308173 2.37008374 12.43632136
|
||||
v -6.67781474 2.44604496 28.40686319
|
||||
v -7.92231657 2.23071969 18.08938295
|
||||
v -8.67465140 2.37217143 6.62526268
|
||||
v -8.12663140 4.10835071 5.90795101
|
||||
v -6.58245892 3.80002568 8.12575754
|
||||
v 1.92380381 5.90630332 19.63364340
|
||||
v -1.94537491 5.85504527 19.75550715
|
||||
v 6.24046765 4.00344013 27.68537316
|
||||
v -4.76083302 0.19773252 9.56727057
|
||||
v -4.75478027 3.99592541 8.50326258
|
||||
v -4.75356295 5.97374027 6.11144414
|
||||
v -6.19869154 4.20600270 27.39431646
|
||||
v -5.20052903 4.17754391 29.34088490
|
||||
v 0.01018935 4.18887797 30.19189119
|
||||
v 7.34531069 4.04378806 18.31192060
|
||||
v 2.64767041 4.15626461 31.19604383
|
||||
v 2.91940624 1.81677046 31.93396132
|
||||
v 5.78947517 2.14245583 29.91917121
|
||||
v -1.87245751 4.05085463 31.50866574
|
||||
v -4.49162410 2.16490945 31.16573852
|
||||
v -7.42203042 4.05853555 17.84804552
|
||||
v 7.29060088 5.35550655 5.82194916
|
||||
v 6.19559178 6.24745900 5.92513728
|
||||
v -3.40716650 6.29775703 6.47339043
|
||||
v 3.31059343 6.36237014 6.86894461
|
||||
v -6.35243334 6.14850129 6.12121855
|
||||
v -6.78456749 5.15932446 15.32211577
|
||||
v -4.05237357 5.34508952 18.89352070
|
||||
v -4.05927069 5.50641088 11.20495569
|
||||
v -4.23292864 4.98830910 8.61492912
|
||||
v -3.66587380 6.49086575 7.52061777
|
||||
v 4.13979503 5.18578137 8.51712956
|
||||
v 4.05000000 4.33266300 8.79873500
|
||||
v 4.06449151 5.74468123 11.12190375
|
||||
v 4.05098944 5.34622564 18.89279302
|
||||
v 2.60006316 5.62573869 11.89468890
|
||||
v 2.88494951 6.45245533 7.52121533
|
||||
v -2.33032286 6.21437152 7.68457736
|
||||
v -1.89095108 5.87781213 8.80650730
|
||||
v -2.41607436 5.75672005 11.75714274
|
||||
v -3.08457284 6.61379088 11.24407430
|
||||
v 2.84135055 6.51445336 11.33588395
|
||||
v 1.87137543 5.90276285 8.81500425
|
||||
f 1 2 3
|
||||
f 1 3 4
|
||||
f 3 5 6
|
||||
f 8 9 7
|
||||
f 7 9 10
|
||||
f 18 19 17
|
||||
f 12 22 16
|
||||
f 13 12 16
|
||||
f 13 20 21
|
||||
f 21 12 13
|
||||
f 14 11 20
|
||||
f 20 11 21
|
||||
f 20 13 15
|
||||
f 16 15 13
|
||||
f 21 11 18
|
||||
f 18 11 19
|
||||
f 21 18 12
|
||||
f 12 18 17
|
||||
f 23 15 16
|
||||
f 23 16 24
|
||||
f 24 16 25
|
||||
f 25 16 22
|
||||
f 27 29 28
|
||||
f 29 30 28
|
||||
f 27 31 23
|
||||
f 23 31 15
|
||||
f 32 19 14
|
||||
f 14 19 11
|
||||
f 14 20 31
|
||||
f 15 31 20
|
||||
f 33 34 19
|
||||
f 33 19 32
|
||||
f 1 33 35
|
||||
f 35 33 32
|
||||
f 35 32 36
|
||||
f 35 2 1
|
||||
f 19 34 17
|
||||
f 12 38 37
|
||||
f 37 38 39
|
||||
f 37 22 12
|
||||
f 17 38 12
|
||||
f 25 22 40
|
||||
f 40 22 37
|
||||
f 39 41 37
|
||||
f 37 41 40
|
||||
f 25 40 45
|
||||
f 43 44 42
|
||||
f 42 45 43
|
||||
f 41 43 45
|
||||
f 41 45 40
|
||||
f 27 23 46
|
||||
f 30 48 26
|
||||
f 29 27 46
|
||||
f 49 23 24
|
||||
f 49 46 23
|
||||
f 47 29 49
|
||||
f 24 25 49
|
||||
f 46 49 29
|
||||
f 29 47 30
|
||||
f 47 42 44
|
||||
f 47 44 48
|
||||
f 25 45 49
|
||||
f 49 45 42
|
||||
f 49 42 47
|
||||
f 47 48 30
|
||||
f 50 48 44
|
||||
f 50 26 48
|
||||
f 26 51 52
|
||||
f 51 26 53
|
||||
f 36 14 31
|
||||
f 28 52 27
|
||||
f 36 31 52
|
||||
f 52 31 27
|
||||
f 14 36 32
|
||||
f 30 26 52
|
||||
f 30 52 28
|
||||
f 54 56 55
|
||||
f 33 1 4
|
||||
f 33 4 57
|
||||
f 59 58 2
|
||||
f 36 52 51
|
||||
f 36 51 35
|
||||
f 35 51 59
|
||||
f 35 59 2
|
||||
f 5 3 2
|
||||
f 51 53 59
|
||||
f 65 63 66
|
||||
f 66 63 64
|
||||
f 68 17 34
|
||||
f 70 71 50
|
||||
f 70 50 44
|
||||
f 70 44 69
|
||||
f 53 26 71
|
||||
f 72 73 71
|
||||
f 71 73 53
|
||||
f 74 67 66
|
||||
f 54 55 66
|
||||
f 66 55 74
|
||||
f 67 74 75
|
||||
f 67 75 65
|
||||
f 65 75 60
|
||||
f 60 62 65
|
||||
f 68 34 33
|
||||
f 77 58 59
|
||||
f 58 77 79
|
||||
f 2 58 5
|
||||
f 5 58 79
|
||||
f 5 79 6
|
||||
f 59 53 77
|
||||
f 77 53 78
|
||||
f 53 73 78
|
||||
f 60 61 62
|
||||
f 65 62 81
|
||||
f 63 65 81
|
||||
f 82 63 81
|
||||
f 63 82 64
|
||||
f 67 65 66
|
||||
f 66 64 54
|
||||
f 54 64 76
|
||||
f 17 68 83
|
||||
f 17 83 76
|
||||
f 68 57 83
|
||||
f 84 86 64
|
||||
f 84 85 86
|
||||
f 86 76 64
|
||||
f 86 17 76
|
||||
f 38 86 85
|
||||
f 38 85 39
|
||||
f 17 86 38
|
||||
f 41 39 85
|
||||
f 41 85 87
|
||||
f 87 85 84
|
||||
f 81 88 87
|
||||
f 41 87 88
|
||||
f 41 88 43
|
||||
f 69 44 43
|
||||
f 88 81 69
|
||||
f 88 69 43
|
||||
f 69 81 80
|
||||
f 69 80 89
|
||||
f 69 89 70
|
||||
f 71 70 89
|
||||
f 71 89 72
|
||||
f 26 50 71
|
||||
f 60 7 61
|
||||
f 55 56 8
|
||||
f 54 76 56
|
||||
f 90 57 4
|
||||
f 57 68 33
|
||||
f 4 6 91
|
||||
f 4 91 90
|
||||
f 6 4 3
|
||||
f 76 83 56
|
||||
f 8 91 9
|
||||
f 91 8 56
|
||||
f 91 56 90
|
||||
f 90 56 83
|
||||
f 90 83 57
|
||||
f 79 77 78
|
||||
f 92 6 93
|
||||
f 93 6 79
|
||||
f 93 79 92
|
||||
f 73 72 94
|
||||
f 73 94 78
|
||||
f 92 10 6
|
||||
f 6 10 9
|
||||
f 10 92 94
|
||||
f 79 78 94
|
||||
f 92 79 94
|
||||
f 91 6 9
|
||||
f 7 60 75
|
||||
f 74 8 7
|
||||
f 74 7 75
|
||||
f 55 8 74
|
||||
f 62 61 80
|
||||
f 80 81 62
|
||||
f 84 82 87
|
||||
f 82 84 64
|
||||
f 82 81 87
|
||||
f 7 10 94
|
||||
f 80 61 95
|
||||
f 94 72 95
|
||||
f 95 72 89
|
||||
f 89 80 95
|
||||
f 95 61 94
|
||||
f 94 61 7
|
||||
f 97 96 98
|
||||
f 97 98 99
|
||||
f 102 100 103
|
||||
f 102 103 104
|
||||
f 105 101 100
|
||||
f 101 105 98
|
||||
f 98 105 106
|
||||
f 98 106 99
|
||||
f 107 108 106
|
||||
f 106 108 109
|
||||
f 106 109 99
|
||||
f 97 99 109
|
||||
f 100 102 105
|
||||
f 105 102 110
|
||||
f 104 111 105
|
||||
f 104 105 110
|
||||
f 96 97 108
|
||||
f 102 104 110
|
||||
f 105 111 106
|
||||
f 111 107 106
|
||||
f 108 97 109
|
||||
f 101 98 107
|
||||
f 107 111 101
|
||||
f 101 111 100
|
||||
f 108 98 96
|
||||
f 98 108 107
|
||||
f 104 103 100
|
||||
f 100 111 104
|
||||
@@ -0,0 +1,288 @@
|
||||
# https://github.com/mikedh/trimesh
|
||||
v -3.87874325 8.97064419 20.09696708
|
||||
v 3.33570154 8.98693768 20.00768455
|
||||
v 3.97136533 8.99677039 3.41064184
|
||||
v -4.40959718 8.98399823 3.59941665
|
||||
v 5.04763382 5.62557226 9.36068667
|
||||
v -5.02436772 5.66797804 9.33833176
|
||||
v 8.42356732 7.11834272 5.65561215
|
||||
v 7.16907807 4.98106463 9.48168643
|
||||
v 6.93022084 7.11209326 7.67445153
|
||||
v 6.00313716 8.54341758 5.23598706
|
||||
v -7.17607178 4.97689550 9.48109597
|
||||
v -7.53652139 7.70583276 6.21388495
|
||||
v -6.33457230 8.38034856 5.54912333
|
||||
v -6.36194547 7.11256003 7.72303997
|
||||
v -7.41801011 -7.02747341 9.48715299
|
||||
v -9.00250133 -6.21476019 10.39855164
|
||||
v -10.56978774 -2.49716127 10.09559906
|
||||
v -10.62194494 2.32975955 10.22490268
|
||||
v -10.00924604 1.94777627 9.50486470
|
||||
v -9.28319438 -4.73784570 9.49027691
|
||||
v 7.15147452 -7.02207443 9.48858316
|
||||
v -9.34805732 1.62432463 28.12775447
|
||||
v -8.41997159 3.26874043 30.22096769
|
||||
v -8.38566584 0.49128714 30.36535708
|
||||
v -9.99533054 -1.50299841 20.01519589
|
||||
v -10.12283126 3.11061820 17.96099483
|
||||
v -5.02698431 -8.76602969 11.28385625
|
||||
v 0.11482147 2.98923697 34.94012405
|
||||
v 3.25105044 2.19295301 34.54920903
|
||||
v 5.77625527 2.34301591 33.22697054
|
||||
v 7.01215516 0.22222499 31.98819736
|
||||
v 8.68817366 2.25705075 30.03040785
|
||||
v -5.94953357 1.96627038 33.13643548
|
||||
v -3.10200136 1.12817921 34.55661511
|
||||
v -6.94802143 -2.98867327 29.90330874
|
||||
v -3.47954158 -4.98943428 29.95337100
|
||||
v -5.59633128 -1.82989710 32.29260356
|
||||
v -8.67271027 -1.94975485 27.24043087
|
||||
v 7.14653336 -7.76840294 10.18077314
|
||||
v 10.51833151 -2.80994991 10.10868653
|
||||
v 10.54668917 3.02343530 9.94316907
|
||||
v 9.44180322 -4.52842458 9.49224422
|
||||
v 9.97350136 4.97225815 9.53086440
|
||||
v 9.38518238 2.54311294 28.25864799
|
||||
v -3.66496455 -8.34115937 17.12281688
|
||||
v -3.21069565 -6.86293492 24.40581797
|
||||
v -7.62899787 -6.84366752 15.55638216
|
||||
v -9.34593926 -4.80200843 15.86489224
|
||||
v -5.95568387 -5.51044178 25.65788219
|
||||
v -7.53474573 -5.29387205 22.74512105
|
||||
v -5.18778033 -7.66186405 19.12469359
|
||||
v 3.78124546 -8.84686073 11.50506732
|
||||
v 3.09549482 -2.18508034 33.34215978
|
||||
v 0.78768397 -0.02478223 34.72533610
|
||||
v 2.68990799 -4.86100483 30.49338459
|
||||
v -1.41877649 -2.77285241 33.25112683
|
||||
v 6.19646739 -2.41296064 31.29605596
|
||||
v 4.73049801 -4.38124345 30.16120258
|
||||
v 9.75367743 -4.99253587 10.49735095
|
||||
v 10.10181125 -1.60374544 18.71064990
|
||||
v 10.13395072 3.11384449 17.82424622
|
||||
v 7.29850531 -2.94041893 29.32535499
|
||||
v 8.74400152 -0.63822020 28.98680478
|
||||
v 3.18013135 -6.80211841 24.62651460
|
||||
v 4.32813053 -8.27393555 17.35176564
|
||||
v 8.80732741 -4.05818076 21.89903631
|
||||
v 8.24643786 -6.12345471 16.96933119
|
||||
v 5.84426791 -6.26782767 23.59689283
|
||||
v 7.25462912 5.25085258 30.81490723
|
||||
v -7.00354422 5.23761054 31.02656238
|
||||
v 8.53569245 4.96773714 28.52913924
|
||||
v -1.81751594 5.12508889 34.07456292
|
||||
v -5.53904331 5.06936882 32.51863349
|
||||
v 3.49520113 5.05850645 33.65201689
|
||||
v -8.80327393 4.92438086 27.71651001
|
||||
v -9.86364913 4.99133321 9.63484711
|
||||
v 9.92455642 4.99944390 5.81479466
|
||||
v -10.02391542 5.04284006 5.60395859
|
||||
v 0.45968813 7.52595152 19.96784167
|
||||
v 4.00318133 7.10315704 20.34590709
|
||||
v 5.55947365 8.34241193 21.24125621
|
||||
v 6.52790384 6.33851034 22.92923670
|
||||
v 7.37650233 7.00282503 24.49933456
|
||||
v -6.71762637 7.61275679 22.70424477
|
||||
v -6.27464471 6.43274232 22.14945120
|
||||
v -3.25255024 7.24845029 20.11592765
|
||||
v 1.91096432 7.43638768 34.84101849
|
||||
v 5.17432765 6.75352949 33.87613774
|
||||
v 7.52432026 5.93372585 30.18058739
|
||||
v -3.24835546 7.28503241 34.62798014
|
||||
v -7.16568535 5.90046745 31.83913660
|
||||
v -7.54060967 6.36598267 28.22611851
|
||||
v -8.67169909 6.20329766 15.65355212
|
||||
v 4.56589868 6.56222219 34.44826307
|
||||
v -4.37970367 6.60081864 34.41987413
|
||||
v 3.35922860 6.54988825 33.55138553
|
||||
v -1.58205973 6.84531224 34.12029778
|
||||
f 2 3 1
|
||||
f 1 3 4
|
||||
f 10 7 9
|
||||
f 14 6 11
|
||||
f 11 12 14
|
||||
f 14 12 13
|
||||
f 6 14 5
|
||||
f 4 3 10
|
||||
f 8 5 9
|
||||
f 9 5 14
|
||||
f 9 14 10
|
||||
f 10 14 13
|
||||
f 10 13 4
|
||||
f 17 18 19
|
||||
f 19 20 17
|
||||
f 25 26 17
|
||||
f 17 26 18
|
||||
f 22 26 25
|
||||
f 17 20 16
|
||||
f 16 20 15
|
||||
f 16 15 27
|
||||
f 24 23 22
|
||||
f 22 38 24
|
||||
f 24 35 37
|
||||
f 24 38 35
|
||||
f 36 37 35
|
||||
f 39 27 15
|
||||
f 15 21 39
|
||||
f 42 43 41
|
||||
f 42 41 40
|
||||
f 16 27 47
|
||||
f 16 47 48
|
||||
f 16 48 17
|
||||
f 22 25 38
|
||||
f 49 35 50
|
||||
f 50 35 38
|
||||
f 46 36 49
|
||||
f 49 36 35
|
||||
f 50 48 47
|
||||
f 50 47 51
|
||||
f 25 17 48
|
||||
f 47 27 51
|
||||
f 51 27 45
|
||||
f 49 50 51
|
||||
f 49 51 46
|
||||
f 38 25 48
|
||||
f 38 48 50
|
||||
f 46 51 45
|
||||
f 52 27 39
|
||||
f 53 56 55
|
||||
f 54 29 28
|
||||
f 53 58 57
|
||||
f 29 54 53
|
||||
f 29 53 31
|
||||
f 31 53 57
|
||||
f 54 56 53
|
||||
f 55 58 53
|
||||
f 30 29 31
|
||||
f 24 37 33
|
||||
f 54 28 34
|
||||
f 36 56 37
|
||||
f 55 56 36
|
||||
f 56 54 34
|
||||
f 34 33 37
|
||||
f 34 37 56
|
||||
f 40 59 42
|
||||
f 42 59 39
|
||||
f 42 39 21
|
||||
f 40 41 60
|
||||
f 60 41 61
|
||||
f 61 44 60
|
||||
f 31 57 62
|
||||
f 58 62 57
|
||||
f 62 63 31
|
||||
f 32 31 63
|
||||
f 44 32 63
|
||||
f 64 55 36
|
||||
f 64 36 46
|
||||
f 64 46 65
|
||||
f 65 46 45
|
||||
f 27 52 45
|
||||
f 45 52 65
|
||||
f 62 66 63
|
||||
f 59 67 39
|
||||
f 39 65 52
|
||||
f 62 58 68
|
||||
f 68 58 55
|
||||
f 67 66 68
|
||||
f 65 68 64
|
||||
f 60 44 63
|
||||
f 60 63 66
|
||||
f 66 62 68
|
||||
f 64 68 55
|
||||
f 40 60 59
|
||||
f 59 60 66
|
||||
f 59 66 67
|
||||
f 39 67 65
|
||||
f 65 67 68
|
||||
f 30 32 69
|
||||
f 31 32 30
|
||||
f 29 74 28
|
||||
f 28 74 72
|
||||
f 30 69 74
|
||||
f 30 74 29
|
||||
f 28 72 34
|
||||
f 34 72 73
|
||||
f 34 73 33
|
||||
f 24 33 23
|
||||
f 23 33 73
|
||||
f 23 73 70
|
||||
f 75 23 70
|
||||
f 75 22 23
|
||||
f 76 18 26
|
||||
f 76 26 75
|
||||
f 26 22 75
|
||||
f 8 43 42
|
||||
f 8 42 21
|
||||
f 15 8 21
|
||||
f 11 19 76
|
||||
f 11 5 8
|
||||
f 11 6 5
|
||||
f 11 8 15
|
||||
f 11 15 20
|
||||
f 11 20 19
|
||||
f 76 19 18
|
||||
f 43 61 41
|
||||
f 44 61 43
|
||||
f 44 43 71
|
||||
f 32 44 71
|
||||
f 32 71 69
|
||||
f 77 43 8
|
||||
f 76 78 11
|
||||
f 79 80 2
|
||||
f 2 80 81
|
||||
f 81 80 82
|
||||
f 81 82 83
|
||||
f 84 85 1
|
||||
f 1 85 86
|
||||
f 79 87 80
|
||||
f 89 82 88
|
||||
f 87 88 80
|
||||
f 80 88 82
|
||||
f 87 79 90
|
||||
f 90 79 86
|
||||
f 85 91 90
|
||||
f 90 86 85
|
||||
f 89 69 71
|
||||
f 83 82 89
|
||||
f 7 77 9
|
||||
f 77 8 9
|
||||
f 1 86 79
|
||||
f 1 79 2
|
||||
f 10 81 7
|
||||
f 43 77 7
|
||||
f 83 89 71
|
||||
f 83 71 43
|
||||
f 3 2 10
|
||||
f 10 2 81
|
||||
f 7 81 83
|
||||
f 83 43 7
|
||||
f 92 85 84
|
||||
f 70 92 75
|
||||
f 78 12 11
|
||||
f 93 92 84
|
||||
f 93 76 75
|
||||
f 76 93 78
|
||||
f 93 12 78
|
||||
f 1 4 13
|
||||
f 1 13 12
|
||||
f 1 12 84
|
||||
f 84 12 93
|
||||
f 93 75 92
|
||||
f 94 88 87
|
||||
f 91 95 90
|
||||
f 94 89 88
|
||||
f 69 89 94
|
||||
f 96 94 97
|
||||
f 97 94 95
|
||||
f 97 95 91
|
||||
f 90 95 87
|
||||
f 95 94 87
|
||||
f 72 97 73
|
||||
f 73 97 70
|
||||
f 74 97 72
|
||||
f 74 96 97
|
||||
f 74 69 96
|
||||
f 85 92 91
|
||||
f 70 91 92
|
||||
f 91 70 97
|
||||
f 94 96 69
|
||||
@@ -1,7 +1,7 @@
|
||||
<mujoco model="right_shadow_hand">
|
||||
<compiler angle="radian" meshdir="assets" autolimits="true"/>
|
||||
|
||||
<option impratio="10" iterations="1" ls_iterations="4">
|
||||
<option impratio="10" iterations="1" ls_iterations="4" timestep="0.001">
|
||||
<flag eulerdamp="disable"/>
|
||||
</option>
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
<geom type="mesh" material="black" contype="0" conaffinity="0" group="2"/>
|
||||
</default>
|
||||
<default class="plastic_collision">
|
||||
<geom group="3" contype="0" conaffinity="1"/>
|
||||
<geom group="3" contype="1" conaffinity="1"/>
|
||||
</default>
|
||||
</default>
|
||||
</default>
|
||||
@@ -94,10 +94,12 @@
|
||||
<mesh class="right_hand" file="f_proximal.obj"/>
|
||||
<mesh class="right_hand" file="f_middle.obj"/>
|
||||
<mesh class="right_hand" file="f_distal_pst.obj"/>
|
||||
<mesh class="right_hand" file="f_distal_pst_214.obj"/>
|
||||
<mesh class="right_hand" file="lf_metacarpal.obj"/>
|
||||
<mesh class="right_hand" file="th_proximal.obj"/>
|
||||
<mesh class="right_hand" file="th_middle.obj"/>
|
||||
<mesh class="right_hand" file="th_distal_pst.obj"/>
|
||||
<mesh class="right_hand" file="th_distal_pst_190.obj"/>
|
||||
</asset>
|
||||
|
||||
<worldbody>
|
||||
@@ -105,16 +107,16 @@
|
||||
<inertial mass="3" pos="0 0 0.09" diaginertia="0.0138 0.0138 0.00744"/>
|
||||
<geom class="plastic_visual" mesh="forearm_0" material="gray"/>
|
||||
<geom class="plastic_visual" mesh="forearm_1"/>
|
||||
<geom class="plastic_collision" type="mesh" mesh="forearm_collision" conaffinity="0"/>
|
||||
<geom class="plastic_collision" type="mesh" mesh="forearm_collision" conaffinity="0" contype="0"/>
|
||||
<geom class="plastic_collision" size="0.035 0.035 0.035" pos="0 -0.01 0.181" quat="0.924909 0 0.380188 0"
|
||||
type="box" conaffinity="0"/>
|
||||
<body name="rh_wrist" pos="0 -0.01 0.21301">
|
||||
<inertial mass="0.1" pos="0 0 0.029" quat="0.5 0.5 0.5 0.5" diaginertia="6.4e-05 4.38e-05 3.5e-05"/>
|
||||
<joint class="wrist_y" name="rh_WRJ2"/>
|
||||
<geom class="plastic_visual" mesh="wrist" material="metallic"/>
|
||||
<geom size="0.0135 0.015" quat="0.499998 0.5 0.5 -0.500002" type="cylinder" class="plastic_collision" conaffinity="0"/>
|
||||
<geom size="0.011 0.005" pos="-0.026 0 0.034" quat="1 0 1 0" type="cylinder" class="plastic_collision" conaffinity="0"/>
|
||||
<geom size="0.011 0.005" pos="0.031 0 0.034" quat="1 0 1 0" type="cylinder" class="plastic_collision" conaffinity="0"/>
|
||||
<geom size="0.0135 0.015" quat="0.499998 0.5 0.5 -0.500002" type="cylinder" class="plastic_collision" conaffinity="0" contype="0"/>
|
||||
<geom size="0.011 0.005" pos="-0.026 0 0.034" quat="1 0 1 0" type="cylinder" class="plastic_collision" conaffinity="0" contype="0"/>
|
||||
<geom size="0.011 0.005" pos="0.031 0 0.034" quat="1 0 1 0" type="cylinder" class="plastic_collision" conaffinity="0" contype="0"/>
|
||||
<geom size="0.0135 0.009 0.005" pos="-0.021 0 0.011" quat="0.923879 0 0.382684 0" type="box"
|
||||
class="plastic_collision" conaffinity="0"/>
|
||||
<geom size="0.0135 0.009 0.005" pos="0.026 0 0.01" quat="0.923879 0 -0.382684 0" type="box"
|
||||
@@ -138,23 +140,23 @@
|
||||
<inertial mass="0.008" pos="0 0 0" quat="0.5 0.5 -0.5 0.5" diaginertia="3.2e-07 2.6e-07 2.6e-07"/>
|
||||
<joint name="rh_FFJ4" class="knuckle"/>
|
||||
<geom pos="0 0 0.0005" class="plastic_visual" mesh="f_knuckle" material="metallic"/>
|
||||
<geom size="0.009 0.009" quat="1 0 1 0" type="cylinder" class="plastic_collision" conaffinity="0"/>
|
||||
<geom size="0.009 0.009" quat="1 0 1 0" type="cylinder" class="plastic_collision" conaffinity="0" contype="0"/>
|
||||
<body name="rh_ffproximal">
|
||||
<inertial mass="0.03" pos="0 0 0.0225" quat="1 0 0 1" diaginertia="1e-05 9.8e-06 1.8e-06"/>
|
||||
<joint name="rh_FFJ3" class="proximal"/>
|
||||
<geom class="plastic_visual" mesh="f_proximal"/>
|
||||
<geom size="0.009 0.02" pos="0 0 0.025" type="capsule" class="plastic_collision" contype="1"/>
|
||||
<geom size="0.009 0.02" pos="0 0 0.025" type="capsule" class="plastic_collision"/>
|
||||
<body name="rh_ffmiddle" pos="0 0 0.045">
|
||||
<inertial mass="0.017" pos="0 0 0.0125" quat="1 0 0 1" diaginertia="2.7e-06 2.6e-06 8.7e-07"/>
|
||||
<joint name="rh_FFJ2" class="middle_distal"/>
|
||||
<geom class="plastic_visual" mesh="f_middle"/>
|
||||
<geom size="0.009 0.0125" pos="0 0 0.0125" type="capsule" class="plastic_collision" contype="1"/>
|
||||
<geom size="0.009 0.0125" pos="0 0 0.0125" type="capsule" class="plastic_collision"/>
|
||||
<body name="rh_ffdistal" pos="0 0 0.025">
|
||||
<inertial mass="0.013" pos="0 0 0.0130769" quat="1 0 0 1"
|
||||
diaginertia="1.28092e-06 1.12092e-06 5.3e-07"/>
|
||||
<joint name="rh_FFJ1" class="middle_distal"/>
|
||||
<geom class="plastic_visual" mesh="f_distal_pst"/>
|
||||
<geom class="plastic_collision" type="mesh" mesh="f_distal_pst"/>
|
||||
<geom class="plastic_collision" type="mesh" mesh="f_distal_pst_214"/>
|
||||
</body>
|
||||
</body>
|
||||
</body>
|
||||
@@ -163,23 +165,23 @@
|
||||
<inertial mass="0.008" pos="0 0 0" quat="0.5 0.5 -0.5 0.5" diaginertia="3.2e-07 2.6e-07 2.6e-07"/>
|
||||
<joint name="rh_MFJ4" class="knuckle"/>
|
||||
<geom pos="0 0 0.0005" class="plastic_visual" mesh="f_knuckle" material="metallic"/>
|
||||
<geom size="0.009 0.009" quat="1 0 1 0" type="cylinder" class="plastic_collision" conaffinity="0"/>
|
||||
<geom size="0.009 0.009" quat="1 0 1 0" type="cylinder" class="plastic_collision" conaffinity="0" contype="0"/>
|
||||
<body name="rh_mfproximal">
|
||||
<inertial mass="0.03" pos="0 0 0.0225" quat="1 0 0 1" diaginertia="1e-05 9.8e-06 1.8e-06"/>
|
||||
<joint name="rh_MFJ3" class="proximal"/>
|
||||
<geom class="plastic_visual" mesh="f_proximal"/>
|
||||
<geom size="0.009 0.02" pos="0 0 0.025" type="capsule" class="plastic_collision" contype="1"/>
|
||||
<geom size="0.009 0.02" pos="0 0 0.025" type="capsule" class="plastic_collision"/>
|
||||
<body name="rh_mfmiddle" pos="0 0 0.045">
|
||||
<inertial mass="0.017" pos="0 0 0.0125" quat="1 0 0 1" diaginertia="2.7e-06 2.6e-06 8.7e-07"/>
|
||||
<joint name="rh_MFJ2" class="middle_distal"/>
|
||||
<geom class="plastic_visual" mesh="f_middle"/>
|
||||
<geom size="0.009 0.0125" pos="0 0 0.0125" type="capsule" class="plastic_collision" contype="1"/>
|
||||
<geom size="0.009 0.0125" pos="0 0 0.0125" type="capsule" class="plastic_collision"/>
|
||||
<body name="rh_mfdistal" pos="0 0 0.025">
|
||||
<inertial mass="0.013" pos="0 0 0.0130769" quat="1 0 0 1"
|
||||
diaginertia="1.28092e-06 1.12092e-06 5.3e-07"/>
|
||||
<joint name="rh_MFJ1" class="middle_distal"/>
|
||||
<geom class="plastic_visual" mesh="f_distal_pst"/>
|
||||
<geom class="plastic_collision" type="mesh" mesh="f_distal_pst"/>
|
||||
<geom class="plastic_collision" type="mesh" mesh="f_distal_pst_214"/>
|
||||
</body>
|
||||
</body>
|
||||
</body>
|
||||
@@ -188,23 +190,23 @@
|
||||
<inertial mass="0.008" pos="0 0 0" quat="0.5 0.5 -0.5 0.5" diaginertia="3.2e-07 2.6e-07 2.6e-07"/>
|
||||
<joint name="rh_RFJ4" class="knuckle" axis="0 1 0"/>
|
||||
<geom pos="0 0 0.0005" class="plastic_visual" mesh="f_knuckle" material="metallic"/>
|
||||
<geom size="0.009 0.009" quat="1 0 1 0" type="cylinder" class="plastic_collision" conaffinity="0"/>
|
||||
<geom size="0.009 0.009" quat="1 0 1 0" type="cylinder" class="plastic_collision" conaffinity="0" contype="0"/>
|
||||
<body name="rh_rfproximal">
|
||||
<inertial mass="0.03" pos="0 0 0.0225" quat="1 0 0 1" diaginertia="1e-05 9.8e-06 1.8e-06"/>
|
||||
<joint name="rh_RFJ3" class="proximal"/>
|
||||
<geom class="plastic_visual" mesh="f_proximal"/>
|
||||
<geom size="0.009 0.02" pos="0 0 0.025" type="capsule" class="plastic_collision" contype="1"/>
|
||||
<geom size="0.009 0.02" pos="0 0 0.025" type="capsule" class="plastic_collision"/>
|
||||
<body name="rh_rfmiddle" pos="0 0 0.045">
|
||||
<inertial mass="0.017" pos="0 0 0.0125" quat="1 0 0 1" diaginertia="2.7e-06 2.6e-06 8.7e-07"/>
|
||||
<joint name="rh_RFJ2" class="middle_distal"/>
|
||||
<geom class="plastic_visual" mesh="f_middle"/>
|
||||
<geom size="0.009 0.0125" pos="0 0 0.0125" type="capsule" class="plastic_collision" contype="1"/>
|
||||
<geom size="0.009 0.0125" pos="0 0 0.0125" type="capsule" class="plastic_collision"/>
|
||||
<body name="rh_rfdistal" pos="0 0 0.025">
|
||||
<inertial mass="0.013" pos="0 0 0.0130769" quat="1 0 0 1"
|
||||
diaginertia="1.28092e-06 1.12092e-06 5.3e-07"/>
|
||||
<joint name="rh_RFJ1" class="middle_distal"/>
|
||||
<geom class="plastic_visual" mesh="f_distal_pst"/>
|
||||
<geom class="plastic_collision" type="mesh" mesh="f_distal_pst"/>
|
||||
<geom class="plastic_collision" type="mesh" mesh="f_distal_pst_214"/>
|
||||
</body>
|
||||
</body>
|
||||
</body>
|
||||
@@ -218,23 +220,23 @@
|
||||
<inertial mass="0.008" pos="0 0 0" quat="0.5 0.5 -0.5 0.5" diaginertia="3.2e-07 2.6e-07 2.6e-07"/>
|
||||
<joint name="rh_LFJ4" class="knuckle" axis="0 1 0"/>
|
||||
<geom pos="0 0 0.0005" class="plastic_visual" mesh="f_knuckle" material="metallic"/>
|
||||
<geom size="0.009 0.009" quat="1 0 1 0" type="cylinder" class="plastic_collision" conaffinity="0"/>
|
||||
<geom size="0.009 0.009" quat="1 0 1 0" type="cylinder" class="plastic_collision" conaffinity="0" contype="0"/>
|
||||
<body name="rh_lfproximal">
|
||||
<inertial mass="0.03" pos="0 0 0.0225" quat="1 0 0 1" diaginertia="1e-05 9.8e-06 1.8e-06"/>
|
||||
<joint name="rh_LFJ3" class="proximal"/>
|
||||
<geom class="plastic_visual" mesh="f_proximal"/>
|
||||
<geom size="0.009 0.02" pos="0 0 0.025" type="capsule" class="plastic_collision" contype="1"/>
|
||||
<geom size="0.009 0.02" pos="0 0 0.025" type="capsule" class="plastic_collision"/>
|
||||
<body name="rh_lfmiddle" pos="0 0 0.045">
|
||||
<inertial mass="0.017" pos="0 0 0.0125" quat="1 0 0 1" diaginertia="2.7e-06 2.6e-06 8.7e-07"/>
|
||||
<joint name="rh_LFJ2" class="middle_distal"/>
|
||||
<geom class="plastic_visual" mesh="f_middle"/>
|
||||
<geom size="0.009 0.0125" pos="0 0 0.0125" type="capsule" class="plastic_collision" contype="1"/>
|
||||
<geom size="0.009 0.0125" pos="0 0 0.0125" type="capsule" class="plastic_collision"/>
|
||||
<body name="rh_lfdistal" pos="0 0 0.025">
|
||||
<inertial mass="0.013" pos="0 0 0.0130769" quat="1 0 0 1"
|
||||
diaginertia="1.28092e-06 1.12092e-06 5.3e-07"/>
|
||||
<joint name="rh_LFJ1" class="middle_distal"/>
|
||||
<geom class="plastic_visual" mesh="f_distal_pst"/>
|
||||
<geom class="plastic_collision" type="mesh" mesh="f_distal_pst"/>
|
||||
<geom class="plastic_collision" type="mesh" mesh="f_distal_pst_214"/>
|
||||
</body>
|
||||
</body>
|
||||
</body>
|
||||
@@ -248,23 +250,23 @@
|
||||
<inertial mass="0.04" pos="0 0 0.019" diaginertia="1.36e-05 1.36e-05 3.13e-06"/>
|
||||
<joint name="rh_THJ4" class="thproximal"/>
|
||||
<geom class="plastic_visual" mesh="th_proximal"/>
|
||||
<geom class="plastic_collision" size="0.0105 0.009" pos="0 0 0.02" type="capsule" contype="1"/>
|
||||
<geom class="plastic_collision" size="0.0105 0.009" pos="0 0 0.02" type="capsule"/>
|
||||
<body name="rh_thhub" pos="0 0 0.038">
|
||||
<inertial mass="0.005" pos="0 0 0" diaginertia="1e-06 1e-06 3e-07"/>
|
||||
<joint name="rh_THJ3" class="thhub"/>
|
||||
<geom size="0.011" class="plastic_collision" contype="1"/>
|
||||
<geom size="0.011" class="plastic_collision"/>
|
||||
<body name="rh_thmiddle">
|
||||
<inertial mass="0.02" pos="0 0 0.016" diaginertia="5.1e-06 5.1e-06 1.21e-06"/>
|
||||
<joint name="rh_THJ2" class="thmiddle"/>
|
||||
<geom class="plastic_visual" mesh="th_middle"/>
|
||||
<geom size="0.009 0.009" pos="0 0 0.012" type="capsule" class="plastic_collision" contype="1"/>
|
||||
<geom size="0.009 0.009" pos="0 0 0.012" type="capsule" class="plastic_collision"/>
|
||||
<geom size="0.01" pos="0 0 0.03" class="plastic_collision"/>
|
||||
<body name="rh_thdistal" pos="0 0 0.032" quat="1 0 0 -1">
|
||||
<inertial mass="0.017" pos="0 0 0.0145588" quat="1 0 0 1"
|
||||
diaginertia="2.37794e-06 2.27794e-06 1e-06"/>
|
||||
<joint name="rh_THJ1" class="thdistal"/>
|
||||
<geom class="plastic_visual" mesh="th_distal_pst"/>
|
||||
<geom class="plastic_collision" type="mesh" mesh="th_distal_pst"/>
|
||||
<geom class="plastic_collision" type="mesh" mesh="th_distal_pst_190"/>
|
||||
</body>
|
||||
</body>
|
||||
</body>
|
||||
|
||||
@@ -52,7 +52,6 @@ def _main(argv: Sequence[str]) -> None:
|
||||
while True:
|
||||
start = time.time()
|
||||
|
||||
# TODO(robotics-simulation): debug xfrc_applied sometimes causing NaN
|
||||
# TODO(robotics-simulation): recompile when changing disable flags, etc.
|
||||
dx = dx.replace(ctrl=d.ctrl, xfrc_applied=d.xfrc_applied)
|
||||
dx = dx.replace(qpos=d.qpos, qvel=d.qvel, time=d.time) # handle resets
|
||||
|
||||
Reference in New Issue
Block a user