Import google-deepmind/mujoco_warp from GitHub.

PiperOrigin-RevId: 868759523
Change-Id: I83a9f014840e66d7775a3684aa2e59ec5a077191
This commit is contained in:
Taylor Howell
2026-02-11 11:13:06 -08:00
committed by Copybara-Service
parent 883ba2134d
commit 94e834bf4f
33 changed files with 6783 additions and 2897 deletions
+2 -2
View File
@@ -871,10 +871,10 @@ class Model(PyTreeNode):
cam_poscom0: jax.Array
cam_pos0: jax.Array
cam_mat0: jax.Array
cam_fovy: np.ndarray
cam_fovy: jax.Array
cam_resolution: np.ndarray
cam_sensorsize: np.ndarray
cam_intrinsic: np.ndarray
cam_intrinsic: jax.Array
light_mode: np.ndarray
light_type: jax.Array
light_castshadow: jax.Array
+8
View File
@@ -28,6 +28,7 @@ from mujoco.mjx.third_party.mujoco_warp._src.types import Model as Model
from mujoco.mjx.third_party.mujoco_warp._src.types import Data as Data
# isort: on
from mujoco.mjx.third_party.mujoco_warp._src.bvh import refit_bvh as refit_bvh
from mujoco.mjx.third_party.mujoco_warp._src.collision_driver import collision as collision
from mujoco.mjx.third_party.mujoco_warp._src.collision_driver import nxn_broadphase as nxn_broadphase
from mujoco.mjx.third_party.mujoco_warp._src.collision_driver import sap_broadphase as sap_broadphase
@@ -46,14 +47,19 @@ from mujoco.mjx.third_party.mujoco_warp._src.forward import rungekutta4 as runge
from mujoco.mjx.third_party.mujoco_warp._src.forward import step1 as step1
from mujoco.mjx.third_party.mujoco_warp._src.forward import step2 as step2
from mujoco.mjx.third_party.mujoco_warp._src.inverse import inverse as inverse
from mujoco.mjx.third_party.mujoco_warp._src.io import create_render_context as create_render_context
from mujoco.mjx.third_party.mujoco_warp._src.io import get_data_into as get_data_into
from mujoco.mjx.third_party.mujoco_warp._src.io import make_data as make_data
from mujoco.mjx.third_party.mujoco_warp._src.io import put_data as put_data
from mujoco.mjx.third_party.mujoco_warp._src.io import put_model as put_model
from mujoco.mjx.third_party.mujoco_warp._src.io import reset_data as reset_data
from mujoco.mjx.third_party.mujoco_warp._src.io import set_const as set_const
from mujoco.mjx.third_party.mujoco_warp._src.io import set_const_0 as set_const_0
from mujoco.mjx.third_party.mujoco_warp._src.io import set_const_fixed as set_const_fixed
from mujoco.mjx.third_party.mujoco_warp._src.passive import passive as passive
from mujoco.mjx.third_party.mujoco_warp._src.ray import ray as ray
from mujoco.mjx.third_party.mujoco_warp._src.ray import rays as rays
from mujoco.mjx.third_party.mujoco_warp._src.render import render as render
from mujoco.mjx.third_party.mujoco_warp._src.sensor import energy_pos as energy_pos
from mujoco.mjx.third_party.mujoco_warp._src.sensor import energy_vel as energy_vel
from mujoco.mjx.third_party.mujoco_warp._src.sensor import sensor_acc as sensor_acc
@@ -75,6 +81,7 @@ from mujoco.mjx.third_party.mujoco_warp._src.smooth import transmission as trans
from mujoco.mjx.third_party.mujoco_warp._src.solver import solve as solve
from mujoco.mjx.third_party.mujoco_warp._src.support import contact_force as contact_force
from mujoco.mjx.third_party.mujoco_warp._src.support import get_state as get_state
from mujoco.mjx.third_party.mujoco_warp._src.support import jac as jac
from mujoco.mjx.third_party.mujoco_warp._src.support import mul_m as mul_m
from mujoco.mjx.third_party.mujoco_warp._src.support import set_state as set_state
from mujoco.mjx.third_party.mujoco_warp._src.support import xfrc_accumulate as xfrc_accumulate
@@ -92,6 +99,7 @@ from mujoco.mjx.third_party.mujoco_warp._src.types import GeomType as GeomType
from mujoco.mjx.third_party.mujoco_warp._src.types import IntegratorType as IntegratorType
from mujoco.mjx.third_party.mujoco_warp._src.types import JointType as JointType
from mujoco.mjx.third_party.mujoco_warp._src.types import Option as Option
from mujoco.mjx.third_party.mujoco_warp._src.types import RenderContext as RenderContext
from mujoco.mjx.third_party.mujoco_warp._src.types import SolverType as SolverType
from mujoco.mjx.third_party.mujoco_warp._src.types import State as State
from mujoco.mjx.third_party.mujoco_warp._src.types import Statistic as Statistic
+13 -4
View File
@@ -16,7 +16,7 @@
"""Utilities for benchmarking MuJoCo Warp."""
import time
from typing import Callable, Optional, Tuple
from typing import Callable, Tuple
import numpy as np
import warp as wp
@@ -24,6 +24,7 @@ import warp as wp
from mujoco.mjx.third_party.mujoco_warp._src import warp_util
from mujoco.mjx.third_party.mujoco_warp._src.types import Data
from mujoco.mjx.third_party.mujoco_warp._src.types import Model
from mujoco.mjx.third_party.mujoco_warp._src.types import RenderContext
from mujoco.mjx.third_party.mujoco_warp._src.util_misc import halton
@@ -87,10 +88,11 @@ def benchmark(
m: Model,
d: Data,
nstep: int,
ctrls: Optional[np.ndarray] = None,
ctrls: np.ndarray | None = None,
event_trace: bool = False,
measure_alloc: bool = False,
measure_solver_niter: bool = False,
render_context: RenderContext | None = None,
) -> Tuple[float, float, dict, list, list, list, int]:
"""Benchmark a function of Model and Data.
@@ -103,6 +105,7 @@ def benchmark(
event_trace: If True, time routines decorated with @event_scope.
measure_alloc: If True, record number of contacts and constraints.
measure_solver_niter: If True, record the number of solver iterations.
render_context: The render context to use for rendering.
Returns:
- Time to JIT fn.
@@ -120,8 +123,14 @@ def benchmark(
with warp_util.EventTracer(enabled=event_trace) as tracer:
# capture the whole function as a CUDA graph
jit_beg = time.perf_counter()
with wp.ScopedCapture() as capture:
fn(m, d)
if render_context is not None:
with wp.ScopedCapture() as capture:
fn(m, d, render_context)
else:
with wp.ScopedCapture() as capture:
fn(m, d)
jit_end = time.perf_counter()
jit_duration = jit_end - jit_beg
File diff suppressed because it is too large Load Diff
+228 -249
View File
@@ -29,6 +29,8 @@ from mujoco.mjx.third_party.mujoco_warp._src.math import upper_trid_index
from mujoco.mjx.third_party.mujoco_warp._src.types import MJ_MAX_EPAFACES
from mujoco.mjx.third_party.mujoco_warp._src.types import MJ_MAX_EPAHORIZON
from mujoco.mjx.third_party.mujoco_warp._src.types import MJ_MAXCONPAIR
from mujoco.mjx.third_party.mujoco_warp._src.types import MJ_MAXVAL
from mujoco.mjx.third_party.mujoco_warp._src.types import CollisionContext
from mujoco.mjx.third_party.mujoco_warp._src.types import Data
from mujoco.mjx.third_party.mujoco_warp._src.types import EnableBit
from mujoco.mjx.third_party.mujoco_warp._src.types import GeomType
@@ -38,7 +40,6 @@ from mujoco.mjx.third_party.mujoco_warp._src.types import mat63
from mujoco.mjx.third_party.mujoco_warp._src.types import vec5
from mujoco.mjx.third_party.mujoco_warp._src.warp_util import cache_kernel
from mujoco.mjx.third_party.mujoco_warp._src.warp_util import event_scope
from mujoco.mjx.third_party.mujoco_warp._src.warp_util import nested_kernel
# TODO(team): improve compile time to enable backward pass
wp.set_module_options({"enable_backward": False})
@@ -46,42 +47,6 @@ wp.set_module_options({"enable_backward": False})
vec_maxconpair = wp.types.vector(length=MJ_MAXCONPAIR, dtype=float)
mat_maxconpair = wp.types.matrix(shape=(MJ_MAXCONPAIR, 3), dtype=float)
_CONVEX_COLLISION_PAIRS = [
(GeomType.HFIELD, GeomType.SPHERE),
(GeomType.HFIELD, GeomType.CAPSULE),
(GeomType.HFIELD, GeomType.ELLIPSOID),
(GeomType.HFIELD, GeomType.CYLINDER),
(GeomType.HFIELD, GeomType.BOX),
(GeomType.HFIELD, GeomType.MESH),
(GeomType.SPHERE, GeomType.ELLIPSOID),
(GeomType.SPHERE, GeomType.MESH),
(GeomType.CAPSULE, GeomType.ELLIPSOID),
(GeomType.CAPSULE, GeomType.CYLINDER),
(GeomType.CAPSULE, GeomType.MESH),
(GeomType.ELLIPSOID, GeomType.ELLIPSOID),
(GeomType.ELLIPSOID, GeomType.CYLINDER),
(GeomType.ELLIPSOID, GeomType.BOX),
(GeomType.ELLIPSOID, GeomType.MESH),
(GeomType.CYLINDER, GeomType.CYLINDER),
(GeomType.CYLINDER, GeomType.BOX),
(GeomType.CYLINDER, GeomType.MESH),
(GeomType.BOX, GeomType.MESH),
(GeomType.MESH, GeomType.MESH),
]
def _check_convex_collision_pairs():
prev_idx = -1
for pair in _CONVEX_COLLISION_PAIRS:
idx = upper_trid_index(len(GeomType), pair[0].value, pair[1].value)
if pair[1] < pair[0] or idx <= prev_idx:
return False
prev_idx = idx
return True
assert _check_convex_collision_pairs(), "_CONVEX_COLLISION_PAIRS is in invalid order."
@wp.func
def _hfield_filter(
@@ -91,6 +56,11 @@ def _hfield_filter(
geom_size: wp.array2d(dtype=wp.vec3),
geom_rbound: wp.array2d(dtype=float),
geom_margin: wp.array2d(dtype=float),
mesh_vertadr: wp.array(dtype=int),
mesh_vertnum: wp.array(dtype=int),
mesh_graphadr: wp.array(dtype=int),
mesh_vert: wp.array(dtype=wp.vec3),
mesh_graph: wp.array(dtype=int),
hfield_size: wp.array(dtype=wp.vec4),
# Data in:
geom_xpos_in: wp.array2d(dtype=wp.vec3),
@@ -126,14 +96,14 @@ def _hfield_filter(
# box-sphere test: horizontal plane
for i in range(2):
if (size1[i] < pos[i] - r2 - margin) or (-size1[i] > pos[i] + r2 + margin):
return True, wp.inf, wp.inf, wp.inf, wp.inf, wp.inf, wp.inf
return True, MJ_MAXVAL, MJ_MAXVAL, MJ_MAXVAL, MJ_MAXVAL, MJ_MAXVAL, MJ_MAXVAL
# box-sphere test: vertical direction
if size1[2] < pos[2] - r2 - margin: # up
return True, wp.inf, wp.inf, wp.inf, wp.inf, wp.inf, wp.inf
return True, MJ_MAXVAL, MJ_MAXVAL, MJ_MAXVAL, MJ_MAXVAL, MJ_MAXVAL, MJ_MAXVAL
if -size1[3] > pos[2] + r2 + margin: # down
return True, wp.inf, wp.inf, wp.inf, wp.inf, wp.inf, wp.inf
return True, MJ_MAXVAL, MJ_MAXVAL, MJ_MAXVAL, MJ_MAXVAL, MJ_MAXVAL, MJ_MAXVAL
mat2 = geom_xmat_in[worldid, g2]
mat = mat1T @ mat2
@@ -148,6 +118,15 @@ def _hfield_filter(
geomtype2 = geom_type[g2]
# load mesh vertex data for support function queries
if geomtype2 == GeomType.MESH:
dataid = geom_dataid[g2]
geom2.vertadr = wp.where(dataid >= 0, mesh_vertadr[dataid], -1)
geom2.vertnum = wp.where(dataid >= 0, mesh_vertnum[dataid], -1)
geom2.graphadr = wp.where(dataid >= 0, mesh_graphadr[dataid], -1)
geom2.vert = mesh_vert
geom2.graph = mesh_graph
# use support functions for tight AABB bounds
xmax = support(geom2, geomtype2, wp.vec3(1.0, 0.0, 0.0)).point[0]
xmin = support(geom2, geomtype2, wp.vec3(-1.0, 0.0, 0.0)).point[0]
@@ -165,7 +144,7 @@ def _hfield_filter(
or (zmin - margin > size1[2])
or (zmax + margin < -size1[3])
):
return True, wp.inf, wp.inf, wp.inf, wp.inf, wp.inf, wp.inf
return True, MJ_MAXVAL, MJ_MAXVAL, MJ_MAXVAL, MJ_MAXVAL, MJ_MAXVAL, MJ_MAXVAL
else:
return False, xmin, xmax, ymin, ymax, zmin, zmax
@@ -176,11 +155,12 @@ def ccd_hfield_kernel_builder(
geomtype2: int,
gjk_iterations: int,
epa_iterations: int,
geomgeomid: int,
):
"""Kernel builder for heightfield CCD collisions (no multiccd args)."""
# runs convex collision on a set of geom pairs to recover contact info
@nested_kernel(module="unique", enable_backward=False)
@wp.kernel(module="unique", enable_backward=False)
def ccd_hfield_kernel(
# Model:
opt_ccd_tolerance: wp.array(dtype=float),
@@ -196,15 +176,10 @@ def ccd_hfield_kernel_builder(
geom_friction: wp.array2d(dtype=wp.vec3),
geom_margin: wp.array2d(dtype=float),
geom_gap: wp.array2d(dtype=float),
hfield_adr: wp.array(dtype=int),
hfield_nrow: wp.array(dtype=int),
hfield_ncol: wp.array(dtype=int),
hfield_size: wp.array(dtype=wp.vec4),
hfield_data: wp.array(dtype=float),
mesh_vertadr: wp.array(dtype=int),
mesh_vertnum: wp.array(dtype=int),
mesh_vert: wp.array(dtype=wp.vec3),
mesh_graphadr: wp.array(dtype=int),
mesh_vert: wp.array(dtype=wp.vec3),
mesh_graph: wp.array(dtype=int),
mesh_polynum: wp.array(dtype=int),
mesh_polyadr: wp.array(dtype=int),
@@ -215,6 +190,11 @@ def ccd_hfield_kernel_builder(
mesh_polymapadr: wp.array(dtype=int),
mesh_polymapnum: wp.array(dtype=int),
mesh_polymap: wp.array(dtype=int),
hfield_size: wp.array(dtype=wp.vec4),
hfield_nrow: wp.array(dtype=int),
hfield_ncol: wp.array(dtype=int),
hfield_adr: wp.array(dtype=int),
hfield_data: wp.array(dtype=float),
pair_dim: wp.array(dtype=int),
pair_solref: wp.array2d(dtype=wp.vec2),
pair_solreffriction: wp.array2d(dtype=wp.vec2),
@@ -223,24 +203,23 @@ def ccd_hfield_kernel_builder(
pair_gap: wp.array2d(dtype=float),
pair_friction: wp.array2d(dtype=vec5),
# Data in:
naconmax_in: int,
geom_xpos_in: wp.array2d(dtype=wp.vec3),
geom_xmat_in: wp.array2d(dtype=wp.mat33),
naconmax_in: int,
naccdmax_in: int,
ncollision_in: wp.array(dtype=int),
# In:
collision_pair_in: wp.array(dtype=wp.vec2i),
collision_pairid_in: wp.array(dtype=wp.vec2i),
collision_worldid_in: wp.array(dtype=int),
ncollision_in: wp.array(dtype=int),
# In:
epa_vert1_in: wp.array2d(dtype=wp.vec3),
epa_vert2_in: wp.array2d(dtype=wp.vec3),
epa_vert_index1_in: wp.array2d(dtype=int),
epa_vert_index2_in: wp.array2d(dtype=int),
epa_vert_in: wp.array2d(dtype=wp.vec3),
epa_vert_index_in: wp.array2d(dtype=int),
epa_face_in: wp.array2d(dtype=int),
epa_pr_in: wp.array2d(dtype=wp.vec3),
epa_norm2_in: wp.array2d(dtype=float),
epa_horizon_in: wp.array2d(dtype=int),
nccd_in: wp.array(dtype=int),
# Data out:
nacon_out: wp.array(dtype=int),
contact_dist_out: wp.array(dtype=float),
contact_pos_out: wp.array(dtype=wp.vec3),
contact_frame_out: wp.array(dtype=wp.mat33),
@@ -254,27 +233,48 @@ def ccd_hfield_kernel_builder(
contact_worldid_out: wp.array(dtype=int),
contact_type_out: wp.array(dtype=int),
contact_geomcollisionid_out: wp.array(dtype=int),
nacon_out: wp.array(dtype=int),
):
tid = wp.tid()
if tid >= ncollision_in[0]:
collisionid = wp.tid()
if collisionid >= ncollision_in[0]:
return
geoms = collision_pair_in[tid]
geoms = collision_pair_in[collisionid]
g1 = geoms[0]
g2 = geoms[1]
if geom_type[g1] != geomtype1 or geom_type[g2] != geomtype2:
return
worldid = collision_worldid_in[tid]
worldid = collision_worldid_in[collisionid]
# height field filter
no_hf_collision, xmin, xmax, ymin, ymax, zmin, zmax = _hfield_filter(
geom_type, geom_dataid, geom_size, geom_rbound, geom_margin, hfield_size, geom_xpos_in, geom_xmat_in, worldid, g1, g2
geom_type,
geom_dataid,
geom_size,
geom_rbound,
geom_margin,
mesh_vertadr,
mesh_vertnum,
mesh_graphadr,
mesh_vert,
mesh_graph,
hfield_size,
geom_xpos_in,
geom_xmat_in,
worldid,
g1,
g2,
)
if no_hf_collision:
return
ccdid = wp.atomic_add(nccd_in, wp.static(geomgeomid), 1)
if ccdid >= naccdmax_in:
wp.printf("CCD overflow - please increase naccdmax to %u\n", ccdid)
return
_, margin, gap, condim, friction, solref, solreffriction, solimp = contact_params(
geom_condim,
geom_priority,
@@ -293,7 +293,7 @@ def ccd_hfield_kernel_builder(
pair_friction,
collision_pair_in,
collision_pairid_in,
tid,
collisionid,
worldid,
)
@@ -365,26 +365,23 @@ def ccd_hfield_kernel_builder(
hfield_contact_dist = vec_maxconpair()
hfield_contact_pos = mat_maxconpair()
hfield_contact_normal = mat_maxconpair()
min_dist = float(wp.inf)
min_normal = wp.vec3(wp.inf, wp.inf, wp.inf)
min_pos = wp.vec3(wp.inf, wp.inf, wp.inf)
min_dist = float(MJ_MAXVAL)
min_normal = wp.vec3(MJ_MAXVAL, MJ_MAXVAL, MJ_MAXVAL)
min_pos = wp.vec3(MJ_MAXVAL, MJ_MAXVAL, MJ_MAXVAL)
min_id = int(-1)
# TODO(team): height field margin?
geom1.margin = margin
# geom1 margin added to z-axis of hfield prism top points below
geom2.margin = margin
# EPA memory
epa_vert1 = epa_vert1_in[tid]
epa_vert2 = epa_vert2_in[tid]
epa_vert_index1 = epa_vert_index1_in[tid]
epa_vert_index2 = epa_vert_index2_in[tid]
epa_face = epa_face_in[tid]
epa_pr = epa_pr_in[tid]
epa_norm2 = epa_norm2_in[tid]
epa_horizon = epa_horizon_in[tid]
epa_vert = epa_vert_in[ccdid]
epa_vert_index = epa_vert_index_in[ccdid]
epa_face = epa_face_in[ccdid]
epa_pr = epa_pr_in[ccdid]
epa_norm2 = epa_norm2_in[ccdid]
epa_horizon = epa_horizon_in[ccdid]
collision_pairid = collision_pairid_in[tid]
collision_pairid = collision_pairid_in[collisionid]
# process all prisms in subgrid
count = int(0)
@@ -439,11 +436,7 @@ def ccd_hfield_kernel_builder(
geom1.hfprism = prism
# prism center
x1 = geom1.pos
x1_ = wp.vec3(0.0, 0.0, 0.0)
for i in range(6):
x1_ += prism[i]
x1 += geom1.rot @ (x1_ / 6.0)
x1 = geom1.pos + geom1.rot @ (prism[0] + prism[1] + prism[2] + prism[3] + prism[4] + prism[5]) * wp.static(1.0 / 6.0)
dist, ncontact, w1, w2, idx = ccd(
opt_ccd_tolerance[worldid % opt_ccd_tolerance.shape[0]],
@@ -456,10 +449,8 @@ def ccd_hfield_kernel_builder(
geomtype2,
x1,
geom2.pos,
epa_vert1,
epa_vert2,
epa_vert_index1,
epa_vert_index2,
epa_vert,
epa_vert_index,
epa_face,
epa_pr,
epa_norm2,
@@ -529,13 +520,12 @@ def ccd_hfield_kernel_builder(
)
# TODO(team): routine for select subset of contacts
# TODO(team): if use_multiccd?
if wp.static(True):
MIN_DIST_TO_NEXT_CONTACT = 1.0e-3
# contact 1: furthest from minimum distance contact
id1 = int(-1)
dist1 = float(-wp.inf)
dist1 = float(-MJ_MAXVAL)
for i in range(count):
if i == min_id:
continue
@@ -589,7 +579,7 @@ def ccd_hfield_kernel_builder(
dist_min1 = wp.cross(min_normal, min_pos - pos1)
id2 = int(-1)
dist_12 = float(-wp.inf)
dist_12 = float(-MJ_MAXVAL)
for i in range(count):
if i == min_id or i == id1:
continue
@@ -644,7 +634,7 @@ def ccd_hfield_kernel_builder(
vec_12 = wp.cross(min_normal, pos1 - pos2)
id3 = int(-1)
dist3 = float(-wp.inf)
dist3 = float(-MJ_MAXVAL)
for i in range(count):
if i == min_id or i == id1 or i == id2:
continue
@@ -704,6 +694,7 @@ def ccd_kernel_builder(
gjk_iterations: int,
epa_iterations: int,
use_multiccd: bool,
geomgeomid: int,
):
"""Kernel builder for non-heightfield CCD collisions (no hfield args)."""
@@ -711,14 +702,11 @@ def ccd_kernel_builder(
def eval_ccd_write_contact(
# Model:
opt_ccd_tolerance: wp.array(dtype=float),
geom_type: wp.array(dtype=int),
# Data in:
naconmax_in: int,
# In:
epa_vert1_in: wp.array2d(dtype=wp.vec3),
epa_vert2_in: wp.array2d(dtype=wp.vec3),
epa_vert_index1_in: wp.array2d(dtype=int),
epa_vert_index2_in: wp.array2d(dtype=int),
epa_vert_in: wp.array2d(dtype=wp.vec3),
epa_vert_index_in: wp.array2d(dtype=int),
epa_face_in: wp.array2d(dtype=int),
epa_pr_in: wp.array2d(dtype=wp.vec3),
epa_norm2_in: wp.array2d(dtype=float),
@@ -738,7 +726,7 @@ def ccd_kernel_builder(
geom2: Geom,
geoms: wp.vec2i,
worldid: int,
tid: int,
ccdid: int,
margin: float,
gap: float,
condim: int,
@@ -748,7 +736,6 @@ def ccd_kernel_builder(
solimp: vec5,
x1: wp.vec3,
x2: wp.vec3,
count: int,
pairid: wp.vec2i,
# Data out:
contact_dist_out: wp.array(dtype=float),
@@ -771,12 +758,12 @@ def ccd_kernel_builder(
witness2 = mat43()
geom1.margin = margin
geom2.margin = margin
if pairid[1] >= 0:
# if collision sensor, set large cutoff to work with various sensor cutoff values
is_collision_sensor = pairid[1] >= 0
if is_collision_sensor:
cutoff = 1.0e32
else:
cutoff = 0.0
dist, ncontact, w1, w2, idx = ccd(
dist, ncollision, w1, w2, multiccd_idx = ccd(
opt_ccd_tolerance[worldid % opt_ccd_tolerance.shape[0]],
cutoff,
gjk_iterations,
@@ -787,14 +774,12 @@ def ccd_kernel_builder(
geomtype2,
x1,
x2,
epa_vert1_in[tid],
epa_vert2_in[tid],
epa_vert_index1_in[tid],
epa_vert_index2_in[tid],
epa_face_in[tid],
epa_pr_in[tid],
epa_norm2_in[tid],
epa_horizon_in[tid],
epa_vert_in[ccdid],
epa_vert_index_in[ccdid],
epa_face_in[ccdid],
epa_pr_in[ccdid],
epa_norm2_in[ccdid],
epa_horizon_in[ccdid],
)
if dist >= 0.0 and pairid[1] == -1:
@@ -803,30 +788,33 @@ def ccd_kernel_builder(
witness1[0] = w1
witness2[0] = w2
if wp.static(use_multiccd):
if (
geom1.margin == 0.0
and geom2.margin == 0.0
and (geomtype1 == GeomType.BOX or (geomtype1 == GeomType.MESH and geom1.mesh_polyadr > -1))
and (geomtype2 == GeomType.BOX or (geomtype2 == GeomType.MESH and geom2.mesh_polyadr > -1))
):
ncontact, witness1, witness2 = multicontact(
multiccd_polygon_in[tid],
multiccd_clipped_in[tid],
multiccd_pnormal_in[tid],
multiccd_pdist_in[tid],
multiccd_idx1_in[tid],
multiccd_idx2_in[tid],
multiccd_n1_in[tid],
multiccd_n2_in[tid],
multiccd_endvert_in[tid],
multiccd_face1_in[tid],
multiccd_face2_in[tid],
epa_vert1_in[tid],
epa_vert2_in[tid],
epa_vert_index1_in[tid],
epa_vert_index2_in[tid],
epa_face_in[tid, idx],
if wp.static(use_multiccd or (geomtype1 == GeomType.BOX and geomtype2 == GeomType.BOX)):
if wp.static(geomtype1 == GeomType.MESH):
# verify that geom1 mesh data is present for multicontact
if geom1.mesh_polyadr < 0:
multiccd_idx = -1
if wp.static(geomtype2 == GeomType.MESH):
# verify that geom2 mesh data is present for multicontact
if geom2.mesh_polyadr < 0:
multiccd_idx = -1
if multiccd_idx > -1:
ncollision, witness1, witness2 = multicontact(
multiccd_polygon_in[ccdid],
multiccd_clipped_in[ccdid],
multiccd_pnormal_in[ccdid],
multiccd_pdist_in[ccdid],
multiccd_idx1_in[ccdid],
multiccd_idx2_in[ccdid],
multiccd_n1_in[ccdid],
multiccd_n2_in[ccdid],
multiccd_endvert_in[ccdid],
multiccd_face1_in[ccdid],
multiccd_face2_in[ccdid],
epa_vert_in[ccdid],
epa_vert_index_in[ccdid],
epa_face_in[ccdid, multiccd_idx],
w1,
w2,
geom1,
@@ -835,7 +823,7 @@ def ccd_kernel_builder(
geomtype2,
)
for i in range(ncontact):
for i in range(ncollision):
points[i] = 0.5 * (witness1[i] + witness2[i])
normal = witness1[0] - witness2[0]
frame = make_frame(normal)
@@ -845,8 +833,9 @@ def ccd_kernel_builder(
frame *= -1.0
geoms = wp.vec2i(geoms[1], geoms[0])
for i in range(ncontact):
write_contact(
nactive = int(0) # number of contacts contributing to the physics
for i in range(ncollision):
active = write_contact(
naconmax_in,
i,
dist,
@@ -877,13 +866,12 @@ def ccd_kernel_builder(
contact_geomcollisionid_out,
nacon_out,
)
if count + (i + 1) >= MJ_MAXCONPAIR:
return i + 1
nactive += active
return ncontact
return nactive
# runs convex collision on a set of geom pairs to recover contact info (non-heightfield)
@nested_kernel(module="unique", enable_backward=False)
@wp.kernel(module="unique", enable_backward=False)
def ccd_kernel(
# Model:
opt_ccd_tolerance: wp.array(dtype=float),
@@ -900,8 +888,8 @@ def ccd_kernel_builder(
geom_gap: wp.array2d(dtype=float),
mesh_vertadr: wp.array(dtype=int),
mesh_vertnum: wp.array(dtype=int),
mesh_vert: wp.array(dtype=wp.vec3),
mesh_graphadr: wp.array(dtype=int),
mesh_vert: wp.array(dtype=wp.vec3),
mesh_graph: wp.array(dtype=int),
mesh_polynum: wp.array(dtype=int),
mesh_polyadr: wp.array(dtype=int),
@@ -920,18 +908,17 @@ def ccd_kernel_builder(
pair_gap: wp.array2d(dtype=float),
pair_friction: wp.array2d(dtype=vec5),
# Data in:
naconmax_in: int,
geom_xpos_in: wp.array2d(dtype=wp.vec3),
geom_xmat_in: wp.array2d(dtype=wp.mat33),
naconmax_in: int,
naccdmax_in: int,
ncollision_in: wp.array(dtype=int),
# In:
collision_pair_in: wp.array(dtype=wp.vec2i),
collision_pairid_in: wp.array(dtype=wp.vec2i),
collision_worldid_in: wp.array(dtype=int),
ncollision_in: wp.array(dtype=int),
# In:
epa_vert1_in: wp.array2d(dtype=wp.vec3),
epa_vert2_in: wp.array2d(dtype=wp.vec3),
epa_vert_index1_in: wp.array2d(dtype=int),
epa_vert_index2_in: wp.array2d(dtype=int),
epa_vert_in: wp.array2d(dtype=wp.vec3),
epa_vert_index_in: wp.array2d(dtype=int),
epa_face_in: wp.array2d(dtype=int),
epa_pr_in: wp.array2d(dtype=wp.vec3),
epa_norm2_in: wp.array2d(dtype=float),
@@ -947,8 +934,8 @@ def ccd_kernel_builder(
multiccd_endvert_in: wp.array2d(dtype=wp.vec3),
multiccd_face1_in: wp.array2d(dtype=wp.vec3),
multiccd_face2_in: wp.array2d(dtype=wp.vec3),
nccd_in: wp.array(dtype=int),
# Data out:
nacon_out: wp.array(dtype=int),
contact_dist_out: wp.array(dtype=float),
contact_pos_out: wp.array(dtype=wp.vec3),
contact_frame_out: wp.array(dtype=wp.mat33),
@@ -962,19 +949,25 @@ def ccd_kernel_builder(
contact_worldid_out: wp.array(dtype=int),
contact_type_out: wp.array(dtype=int),
contact_geomcollisionid_out: wp.array(dtype=int),
nacon_out: wp.array(dtype=int),
):
tid = wp.tid()
if tid >= ncollision_in[0]:
collisionid = wp.tid()
if collisionid >= ncollision_in[0]:
return
geoms = collision_pair_in[tid]
geoms = collision_pair_in[collisionid]
g1 = geoms[0]
g2 = geoms[1]
if geom_type[g1] != geomtype1 or geom_type[g2] != geomtype2:
return
worldid = collision_worldid_in[tid]
ccdid = wp.atomic_add(nccd_in, wp.static(geomgeomid), 1)
if ccdid >= naccdmax_in:
wp.printf("CCD overflow - please increase naccdmax to %u\n", ccdid)
return
worldid = collision_worldid_in[collisionid]
_, margin, gap, condim, friction, solref, solreffriction, solimp = contact_params(
geom_condim,
@@ -994,7 +987,7 @@ def ccd_kernel_builder(
pair_friction,
collision_pair_in,
collision_pairid_in,
tid,
collisionid,
worldid,
)
@@ -1024,12 +1017,9 @@ def ccd_kernel_builder(
eval_ccd_write_contact(
opt_ccd_tolerance,
geom_type,
naconmax_in,
epa_vert1_in,
epa_vert2_in,
epa_vert_index1_in,
epa_vert_index2_in,
epa_vert_in,
epa_vert_index_in,
epa_face_in,
epa_pr_in,
epa_norm2_in,
@@ -1049,7 +1039,7 @@ def ccd_kernel_builder(
geom2,
geoms,
worldid,
tid,
ccdid,
margin,
gap,
condim,
@@ -1059,8 +1049,7 @@ def ccd_kernel_builder(
solimp,
geom1.pos,
geom2.pos,
0,
collision_pairid_in[tid],
collision_pairid_in[collisionid],
contact_dist_out,
contact_pos_out,
contact_frame_out,
@@ -1080,37 +1069,8 @@ def ccd_kernel_builder(
return ccd_kernel
# Heightfield collision pairs handled by ccd_hfield_kernel_builder
_HFIELD_COLLISION_PAIRS = [
(GeomType.HFIELD, GeomType.SPHERE),
(GeomType.HFIELD, GeomType.CAPSULE),
(GeomType.HFIELD, GeomType.ELLIPSOID),
(GeomType.HFIELD, GeomType.CYLINDER),
(GeomType.HFIELD, GeomType.BOX),
(GeomType.HFIELD, GeomType.MESH),
]
# Non-heightfield collision pairs handled by ccd_kernel_builder
_NON_HFIELD_COLLISION_PAIRS = [
(GeomType.SPHERE, GeomType.ELLIPSOID),
(GeomType.SPHERE, GeomType.MESH),
(GeomType.CAPSULE, GeomType.ELLIPSOID),
(GeomType.CAPSULE, GeomType.CYLINDER),
(GeomType.CAPSULE, GeomType.MESH),
(GeomType.ELLIPSOID, GeomType.ELLIPSOID),
(GeomType.ELLIPSOID, GeomType.CYLINDER),
(GeomType.ELLIPSOID, GeomType.BOX),
(GeomType.ELLIPSOID, GeomType.MESH),
(GeomType.CYLINDER, GeomType.CYLINDER),
(GeomType.CYLINDER, GeomType.BOX),
(GeomType.CYLINDER, GeomType.MESH),
(GeomType.BOX, GeomType.MESH),
(GeomType.MESH, GeomType.MESH),
]
@event_scope
def convex_narrowphase(m: Model, d: Data):
def convex_narrowphase(m: Model, d: Data, ctx: CollisionContext, collision_table: list[tuple[GeomType, GeomType]]):
"""Runs narrowphase collision detection for convex geom pairs.
This function handles collision detection for pairs of convex geometries that were
@@ -1126,40 +1086,56 @@ def convex_narrowphase(m: Model, d: Data):
computations for non-existent pair types.
"""
def _pair_count(p1: int, p2: int) -> int:
return m.geom_pair_type_count[upper_trid_index(len(GeomType), p1, p2)]
def _pair_count(p1: int, p2: int) -> Tuple[int, int]:
idx = upper_trid_index(len(GeomType), p1, p2)
return m.geom_pair_type_count[idx], idx
ncollision = sum(_pair_count(g[0].value, g[1].value)[0] for g in collision_table)
# no convex collisions, early return
if not any(_pair_count(g[0].value, g[1].value) for g in _CONVEX_COLLISION_PAIRS):
if ncollision == 0:
return
epa_iterations = m.opt.ccd_iterations
# compute nmaxpolygon and nmaxmeshdeg given the geom pairs for the model
nboxbox, _ = _pair_count(GeomType.BOX.value, GeomType.BOX.value)
if (GeomType.BOX, GeomType.BOX) not in collision_table:
nboxbox = 0
nboxmesh, _ = _pair_count(GeomType.BOX.value, GeomType.MESH.value)
nmeshmesh, _ = _pair_count(GeomType.MESH.value, GeomType.MESH.value)
epa_iterations = 16 if nboxbox == ncollision else m.opt.ccd_iterations
# set to true to enable multiccd
use_multiccd = m.opt.enableflags & EnableBit.MULTICCD
nmaxpolygon = m.nmaxpolygon if use_multiccd else 0
nmaxmeshdeg = m.nmaxmeshdeg if use_multiccd else 0
# epa_vert1: vertices in EPA polytope in geom 1 space
epa_vert1 = wp.empty(shape=(d.naconmax, 5 + epa_iterations), dtype=wp.vec3)
# epa_vert2: vertices in EPA polytope in geom 2 space
epa_vert2 = wp.empty(shape=(d.naconmax, 5 + epa_iterations), dtype=wp.vec3)
# epa_vert_index1: vertex indices in EPA polytope for geom 1
epa_vert_index1 = wp.empty(shape=(d.naconmax, 5 + epa_iterations), dtype=int)
# epa_vert_index2: vertex indices in EPA polytope for geom 2 (naconmax, 5 + CCDiter)
epa_vert_index2 = wp.empty(shape=(d.naconmax, 5 + epa_iterations), dtype=int)
# need at least 4 (square sides) if there's a box collision needing multiccd
nmaxpolygon = 4 if nboxbox > 0 else 0
nmaxmeshdeg = 3 if nboxbox > 0 else 0
# need to allocate more memory if there's meshes
if use_multiccd and nmeshmesh + nboxmesh > 0:
minval = 4 if nboxmesh else nmaxpolygon
nmaxpolygon = max(m.nmaxpolygon, minval)
nmaxmeshdeg = max(m.nmaxmeshdeg, 3)
# ccd collider count
nccd = wp.zeros(len(GeomType) * (len(GeomType) + 1) // 2, dtype=int)
# epa_vert: vertices in EPA polytope
epa_vert = wp.empty(shape=(d.naccdmax, 10 + 2 * epa_iterations), dtype=wp.vec3)
# epa_vert_index: vertex indices in EPA polytope
epa_vert_index = wp.empty(shape=(d.naccdmax, 10 + 2 * epa_iterations), dtype=int)
# epa_face: faces of polytope represented by three indices
epa_face = wp.empty(shape=(d.naconmax, 6 + MJ_MAX_EPAFACES * epa_iterations), dtype=int)
epa_face = wp.empty(shape=(d.naccdmax, 6 + MJ_MAX_EPAFACES * epa_iterations), dtype=int)
# epa_pr: projection of origin on polytope faces
epa_pr = wp.empty(shape=(d.naconmax, 6 + MJ_MAX_EPAFACES * epa_iterations), dtype=wp.vec3)
epa_pr = wp.empty(shape=(d.naccdmax, 6 + MJ_MAX_EPAFACES * epa_iterations), dtype=wp.vec3)
# epa_norm2: epa_pr * epa_pr
epa_norm2 = wp.empty(shape=(d.naconmax, 6 + MJ_MAX_EPAFACES * epa_iterations), dtype=float)
epa_norm2 = wp.empty(shape=(d.naccdmax, 6 + MJ_MAX_EPAFACES * epa_iterations), dtype=float)
# epa_horizon: index pair (i j) of edges on horizon
epa_horizon = wp.empty(shape=(d.naconmax, MJ_MAX_EPAHORIZON), dtype=int)
epa_horizon = wp.empty(shape=(d.naccdmax, MJ_MAX_EPAHORIZON), dtype=int)
# Contact outputs
contact_outputs = [
d.nacon,
d.contact.dist,
d.contact.pos,
d.contact.frame,
@@ -1173,15 +1149,17 @@ def convex_narrowphase(m: Model, d: Data):
d.contact.worldid,
d.contact.type,
d.contact.geomcollisionid,
d.nacon,
]
# Launch heightfield collision kernels (no multiccd args, 72 args total)
for geom_pair in _HFIELD_COLLISION_PAIRS:
for geom_pair in collision_table:
g1 = geom_pair[0].value
g2 = geom_pair[1].value
if _pair_count(g1, g2):
count, geomgeomid = _pair_count(g1, g2)
if (g1 == GeomType.HFIELD or g2 == GeomType.HFIELD) and count:
wp.launch(
ccd_hfield_kernel_builder(g1, g2, m.opt.ccd_iterations, epa_iterations),
ccd_hfield_kernel_builder(g1, g2, m.opt.ccd_iterations, epa_iterations, geomgeomid),
dim=d.naconmax,
inputs=[
m.opt.ccd_tolerance,
@@ -1197,15 +1175,10 @@ def convex_narrowphase(m: Model, d: Data):
m.geom_friction,
m.geom_margin,
m.geom_gap,
m.hfield_adr,
m.hfield_nrow,
m.hfield_ncol,
m.hfield_size,
m.hfield_data,
m.mesh_vertadr,
m.mesh_vertnum,
m.mesh_vert,
m.mesh_graphadr,
m.mesh_vert,
m.mesh_graph,
m.mesh_polynum,
m.mesh_polyadr,
@@ -1216,6 +1189,11 @@ def convex_narrowphase(m: Model, d: Data):
m.mesh_polymapadr,
m.mesh_polymapnum,
m.mesh_polymap,
m.hfield_size,
m.hfield_nrow,
m.hfield_ncol,
m.hfield_adr,
m.hfield_data,
m.pair_dim,
m.pair_solref,
m.pair_solreffriction,
@@ -1223,56 +1201,57 @@ def convex_narrowphase(m: Model, d: Data):
m.pair_margin,
m.pair_gap,
m.pair_friction,
d.naconmax,
d.geom_xpos,
d.geom_xmat,
d.collision_pair,
d.collision_pairid,
d.collision_worldid,
d.naconmax,
d.naccdmax,
d.ncollision,
epa_vert1,
epa_vert2,
epa_vert_index1,
epa_vert_index2,
ctx.collision_pair,
ctx.collision_pairid,
ctx.collision_worldid,
epa_vert,
epa_vert_index,
epa_face,
epa_pr,
epa_norm2,
epa_horizon,
nccd,
],
outputs=contact_outputs,
)
# Allocate multiccd arrays only for non-heightfield collisions
# multiccd_polygon: clipped contact surface
multiccd_polygon = wp.empty(shape=(d.naconmax, 2 * nmaxpolygon), dtype=wp.vec3)
multiccd_polygon = wp.empty(shape=(d.naccdmax, 2 * nmaxpolygon), dtype=wp.vec3)
# multiccd_clipped: clipped contact surface (intermediate)
multiccd_clipped = wp.empty(shape=(d.naconmax, 2 * nmaxpolygon), dtype=wp.vec3)
multiccd_clipped = wp.empty(shape=(d.naccdmax, 2 * nmaxpolygon), dtype=wp.vec3)
# multiccd_pnormal: plane normal of clipping polygon
multiccd_pnormal = wp.empty(shape=(d.naconmax, nmaxpolygon), dtype=wp.vec3)
multiccd_pnormal = wp.empty(shape=(d.naccdmax, nmaxpolygon), dtype=wp.vec3)
# multiccd_pdist: plane distance of clipping polygon
multiccd_pdist = wp.empty(shape=(d.naconmax, nmaxpolygon), dtype=float)
multiccd_pdist = wp.empty(shape=(d.naccdmax, nmaxpolygon), dtype=float)
# multiccd_idx1: list of normal index candidates for Geom 1
multiccd_idx1 = wp.empty(shape=(d.naconmax, nmaxmeshdeg), dtype=int)
multiccd_idx1 = wp.empty(shape=(d.naccdmax, nmaxmeshdeg), dtype=int)
# multiccd_idx2: list of normal index candidates for Geom 2
multiccd_idx2 = wp.empty(shape=(d.naconmax, nmaxmeshdeg), dtype=int)
multiccd_idx2 = wp.empty(shape=(d.naccdmax, nmaxmeshdeg), dtype=int)
# multiccd_n1: list of normal candidates for Geom 1
multiccd_n1 = wp.empty(shape=(d.naconmax, nmaxmeshdeg), dtype=wp.vec3)
multiccd_n1 = wp.empty(shape=(d.naccdmax, nmaxmeshdeg), dtype=wp.vec3)
# multiccd_n2: list of normal candidates for Geom 1
multiccd_n2 = wp.empty(shape=(d.naconmax, nmaxmeshdeg), dtype=wp.vec3)
multiccd_n2 = wp.empty(shape=(d.naccdmax, nmaxmeshdeg), dtype=wp.vec3)
# multiccd_endvert: list of edge vertices candidates
multiccd_endvert = wp.empty(shape=(d.naconmax, nmaxmeshdeg), dtype=wp.vec3)
multiccd_endvert = wp.empty(shape=(d.naccdmax, nmaxmeshdeg), dtype=wp.vec3)
# multiccd_face1: contact face
multiccd_face1 = wp.empty(shape=(d.naconmax, nmaxpolygon), dtype=wp.vec3)
multiccd_face1 = wp.empty(shape=(d.naccdmax, nmaxpolygon), dtype=wp.vec3)
# multiccd_face2: contact face
multiccd_face2 = wp.empty(shape=(d.naconmax, nmaxpolygon), dtype=wp.vec3)
multiccd_face2 = wp.empty(shape=(d.naccdmax, nmaxpolygon), dtype=wp.vec3)
# Launch non-heightfield collision kernels (no hfield args, 78 args total)
for geom_pair in _NON_HFIELD_COLLISION_PAIRS:
for geom_pair in collision_table:
g1 = geom_pair[0].value
g2 = geom_pair[1].value
if _pair_count(g1, g2):
count, geomgeomid = _pair_count(g1, g2)
if g1 != GeomType.HFIELD and g2 != GeomType.HFIELD and count:
wp.launch(
ccd_kernel_builder(g1, g2, m.opt.ccd_iterations, epa_iterations, use_multiccd),
ccd_kernel_builder(g1, g2, m.opt.ccd_iterations, epa_iterations, use_multiccd, geomgeomid),
dim=d.naconmax,
inputs=[
m.opt.ccd_tolerance,
@@ -1289,8 +1268,8 @@ def convex_narrowphase(m: Model, d: Data):
m.geom_gap,
m.mesh_vertadr,
m.mesh_vertnum,
m.mesh_vert,
m.mesh_graphadr,
m.mesh_vert,
m.mesh_graph,
m.mesh_polynum,
m.mesh_polyadr,
@@ -1308,17 +1287,16 @@ def convex_narrowphase(m: Model, d: Data):
m.pair_margin,
m.pair_gap,
m.pair_friction,
d.naconmax,
d.geom_xpos,
d.geom_xmat,
d.collision_pair,
d.collision_pairid,
d.collision_worldid,
d.naconmax,
d.naccdmax,
d.ncollision,
epa_vert1,
epa_vert2,
epa_vert_index1,
epa_vert_index2,
ctx.collision_pair,
ctx.collision_pairid,
ctx.collision_worldid,
epa_vert,
epa_vert_index,
epa_face,
epa_pr,
epa_norm2,
@@ -1334,6 +1312,7 @@ def convex_narrowphase(m: Model, d: Data):
multiccd_endvert,
multiccd_face1,
multiccd_face2,
nccd,
],
outputs=contact_outputs,
)
@@ -24,17 +24,65 @@ from mujoco.mjx.third_party.mujoco_warp._src.math import upper_tri_index
from mujoco.mjx.third_party.mujoco_warp._src.types import MJ_MAXVAL
from mujoco.mjx.third_party.mujoco_warp._src.types import BroadphaseFilter
from mujoco.mjx.third_party.mujoco_warp._src.types import BroadphaseType
from mujoco.mjx.third_party.mujoco_warp._src.types import CollisionContext
from mujoco.mjx.third_party.mujoco_warp._src.types import CollisionType
from mujoco.mjx.third_party.mujoco_warp._src.types import Data
from mujoco.mjx.third_party.mujoco_warp._src.types import DisableBit
from mujoco.mjx.third_party.mujoco_warp._src.types import GeomType
from mujoco.mjx.third_party.mujoco_warp._src.types import Model
from mujoco.mjx.third_party.mujoco_warp._src.types import mat23
from mujoco.mjx.third_party.mujoco_warp._src.types import mat63
from mujoco.mjx.third_party.mujoco_warp._src.warp_util import cache_kernel
from mujoco.mjx.third_party.mujoco_warp._src.warp_util import event_scope
from mujoco.mjx.third_party.mujoco_warp._src.warp_util import nested_kernel
wp.set_module_options({"enable_backward": False})
# Corresponding table to MuJoCo's mjCOLLISIONFUNC table in engine_collision_driver.c
MJ_COLLISION_TABLE = {
(GeomType.PLANE, GeomType.SPHERE): CollisionType.PRIMITIVE,
(GeomType.PLANE, GeomType.CAPSULE): CollisionType.PRIMITIVE,
(GeomType.PLANE, GeomType.ELLIPSOID): CollisionType.PRIMITIVE,
(GeomType.PLANE, GeomType.CYLINDER): CollisionType.PRIMITIVE,
(GeomType.PLANE, GeomType.BOX): CollisionType.PRIMITIVE,
(GeomType.PLANE, GeomType.MESH): CollisionType.PRIMITIVE,
(GeomType.HFIELD, GeomType.SPHERE): CollisionType.CONVEX,
(GeomType.HFIELD, GeomType.CAPSULE): CollisionType.CONVEX,
(GeomType.HFIELD, GeomType.ELLIPSOID): CollisionType.CONVEX,
(GeomType.HFIELD, GeomType.CYLINDER): CollisionType.CONVEX,
(GeomType.HFIELD, GeomType.BOX): CollisionType.CONVEX,
(GeomType.HFIELD, GeomType.MESH): CollisionType.CONVEX,
(GeomType.SPHERE, GeomType.SPHERE): CollisionType.PRIMITIVE,
(GeomType.SPHERE, GeomType.CAPSULE): CollisionType.PRIMITIVE,
(GeomType.SPHERE, GeomType.ELLIPSOID): CollisionType.CONVEX,
(GeomType.SPHERE, GeomType.CYLINDER): CollisionType.PRIMITIVE,
(GeomType.SPHERE, GeomType.BOX): CollisionType.PRIMITIVE,
(GeomType.SPHERE, GeomType.MESH): CollisionType.CONVEX,
(GeomType.CAPSULE, GeomType.CAPSULE): CollisionType.PRIMITIVE,
(GeomType.CAPSULE, GeomType.ELLIPSOID): CollisionType.CONVEX,
(GeomType.CAPSULE, GeomType.CYLINDER): CollisionType.CONVEX,
(GeomType.CAPSULE, GeomType.BOX): CollisionType.PRIMITIVE,
(GeomType.CAPSULE, GeomType.MESH): CollisionType.CONVEX,
(GeomType.ELLIPSOID, GeomType.ELLIPSOID): CollisionType.CONVEX,
(GeomType.ELLIPSOID, GeomType.CYLINDER): CollisionType.CONVEX,
(GeomType.ELLIPSOID, GeomType.BOX): CollisionType.CONVEX,
(GeomType.ELLIPSOID, GeomType.MESH): CollisionType.CONVEX,
(GeomType.CYLINDER, GeomType.CYLINDER): CollisionType.CONVEX,
(GeomType.CYLINDER, GeomType.BOX): CollisionType.CONVEX,
(GeomType.CYLINDER, GeomType.MESH): CollisionType.CONVEX,
(GeomType.BOX, GeomType.BOX): CollisionType.CONVEX, # overwritten by NATIVECCD disable flag
(GeomType.BOX, GeomType.MESH): CollisionType.CONVEX,
(GeomType.MESH, GeomType.MESH): CollisionType.CONVEX,
}
def create_collision_context(naconmax: int) -> CollisionContext:
"""Create a CollisionContext with allocated arrays."""
return CollisionContext(
collision_pair=wp.empty(naconmax, dtype=wp.vec2i),
collision_pairid=wp.empty(naconmax, dtype=wp.vec2i),
collision_worldid=wp.empty(naconmax, dtype=int),
)
@wp.kernel
def _zero_nacon_ncollision(
@@ -293,10 +341,11 @@ def _add_geom_pair(
worldid: int,
nxnid: int,
# Data out:
ncollision_out: wp.array(dtype=int),
# Out:
collision_pair_out: wp.array(dtype=wp.vec2i),
collision_pairid_out: wp.array(dtype=wp.vec2i),
collision_worldid_out: wp.array(dtype=int),
ncollision_out: wp.array(dtype=int),
):
pairid = wp.atomic_add(ncollision_out, 0, 1)
@@ -329,15 +378,15 @@ def _binary_search(values: wp.array(dtype=Any), value: Any, lower: int, upper: i
def _sap_project(opt_broadphase: int):
@nested_kernel(module="unique", enable_backward=False)
@wp.kernel(module="unique", enable_backward=False)
def sap_project(
# Model:
ngeom: int,
geom_rbound: wp.array2d(dtype=float),
geom_margin: wp.array2d(dtype=float),
# Data in:
nworld_in: int,
geom_xpos_in: wp.array2d(dtype=wp.vec3),
nworld_in: int,
# In:
direction_in: wp.vec3,
# Out:
@@ -402,7 +451,7 @@ def _sap_range(
@cache_kernel
def _sap_broadphase(opt_broadphase_filter: int, ngeom_aabb: int, ngeom_rbound: int, ngeom_margin: int):
@nested_kernel(module="unique", enable_backward=False)
@wp.kernel(module="unique", enable_backward=False)
def kernel(
# Model:
ngeom: int,
@@ -412,19 +461,20 @@ def _sap_broadphase(opt_broadphase_filter: int, ngeom_aabb: int, ngeom_rbound: i
geom_margin: wp.array2d(dtype=float),
nxn_pairid: wp.array(dtype=wp.vec2i),
# Data in:
nworld_in: int,
naconmax_in: int,
geom_xpos_in: wp.array2d(dtype=wp.vec3),
geom_xmat_in: wp.array2d(dtype=wp.mat33),
nworld_in: int,
naconmax_in: int,
# In:
sort_index_in: wp.array2d(dtype=int),
cumulative_sum_in: wp.array(dtype=int),
nsweep_in: int,
# Data out:
ncollision_out: wp.array(dtype=int),
# Out:
collision_pair_out: wp.array(dtype=wp.vec2i),
collision_pairid_out: wp.array(dtype=wp.vec2i),
collision_worldid_out: wp.array(dtype=int),
ncollision_out: wp.array(dtype=int),
):
worldgeomid = wp.tid()
@@ -472,17 +522,17 @@ def _sap_broadphase(opt_broadphase_filter: int, ngeom_aabb: int, ngeom_rbound: i
geom2,
worldid,
idx,
ncollision_out,
collision_pair_out,
collision_pairid_out,
collision_worldid_out,
ncollision_out,
)
return kernel
def _segmented_sort(tile_size: int):
@wp.kernel
@wp.kernel(module="unique")
def segmented_sort(
# In:
projection_lower_in: wp.array2d(dtype=float),
@@ -508,7 +558,7 @@ def _segmented_sort(tile_size: int):
@event_scope
def sap_broadphase(m: Model, d: Data):
def sap_broadphase(m: Model, d: Data, ctx: CollisionContext):
"""Runs broadphase collision detection using a sweep-and-prune (SAP) algorithm.
This method is more efficient than the N-squared approach for large numbers of
@@ -543,7 +593,7 @@ def sap_broadphase(m: Model, d: Data):
wp.launch(
kernel=_sap_project(m.opt.broadphase),
dim=(d.nworld, m.ngeom),
inputs=[m.ngeom, m.geom_rbound, m.geom_margin, d.nworld, d.geom_xpos, direction],
inputs=[m.ngeom, m.geom_rbound, m.geom_margin, d.geom_xpos, d.nworld, direction],
outputs=[
projection_lower.reshape((-1, m.ngeom)),
projection_upper,
@@ -588,21 +638,21 @@ def sap_broadphase(m: Model, d: Data):
m.geom_rbound,
m.geom_margin,
m.nxn_pairid,
d.nworld,
d.naconmax,
d.geom_xpos,
d.geom_xmat,
d.nworld,
d.naconmax,
sort_index.reshape((-1, m.ngeom)),
cumulative_sum.reshape(-1),
nsweep,
],
outputs=[d.collision_pair, d.collision_pairid, d.collision_worldid, d.ncollision],
outputs=[d.ncollision, ctx.collision_pair, ctx.collision_pairid, ctx.collision_worldid],
)
@cache_kernel
def _nxn_broadphase(opt_broadphase_filter: int, ngeom_aabb: int, ngeom_rbound: int, ngeom_margin: int):
@nested_kernel(module="unique", enable_backward=False)
@wp.kernel(module="unique", enable_backward=False)
def kernel(
# Model:
geom_type: wp.array(dtype=int),
@@ -612,14 +662,15 @@ def _nxn_broadphase(opt_broadphase_filter: int, ngeom_aabb: int, ngeom_rbound: i
nxn_geom_pair: wp.array(dtype=wp.vec2i),
nxn_pairid: wp.array(dtype=wp.vec2i),
# Data in:
naconmax_in: int,
geom_xpos_in: wp.array2d(dtype=wp.vec3),
geom_xmat_in: wp.array2d(dtype=wp.mat33),
naconmax_in: int,
# Data out:
ncollision_out: wp.array(dtype=int),
# Out:
collision_pair_out: wp.array(dtype=wp.vec2i),
collision_pairid_out: wp.array(dtype=wp.vec2i),
collision_worldid_out: wp.array(dtype=int),
ncollision_out: wp.array(dtype=int),
):
worldid, elementid = wp.tid()
@@ -641,17 +692,17 @@ def _nxn_broadphase(opt_broadphase_filter: int, ngeom_aabb: int, ngeom_rbound: i
geom2,
worldid,
elementid,
ncollision_out,
collision_pair_out,
collision_pairid_out,
collision_worldid_out,
ncollision_out,
)
return kernel
@event_scope
def nxn_broadphase(m: Model, d: Data):
def nxn_broadphase(m: Model, d: Data, ctx: CollisionContext):
"""Runs broadphase collision detection using a brute-force N-squared approach.
This function iterates through a pre-filtered list of all possible geometry pairs and
@@ -674,27 +725,34 @@ def nxn_broadphase(m: Model, d: Data):
m.geom_margin,
m.nxn_geom_pair_filtered,
m.nxn_pairid_filtered,
d.naconmax,
d.geom_xpos,
d.geom_xmat,
d.naconmax,
],
outputs=[
d.collision_pair,
d.collision_pairid,
d.collision_worldid,
d.ncollision,
ctx.collision_pair,
ctx.collision_pairid,
ctx.collision_worldid,
],
)
def _narrowphase(m, d):
def _narrowphase(m: Model, d: Data, ctx: CollisionContext):
collision_table = MJ_COLLISION_TABLE
if m.opt.disableflags & DisableBit.NATIVECCD:
collision_table[(GeomType.BOX, GeomType.BOX)] = CollisionType.PRIMITIVE
convex_pairs = [key for key, value in collision_table.items() if value == CollisionType.CONVEX]
primitive_pairs = [key for key, value in collision_table.items() if value == CollisionType.PRIMITIVE]
# TODO(team): we should reject far-away contacts in the narrowphase instead of constraint
# partitioning because we can move some pressure of the atomics
convex_narrowphase(m, d)
primitive_narrowphase(m, d)
convex_narrowphase(m, d, ctx, convex_pairs)
primitive_narrowphase(m, d, ctx, primitive_pairs)
if m.has_sdf_geom:
sdf_narrowphase(m, d)
sdf_narrowphase(m, d, ctx)
@event_scope
@@ -715,15 +773,18 @@ def collision(m: Model, d: Data):
This function will do nothing except zero out arrays if collision detection is disabled
via `m.opt.disableflags` or if `d.nacon` is 0.
"""
# zero contact and collision counters
wp.launch(_zero_nacon_ncollision, dim=1, outputs=[d.nacon, d.ncollision])
if d.naconmax == 0 or m.opt.disableflags & (DisableBit.CONSTRAINT | DisableBit.CONTACT):
d.nacon.zero_()
return
if m.opt.broadphase == BroadphaseType.NXN:
nxn_broadphase(m, d)
else:
sap_broadphase(m, d)
ctx = create_collision_context(d.naconmax)
_narrowphase(m, d)
# zero counters
wp.launch(_zero_nacon_ncollision, dim=1, outputs=[d.nacon, d.ncollision])
if m.opt.broadphase == BroadphaseType.NXN:
nxn_broadphase(m, d, ctx)
else:
sap_broadphase(m, d, ctx)
_narrowphase(m, d, ctx)
+176 -177
View File
@@ -13,6 +13,7 @@
# limitations under the License.
# ==============================================================================
import math
from typing import Tuple
import warp as wp
@@ -30,9 +31,11 @@ FLOAT_MAX = 1e30
MINVAL = 1e-15
MIN_DIST = 1e-10
# TODO(kbayes): write out formulas to derive these constants
FACE_TOL = 0.99999872
EDGE_TOL = 0.00159999931
FACE_TOL = wp.static(math.cos(0.0016))
EDGE_TOL = wp.static(math.sin(0.0016))
# tolarance used by multicontact for intersecting a plane and a line segment
INTERSECT_TOL = 0.0000003
# Bit flags for face status in EPA polytope.
# Defined at module scope to avoid Warp's intermediate type issues with literals.
@@ -59,11 +62,9 @@ class GJKResult:
class Polytope:
status: int
# vertices in polytope
vert1: wp.array(dtype=wp.vec3)
vert2: wp.array(dtype=wp.vec3)
vert_index1: wp.array(dtype=int)
vert_index2: wp.array(dtype=int)
# vertices in polytope (packed geom1 followed by geom2)
vert: wp.array(dtype=wp.vec3)
vert_index: wp.array(dtype=int)
nvert: int
# faces in polytope
@@ -107,9 +108,9 @@ def support(geom: Geom, geomtype: int, dir: wp.vec3) -> SupportPoint:
tmp = wp.sign(local_dir)
res = wp.cw_mul(tmp, geom.size)
sp.point = geom.rot @ res + geom.pos
sp.vertex_index = wp.where(tmp[0] > 0, 1, 0)
sp.vertex_index += wp.where(tmp[1] > 0, 2, 0)
sp.vertex_index += wp.where(tmp[2] > 0, 4, 0)
sp.vertex_index = wp.where(tmp[0] > 0.0, 1, 0)
sp.vertex_index += wp.where(tmp[1] > 0.0, 2, 0)
sp.vertex_index += wp.where(tmp[2] > 0.0, 4, 0)
elif geomtype == GeomType.CAPSULE:
res = local_dir * geom.size[0]
# add cylinder contribution
@@ -177,7 +178,7 @@ def support(geom: Geom, geomtype: int, dir: wp.vec3) -> SupportPoint:
elif geomtype == GeomType.HFIELD:
max_dist = float(FLOAT_MIN)
# TODO(kbayes): Support edge prisms
sp.vertex_index = wp.where(dir[2] < 0, -2, -3)
sp.vertex_index = wp.where(dir[2] < 0.0, -2, -3)
for i in range(6):
vert = geom.hfprism[i]
dist = wp.dot(vert, dir)
@@ -197,7 +198,10 @@ def _attach_face(pt: Polytope, idx: int, v1: int, v2: int, v3: int) -> float:
return 0.0
# compute witness point v
r, ret = _project_origin_plane(pt.vert1[v3] - pt.vert2[v3], pt.vert1[v2] - pt.vert2[v2], pt.vert1[v1] - pt.vert2[v1])
p1 = pt.vert[2 * v1] - pt.vert[2 * v1 + 1]
p2 = pt.vert[2 * v2] - pt.vert[2 * v2 + 1]
p3 = pt.vert[2 * v3] - pt.vert[2 * v3 + 1]
r, ret = _project_origin_plane(p3, p2, p1)
if ret:
return 0.0
@@ -214,13 +218,13 @@ def _epa_support(
pt: Polytope, idx: int, geom1: Geom, geom2: Geom, geom1_type: int, geom2_type: int, dir: wp.vec3
) -> Tuple[int, int]:
sp = support(geom1, geom1_type, dir)
pt.vert1[idx] = sp.point
pt.vert_index1[idx] = sp.vertex_index
pt.vert[2 * idx] = sp.point
pt.vert_index[2 * idx] = sp.vertex_index
index1 = sp.cached_index
sp = support(geom2, geom2_type, -dir)
pt.vert2[idx] = sp.point
pt.vert_index2[idx] = sp.vertex_index
pt.vert[2 * idx + 1] = sp.point
pt.vert_index[2 * idx + 1] = sp.vertex_index
index2 = sp.cached_index
return index1, index2
@@ -265,9 +269,9 @@ def _det3(v1: wp.vec3, v2: wp.vec3, v3: wp.vec3) -> float:
@wp.func
def _same_sign(a: float, b: float) -> int:
if a > 0 and b > 0:
if a > 0.0 and b > 0.0:
return 1
if a < 0 and b < 0:
if a < 0.0 and b < 0.0:
return -1
return 0
@@ -290,28 +294,25 @@ def _project_origin_plane(v1: wp.vec3, v2: wp.vec3, v3: wp.vec3) -> Tuple[wp.vec
n = wp.cross(diff32, diff21)
nv = wp.dot(n, v2)
nn = wp.dot(n, n)
if nn == 0:
if nn == 0.0:
return z, 1
if nv != 0 and nn > MINVAL:
v = (nv / nn) * n
return v, 0
if nv != 0.0 and nn > MINVAL:
return (nv / nn) * n, 0
# n = (v2 - v1) x (v3 - v1)
n = wp.cross(diff21, diff31)
nv = wp.dot(n, v1)
nn = wp.dot(n, n)
if nn == 0:
if nn == 0.0:
return z, 1
if nv != 0 and nn > MINVAL:
v = (nv / nn) * n
return v, 0
if nv != 0.0 and nn > MINVAL:
return (nv / nn) * n, 0
# n = (v1 - v3) x (v2 - v3)
n = wp.cross(diff31, diff32)
nv = wp.dot(n, v3)
nn = wp.dot(n, n)
v = (nv / nn) * n
return v, 0
return (nv / nn) * n, 0
@wp.func
@@ -610,14 +611,14 @@ def gjk(
simplex[n] = simplex1[n] - simplex2[n]
if cutoff == 0.0:
if wp.dot(x_k, simplex[n]) > 0:
if wp.dot(x_k, simplex[n]) > 0.0:
result = GJKResult()
result.dim = 0
result.dist = FLOAT_MAX
return result
elif cutoff < FLOAT_MAX:
vs = wp.dot(x_k, simplex[n])
if wp.dot(x_k, simplex[n]) > 0 and (vs * vs / xnorm) >= cutoff2:
if wp.dot(x_k, simplex[n]) > 0.0 and (vs * vs / xnorm) >= cutoff2:
result = GJKResult()
result.dim = 0
result.dist = FLOAT_MAX
@@ -635,7 +636,7 @@ def gjk(
# remove vertices from the simplex no longer needed
n = int(0)
for i in range(4):
if coordinates[i] == 0:
if coordinates[i] == 0.0:
continue
simplex[n] = simplex[i]
@@ -692,7 +693,7 @@ def _same_side(p0: wp.vec3, p1: wp.vec3, p2: wp.vec3, p3: wp.vec3) -> bool:
n = wp.cross(p1 - p0, p2 - p0)
dot1 = wp.dot(n, p3 - p0)
dot2 = wp.dot(n, -p0)
return (dot1 > 0 and dot2 > 0) or (dot1 < 0 and dot2 < 0)
return (dot1 > 0.0 and dot2 > 0.0) or (dot1 < 0.0 and dot2 < 0.0)
@wp.func
@@ -750,7 +751,7 @@ def _tri_point_intersect(v1: wp.vec3, v2: wp.vec3, v3: wp.vec3, p: wp.vec3) -> b
l2 = coordinates[1]
l3 = coordinates[2]
if l1 < 0 or l2 < 0 or l3 < 0:
if l1 < 0.0 or l2 < 0.0 or l3 < 0.0:
return False
pr = wp.vec3()
@@ -766,14 +767,14 @@ def _replace_simplex3(pt: Polytope, v1: int, v2: int, v3: int) -> GJKResult:
# reset GJK simplex
simplex1 = mat43()
simplex1[0] = pt.vert1[v1]
simplex1[1] = pt.vert1[v2]
simplex1[2] = pt.vert1[v3]
simplex1[0] = pt.vert[2 * v1]
simplex1[1] = pt.vert[2 * v2]
simplex1[2] = pt.vert[2 * v3]
simplex2 = mat43()
simplex2[0] = pt.vert2[v1]
simplex2[1] = pt.vert2[v2]
simplex2[2] = pt.vert2[v3]
simplex2[0] = pt.vert[2 * v1 + 1]
simplex2[1] = pt.vert[2 * v2 + 1]
simplex2[2] = pt.vert[2 * v3 + 1]
simplex = mat43()
simplex[0] = simplex1[0] - simplex2[0]
@@ -781,14 +782,14 @@ def _replace_simplex3(pt: Polytope, v1: int, v2: int, v3: int) -> GJKResult:
simplex[2] = simplex1[2] - simplex2[2]
simplex_index1 = wp.vec4i()
simplex_index1[0] = pt.vert_index1[v1]
simplex_index1[1] = pt.vert_index1[v2]
simplex_index1[2] = pt.vert_index1[v3]
simplex_index1[0] = pt.vert_index[2 * v1]
simplex_index1[1] = pt.vert_index[2 * v2]
simplex_index1[2] = pt.vert_index[2 * v3]
simplex_index2 = wp.vec4i()
simplex_index2[0] = pt.vert_index2[v1]
simplex_index2[1] = pt.vert_index2[v2]
simplex_index2[2] = pt.vert_index2[v3]
simplex_index2[0] = pt.vert_index[2 * v1 + 1]
simplex_index2[1] = pt.vert_index[2 * v2 + 1]
simplex_index2[2] = pt.vert_index[2 * v3 + 1]
result.simplex = simplex
result.simplex1 = simplex1
@@ -827,9 +828,9 @@ def _ray_triangle(v1: wp.vec3, v2: wp.vec3, v3: wp.vec3, v4: wp.vec3, v5: wp.vec
vol2 = _det3(v4 - v1, v5 - v1, v2 - v1)
vol3 = _det3(v5 - v1, v3 - v1, v2 - v1)
if vol1 >= 0 and vol2 >= 0 and vol3 >= 0:
if vol1 >= 0.0 and vol2 >= 0.0 and vol3 >= 0.0:
return 1
if vol1 <= 0 and vol2 <= 0 and vol3 <= 0:
if vol1 <= 0.0 and vol2 <= 0.0 and vol3 <= 0.0:
return -1
return 0
@@ -867,9 +868,9 @@ def _epa_witness(
) -> Tuple[wp.vec3, wp.vec3, float]:
face = _get_face_verts(pt.face[face_idx])
# compute affine coordinates for witness points on plane defined by face
v1 = pt.vert1[face[0]] - pt.vert2[face[0]]
v2 = pt.vert1[face[1]] - pt.vert2[face[1]]
v3 = pt.vert1[face[2]] - pt.vert2[face[2]]
v1 = pt.vert[2 * face[0]] - pt.vert[2 * face[0] + 1]
v2 = pt.vert[2 * face[1]] - pt.vert[2 * face[1] + 1]
v3 = pt.vert[2 * face[2]] - pt.vert[2 * face[2] + 1]
coordinates = _tri_affine_coord(v1, v2, v3, pt.face_pr[face_idx])
l1 = coordinates[0]
@@ -877,18 +878,18 @@ def _epa_witness(
l3 = coordinates[2]
# face on geom 2
v1 = pt.vert2[face[0]]
v2 = pt.vert2[face[1]]
v3 = pt.vert2[face[2]]
v1 = pt.vert[2 * face[0] + 1]
v2 = pt.vert[2 * face[1] + 1]
v3 = pt.vert[2 * face[2] + 1]
x2 = wp.vec3()
x2[0] = v1[0] * l1 + v2[0] * l2 + v3[0] * l3
x2[1] = v1[1] * l1 + v2[1] * l2 + v3[1] * l3
x2[2] = v1[2] * l1 + v2[2] * l2 + v3[2] * l3
# correct witness points for hfield geoms
i1 = pt.vert_index1[face[0]]
i2 = pt.vert_index1[face[1]]
i3 = pt.vert_index1[face[2]]
i1 = pt.vert_index[2 * face[0]]
i2 = pt.vert_index[2 * face[1]]
i3 = pt.vert_index[2 * face[2]]
if geomtype1 == GeomType.HFIELD and (i1 != i2 or i1 != i3):
# TODO(kbayes): Fix case where geom2 is near bottom of height field or "extreme" prism heights
n = wp.vec3(0.0, 0.0, 1.0)
@@ -914,7 +915,7 @@ def _epa_witness(
x2 = sp.point
coordinates2 = _tri_affine_coord(a, b, c, x2)
if coordinates2[0] > 0 and coordinates2[1] > 0 and coordinates2[2] > 0:
if coordinates2[0] > 0.0 and coordinates2[1] > 0.0 and coordinates2[2] > 0.0:
x1 = coordinates2[0] * a + coordinates2[1] * b + coordinates2[2] * c
else:
p = c
@@ -924,9 +925,9 @@ def _epa_witness(
return x1, x2, -wp.norm_l2(x1 - x2)
# face on geom 1
v1 = pt.vert1[face[0]]
v2 = pt.vert1[face[1]]
v3 = pt.vert1[face[2]]
v1 = pt.vert[2 * face[0]]
v2 = pt.vert[2 * face[1]]
v3 = pt.vert[2 * face[2]]
x1 = wp.vec3()
x1[0] = v1[0] * l1 + v2[0] * l2 + v3[0] * l3
x1[1] = v1[1] * l1 + v2[1] * l2 + v3[1] * l3
@@ -971,17 +972,15 @@ def _polytope2(
d3 = R @ d2
# save vertices and get indices for each one
pt.vert1[0] = simplex1[0]
pt.vert1[1] = simplex1[1]
pt.vert[0] = simplex1[0]
pt.vert[1] = simplex2[0]
pt.vert[2] = simplex1[1]
pt.vert[3] = simplex2[1]
pt.vert_index1[0] = simplex_index1[0]
pt.vert_index1[1] = simplex_index1[1]
pt.vert2[0] = simplex2[0]
pt.vert2[1] = simplex2[1]
pt.vert_index2[0] = simplex_index2[0]
pt.vert_index2[1] = simplex_index2[1]
pt.vert_index[0] = simplex_index1[0]
pt.vert_index[1] = simplex_index2[0]
pt.vert_index[2] = simplex_index1[1]
pt.vert_index[3] = simplex_index2[1]
_epa_support(pt, 2, geom1, geom2, geomtype1, geomtype2, d1 / wp.norm_l2(d1))
_epa_support(pt, 3, geom1, geom2, geomtype1, geomtype2, d2 / wp.norm_l2(d2))
@@ -1013,9 +1012,9 @@ def _polytope2(
return pt, _replace_simplex3(pt, 1, 4, 3)
# check hexahedron is convex
v2 = pt.vert1[2] - pt.vert2[2]
v3 = pt.vert1[3] - pt.vert2[3]
v4 = pt.vert1[4] - pt.vert2[4]
v2 = pt.vert[4] - pt.vert[5]
v3 = pt.vert[6] - pt.vert[7]
v4 = pt.vert[8] - pt.vert[9]
if not _ray_triangle(simplex[0], simplex[1], v2, v3, v4):
pt.status = 1
return pt, GJKResult()
@@ -1049,21 +1048,19 @@ def _polytope3(
pt.status = 2
return pt
pt.vert1[0] = simplex1[0]
pt.vert1[1] = simplex1[1]
pt.vert1[2] = simplex1[2]
pt.vert[0] = simplex1[0]
pt.vert[1] = simplex2[0]
pt.vert[2] = simplex1[1]
pt.vert[3] = simplex2[1]
pt.vert[4] = simplex1[2]
pt.vert[5] = simplex2[2]
pt.vert_index1[0] = simplex_index1[0]
pt.vert_index1[1] = simplex_index1[1]
pt.vert_index1[2] = simplex_index1[2]
pt.vert2[0] = simplex2[0]
pt.vert2[1] = simplex2[1]
pt.vert2[2] = simplex2[2]
pt.vert_index2[0] = simplex_index2[0]
pt.vert_index2[1] = simplex_index2[1]
pt.vert_index2[2] = simplex_index2[2]
pt.vert_index[0] = simplex_index1[0]
pt.vert_index[1] = simplex_index2[0]
pt.vert_index[2] = simplex_index1[1]
pt.vert_index[3] = simplex_index2[1]
pt.vert_index[4] = simplex_index1[2]
pt.vert_index[5] = simplex_index2[2]
_epa_support(pt, 3, geom1, geom2, geomtype1, geomtype2, -n)
_epa_support(pt, 4, geom1, geom2, geomtype1, geomtype2, n)
@@ -1071,8 +1068,8 @@ def _polytope3(
v1 = simplex[0]
v2 = simplex[1]
v3 = simplex[2]
v4 = pt.vert1[3] - pt.vert2[3]
v5 = pt.vert1[4] - pt.vert2[4]
v4 = pt.vert[6] - pt.vert[7]
v5 = pt.vert[8] - pt.vert[9]
# check that v4 is not contained in the 2-simplex
if _tri_point_intersect(v1, v2, v3, v4):
@@ -1128,25 +1125,23 @@ def _polytope4(
simplex_index2: wp.vec4i,
) -> Tuple[Polytope, GJKResult]:
"""Create polytope for EPA given a 3-simplex from GJK."""
pt.vert1[0] = simplex1[0]
pt.vert1[1] = simplex1[1]
pt.vert1[2] = simplex1[2]
pt.vert1[3] = simplex1[3]
pt.vert[0] = simplex1[0]
pt.vert[1] = simplex2[0]
pt.vert[2] = simplex1[1]
pt.vert[3] = simplex2[1]
pt.vert[4] = simplex1[2]
pt.vert[5] = simplex2[2]
pt.vert[6] = simplex1[3]
pt.vert[7] = simplex2[3]
pt.vert_index1[0] = simplex_index1[0]
pt.vert_index1[1] = simplex_index1[1]
pt.vert_index1[2] = simplex_index1[2]
pt.vert_index1[3] = simplex_index1[3]
pt.vert2[0] = simplex2[0]
pt.vert2[1] = simplex2[1]
pt.vert2[2] = simplex2[2]
pt.vert2[3] = simplex2[3]
pt.vert_index2[0] = simplex_index2[0]
pt.vert_index2[1] = simplex_index2[1]
pt.vert_index2[2] = simplex_index2[2]
pt.vert_index2[3] = simplex_index2[3]
pt.vert_index[0] = simplex_index1[0]
pt.vert_index[1] = simplex_index2[0]
pt.vert_index[2] = simplex_index1[1]
pt.vert_index[3] = simplex_index2[1]
pt.vert_index[4] = simplex_index1[2]
pt.vert_index[5] = simplex_index2[2]
pt.vert_index[6] = simplex_index1[3]
pt.vert_index[7] = simplex_index2[3]
# if the origin is on a face, replace the 3-simplex with a 2-simplex
if _attach_face(pt, 0, 0, 1, 2) < MIN_DIST:
@@ -1249,7 +1244,7 @@ def _epa(
break
# check if lower bound is 0
if lower2 <= 0:
if lower2 <= 0.0:
break
# compute support point w from the closest face's normal
@@ -1257,7 +1252,7 @@ def _epa(
wi = pt.nvert
face_pr_normalized = pt.face_pr[idx] / lower
i1, i2 = _epa_support(pt, wi, geom1, geom2, geomtype1, geomtype2, face_pr_normalized)
w = pt.vert1[wi] - pt.vert2[wi]
w = pt.vert[2 * wi] - pt.vert[2 * wi + 1]
geom1.index = i1
geom2.index = i2
pt.nvert += 1
@@ -1275,7 +1270,7 @@ def _epa(
if is_discrete:
found_repeated = bool(False)
for i in range(pt.nvert - 1):
if pt.vert_index1[i] == pt.vert_index1[wi] and pt.vert_index2[i] == pt.vert_index2[wi]:
if pt.vert_index[2 * i] == pt.vert_index[2 * wi] and pt.vert_index[2 * i + 1] == pt.vert_index[2 * wi + 1]:
found_repeated = True
break
if found_repeated:
@@ -1313,7 +1308,7 @@ def _epa(
for i in range(pt.nhorizon):
edge = _get_edge(pt.horizon[i])
dist2 = _attach_face(pt, pt.nface, wi, edge[0], edge[1])
if dist2 == 0:
if dist2 == 0.0:
idx = -1
break
@@ -1348,72 +1343,66 @@ def _area4(a: wp.vec3, b: wp.vec3, c: wp.vec3, d: wp.vec3) -> float:
return 0.5 * wp.norm_l2(wp.cross(a - d, d - b) + wp.cross(b - c, c - a))
@wp.func
def _next(n: int, i: int) -> int:
"""Returns (i + 1) mod n for 0 <= i <= n - 1."""
return wp.where(i == n - 1, 0, i + 1)
@wp.func
def _polygon_quad(polygon: wp.array(dtype=wp.vec3), npolygon: int) -> wp.vec4i:
"""Returns the indices of a quadrilateral of maximum area in a convex polygon."""
b = _next(npolygon, 0)
c = _next(npolygon, b)
d = _next(npolygon, c)
"""Returns the indices of a quadrilateral of maximum area in a convex polygon (npolygon > 4)."""
b = int(1)
c = int(2)
d = int(3)
res = wp.vec4i(0, b, c, d)
m = _area4(polygon[0], polygon[b], polygon[c], polygon[d])
for a in range(npolygon):
while True:
m_next = _area4(polygon[a], polygon[b], polygon[c], polygon[_next(npolygon, d)])
m_next = _area4(polygon[a], polygon[b], polygon[c], polygon[(d + 1) % npolygon])
if m_next <= m:
break
m = m_next
d = _next(npolygon, d)
d = (d + 1) % npolygon
res = wp.vec4i(a, b, c, d)
while True:
m_next = _area4(polygon[a], polygon[b], polygon[_next(npolygon, c)], polygon[d])
m_next = _area4(polygon[a], polygon[b], polygon[(c + 1) % npolygon], polygon[d])
if m_next <= m:
break
m = m_next
c = _next(npolygon, c)
c = (c + 1) % npolygon
res = wp.vec4i(a, b, c, d)
while True:
m_next = _area4(polygon[a], polygon[_next(npolygon, b)], polygon[c], polygon[d])
m_next = _area4(polygon[a], polygon[(b + 1) % npolygon], polygon[c], polygon[d])
if m_next <= m:
break
m = m_next
b = _next(npolygon, b)
b = (b + 1) % npolygon
res = wp.vec4i(a, b, c, d)
if b == a:
b = _next(npolygon, b)
b = (b + 1) % npolygon
if c == b:
c = _next(npolygon, c)
c = (c + 1) % npolygon
if d == c:
d = _next(npolygon, d)
d = (d + 1) % npolygon
return res
# return number (1, 2 or 3) of dimensions of a simplex; reorder vertices if necessary
@wp.func
def _feature_dim(
face: wp.vec3i, vert_index: wp.array(dtype=int), vert: wp.array(dtype=wp.vec3)
face: wp.vec3i, vert_index: wp.array(dtype=int), vert: wp.array(dtype=wp.vec3), offset: int
) -> Tuple[int, wp.vec3i, wp.mat33]:
v1i = vert_index[face[0]]
v2i = vert_index[face[1]]
v3i = vert_index[face[2]]
v1i = vert_index[2 * face[0] + offset]
v2i = vert_index[2 * face[1] + offset]
v3i = vert_index[2 * face[2] + offset]
feature_index = wp.vec3i(v1i, v2i, v3i)
feature_vert = wp.mat33()
feature_vert[0] = vert[face[0]]
feature_vert[1] = vert[face[1]]
feature_vert[2] = vert[face[2]]
feature_vert[0] = vert[2 * face[0] + offset]
feature_vert[1] = vert[2 * face[1] + offset]
feature_vert[2] = vert[2 * face[2] + offset]
if v1i != v2i:
dim = wp.where(v3i == v1i or v3i == v2i, 2, 3)
return dim, feature_index, feature_vert
feature_index[1] = v3i
feature_vert[1] = vert[face[2]]
feature_vert[1] = vert[2 * face[2] + offset]
dim = wp.where(v1i != v3i, 2, 1)
return dim, feature_index, feature_vert
@@ -1675,7 +1664,7 @@ def _box_normals(
y = float((v1 & 2) and (v2 & 2)) - float(not (v1 & 2) and not (v2 & 2))
z = float((v1 & 4) and (v2 & 4)) - float(not (v1 & 4) and not (v2 & 4))
if x != 0.0:
normal_out[c] = mat @ wp.vec3(float(x), 0.0, 0.0)
normal_out[c] = mat @ wp.vec3(x, 0.0, 0.0)
index_out[c] = wp.where(x > 0.0, 0, 1)
c += 1
if y != 0.0:
@@ -1686,7 +1675,9 @@ def _box_normals(
normal_out[c] = mat @ wp.vec3(0.0, 0.0, z)
index_out[c] = wp.where(z > 0.0, 4, 5)
c += 1
if c == 2:
# c is 1 if edge is diagonal of a box face
# c is 2 if edge is an external edge of box
if c == 1 or c == 2:
return 2
return _box_normals2(mat, dir, normal_out, index_out)
@@ -1824,18 +1815,15 @@ def _halfspace(a: wp.vec3, n: wp.vec3, p: wp.vec3) -> bool:
@wp.func
def _plane_intersect(pn: wp.vec3, pd: float, a: wp.vec3, b: wp.vec3) -> Tuple[float, wp.vec3]:
res = wp.vec3()
ab = b - a
temp = wp.dot(pn, ab)
if temp == 0.0:
return FLOAT_MAX, res # parallel; no intersection
t = (pd - wp.dot(pn, a)) / temp
if t >= 0.0 and t <= 1.0:
res[0] = a[0] + t * ab[0]
res[1] = a[1] + t * ab[1]
res[2] = a[2] + t * ab[2]
return t, res
def _plane_intersect(pn: wp.vec3, pd: float, a: wp.vec3, b: wp.vec3) -> float:
"""Returns the parameter t where the line a + t(b - a) intersects the given plane."""
dot = wp.dot(pn, b - a)
# parallel; no intersection
if wp.abs(dot) < 1e-10:
return FLOAT_MAX
return (pd - wp.dot(pn, a)) / dot
# clip a polygon against another polygon
@@ -1901,9 +1889,10 @@ def _polygon_clip(
continue
# add new vertex to clipped polygon where PQ intersects the clipping edge
t, res = _plane_intersect(pn[e], pd[e], P, Q)
if t >= 0.0 and t <= 1.0:
clipped_out[nclipped] = res
t = _plane_intersect(pn[e], pd[e], P, Q)
if t > -INTERSECT_TOL and t < 1.0 + INTERSECT_TOL:
t = wp.clamp(t, 0.0, 1.0)
clipped_out[nclipped] = P + t * (Q - P)
nclipped += 1
# add Q as PQ is now back inside the clipping edge
@@ -1937,9 +1926,16 @@ def _polygon_clip(
@wp.func
def _set_edge(
vert1: wp.array(dtype=wp.vec3), vert2: wp.array(dtype=wp.vec3), start: int, end: int, face_out: wp.array(dtype=wp.vec3)
# In:
vert1: wp.array(dtype=wp.vec3),
vert2: wp.array(dtype=wp.vec3),
start: int,
end: int,
offset: int,
# Out:
face_out: wp.array(dtype=wp.vec3),
) -> int:
face_out[0] = vert1[start]
face_out[0] = vert1[2 * start + offset]
face_out[1] = vert2[end]
return 2
@@ -1959,10 +1955,8 @@ def multicontact(
endvert: wp.array(dtype=wp.vec3),
face1: wp.array(dtype=wp.vec3),
face2: wp.array(dtype=wp.vec3),
epa_vert1: wp.array(dtype=wp.vec3),
epa_vert2: wp.array(dtype=wp.vec3),
epa_vert_index1: wp.array(dtype=int),
epa_vert_index2: wp.array(dtype=int),
epa_vert: wp.array(dtype=wp.vec3),
epa_vert_index: wp.array(dtype=int),
epa_face: int,
x1: wp.vec3,
x2: wp.vec3,
@@ -1998,8 +1992,8 @@ def multicontact(
face = _get_face_verts(epa_face)
# get dimensions of features of geoms 1 and 2
nface1, feature_index1, feature_vertex1 = _feature_dim(face, epa_vert_index1, epa_vert1)
nface2, feature_index2, feature_vertex2 = _feature_dim(face, epa_vert_index2, epa_vert2)
nface1, feature_index1, feature_vertex1 = _feature_dim(face, epa_vert_index, epa_vert, 0)
nface2, feature_index2, feature_vertex2 = _feature_dim(face, epa_vert_index, epa_vert, 1)
dir = x2 - x1
dir_neg = -dir
@@ -2115,7 +2109,7 @@ def multicontact(
# recover geom1 matching edge or face
if is_edge_contact_geom1:
nface1 = _set_edge(epa_vert1, endvert, face[0], i, face1)
nface1 = _set_edge(epa_vert, endvert, face[0], i, 0, face1)
else:
ind = wp.where(is_edge_contact_geom2, idx1[j], idx1[i])
if geomtype1 == GeomType.BOX:
@@ -2136,7 +2130,7 @@ def multicontact(
# recover geom2 matching edge or face
if is_edge_contact_geom2:
nface2 = _set_edge(epa_vert2, endvert, face[0], i, face2)
nface2 = _set_edge(epa_vert, endvert, face[0], i, 1, face2)
else:
if geomtype2 == GeomType.BOX:
nface2 = _box_face(geom2.rot, geom2.pos, geom2.size, idx2[j], face2)
@@ -2197,12 +2191,12 @@ def _inflate(
c = geom1.hfprism[5]
coordinates = _tri_affine_coord(a, b, c, x2)
if coordinates[0] > 0 and coordinates[1] > 0 and coordinates[2] > 0:
if coordinates[0] > 0.0 and coordinates[1] > 0.0 and coordinates[2] > 0.0:
x1 = coordinates[0] * a + coordinates[1] * b + coordinates[2] * c
else:
p = c
p = wp.where(coordinates[1] > 0, b, p)
p = wp.where(coordinates[0] > 0, a, p)
p = wp.where(coordinates[1] > 0.0, b, p)
p = wp.where(coordinates[0] > 0.0, a, p)
x1 = x2 - wp.dot(x2 - p, n) * n
dist = -wp.norm_l2(x1 - x2)
return dist, x1, x2
@@ -2230,10 +2224,8 @@ def ccd(
geomtype2: int,
x_1: wp.vec3,
x_2: wp.vec3,
vert1: wp.array(dtype=wp.vec3),
vert2: wp.array(dtype=wp.vec3),
vert_index1: wp.array(dtype=int),
vert_index2: wp.array(dtype=int),
vert: wp.array(dtype=wp.vec3),
vert_index: wp.array(dtype=int),
face: wp.array(dtype=int),
face_pr: wp.array(dtype=wp.vec3),
face_norm2: wp.array(dtype=float),
@@ -2289,10 +2281,8 @@ def ccd(
pt.nface = 0
pt.nvert = 0
pt.nhorizon = 0
pt.vert1 = vert1
pt.vert2 = vert2
pt.vert_index1 = vert_index1
pt.vert_index2 = vert_index2
pt.vert = vert
pt.vert_index = vert_index
pt.face = face
pt.face_pr = face_pr
pt.face_norm2 = face_norm2
@@ -2358,4 +2348,13 @@ def ccd(
dist, x1, x2, idx = _epa(tolerance, gjk_iterations, epa_iterations, pt, geom1, geom2, geomtype1, geomtype2, is_discrete)
if idx == -1:
return FLOAT_MAX, 0, wp.vec3(), wp.vec3(), -1
# multicontact not supported for margin
if geom1.margin != 0.0 or geom2.margin != 0.0:
idx = -1
# multicontact only supported for boxes and meshes
if (geomtype1 != GeomType.BOX and geomtype1 != GeomType.MESH) or (geomtype2 != GeomType.BOX and geomtype2 != GeomType.MESH):
idx = -1
return dist, 1, x1, x2, idx
@@ -32,8 +32,10 @@ from mujoco.mjx.third_party.mujoco_warp._src.collision_primitive_core import sph
from mujoco.mjx.third_party.mujoco_warp._src.math import make_frame
from mujoco.mjx.third_party.mujoco_warp._src.math import safe_div
from mujoco.mjx.third_party.mujoco_warp._src.math import upper_trid_index
from mujoco.mjx.third_party.mujoco_warp._src.types import MJ_MAXVAL
from mujoco.mjx.third_party.mujoco_warp._src.types import MJ_MINMU
from mujoco.mjx.third_party.mujoco_warp._src.types import MJ_MINVAL
from mujoco.mjx.third_party.mujoco_warp._src.types import CollisionContext
from mujoco.mjx.third_party.mujoco_warp._src.types import ContactType
from mujoco.mjx.third_party.mujoco_warp._src.types import Data
from mujoco.mjx.third_party.mujoco_warp._src.types import GeomType
@@ -43,7 +45,6 @@ from mujoco.mjx.third_party.mujoco_warp._src.types import mat63
from mujoco.mjx.third_party.mujoco_warp._src.types import vec5
from mujoco.mjx.third_party.mujoco_warp._src.warp_util import cache_kernel
from mujoco.mjx.third_party.mujoco_warp._src.warp_util import event_scope
from mujoco.mjx.third_party.mujoco_warp._src.warp_util import nested_kernel
wp.set_module_options({"enable_backward": False})
@@ -173,13 +174,13 @@ def plane_convex(plane_normal: wp.vec3, plane_pos: wp.vec3, convex: Geom) -> Tup
convex: Convex geometry object containing position, rotation, and mesh data.
Returns:
- Vector of contact distances (wp.inf for unpopulated contacts).
- Vector of contact distances (MJ_MAXVAL for unpopulated contacts).
- Matrix of contact positions (one per row).
- Matrix of contact normal vectors (one per row).
"""
_HUGE_VAL = 1e6
contact_dist = wp.vec4(wp.inf)
contact_dist = wp.vec4(MJ_MAXVAL)
contact_pos = mat43()
contact_count = int(0)
@@ -426,12 +427,12 @@ def write_contact(
contact_type_out: wp.array(dtype=int),
contact_geomcollisionid_out: wp.array(dtype=int),
nacon_out: wp.array(dtype=int),
):
) -> int:
active = dist_in < margin_in
# skip contact and no collision sensor
if (pairid_in[0] == -2 or not active) and pairid_in[1] == -1:
return
return 0
contact_type = 0
@@ -457,6 +458,8 @@ def write_contact(
contact_solimp_out[cid] = solimp_in
contact_type_out[cid] = contact_type
contact_geomcollisionid_out[cid] = id_
return int(active)
return 0
@wp.func
@@ -477,10 +480,9 @@ def contact_params(
pair_margin: wp.array2d(dtype=float),
pair_gap: wp.array2d(dtype=float),
pair_friction: wp.array2d(dtype=vec5),
# Data in:
# In:
collision_pair_in: wp.array(dtype=wp.vec2i),
collision_pairid_in: wp.array(dtype=wp.vec2i),
# In:
cid: int,
worldid: int,
):
@@ -1540,6 +1542,7 @@ def box_box_wrapper(
)
# Map of supported primitive collision functions
_PRIMITIVE_COLLISIONS = {
(GeomType.PLANE, GeomType.SPHERE): plane_sphere_wrapper,
(GeomType.PLANE, GeomType.CAPSULE): plane_capsule_wrapper,
@@ -1557,23 +1560,9 @@ _PRIMITIVE_COLLISIONS = {
}
# TODO(team): _check_collisions shared utility
def _check_primitive_collisions():
prev_idx = -1
for types in _PRIMITIVE_COLLISIONS.keys():
idx = upper_trid_index(len(GeomType), types[0].value, types[1].value)
if types[1] < types[0] or idx <= prev_idx:
return False
prev_idx = idx
return True
assert _check_primitive_collisions(), "_PRIMITIVE_COLLISIONS is in invalid order"
@cache_kernel
def _primitive_narrowphase(primitive_collisions_types, primitive_collisions_func):
@nested_kernel(module="unique", enable_backward=False)
@wp.kernel(module="unique", enable_backward=False)
def primitive_narrowphase(
# Model:
geom_type: wp.array(dtype=int),
@@ -1612,10 +1601,11 @@ def _primitive_narrowphase(primitive_collisions_types, primitive_collisions_func
geom_xpos_in: wp.array2d(dtype=wp.vec3),
geom_xmat_in: wp.array2d(dtype=wp.mat33),
naconmax_in: int,
ncollision_in: wp.array(dtype=int),
# In:
collision_pair_in: wp.array(dtype=wp.vec2i),
collision_pairid_in: wp.array(dtype=wp.vec2i),
collision_worldid_in: wp.array(dtype=int),
ncollision_in: wp.array(dtype=int),
# Data out:
contact_dist_out: wp.array(dtype=float),
contact_pos_out: wp.array(dtype=wp.vec3),
@@ -1730,7 +1720,7 @@ _PRIMITIVE_COLLISION_FUNC = []
@event_scope
def primitive_narrowphase(m: Model, d: Data):
def primitive_narrowphase(m: Model, d: Data, ctx: CollisionContext, collision_table: list[tuple[GeomType, GeomType]]):
"""Runs collision detection on primitive geom pairs discovered during broadphase.
This function processes collision pairs involving primitive shapes that were
@@ -1749,6 +1739,8 @@ def primitive_narrowphase(m: Model, d: Data):
# for pair types without collisions, as well as updating the launch dimensions.
for types, func in _PRIMITIVE_COLLISIONS.items():
if types not in collision_table:
continue
idx = upper_trid_index(len(GeomType), types[0].value, types[1].value)
if m.geom_pair_type_count[idx] and types not in _PRIMITIVE_COLLISION_TYPES:
_PRIMITIVE_COLLISION_TYPES.append(types)
@@ -1793,10 +1785,10 @@ def primitive_narrowphase(m: Model, d: Data):
d.geom_xpos,
d.geom_xmat,
d.naconmax,
d.collision_pair,
d.collision_pairid,
d.collision_worldid,
d.ncollision,
ctx.collision_pair,
ctx.collision_pairid,
ctx.collision_worldid,
],
outputs=[
d.contact.dist,
@@ -18,6 +18,7 @@ from typing import Any, Tuple
import warp as wp
MJ_MINVAL = 1e-15
MJ_MAXVAL = 1e10
wp.set_module_options({"enable_backward": False})
@@ -411,13 +412,13 @@ def plane_box(
margin: Collision tolerance.
Returns:
- Vector of contact distances (wp.inf for unpopulated contacts).
- Vector of contact distances (MJ_MAXVAL for unpopulated contacts).
- Matrix of contact positions (one per row).
- Contact normal vector.
"""
center_dist = wp.dot(box_pos - plane_pos, plane_normal)
dist = vec8f(wp.inf)
dist = vec8f(MJ_MAXVAL)
pos = mat83f()
# test all corners, pick bottom 4
@@ -540,7 +541,7 @@ def plane_cylinder(
- Matrix of contact normal vectors (one per row).
"""
# Initialize output matrices
contact_dist = wp.vec4(wp.inf)
contact_dist = wp.vec4(MJ_MAXVAL)
contact_pos = mat43f()
contact_count = 0
@@ -666,14 +667,14 @@ def box_box(
margin: Collision tolerance.
Returns:
- Vector of contact distances (wp.inf for unpopulated contacts).
- Vector of contact distances (MJ_MAXVAL for unpopulated contacts).
- Matrix of contact positions (one per row).
- Matrix of contact normal vectors (one per row).
"""
# Initialize output matrices
contact_dist = vec8f()
for i in range(8):
contact_dist[i] = wp.inf
contact_dist[i] = MJ_MAXVAL
contact_pos = mat83f()
contact_normals = mat83f()
contact_count = 0
@@ -1176,7 +1177,7 @@ def capsule_box(
box_size: Half-extents of the box along each axis.
Returns:
- Vector of contact distances (wp.inf for unpopulated contacts).
- Vector of contact distances (MJ_MAXVAL for unpopulated contacts).
- Matrix of contact positions (one per row).
- Matrix of contact normal vectors (one per row).
"""
@@ -1348,7 +1349,7 @@ def capsule_box(
c1 = wp.where((ee2 > 0) == w_neg, 1, 2)
if cltype == -4: # invalid type
return wp.vec2(wp.inf), mat23f(), mat23f()
return wp.vec2(MJ_MAXVAL), mat23f(), mat23f()
if cltype >= 0 and cltype // 3 != 1: # closest to a corner of the box
c1 = axisdir ^ clcorner
@@ -1479,7 +1480,7 @@ def capsule_box(
# collide with sphere using core function
dist2, pos2, normal2 = sphere_box(s2_pos_g, capsule_radius, box_pos, box_rot, box_size)
else:
dist2 = wp.inf
dist2 = MJ_MAXVAL
pos2 = wp.vec3()
normal2 = wp.vec3()
@@ -22,6 +22,7 @@ from mujoco.mjx.third_party.mujoco_warp._src.collision_primitive import geom_col
from mujoco.mjx.third_party.mujoco_warp._src.collision_primitive import write_contact
from mujoco.mjx.third_party.mujoco_warp._src.math import make_frame
from mujoco.mjx.third_party.mujoco_warp._src.ray import ray_mesh
from mujoco.mjx.third_party.mujoco_warp._src.types import CollisionContext
from mujoco.mjx.third_party.mujoco_warp._src.types import Data
from mujoco.mjx.third_party.mujoco_warp._src.types import GeomType
from mujoco.mjx.third_party.mujoco_warp._src.types import Model
@@ -68,6 +69,7 @@ class MeshData:
data_id: int
pos: wp.vec3
mat: wp.mat33
size: wp.vec3
pnt: wp.vec3
vec: wp.vec3
valid: bool = False
@@ -375,6 +377,7 @@ def sdf(type: int, p: wp.vec3, attr: wp.vec3, sdf_type: int, volume_data: Volume
mesh_data.data_id,
mesh_data.pos,
mesh_data.mat,
mesh_data.size,
mesh_data.pnt,
mesh_data.vec,
)
@@ -388,6 +391,7 @@ def sdf(type: int, p: wp.vec3, attr: wp.vec3, sdf_type: int, volume_data: Volume
mesh_data.data_id,
mesh_data.pos,
mesh_data.mat,
mesh_data.size,
mesh_data.pnt,
-mesh_data.vec,
)
@@ -425,6 +429,7 @@ def sdf_grad(type: int, p: wp.vec3, attr: wp.vec3, sdf_type: int, volume_data: V
mesh_data.data_id,
mesh_data.pos,
mesh_data.mat,
mesh_data.size,
mesh_data.pnt,
mesh_data.vec,
)
@@ -664,11 +669,11 @@ def _sdf_narrowphase(
geom_xpos_in: wp.array2d(dtype=wp.vec3),
geom_xmat_in: wp.array2d(dtype=wp.mat33),
naconmax_in: int,
ncollision_in: wp.array(dtype=int),
# In:
collision_pair_in: wp.array(dtype=wp.vec2i),
collision_pairid_in: wp.array(dtype=wp.vec2i),
collision_worldid_in: wp.array(dtype=int),
ncollision_in: wp.array(dtype=int),
# In:
sdf_initpoints: int,
sdf_iterations: int,
# Data out:
@@ -785,6 +790,7 @@ def _sdf_narrowphase(
mesh_data1.data_id = geom_dataid[g1]
mesh_data1.pos = geom1.pos
mesh_data1.mat = geom1.rot
mesh_data1.size = geom1.size
mesh_data1.pnt = wp.vec3(-1.0)
mesh_data1.vec = wp.vec3(0.0)
mesh_data1.valid = True
@@ -797,6 +803,7 @@ def _sdf_narrowphase(
mesh_data2.data_id = geom_dataid[g2]
mesh_data2.pos = geom2.pos
mesh_data2.mat = geom2.rot
mesh_data2.size = geom2.size
mesh_data2.pnt = wp.vec3(-1.0)
mesh_data2.vec = wp.vec3(0.0)
mesh_data2.valid = True
@@ -859,7 +866,7 @@ def _sdf_narrowphase(
@event_scope
def sdf_narrowphase(m: Model, d: Data):
def sdf_narrowphase(m: Model, d: Data, ctx: CollisionContext):
wp.launch(
_sdf_narrowphase,
dim=(m.opt.sdf_initpoints, d.naconmax),
@@ -909,10 +916,10 @@ def sdf_narrowphase(m: Model, d: Data):
d.geom_xpos,
d.geom_xmat,
d.naconmax,
d.collision_pair,
d.collision_pairid,
d.collision_worldid,
d.ncollision,
ctx.collision_pair,
ctx.collision_pairid,
ctx.collision_worldid,
m.opt.sdf_initpoints,
m.opt.sdf_iterations,
],
+174 -131
View File
@@ -34,21 +34,11 @@ def _zero_constraint_counts(
nf_out: wp.array(dtype=int),
nl_out: wp.array(dtype=int),
nefc_out: wp.array(dtype=int),
ne_connect_out: wp.array(dtype=int),
ne_weld_out: wp.array(dtype=int),
ne_jnt_out: wp.array(dtype=int),
ne_ten_out: wp.array(dtype=int),
ne_flex_out: wp.array(dtype=int),
):
worldid = wp.tid()
# Zero all constraint counters
ne_out[worldid] = 0
ne_connect_out[worldid] = 0
ne_weld_out[worldid] = 0
ne_jnt_out[worldid] = 0
ne_ten_out[worldid] = 0
ne_flex_out[worldid] = 0
nf_out[worldid] = 0
nl_out[worldid] = 0
nefc_out[worldid] = 0
@@ -155,6 +145,7 @@ def _efc_equality_connect(
# In:
refsafe_in: int,
# Data out:
ne_out: wp.array(dtype=int),
nefc_out: wp.array(dtype=int),
efc_type_out: wp.array2d(dtype=int),
efc_id_out: wp.array2d(dtype=int),
@@ -165,7 +156,6 @@ def _efc_equality_connect(
efc_vel_out: wp.array2d(dtype=float),
efc_aref_out: wp.array2d(dtype=float),
efc_frictionloss_out: wp.array2d(dtype=float),
ne_connect_out: wp.array(dtype=int),
):
"""Calculates constraint rows for connect equality constraints."""
worldid, eqconnectid = wp.tid()
@@ -174,7 +164,7 @@ def _efc_equality_connect(
if not eq_active_in[worldid, eqid]:
return
wp.atomic_add(ne_connect_out, worldid, 3)
wp.atomic_add(ne_out, worldid, 3)
efcid = wp.atomic_add(nefc_out, worldid, 3)
if efcid + 3 >= njmax_in:
@@ -205,7 +195,7 @@ def _efc_equality_connect(
# compute Jacobian difference (opposite of contact: 0 - 1)
Jqvel = wp.vec3f(0.0, 0.0, 0.0)
for dofid in range(nv): # TODO: parallelize
jacp1, _ = support.jac(
jacp1, _ = support.jac_dof(
body_parentid,
body_rootid,
dof_bodyid,
@@ -216,7 +206,7 @@ def _efc_equality_connect(
dofid,
worldid,
)
jacp2, _ = support.jac(
jacp2, _ = support.jac_dof(
body_parentid,
body_rootid,
dof_bodyid,
@@ -293,6 +283,7 @@ def _efc_equality_joint(
# In:
refsafe_in: int,
# Data out:
ne_out: wp.array(dtype=int),
nefc_out: wp.array(dtype=int),
efc_type_out: wp.array2d(dtype=int),
efc_id_out: wp.array2d(dtype=int),
@@ -303,7 +294,6 @@ def _efc_equality_joint(
efc_vel_out: wp.array2d(dtype=float),
efc_aref_out: wp.array2d(dtype=float),
efc_frictionloss_out: wp.array2d(dtype=float),
ne_jnt_out: wp.array(dtype=int),
):
worldid, eqjntid = wp.tid()
eqid = eq_jnt_adr[eqjntid]
@@ -311,7 +301,7 @@ def _efc_equality_joint(
if not eq_active_in[worldid, eqid]:
return
wp.atomic_add(ne_jnt_out, worldid, 1)
wp.atomic_add(ne_out, worldid, 1)
efcid = wp.atomic_add(nefc_out, worldid, 1)
if efcid >= njmax_in:
@@ -399,6 +389,7 @@ def _efc_equality_tendon(
# In:
refsafe_in: int,
# Data out:
ne_out: wp.array(dtype=int),
nefc_out: wp.array(dtype=int),
efc_type_out: wp.array2d(dtype=int),
efc_id_out: wp.array2d(dtype=int),
@@ -409,7 +400,6 @@ def _efc_equality_tendon(
efc_vel_out: wp.array2d(dtype=float),
efc_aref_out: wp.array2d(dtype=float),
efc_frictionloss_out: wp.array2d(dtype=float),
ne_ten_out: wp.array(dtype=int),
):
worldid, eqtenid = wp.tid()
eqid = eq_ten_adr[eqtenid]
@@ -417,7 +407,7 @@ def _efc_equality_tendon(
if not eq_active_in[worldid, eqid]:
return
wp.atomic_add(ne_ten_out, worldid, 1)
wp.atomic_add(ne_out, worldid, 1)
efcid = wp.atomic_add(nefc_out, worldid, 1)
if efcid >= njmax_in:
@@ -494,6 +484,9 @@ def _efc_equality_flex(
opt_timestep: wp.array(dtype=float),
flexedge_length0: wp.array(dtype=float),
flexedge_invweight0: wp.array(dtype=float),
flexedge_J_rownnz: wp.array(dtype=int),
flexedge_J_rowadr: wp.array(dtype=int),
flexedge_J_colind: wp.array(dtype=int),
eq_solref: wp.array2d(dtype=wp.vec2),
eq_solimp: wp.array2d(dtype=vec5),
eq_flex_adr: wp.array(dtype=int),
@@ -505,6 +498,7 @@ def _efc_equality_flex(
# In:
refsafe_in: int,
# Data out:
ne_out: wp.array(dtype=int),
nefc_out: wp.array(dtype=int),
efc_type_out: wp.array2d(dtype=int),
efc_id_out: wp.array2d(dtype=int),
@@ -515,12 +509,11 @@ def _efc_equality_flex(
efc_vel_out: wp.array2d(dtype=float),
efc_aref_out: wp.array2d(dtype=float),
efc_frictionloss_out: wp.array2d(dtype=float),
ne_flex_out: wp.array(dtype=int),
):
worldid, eqflexid, edgeid = wp.tid()
eqid = eq_flex_adr[eqflexid]
wp.atomic_add(ne_flex_out, worldid, 1)
wp.atomic_add(ne_out, worldid, 1)
efcid = wp.atomic_add(nefc_out, worldid, 1)
if efcid >= njmax_in:
@@ -531,10 +524,20 @@ def _efc_equality_flex(
solimp = eq_solimp[worldid % eq_solimp.shape[0], eqid]
Jqvel = float(0.0)
# TODO(team): remove once efc.J is sparse
for i in range(nv):
J = flexedge_J_in[worldid, edgeid, i]
efc_J_out[worldid, efcid, i] = J
Jqvel += J * qvel_in[worldid, i]
efc_J_out[worldid, efcid, i] = 0.0
rownnz = flexedge_J_rownnz[edgeid]
rowadr = flexedge_J_rowadr[edgeid]
for i in range(rownnz):
sparseid = rowadr + i
colind = flexedge_J_colind[sparseid]
J = flexedge_J_in[worldid, 0, sparseid]
# TODO(team): sparse efc.J
efc_J_out[worldid, efcid, colind] = J
Jqvel += J * qvel_in[worldid, colind]
_update_efc_row(
worldid,
@@ -748,6 +751,7 @@ def _efc_equality_weld(
# In:
refsafe_in: int,
# Data out:
ne_out: wp.array(dtype=int),
nefc_out: wp.array(dtype=int),
efc_type_out: wp.array2d(dtype=int),
efc_id_out: wp.array2d(dtype=int),
@@ -758,7 +762,6 @@ def _efc_equality_weld(
efc_vel_out: wp.array2d(dtype=float),
efc_aref_out: wp.array2d(dtype=float),
efc_frictionloss_out: wp.array2d(dtype=float),
ne_weld_out: wp.array(dtype=int),
):
worldid, eqweldid = wp.tid()
eqid = eq_wld_adr[eqweldid]
@@ -766,7 +769,7 @@ def _efc_equality_weld(
if not eq_active_in[worldid, eqid]:
return
wp.atomic_add(ne_weld_out, worldid, 6)
wp.atomic_add(ne_out, worldid, 6)
efcid = wp.atomic_add(nefc_out, worldid, 6)
if efcid + 6 >= njmax_in:
@@ -808,7 +811,7 @@ def _efc_equality_weld(
Jqvelr = wp.vec3f(0.0, 0.0, 0.0)
for dofid in range(nv): # TODO: parallelize
jacp1, jacr1 = support.jac(
jacp1, jacr1 = support.jac_dof(
body_parentid,
body_rootid,
dof_bodyid,
@@ -819,7 +822,7 @@ def _efc_equality_weld(
dofid,
worldid,
)
jacp2, jacr2 = support.jac(
jacp2, jacr2 = support.jac_dof(
body_parentid,
body_rootid,
dof_bodyid,
@@ -1215,8 +1218,12 @@ def _efc_contact_pyramidal(
opt_impratio_invsqrt: wp.array(dtype=float),
body_parentid: wp.array(dtype=int),
body_rootid: wp.array(dtype=int),
body_weldid: wp.array(dtype=int),
body_dofnum: wp.array(dtype=int),
body_dofadr: wp.array(dtype=int),
body_invweight0: wp.array2d(dtype=wp.vec2),
dof_bodyid: wp.array(dtype=int),
dof_parentid: wp.array(dtype=int),
geom_bodyid: wp.array(dtype=int),
# Data in:
qvel_in: wp.array2d(dtype=float),
@@ -1302,49 +1309,73 @@ def _efc_contact_pyramidal(
invweight = invweight * 2.0 * fri0 * fri0 * impratio_invsqrt * impratio_invsqrt
Jqvel = float(0.0)
for i in range(nv):
J = float(0.0)
Ji = float(0.0)
jac1p, jac1r = support.jac(
body_parentid,
body_rootid,
dof_bodyid,
subtree_com_in,
cdof_in,
con_pos,
body1,
i,
worldid,
)
jac2p, jac2r = support.jac(
body_parentid,
body_rootid,
dof_bodyid,
subtree_com_in,
cdof_in,
con_pos,
body2,
i,
worldid,
)
jacp_dif = jac2p - jac1p
for xyz in range(3):
J += frame[0, xyz] * jacp_dif[xyz]
# skip fixed bodies
body1 = body_weldid[body1]
body2 = body_weldid[body2]
da1 = body_dofadr[body1] + body_dofnum[body1] - 1
da2 = body_dofadr[body2] + body_dofnum[body2] - 1
da = wp.max(da1, da2)
for dofid in range(nv - 1, -1, -1):
if dofid == da:
# TODO(team): contact_jacobian
jac1p, jac1r = support.jac_dof(
body_parentid,
body_rootid,
dof_bodyid,
subtree_com_in,
cdof_in,
con_pos,
body1,
dofid,
worldid,
)
jac2p, jac2r = support.jac_dof(
body_parentid,
body_rootid,
dof_bodyid,
subtree_com_in,
cdof_in,
con_pos,
body2,
dofid,
worldid,
)
J = float(0.0)
Ji = float(0.0)
if condim > 1:
dimid2 = dimid / 2 + 1
for xyz in range(3):
jacp_dif = jac2p[xyz] - jac1p[xyz]
J += frame[0, xyz] * jacp_dif
if condim > 1:
if dimid2 < 3:
Ji += frame[dimid2, xyz] * jacp_dif
else:
Ji += frame[dimid2 - 3, xyz] * (jac2r[xyz] - jac1r[xyz])
if condim > 1:
if dimid2 < 3:
Ji += frame[dimid2, xyz] * jacp_dif[xyz]
if dimid % 2 == 0:
J += Ji * frii
else:
Ji += frame[dimid2 - 3, xyz] * (jac2r[xyz] - jac1r[xyz])
J -= Ji * frii
if condim > 1:
if dimid % 2 == 0:
J += Ji * frii
else:
J -= Ji * frii
efc_J_out[worldid, efcid, dofid] = J
Jqvel += J * qvel_in[worldid, dofid]
efc_J_out[worldid, efcid, i] = J
Jqvel += J * qvel_in[worldid, i]
# Advance tree pointers and recompute da for next iteration
if da1 == da:
da1 = dof_parentid[da1]
if da2 == da:
da2 = dof_parentid[da2]
da = wp.max(da1, da2)
else:
efc_J_out[worldid, efcid, dofid] = 0.0
if condim == 1:
efc_type = ConstraintType.CONTACT_FRICTIONLESS
@@ -1385,8 +1416,12 @@ def _efc_contact_elliptic(
opt_impratio_invsqrt: wp.array(dtype=float),
body_parentid: wp.array(dtype=int),
body_rootid: wp.array(dtype=int),
body_weldid: wp.array(dtype=int),
body_dofnum: wp.array(dtype=int),
body_dofadr: wp.array(dtype=int),
body_invweight0: wp.array2d(dtype=wp.vec2),
dof_bodyid: wp.array(dtype=int),
dof_parentid: wp.array(dtype=int),
geom_bodyid: wp.array(dtype=int),
# Data in:
qvel_in: wp.array2d(dtype=float),
@@ -1450,49 +1485,69 @@ def _efc_contact_elliptic(
impratio_invsqrt = opt_impratio_invsqrt[worldid % opt_impratio_invsqrt.shape[0]]
contact_efc_address_out[conid, dimid] = efcid
con_pos = pos_in[conid]
frame = frame_in[conid]
geom = geom_in[conid]
body1 = geom_bodyid[geom[0]]
body2 = geom_bodyid[geom[1]]
cpos = pos_in[conid]
frame = frame_in[conid]
# TODO(team): parallelize J and Jqvel computation?
Jqvel = float(0.0)
for i in range(nv):
J = float(0.0)
jac1p, jac1r = support.jac(
body_parentid,
body_rootid,
dof_bodyid,
subtree_com_in,
cdof_in,
cpos,
body1,
i,
worldid,
)
jac2p, jac2r = support.jac(
body_parentid,
body_rootid,
dof_bodyid,
subtree_com_in,
cdof_in,
cpos,
body2,
i,
worldid,
)
for xyz in range(3):
if dimid < 3:
jac_dif = jac2p[xyz] - jac1p[xyz]
J += frame[dimid, xyz] * jac_dif
else:
jac_dif = jac2r[xyz] - jac1r[xyz]
J += frame[dimid - 3, xyz] * jac_dif
efc_J_out[worldid, efcid, i] = J
Jqvel += J * qvel_in[worldid, i]
# skip fixed bodies
body1 = body_weldid[body1]
body2 = body_weldid[body2]
da1 = body_dofadr[body1] + body_dofnum[body1] - 1
da2 = body_dofadr[body2] + body_dofnum[body2] - 1
da = wp.max(da1, da2)
for dofid in range(nv - 1, -1, -1):
if dofid == da:
# TODO(team): contact jacobian
jac1p, jac1r = support.jac_dof(
body_parentid,
body_rootid,
dof_bodyid,
subtree_com_in,
cdof_in,
con_pos,
body1,
dofid,
worldid,
)
jac2p, jac2r = support.jac_dof(
body_parentid,
body_rootid,
dof_bodyid,
subtree_com_in,
cdof_in,
con_pos,
body2,
dofid,
worldid,
)
J = float(0.0)
for xyz in range(3):
if dimid < 3:
jac_dif = jac2p[xyz] - jac1p[xyz]
J += frame[dimid, xyz] * jac_dif
else:
jac_dif = jac2r[xyz] - jac1r[xyz]
J += frame[dimid - 3, xyz] * jac_dif
efc_J_out[worldid, efcid, dofid] = J
Jqvel += J * qvel_in[worldid, dofid]
# Advance tree pointers and recompute da for next iteration
if da1 == da:
da1 = dof_parentid[da1]
if da2 == da:
da2 = dof_parentid[da2]
da = wp.max(da1, da2)
else:
efc_J_out[worldid, efcid, dofid] = 0.0
body_invweight0_id = worldid % body_invweight0.shape[0]
invweight = body_invweight0[body_invweight0_id, body1][0] + body_invweight0[body_invweight0_id, body2][0]
@@ -1549,29 +1604,13 @@ def _efc_contact_elliptic(
)
@wp.kernel
def _num_equality(
# Data in:
ne_connect_in: wp.array(dtype=int),
ne_weld_in: wp.array(dtype=int),
ne_jnt_in: wp.array(dtype=int),
ne_ten_in: wp.array(dtype=int),
ne_flex_in: wp.array(dtype=int),
# Data out:
ne_out: wp.array(dtype=int),
):
worldid = wp.tid()
ne = ne_connect_in[worldid] + ne_weld_in[worldid] + ne_jnt_in[worldid] + ne_ten_in[worldid] + ne_flex_in[worldid]
ne_out[worldid] = ne
@event_scope
def make_constraint(m: types.Model, d: types.Data):
"""Creates constraint jacobians and other supporting data."""
wp.launch(
_zero_constraint_counts,
dim=d.nworld,
inputs=[d.ne, d.nf, d.nl, d.nefc, d.ne_connect, d.ne_weld, d.ne_jnt, d.ne_ten, d.ne_flex],
inputs=[d.ne, d.nf, d.nl, d.nefc],
)
if not (m.opt.disableflags & types.DisableBit.CONSTRAINT):
@@ -1608,6 +1647,7 @@ def make_constraint(m: types.Model, d: types.Data):
refsafe,
],
outputs=[
d.ne,
d.nefc,
d.efc.type,
d.efc.id,
@@ -1618,7 +1658,6 @@ def make_constraint(m: types.Model, d: types.Data):
d.efc.vel,
d.efc.aref,
d.efc.frictionloss,
d.ne_connect,
],
)
wp.launch(
@@ -1653,6 +1692,7 @@ def make_constraint(m: types.Model, d: types.Data):
refsafe,
],
outputs=[
d.ne,
d.nefc,
d.efc.type,
d.efc.id,
@@ -1663,7 +1703,6 @@ def make_constraint(m: types.Model, d: types.Data):
d.efc.vel,
d.efc.aref,
d.efc.frictionloss,
d.ne_weld,
],
)
wp.launch(
@@ -1689,6 +1728,7 @@ def make_constraint(m: types.Model, d: types.Data):
refsafe,
],
outputs=[
d.ne,
d.nefc,
d.efc.type,
d.efc.id,
@@ -1699,7 +1739,6 @@ def make_constraint(m: types.Model, d: types.Data):
d.efc.vel,
d.efc.aref,
d.efc.frictionloss,
d.ne_jnt,
],
)
wp.launch(
@@ -1724,6 +1763,7 @@ def make_constraint(m: types.Model, d: types.Data):
refsafe,
],
outputs=[
d.ne,
d.nefc,
d.efc.type,
d.efc.id,
@@ -1734,7 +1774,6 @@ def make_constraint(m: types.Model, d: types.Data):
d.efc.vel,
d.efc.aref,
d.efc.frictionloss,
d.ne_ten,
],
)
@@ -1746,6 +1785,9 @@ def make_constraint(m: types.Model, d: types.Data):
m.opt.timestep,
m.flexedge_length0,
m.flexedge_invweight0,
m.flexedge_J_rownnz,
m.flexedge_J_rowadr,
m.flexedge_J_colind,
m.eq_solref,
m.eq_solimp,
m.eq_flex_adr,
@@ -1756,6 +1798,7 @@ def make_constraint(m: types.Model, d: types.Data):
refsafe,
],
outputs=[
d.ne,
d.nefc,
d.efc.type,
d.efc.id,
@@ -1766,17 +1809,9 @@ def make_constraint(m: types.Model, d: types.Data):
d.efc.vel,
d.efc.aref,
d.efc.frictionloss,
d.ne_flex,
],
)
wp.launch(
_num_equality,
dim=d.nworld,
inputs=[d.ne_connect, d.ne_weld, d.ne_jnt, d.ne_ten, d.ne_flex],
outputs=[d.ne],
)
if not (m.opt.disableflags & types.DisableBit.FRICTIONLOSS):
wp.launch(
_efc_friction_dof,
@@ -1957,8 +1992,12 @@ def make_constraint(m: types.Model, d: types.Data):
m.opt.impratio_invsqrt,
m.body_parentid,
m.body_rootid,
m.body_weldid,
m.body_dofnum,
m.body_dofadr,
m.body_invweight0,
m.dof_bodyid,
m.dof_parentid,
m.geom_bodyid,
d.qvel,
d.subtree_com,
@@ -2002,8 +2041,12 @@ def make_constraint(m: types.Model, d: types.Data):
m.opt.impratio_invsqrt,
m.body_parentid,
m.body_rootid,
m.body_weldid,
m.body_dofnum,
m.body_dofadr,
m.body_invweight0,
m.dof_bodyid,
m.dof_parentid,
m.geom_bodyid,
d.qvel,
d.subtree_com,
+12 -13
View File
@@ -25,7 +25,6 @@ from mujoco.mjx.third_party.mujoco_warp._src.types import TileSet
from mujoco.mjx.third_party.mujoco_warp._src.types import vec10f
from mujoco.mjx.third_party.mujoco_warp._src.warp_util import cache_kernel
from mujoco.mjx.third_party.mujoco_warp._src.warp_util import event_scope
from mujoco.mjx.third_party.mujoco_warp._src.warp_util import nested_kernel
wp.set_module_options({"enable_backward": False})
@@ -80,12 +79,12 @@ def _qderiv_actuator_passive_vel(
@cache_kernel
def _qderiv_actuator_passive_actuation_dense(tile: TileSet, nu: int):
@nested_kernel(module="unique", enable_backward=False)
@wp.kernel(module="unique", enable_backward=False)
def kernel(
# Data in:
vel_in: wp.array3d(dtype=float),
actuator_moment_in: wp.array3d(dtype=float),
# In:
vel_in: wp.array3d(dtype=float),
adr: wp.array(dtype=int),
# Out:
qDeriv_out: wp.array3d(dtype=float),
@@ -140,8 +139,8 @@ def _qderiv_actuator_passive(
# Model:
opt_timestep: wp.array(dtype=float),
opt_disableflags: int,
opt_is_sparse: bool,
dof_damping: wp.array2d(dtype=float),
is_sparse: bool,
# Data in:
qM_in: wp.array3d(dtype=float),
# In:
@@ -156,7 +155,7 @@ def _qderiv_actuator_passive(
dofiid = qMi[elemid]
dofjid = qMj[elemid]
if opt_is_sparse:
if is_sparse:
qderiv = qDeriv_in[worldid, 0, elemid]
else:
qderiv = qDeriv_in[worldid, dofiid, dofjid]
@@ -166,7 +165,7 @@ def _qderiv_actuator_passive(
qderiv *= opt_timestep[worldid % opt_timestep.shape[0]]
if opt_is_sparse:
if is_sparse:
qDeriv_out[worldid, 0, elemid] = qM_in[worldid, 0, elemid] - qderiv
else:
qM = qM_in[worldid, dofiid, dofjid] - qderiv
@@ -181,8 +180,8 @@ def _qderiv_tendon_damping(
# Model:
ntendon: int,
opt_timestep: wp.array(dtype=float),
opt_is_sparse: bool,
tendon_damping: wp.array2d(dtype=float),
is_sparse: bool,
# Data in:
ten_J_in: wp.array3d(dtype=float),
# In:
@@ -202,7 +201,7 @@ def _qderiv_tendon_damping(
qderiv *= opt_timestep[worldid % opt_timestep.shape[0]]
if opt_is_sparse:
if is_sparse:
qDeriv_out[worldid, 0, elemid] -= qderiv
else:
qDeriv_out[worldid, dofiid, dofjid] -= qderiv
@@ -245,7 +244,7 @@ def deriv_smooth_vel(m: Model, d: Data, out: wp.array2d(dtype=float)):
],
outputs=[vel],
)
if m.opt.is_sparse:
if m.is_sparse:
wp.launch(
_qderiv_actuator_passive_actuation_sparse,
dim=(d.nworld, qMi.size),
@@ -258,9 +257,9 @@ def deriv_smooth_vel(m: Model, d: Data, out: wp.array2d(dtype=float)):
wp.launch_tiled(
_qderiv_actuator_passive_actuation_dense(tile, m.nu),
dim=(d.nworld, tile.adr.size),
inputs=[vel_3d, d.actuator_moment, tile.adr],
inputs=[d.actuator_moment, vel_3d, tile.adr],
outputs=[out],
block_dim=m.block_dim.mul_m_dense,
block_dim=m.block_dim.qderiv_actuator_dense,
)
wp.launch(
_qderiv_actuator_passive,
@@ -268,8 +267,8 @@ def deriv_smooth_vel(m: Model, d: Data, out: wp.array2d(dtype=float)):
inputs=[
m.opt.timestep,
m.opt.disableflags,
m.opt.is_sparse,
m.dof_damping,
m.is_sparse,
d.qM,
qMi,
qMj,
@@ -285,7 +284,7 @@ def deriv_smooth_vel(m: Model, d: Data, out: wp.array2d(dtype=float)):
wp.launch(
_qderiv_tendon_damping,
dim=(d.nworld, qMi.size),
inputs=[m.ntendon, m.opt.timestep, m.opt.is_sparse, m.tendon_damping, d.ten_J, qMi, qMj],
inputs=[m.ntendon, m.opt.timestep, m.tendon_damping, m.is_sparse, d.ten_J, qMi, qMj],
outputs=[out],
)
+8 -9
View File
@@ -42,7 +42,6 @@ from mujoco.mjx.third_party.mujoco_warp._src.types import TrnType
from mujoco.mjx.third_party.mujoco_warp._src.types import vec10f
from mujoco.mjx.third_party.mujoco_warp._src.warp_util import cache_kernel
from mujoco.mjx.third_party.mujoco_warp._src.warp_util import event_scope
from mujoco.mjx.third_party.mujoco_warp._src.warp_util import nested_kernel
wp.set_module_options({"enable_backward": False})
@@ -291,17 +290,17 @@ def _euler_damp_qfrc_sparse(
@cache_kernel
def _tile_euler_dense(tile: TileSet):
@nested_kernel(module="unique", enable_backward=False)
@wp.kernel(module="unique", enable_backward=False)
def euler_dense(
# Model:
dof_damping: wp.array2d(dtype=float),
opt_timestep: wp.array(dtype=float),
dof_damping: wp.array2d(dtype=float),
# Data in:
qM_in: wp.array3d(dtype=float),
efc_Ma_in: wp.array2d(dtype=float),
# In:
adr_in: wp.array(dtype=int),
# Out:
# Data out:
qacc_out: wp.array2d(dtype=float),
):
worldid, nodeid = wp.tid()
@@ -328,7 +327,7 @@ def euler(m: Model, d: Data):
# integrate damping implicitly
if not m.opt.disableflags & (DisableBit.EULERDAMP | DisableBit.DAMPER):
qacc = wp.empty((d.nworld, m.nv), dtype=float)
if m.opt.is_sparse:
if m.is_sparse:
qM = wp.clone(d.qM)
qLD = wp.empty((d.nworld, 1, m.nC), dtype=float)
qLDiagInv = wp.empty((d.nworld, m.nv), dtype=float)
@@ -344,7 +343,7 @@ def euler(m: Model, d: Data):
wp.launch_tiled(
_tile_euler_dense(tile),
dim=(d.nworld, tile.adr.size),
inputs=[m.dof_damping, m.opt.timestep, d.qM, d.efc.Ma, tile.adr],
inputs=[m.opt.timestep, m.dof_damping, d.qM, d.efc.Ma, tile.adr],
outputs=[qacc],
block_dim=m.block_dim.euler_dense,
)
@@ -482,7 +481,7 @@ def rungekutta4(m: Model, d: Data):
def implicit(m: Model, d: Data):
"""Integrates fully implicit in velocity."""
if ~(m.opt.disableflags | ~(DisableBit.ACTUATION | DisableBit.SPRING | DisableBit.DAMPER)):
if m.opt.is_sparse:
if m.is_sparse:
qDeriv = wp.empty((d.nworld, 1, m.nM), dtype=float)
qLD = wp.empty((d.nworld, 1, m.nC), dtype=float)
else:
@@ -524,7 +523,7 @@ def fwd_position(m: Model, d: Data, factorize: bool = True):
# TODO(team): sparse actuator_moment version
@cache_kernel
def _actuator_velocity(nv: int):
@nested_kernel(module="unique", enable_backward=False)
@wp.kernel(module="unique", enable_backward=False)
def actuator_velocity(
# Data in:
qvel_in: wp.array2d(dtype=float),
@@ -544,7 +543,7 @@ def _actuator_velocity(nv: int):
@cache_kernel
def _tendon_velocity(nv: int):
@nested_kernel(module="unique", enable_backward=False)
@wp.kernel(module="unique", enable_backward=False)
def tendon_velocity(
# Data in:
qvel_in: wp.array2d(dtype=float),
+3 -5
View File
@@ -99,7 +99,7 @@ def discrete_acc(m: Model, d: Data, qacc: wp.array2d(dtype=float)):
outputs=[qfrc],
)
elif m.opt.integrator == IntegratorType.IMPLICITFAST:
if m.opt.is_sparse:
if m.is_sparse:
qDeriv = wp.empty((d.nworld, 1, m.nM), dtype=float)
else:
qDeriv = wp.empty((d.nworld, m.nv, m.nv), dtype=float)
@@ -120,10 +120,8 @@ def inv_constraint(m: Model, d: Data):
d.qfrc_constraint.zero_()
return
# update
h = wp.empty((d.nworld, 0, 0), dtype=float) # not used
hfactor = wp.empty((d.nworld, 0, 0), dtype=float) # not used
solver.create_context(m, d, h, hfactor, grad=False)
ctx = solver.create_inverse_context(m, d)
solver.init_context(m, d, ctx, grad=False)
def inverse(m: Model, d: Data):
File diff suppressed because it is too large Load Diff
+178
View File
@@ -0,0 +1,178 @@
# Copyright 2026 The Newton Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
import warp as wp
from mujoco.mjx.third_party.mujoco_warp._src import types
from mujoco.mjx.third_party.mujoco_warp._src.types import ConstraintType
from mujoco.mjx.third_party.mujoco_warp._src.types import EqType
from mujoco.mjx.third_party.mujoco_warp._src.types import ObjType
@wp.kernel
def _tree_edges(
# Model:
nv: int,
body_treeid: wp.array(dtype=int),
jnt_dofadr: wp.array(dtype=int),
dof_treeid: wp.array(dtype=int),
geom_bodyid: wp.array(dtype=int),
site_bodyid: wp.array(dtype=int),
eq_type: wp.array(dtype=int),
eq_obj1id: wp.array(dtype=int),
eq_obj2id: wp.array(dtype=int),
eq_objtype: wp.array(dtype=int),
# Data in:
nefc_in: wp.array(dtype=int),
contact_geom_in: wp.array(dtype=wp.vec2i),
efc_type_in: wp.array2d(dtype=int),
efc_id_in: wp.array2d(dtype=int),
efc_J_in: wp.array3d(dtype=float),
njmax_in: int,
# Out:
tree_tree: wp.array3d(dtype=int), # kernel_analyzer: off
):
"""Find tree edges. Launch: (nworld, njmax)."""
worldid, efcid = wp.tid()
# skip if beyond active constraints
if efcid >= wp.min(njmax_in, nefc_in[worldid]):
return
efc_type = efc_type_in[worldid, efcid]
efc_id = efc_id_in[worldid, efcid]
tree0 = int(-1)
tree1 = int(-1)
use_generic = int(0)
# equality (connect/weld)
if efc_type == ConstraintType.EQUALITY:
eq_t = eq_type[efc_id]
if eq_t == EqType.CONNECT or eq_t == EqType.WELD:
b1 = eq_obj1id[efc_id]
b2 = eq_obj2id[efc_id]
# site semantics
if eq_objtype[efc_id] == ObjType.SITE:
b1 = site_bodyid[b1]
b2 = site_bodyid[b2]
tree0 = body_treeid[b1]
tree1 = body_treeid[b2]
else:
# JOINT, TENDON, FLEX
use_generic = 1
# joint friction
elif efc_type == ConstraintType.FRICTION_DOF:
tree0 = dof_treeid[efc_id]
# joint limit
elif efc_type == ConstraintType.LIMIT_JOINT:
tree0 = dof_treeid[jnt_dofadr[efc_id]]
# contact
elif (
efc_type == ConstraintType.CONTACT_FRICTIONLESS
or efc_type == ConstraintType.CONTACT_PYRAMIDAL
or efc_type == ConstraintType.CONTACT_ELLIPTIC
):
geom_pair = contact_geom_in[efc_id]
g1 = geom_pair[0]
g2 = geom_pair[1]
# flex contacts have negative geom ids
if g1 >= 0 and g2 >= 0:
tree0 = body_treeid[geom_bodyid[g1]]
tree1 = body_treeid[geom_bodyid[g2]]
else:
use_generic = 1
# generic
else:
use_generic = 1
# handle static bodies
if use_generic == 0:
# swap so tree0 is non-negative if possible
if tree0 < 0 and tree1 >= 0:
tree0 = tree1
tree1 = -1
# mark the edge
if tree0 >= 0:
if tree1 < 0 or tree0 == tree1:
# self-edge
wp.atomic_max(tree_tree, worldid, tree0, tree0, 1)
else:
# cross-tree edge
t1 = wp.min(tree0, tree1)
t2 = wp.max(tree0, tree1)
wp.atomic_max(tree_tree, worldid, t1, t2, 1)
wp.atomic_max(tree_tree, worldid, t2, t1, 1)
return
# generic: scan Jacobian row
first_tree = int(-1)
has_cross_edge = int(0)
for dof in range(nv):
# TODO(team): sparse efc_J
# TODO(team): tree dof skip
J_val = efc_J_in[worldid, efcid, dof]
if J_val != 0.0:
tree = dof_treeid[dof]
if tree < 0:
continue
if first_tree == -1:
first_tree = tree
elif tree != first_tree:
t1 = wp.min(first_tree, tree)
t2 = wp.max(first_tree, tree)
wp.atomic_max(tree_tree, worldid, t1, t2, 1)
has_cross_edge = 1
if first_tree >= 0 and has_cross_edge == 0:
wp.atomic_max(tree_tree, worldid, first_tree, first_tree, 1)
def tree_edges(m: types.Model, d: types.Data, tree_tree: wp.array3d(dtype=int)):
"""Compute tree-tree adjacency matrix."""
tree_tree.zero_()
wp.launch(
kernel=_tree_edges,
dim=(d.nworld, d.njmax),
inputs=[
m.nv,
m.body_treeid,
m.jnt_dofadr,
m.dof_treeid,
m.geom_bodyid,
m.site_bodyid,
m.eq_type,
m.eq_obj1id,
m.eq_obj2id,
m.eq_objtype,
d.nefc,
d.contact.geom,
d.efc.type,
d.efc.id,
d.efc.J,
d.njmax,
],
outputs=[tree_tree],
)
+6 -6
View File
@@ -258,7 +258,7 @@ def _gravity_force(
if gravcomp:
force = -gravity * body_mass[worldid % body_mass.shape[0], bodyid] * gravcomp
pos = xipos_in[worldid, bodyid]
jac, _ = support.jac(body_parentid, body_rootid, dof_bodyid, subtree_com_in, cdof_in, pos, bodyid, dofid, worldid)
jac, _ = support.jac_dof(body_parentid, body_rootid, dof_bodyid, subtree_com_in, cdof_in, pos, bodyid, dofid, worldid)
wp.atomic_add(qfrc_gravcomp_out[worldid], dofid, wp.dot(jac, force))
@@ -526,9 +526,9 @@ def _fluid(m: Model, d: Data):
@wp.kernel
def _qfrc_passive(
# Model:
opt_has_fluid: bool,
jnt_actgravcomp: wp.array(dtype=int),
dof_jntid: wp.array(dtype=int),
has_fluid: bool,
# Data in:
qfrc_spring_in: wp.array2d(dtype=float),
qfrc_damper_in: wp.array2d(dtype=float),
@@ -548,7 +548,7 @@ def _qfrc_passive(
qfrc_passive += qfrc_gravcomp_in[worldid, dofid]
# add fluid force
if opt_has_fluid:
if has_fluid:
qfrc_passive += qfrc_fluid_in[worldid, dofid]
qfrc_passive_out[worldid, dofid] = qfrc_passive
@@ -703,7 +703,7 @@ def _flex_bending(
force[i, x] -= flex_bending[edgeid, 16] * frc[i, x]
for i in range(nvert):
bodyid = flex_vertbodyid[flex_vertadr[f] + v[i]]
bodyid = flex_vertbodyid[v[i]]
for x in range(3):
wp.atomic_add(qfrc_spring_out, worldid, body_dofadr[bodyid] + x, force[i, x])
@@ -826,16 +826,16 @@ def passive(m: Model, d: Data):
outputs=[d.qfrc_gravcomp],
)
if m.opt.has_fluid:
if m.has_fluid:
_fluid(m, d)
wp.launch(
_qfrc_passive,
dim=(d.nworld, m.nv),
inputs=[
m.opt.has_fluid,
m.jnt_actgravcomp,
m.dof_jntid,
m.has_fluid,
d.qfrc_spring,
d.qfrc_damper,
d.qfrc_gravcomp,
+352 -68
View File
@@ -13,15 +13,17 @@
# limitations under the License.
# ==============================================================================
from typing import Optional, Tuple
from typing import Tuple
import warp as wp
from mujoco.mjx.third_party.mujoco_warp._src.math import safe_div
from mujoco.mjx.third_party.mujoco_warp._src.types import MJ_MAXVAL
from mujoco.mjx.third_party.mujoco_warp._src.types import MJ_MINVAL
from mujoco.mjx.third_party.mujoco_warp._src.types import Data
from mujoco.mjx.third_party.mujoco_warp._src.types import GeomType
from mujoco.mjx.third_party.mujoco_warp._src.types import Model
from mujoco.mjx.third_party.mujoco_warp._src.types import RenderContext
from mujoco.mjx.third_party.mujoco_warp._src.types import vec6
wp.set_module_options({"enable_backward": False})
@@ -183,7 +185,7 @@ def _ray_triangle(
@wp.func
def _ray_plane(pos: wp.vec3, mat: wp.mat33, size: wp.vec3, pnt: wp.vec3, vec: wp.vec3) -> Tuple[float, wp.vec3]:
def ray_plane(pos: wp.vec3, mat: wp.mat33, size: wp.vec3, pnt: wp.vec3, vec: wp.vec3) -> Tuple[float, wp.vec3]:
"""Returns the distance and normal at which a ray intersects with a plane."""
# map to local frame
lpnt, lvec = _ray_map(pos, mat, pnt, vec)
@@ -207,7 +209,7 @@ def _ray_plane(pos: wp.vec3, mat: wp.mat33, size: wp.vec3, pnt: wp.vec3, vec: wp
@wp.func
def _ray_sphere(pos: wp.vec3, dist_sqr: float, pnt: wp.vec3, vec: wp.vec3) -> Tuple[float, wp.vec3]:
def ray_sphere(pos: wp.vec3, dist_sqr: float, pnt: wp.vec3, vec: wp.vec3) -> Tuple[float, wp.vec3]:
"""Returns the distance and normal at which a ray intersects with a sphere."""
dif = pnt - pos
@@ -224,11 +226,11 @@ def _ray_sphere(pos: wp.vec3, dist_sqr: float, pnt: wp.vec3, vec: wp.vec3) -> Tu
@wp.func
def _ray_capsule(pos: wp.vec3, mat: wp.mat33, size: wp.vec3, pnt: wp.vec3, vec: wp.vec3) -> Tuple[float, wp.vec3]:
def ray_capsule(pos: wp.vec3, mat: wp.mat33, size: wp.vec3, pnt: wp.vec3, vec: wp.vec3) -> Tuple[float, wp.vec3]:
"""Returns the distance and normal at which a ray intersects with a capsule."""
# bounding sphere test
ssz = size[0] + size[1]
dist_sphere, normal_sphere = _ray_sphere(pos, ssz * ssz, pnt, vec)
dist_sphere, normal_sphere = ray_sphere(pos, ssz * ssz, pnt, vec)
if dist_sphere < 0:
return -1.0, wp.vec3()
@@ -248,8 +250,9 @@ def _ray_capsule(pos: wp.vec3, mat: wp.mat33, size: wp.vec3, pnt: wp.vec3, vec:
sol, xx = _ray_quad(a, b, c)
part = 0 # -1: bottom, 0: cylinder, 1: top
# make sure round solution is between flat sides
if sol >= 0.0 and wp.abs(lpnt[2] + sol * vec[2]) <= size[1]:
# make sure round solution is between flat sides (must use local z component)
# TODO: We should add a test to catch this case.
if sol >= 0.0 and wp.abs(lpnt[2] + sol * lvec[2]) <= size[1]:
if x < 0.0 or sol < x:
x = sol
@@ -260,7 +263,7 @@ def _ray_capsule(pos: wp.vec3, mat: wp.mat33, size: wp.vec3, pnt: wp.vec3, vec:
c = wp.dot(ldif, ldif) - sq_size0
_, xx = _ray_quad(a, b, c)
# accept only top half of sphere
# accept only top half of sphere (use local z component)
for i in range(2):
if xx[i] >= 0.0 and lpnt[2] + xx[i] * lvec[2] >= size[1]:
if x < 0.0 or xx[i] < x:
@@ -273,7 +276,7 @@ def _ray_capsule(pos: wp.vec3, mat: wp.mat33, size: wp.vec3, pnt: wp.vec3, vec:
c = wp.dot(ldif, ldif) - sq_size0
_, xx = _ray_quad(a, b, c)
# accept only bottom half of sphere
# accept only bottom half of sphere (use local z component)
for i in range(2):
if xx[i] >= 0.0 and lpnt[2] + xx[i] * lvec[2] <= -size[1]:
if x < 0.0 or xx[i] < x:
@@ -297,7 +300,7 @@ def _ray_capsule(pos: wp.vec3, mat: wp.mat33, size: wp.vec3, pnt: wp.vec3, vec:
@wp.func
def _ray_ellipsoid(pos: wp.vec3, mat: wp.mat33, size: wp.vec3, pnt: wp.vec3, vec: wp.vec3) -> Tuple[float, wp.vec3]:
def ray_ellipsoid(pos: wp.vec3, mat: wp.mat33, size: wp.vec3, pnt: wp.vec3, vec: wp.vec3) -> Tuple[float, wp.vec3]:
"""Returns the distance and normal at which a ray intersects with an ellipsoid."""
# map to local frame
lpnt, lvec = _ray_map(pos, mat, pnt, vec)
@@ -328,11 +331,11 @@ def _ray_ellipsoid(pos: wp.vec3, mat: wp.mat33, size: wp.vec3, pnt: wp.vec3, vec
@wp.func
def _ray_cylinder(pos: wp.vec3, mat: wp.mat33, size: wp.vec3, pnt: wp.vec3, vec: wp.vec3) -> Tuple[float, wp.vec3]:
def ray_cylinder(pos: wp.vec3, mat: wp.mat33, size: wp.vec3, pnt: wp.vec3, vec: wp.vec3) -> Tuple[float, wp.vec3]:
"""Returns the distance and normal at which a ray intersects with a cylinder."""
# bounding sphere test
ssz = size[0] * size[0] + size[1] * size[1]
dist_sphere, normal_sphere = _ray_sphere(pos, ssz, pnt, vec)
dist_sphere, normal_sphere = ray_sphere(pos, ssz, pnt, vec)
if dist_sphere < 0:
return -1.0, wp.vec3()
@@ -392,13 +395,13 @@ _IFACE = wp.types.matrix((3, 2), dtype=int)(1, 2, 0, 2, 0, 1)
@wp.func
def _ray_box(pos: wp.vec3, mat: wp.mat33, size: wp.vec3, pnt: wp.vec3, vec: wp.vec3) -> Tuple[float, vec6, wp.vec3]:
def ray_box(pos: wp.vec3, mat: wp.mat33, size: wp.vec3, pnt: wp.vec3, vec: wp.vec3) -> Tuple[float, vec6, wp.vec3]:
"""Returns distance, per side information, and normal at which a ray intersects with a box."""
all = vec6(-1.0, -1.0, -1.0, -1.0, -1.0, -1.0)
# bounding sphere test
ssz = wp.dot(size, size)
dist_sphere, _ = _ray_sphere(pos, ssz, pnt, vec)
dist_sphere, _ = ray_sphere(pos, ssz, pnt, vec)
if dist_sphere < 0:
return -1.0, all, wp.vec3()
@@ -446,7 +449,7 @@ def _ray_box(pos: wp.vec3, mat: wp.mat33, size: wp.vec3, pnt: wp.vec3, vec: wp.v
@wp.func
def _ray_hfield(
def ray_hfield(
# Model:
geom_type: wp.array(dtype=int),
geom_dataid: wp.array(dtype=int),
@@ -487,10 +490,10 @@ def _ray_hfield(
top_pos = pos + mat_col * top_scale
# init: intersection with base box
x, _, normal_base = _ray_box(base_pos, mat, base_size, pnt, vec)
x, _, normal_base = ray_box(base_pos, mat, base_size, pnt, vec)
# check top box: done if no intersection
top_intersect, all, normal_top = _ray_box(top_pos, mat, top_size, pnt, vec)
top_intersect, all, normal_top = ray_box(top_pos, mat, top_size, pnt, vec)
if top_intersect < 0.0:
return x, normal_base
@@ -627,10 +630,16 @@ def ray_mesh(
data_id: int,
pos: wp.vec3,
mat: wp.mat33,
size: wp.vec3,
pnt: wp.vec3,
vec: wp.vec3,
) -> Tuple[float, wp.vec3]:
"""Returns the distance and normal for ray mesh intersections."""
# bounding box test
dist_box, _all, _normal = ray_box(pos, mat, size, pnt, vec)
if dist_box < 0.0:
return -1.0, wp.vec3()
pnt, vec = _ray_map(pos, mat, pnt, vec)
# compute orthogonal basis vectors
@@ -687,6 +696,87 @@ def ray_mesh(
return x, normal
@wp.func
def ray_mesh_with_bvh(
# In:
mesh_bvh_id: wp.array(dtype=wp.uint64),
mesh_geom_id: int,
pos: wp.vec3,
mat: wp.mat33,
pnt: wp.vec3,
vec: wp.vec3,
max_t: float,
) -> Tuple[float, wp.vec3, float, float, int, int]:
"""Returns intersection information for ray mesh intersections.
Requires wp.Mesh be constructed and their ids to be passed.
"""
t = float(-1.0)
u = float(0.0)
v = float(0.0)
sign = float(0.0)
n = wp.vec3(0.0, 0.0, 0.0)
f = int(-1)
lpnt, lvec = _ray_map(pos, mat, pnt, vec)
hit = wp.mesh_query_ray(mesh_bvh_id[mesh_geom_id], lpnt, lvec, max_t, t, u, v, sign, n, f)
if hit and wp.dot(lvec, n) < 0.0: # Backface culling in local space
normal = mat @ n
normal = wp.normalize(normal)
return t, normal, u, v, f, mesh_geom_id
return -1.0, wp.vec3(0.0, 0.0, 0.0), 0.0, 0.0, -1, -1
@wp.func
def ray_mesh_with_bvh_anyhit(
# In:
mesh_bvh_id: wp.array(dtype=wp.uint64),
mesh_geom_id: int,
pos: wp.vec3,
mat: wp.mat33,
pnt: wp.vec3,
vec: wp.vec3,
max_t: float,
) -> bool:
"""Returns True if there is any hit for ray mesh intersections.
Requires wp.Mesh be constructed and their ids to be passed. This variant is useful
for shadow ray casts where the only goal is if there is any ray hit.
"""
lpnt, lvec = _ray_map(pos, mat, pnt, vec)
return wp.mesh_query_ray_anyhit(mesh_bvh_id[mesh_geom_id], lpnt, lvec, max_t)
@wp.func
def ray_flex_with_bvh(
# In:
bvh_id: wp.uint64,
group_root: int,
pnt: wp.vec3,
vec: wp.vec3,
max_t: float,
) -> Tuple[float, wp.vec3, float, float, int]:
"""Returns intersection information for flex intersections.
Requires wp.Mesh be constructed and their ids to be passed. Flex are already in world space.
"""
t = float(-1.0)
u = float(0.0)
v = float(0.0)
sign = float(0.0)
n = wp.vec3(0.0, 0.0, 0.0)
f = int(-1)
hit = wp.mesh_query_ray(bvh_id, pnt, vec, max_t, t, u, v, sign, n, f, group_root)
if hit:
return t, n, u, v, f
return -1.0, wp.vec3(0.0, 0.0, 0.0), 0.0, 0.0, -1
@wp.func
def ray_geom(pos: wp.vec3, mat: wp.mat33, size: wp.vec3, pnt: wp.vec3, vec: wp.vec3, geomtype: int) -> Tuple[float, wp.vec3]:
"""Returns distance along ray to intersection with geom and normal at intersection point.
@@ -695,17 +785,17 @@ def ray_geom(pos: wp.vec3, mat: wp.mat33, size: wp.vec3, pnt: wp.vec3, vec: wp.v
"""
# TODO(team): static loop unrolling to remove unnecessary branching
if geomtype == GeomType.PLANE:
return _ray_plane(pos, mat, size, pnt, vec)
return ray_plane(pos, mat, size, pnt, vec)
elif geomtype == GeomType.SPHERE:
return _ray_sphere(pos, size[0] * size[0], pnt, vec)
return ray_sphere(pos, size[0] * size[0], pnt, vec)
elif geomtype == GeomType.CAPSULE:
return _ray_capsule(pos, mat, size, pnt, vec)
return ray_capsule(pos, mat, size, pnt, vec)
elif geomtype == GeomType.ELLIPSOID:
return _ray_ellipsoid(pos, mat, size, pnt, vec)
return ray_ellipsoid(pos, mat, size, pnt, vec)
elif geomtype == GeomType.CYLINDER:
return _ray_cylinder(pos, mat, size, pnt, vec)
return ray_cylinder(pos, mat, size, pnt, vec)
elif geomtype == GeomType.BOX:
dist, _, normal = _ray_box(pos, mat, size, pnt, vec)
dist, _, normal = ray_box(pos, mat, size, pnt, vec)
return dist, normal
else:
return -1.0, wp.vec3()
@@ -771,11 +861,12 @@ def _ray_geom_mesh(
geom_dataid[geomid],
pos,
mat,
geom_size[worldid % geom_size.shape[0], geomid],
pnt,
vec,
)
elif type == GeomType.HFIELD:
return _ray_hfield(
return ray_hfield(
geom_type,
geom_dataid,
hfield_size,
@@ -836,7 +927,7 @@ def _ray(
num_threads = wp.block_dim()
min_dist = float(wp.inf)
min_dist = float(MJ_MAXVAL)
min_geomid = int(-1)
min_normal = wp.vec3()
@@ -874,9 +965,9 @@ def _ray(
geomid,
)
if dist < 0:
dist = wp.inf
dist = MJ_MAXVAL
else:
dist = wp.inf
dist = MJ_MAXVAL
normal = wp.vec3()
tile_dist = wp.tile(dist)
@@ -891,7 +982,162 @@ def _ray(
min_geomid = tile_geomid[local_min_geomid[0]]
min_normal = tile_normal[local_min_geomid[0]]
if wp.isinf(min_dist):
if min_dist >= MJ_MAXVAL:
dist_out[worldid, rayid] = -1.0
else:
dist_out[worldid, rayid] = min_dist
geomid_out[worldid, rayid] = min_geomid
normal_out[worldid, rayid] = min_normal
@wp.func
def _ray_geom_mesh_bvh(
# Model:
body_weldid: wp.array(dtype=int),
geom_type: wp.array(dtype=int),
geom_bodyid: wp.array(dtype=int),
geom_dataid: wp.array(dtype=int),
geom_matid: wp.array2d(dtype=int),
geom_group: wp.array(dtype=int),
geom_size: wp.array2d(dtype=wp.vec3),
geom_rgba: wp.array2d(dtype=wp.vec4),
mat_rgba: wp.array2d(dtype=wp.vec4),
# Data in:
geom_xpos_in: wp.array2d(dtype=wp.vec3),
geom_xmat_in: wp.array2d(dtype=wp.mat33),
# In:
worldid: int,
pnt: wp.vec3,
vec: wp.vec3,
geomgroup: vec6,
flg_static: bool,
bodyexclude: int,
geomid: int,
mesh_bvh_id: wp.array(dtype=wp.uint64),
hfield_bvh_id: wp.array(dtype=wp.uint64),
min_dist: float,
) -> Tuple[float, wp.vec3]:
if not _ray_eliminate(
body_weldid,
geom_bodyid,
geom_matid[worldid % geom_matid.shape[0]],
geom_group,
geom_rgba[worldid % geom_rgba.shape[0]],
mat_rgba[worldid % mat_rgba.shape[0]],
geomid,
geomgroup,
flg_static,
bodyexclude,
):
pos = geom_xpos_in[worldid, geomid]
mat = geom_xmat_in[worldid, geomid]
gtype = geom_type[geomid]
if gtype == GeomType.MESH or gtype == GeomType.HFIELD:
bvh_ids = mesh_bvh_id if gtype == GeomType.MESH else hfield_bvh_id
t, n, u, v, f, geom_mesh_id = ray_mesh_with_bvh(
bvh_ids,
geom_dataid[geomid],
pos,
mat,
pnt,
vec,
min_dist,
)
if t >= 0.0 and t < min_dist:
return t, n
else:
return ray_geom(
pos,
mat,
geom_size[worldid % geom_size.shape[0], geomid],
pnt,
vec,
gtype,
)
return -1.0, wp.vec3()
@wp.kernel
def _ray_bvh(
# Model:
ngeom: int,
body_weldid: wp.array(dtype=int),
geom_type: wp.array(dtype=int),
geom_bodyid: wp.array(dtype=int),
geom_dataid: wp.array(dtype=int),
geom_matid: wp.array2d(dtype=int),
geom_group: wp.array(dtype=int),
geom_size: wp.array2d(dtype=wp.vec3),
geom_rgba: wp.array2d(dtype=wp.vec4),
mat_rgba: wp.array2d(dtype=wp.vec4),
# Data in:
geom_xpos_in: wp.array2d(dtype=wp.vec3),
geom_xmat_in: wp.array2d(dtype=wp.mat33),
# In:
pnt: wp.array2d(dtype=wp.vec3),
vec: wp.array2d(dtype=wp.vec3),
geomgroup: vec6,
flg_static: bool,
bodyexclude: wp.array(dtype=int),
bvh_id: wp.uint64,
group_root: wp.array(dtype=int),
enabled_geom_ids: wp.array(dtype=int),
mesh_bvh_id: wp.array(dtype=wp.uint64),
hfield_bvh_id: wp.array(dtype=wp.uint64),
# Out:
dist_out: wp.array2d(dtype=float),
geomid_out: wp.array2d(dtype=int),
normal_out: wp.array2d(dtype=wp.vec3),
):
worldid, rayid = wp.tid()
ray_origin = pnt[worldid, rayid]
ray_dir = vec[worldid, rayid]
body_exclude = bodyexclude[rayid]
min_dist = float(MJ_MAXVAL)
min_geomid = int(-1)
min_normal = wp.vec3()
query = wp.bvh_query_ray(bvh_id, ray_origin, ray_dir, group_root[worldid])
bounds_nr = int(0)
while wp.bvh_query_next(query, bounds_nr, min_dist):
bvh_local = bounds_nr - (worldid * ngeom)
geomid = enabled_geom_ids[bvh_local]
dist, normal = _ray_geom_mesh_bvh(
body_weldid,
geom_type,
geom_bodyid,
geom_dataid,
geom_matid,
geom_group,
geom_size,
geom_rgba,
mat_rgba,
geom_xpos_in,
geom_xmat_in,
worldid,
pnt[worldid, rayid],
vec[worldid, rayid],
geomgroup,
flg_static,
body_exclude,
geomid,
mesh_bvh_id,
hfield_bvh_id,
min_dist,
)
if dist >= 0.0 and dist < min_dist:
min_dist = dist
min_geomid = geomid
min_normal = normal
if min_dist >= MJ_MAXVAL:
dist_out[worldid, rayid] = -1.0
else:
dist_out[worldid, rayid] = min_dist
@@ -904,9 +1150,10 @@ def ray(
d: Data,
pnt: wp.array2d(dtype=wp.vec3),
vec: wp.array2d(dtype=wp.vec3),
geomgroup: Optional[vec6] = None,
geomgroup: vec6 | None = None,
flg_static: bool = True,
bodyexclude: int = -1,
rc: RenderContext | None = None,
) -> Tuple[wp.array, wp.array, wp.array]:
"""Returns the distance at which rays intersect with primitive geoms.
@@ -915,9 +1162,11 @@ def ray(
d: The data object containing the current state and output arrays (device).
pnt: Ray origin points.
vec: Ray directions.
geomgroup: Group inclusion/exclusion mask. If all are wp.inf, ignore.
geomgroup: Group inclusion/exclusion mask.
flg_static: If True, allows rays to intersect with static geoms.
bodyexclude: Ignore geoms on specified body id (-1 to disable).
rc: Optional Render context containing BVH information for BVH accelerated ray
intersections.
Returns:
Distances from ray origins to geom surfaces, IDs of intersected geoms (-1 if none),
@@ -935,7 +1184,7 @@ def ray(
ray_geomid = wp.empty((d.nworld, 1), dtype=int)
ray_normal = wp.empty((d.nworld, 1), dtype=wp.vec3)
rays(m, d, pnt, vec, geomgroup, flg_static, ray_bodyexclude, ray_dist, ray_geomid, ray_normal)
rays(m, d, pnt, vec, geomgroup, flg_static, ray_bodyexclude, ray_dist, ray_geomid, ray_normal, rc)
return ray_dist, ray_geomid, ray_normal
@@ -951,6 +1200,7 @@ def rays(
dist: wp.array2d(dtype=float),
geomid: wp.array2d(dtype=int),
normal: wp.array2d(dtype=wp.vec3),
rc: RenderContext | None = None,
):
"""Ray intersection for multiple worlds and multiple rays.
@@ -968,41 +1218,75 @@ def rays(
geomid: Output array for IDs of intersected geoms, shape (nworld, nray). -1
indicates no intersection.
normal: Output array for normals at intersection points, shape (nworld, nray).
rc: Optional Render context containing BVH information for BVH accelerated ray
intersections.
"""
wp.launch_tiled(
_ray,
dim=(d.nworld, pnt.shape[1]),
inputs=[
m.ngeom,
m.nmeshface,
m.body_weldid,
m.geom_type,
m.geom_bodyid,
m.geom_dataid,
m.geom_matid,
m.geom_group,
m.geom_size,
m.geom_rgba,
m.mesh_vertadr,
m.mesh_faceadr,
m.mesh_vert,
m.mesh_face,
m.hfield_size,
m.hfield_nrow,
m.hfield_ncol,
m.hfield_adr,
m.hfield_data,
m.mat_rgba,
d.geom_xpos,
d.geom_xmat,
pnt,
vec,
geomgroup,
flg_static,
bodyexclude,
dist,
geomid,
normal,
],
block_dim=m.block_dim.ray,
)
# TODO: Investigate building rc if none and removing the non-accelerated path
if rc is None:
wp.launch_tiled(
_ray,
dim=(d.nworld, pnt.shape[1]),
inputs=[
m.ngeom,
m.nmeshface,
m.body_weldid,
m.geom_type,
m.geom_bodyid,
m.geom_dataid,
m.geom_matid,
m.geom_group,
m.geom_size,
m.geom_rgba,
m.mesh_vertadr,
m.mesh_faceadr,
m.mesh_vert,
m.mesh_face,
m.hfield_size,
m.hfield_nrow,
m.hfield_ncol,
m.hfield_adr,
m.hfield_data,
m.mat_rgba,
d.geom_xpos,
d.geom_xmat,
pnt,
vec,
geomgroup,
flg_static,
bodyexclude,
dist,
geomid,
normal,
],
block_dim=m.block_dim.ray,
)
else:
wp.launch(
_ray_bvh,
dim=(d.nworld, pnt.shape[1]),
inputs=[
rc.bvh_ngeom,
m.body_weldid,
m.geom_type,
m.geom_bodyid,
m.geom_dataid,
m.geom_matid,
m.geom_group,
m.geom_size,
m.geom_rgba,
m.mat_rgba,
d.geom_xpos,
d.geom_xmat,
pnt,
vec,
geomgroup,
flg_static,
bodyexclude,
rc.bvh_id,
rc.group_root,
rc.enabled_geom_ids,
rc.mesh_bvh_id,
rc.hfield_bvh_id,
],
outputs=[dist, geomid, normal],
)
+696
View File
@@ -0,0 +1,696 @@
# Copyright 2026 The Newton Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
from typing import Tuple
import warp as wp
from mujoco.mjx.third_party.mujoco_warp._src import math
from mujoco.mjx.third_party.mujoco_warp._src.ray import ray_box
from mujoco.mjx.third_party.mujoco_warp._src.ray import ray_capsule
from mujoco.mjx.third_party.mujoco_warp._src.ray import ray_cylinder
from mujoco.mjx.third_party.mujoco_warp._src.ray import ray_ellipsoid
from mujoco.mjx.third_party.mujoco_warp._src.ray import ray_flex_with_bvh
from mujoco.mjx.third_party.mujoco_warp._src.ray import ray_mesh_with_bvh
from mujoco.mjx.third_party.mujoco_warp._src.ray import ray_mesh_with_bvh_anyhit
from mujoco.mjx.third_party.mujoco_warp._src.ray import ray_plane
from mujoco.mjx.third_party.mujoco_warp._src.ray import ray_sphere
from mujoco.mjx.third_party.mujoco_warp._src.render_util import compute_ray
from mujoco.mjx.third_party.mujoco_warp._src.render_util import pack_rgba_to_uint32
from mujoco.mjx.third_party.mujoco_warp._src.types import MJ_MAXVAL
from mujoco.mjx.third_party.mujoco_warp._src.types import Data
from mujoco.mjx.third_party.mujoco_warp._src.types import GeomType
from mujoco.mjx.third_party.mujoco_warp._src.types import Model
from mujoco.mjx.third_party.mujoco_warp._src.types import RenderContext
from mujoco.mjx.third_party.mujoco_warp._src.warp_util import event_scope
wp.set_module_options({"enable_backward": False})
# TODO(team): remove after mjwarp depends on warp-lang >= 1.12 in pyproject.toml
from mujoco.mjx.third_party.mujoco_warp._src.types import TEXTURE_DTYPE
@wp.func
def sample_texture(
# Model:
geom_type: wp.array(dtype=int),
mesh_faceadr: wp.array(dtype=int),
# In:
geom_id: int,
tex_repeat: wp.vec2,
tex: TEXTURE_DTYPE,
pos: wp.vec3,
rot: wp.mat33,
mesh_facetexcoord: wp.array(dtype=wp.vec3i),
mesh_texcoord: wp.array(dtype=wp.vec2),
mesh_texcoord_offsets: wp.array(dtype=int),
hit_point: wp.vec3,
bary_u: float,
bary_v: float,
f: int,
mesh_id: int,
) -> wp.vec3:
uv = wp.vec2(0.0, 0.0)
if geom_type[geom_id] == GeomType.PLANE:
local = wp.transpose(rot) @ (hit_point - pos)
uv = wp.vec2(local[0], local[1])
if geom_type[geom_id] == GeomType.MESH:
if f < 0 or mesh_id < 0:
return wp.vec3(0.0, 0.0, 0.0)
face_adr = mesh_faceadr[mesh_id] + f
uv0 = mesh_texcoord[mesh_texcoord_offsets[mesh_id] + mesh_facetexcoord[face_adr][0]]
uv1 = mesh_texcoord[mesh_texcoord_offsets[mesh_id] + mesh_facetexcoord[face_adr][1]]
uv2 = mesh_texcoord[mesh_texcoord_offsets[mesh_id] + mesh_facetexcoord[face_adr][2]]
uv = uv0 * bary_u + uv1 * bary_v + uv2 * (1.0 - bary_u - bary_v)
u = uv[0] * tex_repeat[0]
v = uv[1] * tex_repeat[1]
u = u - wp.floor(u)
v = v - wp.floor(v)
tex_color = wp.texture_sample(tex, wp.vec2(u, v), dtype=wp.vec4)
return wp.vec3(tex_color[0], tex_color[1], tex_color[2])
# TODO: Investigate combining cast_ray and cast_ray_first_hit
@wp.func
def cast_ray(
# Model:
geom_type: wp.array(dtype=int),
geom_dataid: wp.array(dtype=int),
geom_size: wp.array2d(dtype=wp.vec3),
# Data in:
geom_xpos_in: wp.array2d(dtype=wp.vec3),
geom_xmat_in: wp.array2d(dtype=wp.mat33),
# In:
bvh_id: wp.uint64,
group_root: int,
world_id: int,
bvh_ngeom: int,
enabled_geom_ids: wp.array(dtype=int),
mesh_bvh_id: wp.array(dtype=wp.uint64),
hfield_bvh_id: wp.array(dtype=wp.uint64),
ray_origin_world: wp.vec3,
ray_dir_world: wp.vec3,
) -> Tuple[int, float, wp.vec3, float, float, int, int]:
dist = float(MJ_MAXVAL)
normal = wp.vec3(0.0, 0.0, 0.0)
geom_id = int(-1)
bary_u = float(0.0)
bary_v = float(0.0)
face_idx = int(-1)
geom_mesh_id = int(-1)
query = wp.bvh_query_ray(bvh_id, ray_origin_world, ray_dir_world, group_root)
bounds_nr = int(0)
while wp.bvh_query_next(query, bounds_nr, dist):
gi_global = bounds_nr
gi_bvh_local = gi_global - (world_id * bvh_ngeom)
gi = enabled_geom_ids[gi_bvh_local]
hit_mesh_id = int(-1)
u = float(0.0)
v = float(0.0)
f = int(-1)
n = wp.vec3(0.0, 0.0, 0.0)
# TODO: Investigate branch elimination with static loop unrolling
if geom_type[gi] == GeomType.PLANE:
d, n = ray_plane(
geom_xpos_in[world_id, gi],
geom_xmat_in[world_id, gi],
geom_size[world_id % geom_size.shape[0], gi],
ray_origin_world,
ray_dir_world,
)
if geom_type[gi] == GeomType.HFIELD:
d, n, u, v, f, geom_hfield_id = ray_mesh_with_bvh(
hfield_bvh_id,
geom_dataid[gi],
geom_xpos_in[world_id, gi],
geom_xmat_in[world_id, gi],
ray_origin_world,
ray_dir_world,
dist,
)
if geom_type[gi] == GeomType.SPHERE:
d, n = ray_sphere(
geom_xpos_in[world_id, gi],
geom_size[world_id % geom_size.shape[0], gi][0] * geom_size[world_id % geom_size.shape[0], gi][0],
ray_origin_world,
ray_dir_world,
)
if geom_type[gi] == GeomType.ELLIPSOID:
d, n = ray_ellipsoid(
geom_xpos_in[world_id, gi],
geom_xmat_in[world_id, gi],
geom_size[world_id % geom_size.shape[0], gi],
ray_origin_world,
ray_dir_world,
)
if geom_type[gi] == GeomType.CAPSULE:
d, n = ray_capsule(
geom_xpos_in[world_id, gi],
geom_xmat_in[world_id, gi],
geom_size[world_id % geom_size.shape[0], gi],
ray_origin_world,
ray_dir_world,
)
if geom_type[gi] == GeomType.CYLINDER:
d, n = ray_cylinder(
geom_xpos_in[world_id, gi],
geom_xmat_in[world_id, gi],
geom_size[world_id % geom_size.shape[0], gi],
ray_origin_world,
ray_dir_world,
)
if geom_type[gi] == GeomType.BOX:
d, all, n = ray_box(
geom_xpos_in[world_id, gi],
geom_xmat_in[world_id, gi],
geom_size[world_id % geom_size.shape[0], gi],
ray_origin_world,
ray_dir_world,
)
if geom_type[gi] == GeomType.MESH:
d, n, u, v, f, hit_mesh_id = ray_mesh_with_bvh(
mesh_bvh_id,
geom_dataid[gi],
geom_xpos_in[world_id, gi],
geom_xmat_in[world_id, gi],
ray_origin_world,
ray_dir_world,
dist,
)
if d >= 0.0 and d < dist:
dist = d
normal = n
geom_id = gi
bary_u = u
bary_v = v
face_idx = f
geom_mesh_id = hit_mesh_id
return geom_id, dist, normal, bary_u, bary_v, face_idx, geom_mesh_id
@wp.func
def cast_ray_first_hit(
# Model:
geom_type: wp.array(dtype=int),
geom_dataid: wp.array(dtype=int),
geom_size: wp.array2d(dtype=wp.vec3),
# Data in:
geom_xpos_in: wp.array2d(dtype=wp.vec3),
geom_xmat_in: wp.array2d(dtype=wp.mat33),
# In:
bvh_id: wp.uint64,
group_root: int,
world_id: int,
bvh_ngeom: int,
enabled_geom_ids: wp.array(dtype=int),
mesh_bvh_id: wp.array(dtype=wp.uint64),
hfield_bvh_id: wp.array(dtype=wp.uint64),
ray_origin_world: wp.vec3,
ray_dir_world: wp.vec3,
max_dist: float,
) -> bool:
"""A simpler version of casting rays that only checks for the first hit."""
query = wp.bvh_query_ray(bvh_id, ray_origin_world, ray_dir_world, group_root)
bounds_nr = int(0)
while wp.bvh_query_next(query, bounds_nr, max_dist):
gi_global = bounds_nr
gi_bvh_local = gi_global - (world_id * bvh_ngeom)
gi = enabled_geom_ids[gi_bvh_local]
# TODO: Investigate branch elimination with static loop unrolling
if geom_type[gi] == GeomType.PLANE:
d, n = ray_plane(
geom_xpos_in[world_id, gi],
geom_xmat_in[world_id, gi],
geom_size[world_id % geom_size.shape[0], gi],
ray_origin_world,
ray_dir_world,
)
if geom_type[gi] == GeomType.HFIELD:
d, n, u, v, f, geom_hfield_id = ray_mesh_with_bvh(
hfield_bvh_id,
geom_dataid[gi],
geom_xpos_in[world_id, gi],
geom_xmat_in[world_id, gi],
ray_origin_world,
ray_dir_world,
max_dist,
)
if geom_type[gi] == GeomType.SPHERE:
d, n = ray_sphere(
geom_xpos_in[world_id, gi],
geom_size[world_id % geom_size.shape[0], gi][0] * geom_size[world_id % geom_size.shape[0], gi][0],
ray_origin_world,
ray_dir_world,
)
if geom_type[gi] == GeomType.ELLIPSOID:
d, n = ray_ellipsoid(
geom_xpos_in[world_id, gi],
geom_xmat_in[world_id, gi],
geom_size[world_id % geom_size.shape[0], gi],
ray_origin_world,
ray_dir_world,
)
if geom_type[gi] == GeomType.CAPSULE:
d, n = ray_capsule(
geom_xpos_in[world_id, gi],
geom_xmat_in[world_id, gi],
geom_size[world_id % geom_size.shape[0], gi],
ray_origin_world,
ray_dir_world,
)
if geom_type[gi] == GeomType.CYLINDER:
d, n = ray_cylinder(
geom_xpos_in[world_id, gi],
geom_xmat_in[world_id, gi],
geom_size[world_id % geom_size.shape[0], gi],
ray_origin_world,
ray_dir_world,
)
if geom_type[gi] == GeomType.BOX:
d, all, n = ray_box(
geom_xpos_in[world_id, gi],
geom_xmat_in[world_id, gi],
geom_size[world_id % geom_size.shape[0], gi],
ray_origin_world,
ray_dir_world,
)
if geom_type[gi] == GeomType.MESH:
hit = ray_mesh_with_bvh_anyhit(
mesh_bvh_id,
geom_dataid[gi],
geom_xpos_in[world_id, gi],
geom_xmat_in[world_id, gi],
ray_origin_world,
ray_dir_world,
max_dist,
)
d = 0.0 if hit else -1.0
if d >= 0.0 and d < max_dist:
return True
return False
@wp.func
def compute_lighting(
# Model:
geom_type: wp.array(dtype=int),
geom_dataid: wp.array(dtype=int),
geom_size: wp.array2d(dtype=wp.vec3),
# Data in:
geom_xpos_in: wp.array2d(dtype=wp.vec3),
geom_xmat_in: wp.array2d(dtype=wp.mat33),
# In:
use_shadows: bool,
bvh_id: wp.uint64,
group_root: int,
bvh_ngeom: int,
enabled_geom_ids: wp.array(dtype=int),
world_id: int,
mesh_bvh_id: wp.array(dtype=wp.uint64),
hfield_bvh_id: wp.array(dtype=wp.uint64),
lightactive: bool,
lighttype: int,
lightcastshadow: bool,
lightpos: wp.vec3,
lightdir: wp.vec3,
normal: wp.vec3,
hitpoint: wp.vec3,
) -> float:
light_contribution = float(0.0)
# TODO: We should probably only be looping over active lights
# in the first place with a static loop of enabled light idx?
if not lightactive:
return light_contribution
L = wp.vec3(0.0, 0.0, 0.0)
dist_to_light = float(MJ_MAXVAL)
attenuation = float(1.0)
if lighttype == 1: # directional light
L = wp.normalize(-lightdir)
else:
L, dist_to_light = math.normalize_with_norm(lightpos - hitpoint)
attenuation = 1.0 / (1.0 + 0.02 * dist_to_light * dist_to_light)
if lighttype == 0: # spot light
spot_dir = wp.normalize(lightdir)
cos_theta = wp.dot(-L, spot_dir)
spot_factor = wp.min(1.0, wp.max(0.0, (cos_theta - 0.85) / (0.95 - 0.85)))
attenuation = attenuation * spot_factor
ndotl = wp.max(0.0, wp.dot(normal, L))
if ndotl == 0.0:
return light_contribution
visible = float(1.0)
if use_shadows and lightcastshadow:
# Nudge the origin slightly along the surface normal to avoid
# self-intersection when casting shadow rays
eps = 1.0e-4
shadow_origin = hitpoint + normal * eps
# Distance-limited shadows: cap by dist_to_light (for non-directional)
max_t = float(dist_to_light - 1.0e-3)
if lighttype == 1: # directional light
max_t = float(1.0e8)
shadow_hit = cast_ray_first_hit(
geom_type,
geom_dataid,
geom_size,
geom_xpos_in,
geom_xmat_in,
bvh_id,
group_root,
world_id,
bvh_ngeom,
enabled_geom_ids,
mesh_bvh_id,
hfield_bvh_id,
shadow_origin,
L,
max_t,
)
if shadow_hit:
visible = 0.3
return ndotl * attenuation * visible
@event_scope
def render(m: Model, d: Data, rc: RenderContext):
"""Render the current frame.
Outputs are stored in buffers within the render context.
Args:
m: The model on device.
d: The data on device.
rc: The render context on device.
"""
rc.rgb_data.fill_(rc.background_color)
rc.depth_data.fill_(0.0)
@wp.kernel(module="unique", enable_backward=False)
def _render_megakernel(
# Model:
geom_type: wp.array(dtype=int),
geom_dataid: wp.array(dtype=int),
geom_matid: wp.array2d(dtype=int),
geom_size: wp.array2d(dtype=wp.vec3),
geom_rgba: wp.array2d(dtype=wp.vec4),
cam_projection: wp.array(dtype=int),
cam_fovy: wp.array2d(dtype=float),
cam_sensorsize: wp.array(dtype=wp.vec2),
cam_intrinsic: wp.array2d(dtype=wp.vec4),
light_type: wp.array2d(dtype=int),
light_castshadow: wp.array2d(dtype=bool),
light_active: wp.array2d(dtype=bool),
mesh_faceadr: wp.array(dtype=int),
mat_texid: wp.array3d(dtype=int),
mat_texrepeat: wp.array2d(dtype=wp.vec2),
mat_rgba: wp.array2d(dtype=wp.vec4),
# Data in:
geom_xpos_in: wp.array2d(dtype=wp.vec3),
geom_xmat_in: wp.array2d(dtype=wp.mat33),
cam_xpos_in: wp.array2d(dtype=wp.vec3),
cam_xmat_in: wp.array2d(dtype=wp.mat33),
light_xpos_in: wp.array2d(dtype=wp.vec3),
light_xdir_in: wp.array2d(dtype=wp.vec3),
# In:
nrender: int,
use_shadows: bool,
bvh_ngeom: int,
cam_res: wp.array(dtype=wp.vec2i),
cam_id_map: wp.array(dtype=int),
ray: wp.array(dtype=wp.vec3),
rgb_adr: wp.array(dtype=int),
depth_adr: wp.array(dtype=int),
render_rgb: wp.array(dtype=bool),
render_depth: wp.array(dtype=bool),
bvh_id: wp.uint64,
group_root: wp.array(dtype=int),
flex_bvh_id: wp.uint64,
flex_group_root: wp.array(dtype=int),
enabled_geom_ids: wp.array(dtype=int),
mesh_bvh_id: wp.array(dtype=wp.uint64),
mesh_facetexcoord: wp.array(dtype=wp.vec3i),
mesh_texcoord: wp.array(dtype=wp.vec2),
mesh_texcoord_offsets: wp.array(dtype=int),
hfield_bvh_id: wp.array(dtype=wp.uint64),
flex_rgba: wp.array(dtype=wp.vec4),
# TODO: remove after mjwarp depends on warp-lang >= 1.12 in pyproject.toml
textures: wp.array(dtype=TEXTURE_DTYPE),
# Out:
rgb_out: wp.array2d(dtype=wp.uint32),
depth_out: wp.array2d(dtype=float),
):
world_idx, ray_idx = wp.tid()
# Map global ray_idx -> (cam_idx, ray_idx_local) using cumulative sizes
cam_idx = int(-1)
ray_idx_local = int(-1)
accum = int(0)
for i in range(nrender):
num_i = cam_res[i][0] * cam_res[i][1]
if ray_idx < accum + num_i:
cam_idx = i
ray_idx_local = ray_idx - accum
break
accum += num_i
if cam_idx == -1 or ray_idx_local < 0:
return
if not render_rgb[cam_idx] and not render_depth[cam_idx]:
return
# Map active camera index to MuJoCo camera ID
mujoco_cam_id = cam_id_map[cam_idx]
if wp.static(rc.ray is None):
img_w = cam_res[cam_idx][0]
img_h = cam_res[cam_idx][1]
px = ray_idx_local % img_w
py = ray_idx_local // img_w
ray_dir_local_cam = compute_ray(
cam_projection[mujoco_cam_id],
cam_fovy[world_idx % cam_fovy.shape[0], mujoco_cam_id],
cam_sensorsize[mujoco_cam_id],
cam_intrinsic[world_idx % cam_intrinsic.shape[0], mujoco_cam_id],
img_w,
img_h,
px,
py,
wp.static(rc.znear),
)
else:
ray_dir_local_cam = ray[ray_idx]
ray_dir_world = cam_xmat_in[world_idx, mujoco_cam_id] @ ray_dir_local_cam
ray_origin_world = cam_xpos_in[world_idx, mujoco_cam_id]
geom_id, dist, normal, u, v, f, mesh_id = cast_ray(
geom_type,
geom_dataid,
geom_size,
geom_xpos_in,
geom_xmat_in,
bvh_id,
group_root[world_idx],
world_idx,
bvh_ngeom,
enabled_geom_ids,
mesh_bvh_id,
hfield_bvh_id,
ray_origin_world,
ray_dir_world,
)
if wp.static(m.nflex > 0):
d, n, u, v, f = ray_flex_with_bvh(
flex_bvh_id,
flex_group_root[world_idx],
ray_origin_world,
ray_dir_world,
dist,
)
if d >= 0.0 and d < dist:
dist = d
normal = n
geom_id = -2
# Early Out
if geom_id == -1:
return
if render_depth[cam_idx]:
depth_out[world_idx, depth_adr[cam_idx] + ray_idx_local] = dist
if not render_rgb[cam_idx]:
return
# Shade the pixel
hit_point = ray_origin_world + ray_dir_world * dist
if geom_id == -2:
# TODO: Currently flex textures are not supported, and only the first rgba value
# is used until further flex support is added.
color = flex_rgba[0]
elif geom_matid[world_idx % geom_matid.shape[0], geom_id] == -1:
color = geom_rgba[world_idx % geom_rgba.shape[0], geom_id]
else:
color = mat_rgba[world_idx % mat_rgba.shape[0], geom_matid[world_idx % geom_matid.shape[0], geom_id]]
base_color = wp.vec3(color[0], color[1], color[2])
hit_color = base_color
if wp.static(rc.use_textures):
if geom_id != -2:
mat_id = geom_matid[world_idx % geom_matid.shape[0], geom_id]
if mat_id >= 0:
tex_id = mat_texid[world_idx % mat_texid.shape[0], mat_id, 1]
if tex_id >= 0:
tex_color = sample_texture(
geom_type,
mesh_faceadr,
geom_id,
mat_texrepeat[world_idx % mat_texrepeat.shape[0], mat_id],
textures[tex_id],
geom_xpos_in[world_idx, geom_id],
geom_xmat_in[world_idx, geom_id],
mesh_facetexcoord,
mesh_texcoord,
mesh_texcoord_offsets,
hit_point,
u,
v,
f,
mesh_id,
)
base_color = wp.cw_mul(base_color, tex_color)
len_n = wp.length(normal)
n = normal if len_n > 0.0 else wp.vec3(0.0, 0.0, 1.0)
n = wp.normalize(n)
hemispheric = 0.5 * (n[2] + 1.0)
ambient_color = wp.vec3(0.4, 0.4, 0.45) * hemispheric + wp.vec3(0.1, 0.1, 0.12) * (1.0 - hemispheric)
result = 0.5 * wp.cw_mul(base_color, ambient_color)
# Apply lighting and shadows
for l in range(wp.static(m.nlight)):
light_contribution = compute_lighting(
geom_type,
geom_dataid,
geom_size,
geom_xpos_in,
geom_xmat_in,
use_shadows,
bvh_id,
group_root[world_idx],
bvh_ngeom,
enabled_geom_ids,
world_idx,
mesh_bvh_id,
hfield_bvh_id,
light_active[world_idx % light_active.shape[0], l],
light_type[world_idx % light_type.shape[0], l],
light_castshadow[world_idx % light_castshadow.shape[0], l],
light_xpos_in[world_idx, l],
light_xdir_in[world_idx, l],
normal,
hit_point,
)
result = result + base_color * light_contribution
hit_color = wp.min(result, wp.vec3(1.0, 1.0, 1.0))
hit_color = wp.max(hit_color, wp.vec3(0.0, 0.0, 0.0))
rgb_out[world_idx, rgb_adr[cam_idx] + ray_idx_local] = pack_rgba_to_uint32(
hit_color[0] * 255.0,
hit_color[1] * 255.0,
hit_color[2] * 255.0,
255.0,
)
wp.launch(
kernel=_render_megakernel,
dim=(d.nworld, rc.total_rays),
inputs=[
m.geom_type,
m.geom_dataid,
m.geom_matid,
m.geom_size,
m.geom_rgba,
m.cam_projection,
m.cam_fovy,
m.cam_sensorsize,
m.cam_intrinsic,
m.light_type,
m.light_castshadow,
m.light_active,
m.mesh_faceadr,
m.mat_texid,
m.mat_texrepeat,
m.mat_rgba,
d.geom_xpos,
d.geom_xmat,
d.cam_xpos,
d.cam_xmat,
d.light_xpos,
d.light_xdir,
rc.nrender,
rc.use_shadows,
rc.bvh_ngeom,
rc.cam_res,
rc.cam_id_map,
rc.ray,
rc.rgb_adr,
rc.depth_adr,
rc.render_rgb,
rc.render_depth,
rc.bvh_id,
rc.group_root,
rc.flex_bvh_id,
rc.flex_group_root,
rc.enabled_geom_ids,
rc.mesh_bvh_id,
rc.mesh_facetexcoord,
rc.mesh_texcoord,
rc.mesh_texcoord_offsets,
rc.hfield_bvh_id,
rc.flex_rgba,
rc.textures,
],
outputs=[
rc.rgb_data,
rc.depth_data,
],
)
@@ -0,0 +1,130 @@
# Copyright 2026 The Newton Developers
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
import mujoco
import warp as wp
from mujoco.mjx.third_party.mujoco_warp._src.types import ProjectionType
wp.set_module_options({"enable_backward": False})
@wp.kernel
def _convert_texture_data(
# In:
width: int,
adr: int,
nc: int,
tex_data_in: wp.array(dtype=wp.uint8),
# Out:
tex_data_out: wp.array3d(dtype=float),
):
"""Convert uint8 texture data to vec4 format for efficient sampling."""
x, y = wp.tid()
offset = adr + (y * width + x) * nc
r = tex_data_in[offset + 0] if nc > 0 else wp.uint8(0)
g = tex_data_in[offset + 1] if nc > 1 else wp.uint8(0)
b = tex_data_in[offset + 2] if nc > 2 else wp.uint8(0)
a = wp.uint8(255)
tex_data_out[y, x, 0] = float(r) * wp.static(1.0 / 255.0)
tex_data_out[y, x, 1] = float(g) * wp.static(1.0 / 255.0)
tex_data_out[y, x, 2] = float(b) * wp.static(1.0 / 255.0)
tex_data_out[y, x, 3] = float(a) * wp.static(1.0 / 255.0)
def create_warp_texture(mjm: mujoco.MjModel, tex_id: int) -> wp.array:
"""Create a Warp texture from a MuJoCo model texture data."""
tex_adr = mjm.tex_adr[tex_id]
tex_width = mjm.tex_width[tex_id]
tex_height = mjm.tex_height[tex_id]
nchannel = mjm.tex_nchannel[tex_id]
tex_data = wp.zeros((tex_height, tex_width, 4), dtype=float)
wp.launch(
_convert_texture_data,
dim=(tex_width, tex_height),
inputs=[tex_width, tex_adr, nchannel, wp.array(mjm.tex_data, dtype=wp.uint8)],
outputs=[tex_data],
)
return wp.Texture2D(tex_data, filter_mode=wp.TextureFilterMode.LINEAR)
@wp.func
def compute_ray(
# In:
projection: int,
fovy: float,
sensorsize: wp.vec2,
intrinsic: wp.vec4,
img_w: int,
img_h: int,
px: int,
py: int,
znear: float,
) -> wp.vec3:
"""Compute ray direction for a pixel with per-world camera parameters.
This combines _camera_frustum_bounds and build_primary_rays logic for use
inside a kernel when camera parameters are batched/randomized across worlds.
"""
if projection == ProjectionType.ORTHOGRAPHIC:
return wp.vec3(0.0, 0.0, -1.0)
aspect = float(img_w) / float(img_h)
sensor_h = sensorsize[1]
# Check if we have intrinsics (sensorsize[1] != 0)
if sensor_h != 0.0:
fx = intrinsic[0]
fy = intrinsic[1]
cx = intrinsic[2]
cy = intrinsic[3]
sensor_w = sensorsize[0]
target_aspect = float(img_w) / float(img_h)
sensor_aspect = sensor_w / sensor_h
if target_aspect > sensor_aspect:
sensor_h = sensor_w / target_aspect
elif target_aspect < sensor_aspect:
sensor_w = sensor_h * target_aspect
inv_fx_znear = znear / fx
inv_fy_znear = znear / fy
left = -inv_fx_znear * (sensor_w * 0.5 - cx)
right = inv_fx_znear * (sensor_w * 0.5 + cx)
top = inv_fy_znear * (sensor_h * 0.5 - cy)
bottom = -inv_fy_znear * (sensor_h * 0.5 + cy)
else:
fovy_rad = fovy * wp.static(wp.pi / 180.0)
half_height = znear * wp.tan(0.5 * fovy_rad)
half_width = half_height * aspect
left = -half_width
right = half_width
top = half_height
bottom = -half_height
u = (float(px) + 0.5) / float(img_w)
v = (float(py) + 0.5) / float(img_h)
x = left + (right - left) * u
y = top + (bottom - top) * v
return wp.normalize(wp.vec3(x, y, -znear))
@wp.func
def pack_rgba_to_uint32(r: float, g: float, b: float, a: float) -> wp.uint32:
"""Pack RGBA values into a single uint32 for efficient memory access."""
return wp.uint32((int(a) << int(24)) | (int(r) << int(16)) | (int(g) << int(8)) | int(b))
+48 -52
View File
@@ -23,6 +23,7 @@ from mujoco.mjx.third_party.mujoco_warp._src import smooth
from mujoco.mjx.third_party.mujoco_warp._src import support
from mujoco.mjx.third_party.mujoco_warp._src.collision_sdf import get_sdf_params
from mujoco.mjx.third_party.mujoco_warp._src.collision_sdf import sdf
from mujoco.mjx.third_party.mujoco_warp._src.types import MJ_MAXVAL
from mujoco.mjx.third_party.mujoco_warp._src.types import MJ_MINVAL
from mujoco.mjx.third_party.mujoco_warp._src.types import ConeType
from mujoco.mjx.third_party.mujoco_warp._src.types import ConstraintType
@@ -42,7 +43,6 @@ from mujoco.mjx.third_party.mujoco_warp._src.types import vec8i
from mujoco.mjx.third_party.mujoco_warp._src.util_misc import inside_geom
from mujoco.mjx.third_party.mujoco_warp._src.warp_util import cache_kernel
from mujoco.mjx.third_party.mujoco_warp._src.warp_util import event_scope
from mujoco.mjx.third_party.mujoco_warp._src.warp_util import nested_kernel
wp.set_module_options({"enable_backward": False})
@@ -124,10 +124,10 @@ def _magnetometer(
@wp.func
def _cam_projection(
# Model:
cam_fovy: wp.array(dtype=float),
cam_fovy: wp.array2d(dtype=float),
cam_resolution: wp.array(dtype=wp.vec2i),
cam_sensorsize: wp.array(dtype=wp.vec2),
cam_intrinsic: wp.array(dtype=wp.vec4),
cam_intrinsic: wp.array2d(dtype=wp.vec4),
# Data in:
site_xpos_in: wp.array2d(dtype=wp.vec3),
cam_xpos_in: wp.array2d(dtype=wp.vec3),
@@ -138,8 +138,8 @@ def _cam_projection(
refid: int,
) -> wp.vec2:
sensorsize = cam_sensorsize[refid]
intrinsic = cam_intrinsic[refid]
fovy = cam_fovy[refid]
intrinsic = cam_intrinsic[worldid % cam_intrinsic.shape[0], refid]
fovy = cam_fovy[worldid % cam_fovy.shape[0], refid]
res = cam_resolution[refid]
target_xpos = site_xpos_in[worldid, objid]
@@ -470,10 +470,10 @@ def _sensor_pos(
site_quat: wp.array2d(dtype=wp.quat),
cam_bodyid: wp.array(dtype=int),
cam_quat: wp.array2d(dtype=wp.quat),
cam_fovy: wp.array(dtype=float),
cam_fovy: wp.array2d(dtype=float),
cam_resolution: wp.array(dtype=wp.vec2i),
cam_sensorsize: wp.array(dtype=wp.vec2),
cam_intrinsic: wp.array(dtype=wp.vec4),
cam_intrinsic: wp.array2d(dtype=wp.vec4),
sensor_type: wp.array(dtype=int),
sensor_datatype: wp.array(dtype=int),
sensor_objtype: wp.array(dtype=int),
@@ -782,7 +782,7 @@ def sensor_pos(m: Model, d: Data):
d,
rangefinder_pnt,
rangefinder_vec,
vec6(wp.inf, wp.inf, wp.inf, wp.inf, wp.inf, wp.inf),
vec6(MJ_MAXVAL, MJ_MAXVAL, MJ_MAXVAL, MJ_MAXVAL, MJ_MAXVAL, MJ_MAXVAL),
True,
m.sensor_rangefinder_bodyid,
rangefinder_dist,
@@ -797,8 +797,7 @@ def sensor_pos(m: Model, d: Data):
energy_vel(m, d)
# collision sensors (distance, normal, fromto)
sensor_collision = wp.empty((d.nworld, m.nsensorcollision, 8, 7), dtype=float)
sensor_collision.fill_(1.0e32)
sensor_collision = wp.full((d.nworld, m.nsensorcollision, 8, 7), 1.0e32, dtype=float)
if m.nsensorcollision:
wp.launch(
_sensor_collision,
@@ -1787,38 +1786,12 @@ def _sensor_acc(
nmatch = sensor_contact_nmatch_in[worldid, contactsensorid]
if reduce == 3: # netforce
# compute point: force-weighted centroid of contact position
# Single-pass computation: first compute centroid, then wrench about centroid
# Pass 1: compute force-weighted centroid of contact positions
net_pos = wp.vec3(0.0)
total_force_magnitude = float(0.0)
for i in range(nmatch):
cid = sensor_contact_matchid_in[worldid, contactsensorid, i]
contact_forcetorque = support.contact_force_fn(
opt_cone,
contact_frame_in,
contact_friction_in,
contact_dim_in,
contact_efc_address_in,
efc_force_in,
njmax_in,
nacon_in,
worldid,
cid,
False,
)
weight = wp.norm_l2(wp.spatial_top(contact_forcetorque))
net_pos += weight * contact_pos_in[cid]
total_force_magnitude += weight
net_pos /= wp.max(total_force_magnitude, MJ_MINVAL)
# TODO(team): iterate over matches once
# compute total wrench about point, in the global frame
net_force = wp.vec3(0.0)
net_torque = wp.vec3(0.0)
total_force_magnitude = float(0.0)
for i in range(nmatch):
cid = sensor_contact_matchid_in[worldid, contactsensorid, i]
@@ -1837,8 +1810,15 @@ def _sensor_acc(
cid,
False,
)
contact_forcetorque *= dir
# Accumulate for centroid computation (unsigned force magnitude)
weight = wp.norm_l2(wp.spatial_top(contact_forcetorque))
contact_pos = contact_pos_in[cid]
net_pos += weight * contact_pos
total_force_magnitude += weight
# Apply direction and transform to global frame
contact_forcetorque *= dir
force_local = wp.spatial_top(contact_forcetorque)
torque_local = wp.spatial_bottom(contact_forcetorque)
@@ -1848,12 +1828,18 @@ def _sensor_acc(
force_global = frameT @ force_local
torque_global = frameT @ torque_local
# add to total force, torque
# Accumulate force and torque (about origin for now)
net_force += force_global
net_torque += torque_global
# Accumulate moment contribution: will adjust after centroid is computed
net_torque += wp.cross(contact_pos, force_global)
# add induced moment: torque += (pos - point) x force
net_torque += wp.cross(contact_pos_in[cid] - net_pos, force_global)
# Finalize centroid
net_pos /= wp.max(total_force_magnitude, MJ_MINVAL)
# Adjust torque: subtract moment from centroid (since we accumulated about origin)
# torque_about_centroid = torque_about_origin - centroid x total_force
net_torque -= wp.cross(net_pos, net_force)
adr_slot = adr
@@ -1888,7 +1874,8 @@ def _sensor_acc(
out[adr_slot + 1] = 1.0
out[adr_slot + 2] = 0.0
else:
for i in range(wp.min(nmatch, num)):
nslots = wp.min(nmatch, num)
for i in range(nslots):
# sorted contact id
cid = sensor_contact_matchid_in[worldid, contactsensorid, i]
@@ -2165,7 +2152,15 @@ def _sensor_tactile(
contact_type = geom_type[geom]
plugin_attributes, plugin_index, volume_data, mesh_data = get_sdf_params(
oct_child, oct_aabb, oct_coeff, plugin, plugin_attr, contact_type, geom_size[worldid, geom], plugin_id, mesh_id
oct_child,
oct_aabb,
oct_coeff,
plugin,
plugin_attr,
contact_type,
geom_size[worldid % geom_size.shape[0], geom],
plugin_id,
mesh_id,
)
depth = wp.min(sdf(contact_type, lpos, plugin_attributes, plugin_index, volume_data, mesh_data), 0.0)
@@ -2357,16 +2352,16 @@ def _contact_match(
@cache_kernel
def _contact_sort(maxmatch: int):
@nested_kernel(module="unique", enable_backward=False)
@wp.kernel(module="unique", enable_backward=False)
def contact_sort(
# Model:
sensor_intprm: wp.array2d(dtype=int),
sensor_contact_adr: wp.array(dtype=int),
# Data in:
# In:
sensor_contact_nmatch_in: wp.array2d(dtype=int),
sensor_contact_matchid_in: wp.array3d(dtype=int),
sensor_contact_criteria_in: wp.array3d(dtype=float),
# Data out:
# Out:
sensor_contact_matchid_out: wp.array3d(dtype=int),
):
worldid, contactsensorid = wp.tid()
@@ -2819,13 +2814,13 @@ def energy_pos(m: Model, d: Data):
@cache_kernel
def _energy_vel_kinetic(nv: int):
@nested_kernel(module="unique", enable_backward=False)
@wp.kernel(module="unique", enable_backward=False)
def energy_vel_kinetic(
# Data in:
qvel_in: wp.array2d(dtype=float),
# In:
Mqvel: wp.array2d(dtype=float),
# Out:
# Data out:
energy_out: wp.array(dtype=wp.vec2),
):
worldid = wp.tid()
@@ -2849,12 +2844,13 @@ def energy_vel(m: Model, d: Data):
# kinetic energy: 0.5 * qvel.T @ M @ qvel
# M @ qvel
support.mul_m(m, d, d.efc.mv, d.qvel)
mv = wp.zeros((d.nworld, m.nv), dtype=float)
support.mul_m(m, d, mv, d.qvel)
wp.launch_tiled(
_energy_vel_kinetic(m.nv),
dim=d.nworld,
inputs=[d.qvel, d.efc.mv],
inputs=[d.qvel, mv],
outputs=[d.energy],
block_dim=m.block_dim.energy_vel_kinetic,
)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+110 -85
View File
@@ -23,21 +23,21 @@ from mujoco.mjx.third_party.mujoco_warp._src.types import Data
from mujoco.mjx.third_party.mujoco_warp._src.types import JointType
from mujoco.mjx.third_party.mujoco_warp._src.types import Model
from mujoco.mjx.third_party.mujoco_warp._src.types import State
from mujoco.mjx.third_party.mujoco_warp._src.types import TileSet
from mujoco.mjx.third_party.mujoco_warp._src.types import vec5
from mujoco.mjx.third_party.mujoco_warp._src.warp_util import cache_kernel
from mujoco.mjx.third_party.mujoco_warp._src.warp_util import event_scope
from mujoco.mjx.third_party.mujoco_warp._src.warp_util import nested_kernel
wp.set_module_options({"enable_backward": False})
@cache_kernel
def mul_m_sparse_diag(check_skip: bool):
@nested_kernel(module="unique", enable_backward=False)
def _mul_m_sparse_diag(
def mul_m_sparse(check_skip: bool):
@wp.kernel(module="unique")
def _mul_m_sparse(
# Model:
dof_Madr: wp.array(dtype=int),
qM_mulm_rowadr: wp.array(dtype=int),
qM_mulm_col: wp.array(dtype=int),
qM_mulm_madr: wp.array(dtype=int),
# Data in:
qM_in: wp.array3d(dtype=float),
# In:
@@ -46,26 +46,33 @@ def mul_m_sparse_diag(check_skip: bool):
# Out:
res: wp.array2d(dtype=float),
):
"""Diagonal update for sparse matmul."""
"""Sparse matmul: one thread per DOF, gather-based (no atomics)."""
worldid, dofid = wp.tid()
if wp.static(check_skip):
if skip[worldid]:
return
res[worldid, dofid] = qM_in[worldid, 0, dof_Madr[dofid]] * vec[worldid, dofid]
# Gather all contributions (diagonal + off-diagonal)
acc = float(0.0)
start = qM_mulm_rowadr[dofid]
end = qM_mulm_rowadr[dofid + 1]
for k in range(start, end):
col = qM_mulm_col[k]
madr = qM_mulm_madr[k]
acc += qM_in[worldid, 0, madr] * vec[worldid, col]
return _mul_m_sparse_diag
res[worldid, dofid] = acc
return _mul_m_sparse
@cache_kernel
def mul_m_sparse_ij(check_skip: bool):
@nested_kernel(module="unique", enable_backward=False)
def _mul_m_sparse_ij(
# Model:
qM_mulm_i: wp.array(dtype=int),
qM_mulm_j: wp.array(dtype=int),
qM_madr_ij: wp.array(dtype=int),
def mul_m_dense(nv: int, check_skip: bool):
"""Simple SIMT dense matmul: one thread per output element."""
@wp.kernel(module="unique")
def _mul_m_dense(
# Data in:
qM_in: wp.array3d(dtype=float),
# In:
@@ -74,52 +81,16 @@ def mul_m_sparse_ij(check_skip: bool):
# Out:
res: wp.array2d(dtype=float),
):
"""Off-diagonal update for sparse matmul."""
worldid, elementid = wp.tid()
worldid, i = wp.tid()
if wp.static(check_skip):
if skip[worldid]:
return
i = qM_mulm_i[elementid]
j = qM_mulm_j[elementid]
madr_ij = qM_madr_ij[elementid]
qM_ij = qM_in[worldid, 0, madr_ij]
wp.atomic_add(res[worldid], i, qM_ij * vec[worldid, j])
wp.atomic_add(res[worldid], j, qM_ij * vec[worldid, i])
return _mul_m_sparse_ij
@cache_kernel
def mul_m_dense(tile: TileSet, check_skip: bool):
"""Returns a matmul kernel for some tile size."""
@nested_kernel(module="unique", enable_backward=False)
def _mul_m_dense(
# Data In:
qM_in: wp.array3d(dtype=float),
# In:
adr: wp.array(dtype=int),
vec: wp.array3d(dtype=float),
skip: wp.array(dtype=bool),
# Out:
res: wp.array3d(dtype=float),
):
worldid, nodeid = wp.tid()
TILE_SIZE = wp.static(tile.size)
if wp.static(check_skip):
if skip[worldid]:
return
dofid = adr[nodeid]
qM_tile = wp.tile_load(qM_in[worldid], shape=(TILE_SIZE, TILE_SIZE), offset=(dofid, dofid), bounds_check=False)
vec_tile = wp.tile_load(vec[worldid], shape=(TILE_SIZE, 1), offset=(dofid, 0), bounds_check=False)
res_tile = wp.tile_matmul(qM_tile, vec_tile)
wp.tile_store(res[worldid], res_tile, offset=(dofid, 0), bounds_check=False)
acc = float(0.0)
for j in range(wp.static(nv)):
acc += qM_in[worldid, i, j] * vec[worldid, j]
res[worldid, i] = acc
return _mul_m_dense
@@ -149,36 +120,21 @@ def mul_m(
if M is None:
M = d.qM
if m.opt.is_sparse:
if m.is_sparse:
wp.launch(
mul_m_sparse_diag(check_skip),
mul_m_sparse(check_skip),
dim=(d.nworld, m.nv),
inputs=[m.dof_Madr, M, vec, skip],
outputs=[res],
)
wp.launch(
mul_m_sparse_ij(check_skip),
dim=(d.nworld, m.qM_madr_ij.size),
inputs=[m.qM_mulm_i, m.qM_mulm_j, m.qM_madr_ij, M, vec, skip],
inputs=[m.qM_mulm_rowadr, m.qM_mulm_col, m.qM_mulm_madr, M, vec, skip],
outputs=[res],
)
else:
for tile in m.qM_tiles:
wp.launch_tiled(
mul_m_dense(tile, check_skip),
dim=(d.nworld, tile.adr.size),
inputs=[
M,
tile.adr,
# note reshape: tile_matmul expects 2d input
vec.reshape(vec.shape + (1,)),
skip,
],
outputs=[res.reshape(res.shape + (1,))],
block_dim=m.block_dim.mul_m_dense,
)
wp.launch(
mul_m_dense(m.nv, check_skip),
dim=(d.nworld, m.nv),
inputs=[M, vec, skip],
outputs=[res],
)
@wp.kernel
@@ -408,7 +364,7 @@ def transform_force(frc: wp.spatial_vector, offset: wp.vec3) -> wp.spatial_vecto
@wp.func
def jac(
def jac_dof(
# Model:
body_parentid: wp.array(dtype=int),
body_rootid: wp.array(dtype=int),
@@ -446,8 +402,77 @@ def jac(
return jacp, jacr
@cache_kernel
def _make_jac_kernel(has_jacp: bool, has_jacr: bool):
@wp.kernel(module="unique", enable_backward=False)
def _jac(
# Model:
body_parentid: wp.array(dtype=int),
body_rootid: wp.array(dtype=int),
dof_bodyid: wp.array(dtype=int),
# Data in:
subtree_com_in: wp.array2d(dtype=wp.vec3),
cdof_in: wp.array2d(dtype=wp.spatial_vector),
# In:
point_in: wp.array(dtype=wp.vec3),
bodyid_in: wp.array(dtype=int),
# Out:
jacp_out: wp.array3d(dtype=float),
jacr_out: wp.array3d(dtype=float),
):
worldid, dofid = wp.tid()
jacp_val, jacr_val = jac_dof(
body_parentid, body_rootid, dof_bodyid, subtree_com_in, cdof_in, point_in[worldid], bodyid_in[worldid], dofid, worldid
)
if wp.static(has_jacp):
jacp_out[worldid, 0, dofid] = jacp_val[0]
jacp_out[worldid, 1, dofid] = jacp_val[1]
jacp_out[worldid, 2, dofid] = jacp_val[2]
if wp.static(has_jacr):
jacr_out[worldid, 0, dofid] = jacr_val[0]
jacr_out[worldid, 1, dofid] = jacr_val[1]
jacr_out[worldid, 2, dofid] = jacr_val[2]
return _jac
@event_scope
def jac(
m: Model,
d: Data,
jacp: wp.array | None, # wp.array3d(dtype=float)
jacr: wp.array | None, # wp.array3d(dtype=float)
point: wp.array(dtype=wp.vec3),
body: wp.array(dtype=int),
):
"""Compute translational and rotational Jacobian for point on body.
Args:
m: The model containing kinematic and dynamic information (device).
d: The data object containing the current state (device).
jacp: Output translational Jacobian (optional).
jacr: Output rotational Jacobian (optional).
point: 3D point in global coordinates.
body: Body ID for each world.
"""
kernel = _make_jac_kernel(jacp is not None, jacr is not None)
jacp_arr = jacp or wp.empty((0, 0, 0), dtype=float)
jacr_arr = jacr or wp.empty((0, 0, 0), dtype=float)
wp.launch(
kernel,
dim=(d.nworld, m.nv),
inputs=[m.body_parentid, m.body_rootid, m.dof_bodyid, d.subtree_com, d.cdof, point, body],
outputs=[jacp_arr, jacr_arr],
)
@wp.func
def jac_dot(
def jac_dot_dof(
# Model:
body_parentid: wp.array(dtype=int),
body_rootid: wp.array(dtype=int),
@@ -529,7 +554,7 @@ def get_state(m: Model, d: Data, state: wp.array2d(dtype=float), sig: int, activ
if sig >= (1 << State.NSTATE):
raise ValueError(f"invalid state signature {sig} >= 2^mjNSTATE")
@nested_kernel(module="unique", enable_backward=False)
@wp.kernel(module="unique", enable_backward=False)
def _get_state(
# Model:
nq: int,
@@ -668,7 +693,7 @@ def set_state(m: Model, d: Data, state: wp.array2d(dtype=float), sig: int, activ
if sig >= (1 << State.NSTATE):
raise ValueError(f"invalid state signature {sig} >= 2^mjNSTATE")
@nested_kernel(module="unique", enable_backward=False)
@wp.kernel(module="unique", enable_backward=False)
def _set_state(
# Model:
nq: int,
+216 -85
View File
@@ -32,6 +32,9 @@ MJ_MAX_EPAFACES = 5
TILE_SIZE_JTDAJ_SPARSE = 16
TILE_SIZE_JTDAJ_DENSE = 16
# TODO(team): remove after mjwarp depends on warp-lang >= 1.12 in pyproject.toml
TEXTURE_DTYPE = wp.Texture2D if hasattr(wp, "Texture2D") else int
# TODO(team): add check that all wp.launch_tiled 'block_dim' settings are configurable
@dataclasses.dataclass
@@ -61,9 +64,9 @@ class BlockDim:
update_gradient_cholesky_blocked: int = 32
update_gradient_JTDAJ_sparse: int = 64
update_gradient_JTDAJ_dense: int = 96
linesearch_iterative: int = 64
# support
mul_m_dense: int = 32
linesearch_iterative: int = 32
# derivative
qderiv_actuator_dense: int = 32
class BroadphaseType(enum.IntEnum):
@@ -114,6 +117,23 @@ class CamLightType(enum.IntEnum):
TARGETBODYCOM = mujoco.mjtCamLight.mjCAMLIGHT_TARGETBODYCOM
class ProjectionType(enum.IntEnum):
"""Type of camera projection.
Attributes:
PERSPECTIVE: perspective projection
ORTHOGRAPHIC: orthographic projection
"""
# TODO(team): remove after mjwarp depends on mujoco > 3.4.0 in pyproject.toml
if hasattr(mujoco, "mjtProjection"):
PERSPECTIVE = mujoco.mjtProjection.mjPROJ_PERSPECTIVE
ORTHOGRAPHIC = mujoco.mjtProjection.mjPROJ_ORTHOGRAPHIC
else:
PERSPECTIVE = 0
ORTHOGRAPHIC = 1
class DataType(enum.IntFlag):
"""Sensor data types.
@@ -164,7 +184,8 @@ class DisableBit(enum.IntFlag):
REFSAFE = mujoco.mjtDisableBit.mjDSBL_REFSAFE
SENSOR = mujoco.mjtDisableBit.mjDSBL_SENSOR
EULERDAMP = mujoco.mjtDisableBit.mjDSBL_EULERDAMP
# unsupported: MIDPHASE, AUTORESET, NATIVECCD, ISLAND
NATIVECCD = mujoco.mjtDisableBit.mjDSBL_NATIVECCD
# unsupported: MIDPHASE, AUTORESET, ISLAND
class EnableBit(enum.IntFlag):
@@ -321,6 +342,20 @@ class GeomType(enum.IntEnum):
# unsupported: NGEOMTYPES, ARROW*, LINE, SKIN, LABEL, NONE
class CollisionType(enum.IntEnum):
"""Type of narrowphase collision.
Attributes:
PRIMITIVE: primitive collision
CONVEX: convex collision (CCD)
SDF: sdf collision
"""
PRIMITIVE = 0
CONVEX = 1
SDF = 2
class SolverType(enum.IntEnum):
"""Constraint solver algorithm.
@@ -665,10 +700,8 @@ class Option:
warp only fields:
impratio_invsqrt: ratio of friction-to-normal contact impedance (stored as inverse square root)
is_sparse: whether to use sparse representations
ls_parallel: evaluate engine solver step sizes in parallel
ls_parallel_min_step: minimum step size for solver linesearch
has_fluid: True if wind, density, or viscosity are non-zero at put_model time
broadphase: broadphase type (BroadphaseType)
broadphase_filter: broadphase filter bitflag (BroadphaseFilter)
graph_conditional: flag to use cuda graph conditional
@@ -700,10 +733,8 @@ class Option:
sdf_iterations: int
# warp only fields:
impratio_invsqrt: array("*", float)
is_sparse: bool
ls_parallel: bool
ls_parallel_min_step: float
has_fluid: bool
broadphase: BroadphaseType
broadphase_filter: BroadphaseFilter
graph_conditional: bool
@@ -716,10 +747,10 @@ class Statistic:
"""Model statistics (in qpos0).
Attributes:
meaninertia: mean diagonal inertia
meaninertia: mean diagonal inertia (per-world)
"""
meaninertia: float
meaninertia: array("*", float)
@dataclasses.dataclass
@@ -749,6 +780,7 @@ class Model:
nbody: number of bodies
noct: number of total octree cells in all meshes
njnt: number of joints
ntree: number of kinematic trees
nM: number of non-zeros in sparse inertia matrix
nC: number of non-zeros in sparse body-dof matrix
ngeom: number of geoms
@@ -794,6 +826,7 @@ class Model:
body_jntadr: start addr of joints; -1: no joints (nbody,)
body_dofnum: number of motion degrees of freedom (nbody,)
body_dofadr: start addr of dofs; -1: no dofs (nbody,)
body_treeid: id of body's tree; -1: static (nbody,)
body_geomnum: number of geoms (nbody,)
body_geomadr: start addr of geoms; -1: no geoms (nbody,)
body_pos: position offset rel. to parent body (*, nbody, 3)
@@ -828,6 +861,7 @@ class Model:
dof_bodyid: id of dof's body (nv,)
dof_jntid: id of dof's joint (nv,)
dof_parentid: id of dof's parent; -1: none (nv,)
dof_treeid: id of dof's tree (nv,)
dof_Madr: dof address in M-diagonal (nv,)
dof_solref: constraint solver reference: frictionloss (*, nv, NREF)
dof_solimp: constraint solver impedance: frictionloss (*, nv, NIMP)
@@ -835,6 +869,9 @@ class Model:
dof_armature: dof armature inertia/mass (*, nv)
dof_damping: damping coefficient (*, nv)
dof_invweight0: diag. inverse inertia in qpos0 (*, nv)
tree_bodynum: number of bodies in tree (incl. root) (ntree,)
tree_dofadr: start address of tree's dofs (ntree,)
tree_dofnum: number of dofs in tree (ntree,)
geom_type: geometric type (GeomType) (ngeom,)
geom_contype: geom contact type (ngeom,)
geom_conaffinity: geom contact affinity (ngeom,)
@@ -870,10 +907,11 @@ class Model:
cam_poscom0: global position rel. to sub-com in qpos0 (*, ncam, 3)
cam_pos0: global position rel. to body in qpos0 (*, ncam, 3)
cam_mat0: global orientation in qpos0 (*, ncam, 3, 3)
cam_fovy: y field-of-view (ortho ? len : deg) (ncam,)
cam_projection: projection type (ProjectionType) (ncam,)
cam_fovy: y field-of-view (ortho ? len : deg) (*, ncam)
cam_resolution: resolution: pixels [width, height] (ncam, 2)
cam_sensorsize: sensor size: length [width, height] (ncam, 2)
cam_intrinsic: [focal length; principal point] (ncam, 4)
cam_intrinsic: [focal length; principal point] (*, ncam, 4)
light_mode: light tracking mode (CamLightType) (nlight,)
light_bodyid: id of light's body (nlight,)
light_targetbodyid: id of targeted body; -1: none (nlight,)
@@ -903,6 +941,9 @@ class Model:
flex_stiffness: finite element stiffness matrix (nflexelem, 21)
flex_bending: bending stiffness (nflexedge, 17)
flex_damping: Rayleigh's damping coefficient (nflex,)
flexedge_J_rownnz: number of nonzeros in Jacobian row (nflexedge,)
flexedge_J_rowadr: row start address in colind array (nflexedge,)
flexedge_J_colind: column indices in sparse Jacobian (nJfe,)
mesh_vertadr: first vertex address (nmesh,)
mesh_vertnum: number of vertices (nmesh,)
mesh_faceadr: first face address (nmesh,)
@@ -927,7 +968,7 @@ class Model:
hfield_ncol: number of columns in grid (nhfield,)
hfield_adr: start address in hfield_data (nhfield,)
hfield_data: elevation data (nhfielddata,)
mat_texid: texture id for rendering (nmat, mjNTEXROLE)
mat_texid: texture id for rendering (*, nmat, mjNTEXROLE)
mat_texrepeat: texture repeat for rendering (*, nmat, 2)
mat_rgba: rgba (*, nmat, 4)
pair_dim: contact dimensionality (npair,)
@@ -1008,6 +1049,7 @@ class Model:
mapM2M: index mapping from M (legacy) to M (CSR) (nC)
warp only fields:
nbranch: number of branches (leaf-to-root paths)
nv_pad: number of degrees of freedom + padding
nacttrnbody: number of actuators with body transmission
nsensorcollision: number of unique collisions for
@@ -1019,9 +1061,13 @@ class Model:
nmaxpyramid: maximum number of pyramid directions
nmaxpolygon: maximum number of verts per polygon
nmaxmeshdeg: maximum number of polygons per vert
is_sparse: whether to use sparse representations
has_fluid: True if wind, density, or viscosity are non-zero at put_model time
has_sdf_geom: whether the model contains SDF geoms
block_dim: block dim options
body_tree: list of body ids by tree level
body_branches: flattened body ids for all branches
body_branch_start: start index in body_branches for each branch (nbranch + 1,)
mocap_bodyid: id of body for mocap (nmocap,)
body_fluid_ellipsoid: does body use ellipsoid fluid (nbody,)
jnt_limited_slide_hinge_adr: limited/slide/hinge jntadr
@@ -1085,9 +1131,9 @@ class Model:
qLD_updates: tuple of index triples for sparse factorization
qM_fullm_i: sparse mass matrix addressing
qM_fullm_j: sparse mass matrix addressing
qM_mulm_i: sparse matmul addressing
qM_mulm_j: sparse matmul addressing
qM_madr_ij: sparse matmul addressing
qM_mulm_rowadr: sparse matmul row pointers
qM_mulm_col: sparse matmul column indices
qM_mulm_madr: sparse matmul matrix addresses
"""
nq: int
@@ -1097,6 +1143,7 @@ class Model:
nbody: int
noct: int
njnt: int
ntree: int
nM: int
nC: int
ngeom: int
@@ -1142,6 +1189,7 @@ class Model:
body_jntadr: array("nbody", int)
body_dofnum: array("nbody", int)
body_dofadr: array("nbody", int)
body_treeid: array("nbody", int)
body_geomnum: array("nbody", int)
body_geomadr: array("nbody", int)
body_pos: array("*", "nbody", wp.vec3)
@@ -1176,6 +1224,7 @@ class Model:
dof_bodyid: array("nv", int)
dof_jntid: array("nv", int)
dof_parentid: array("nv", int)
dof_treeid: array("nv", int)
dof_Madr: array("nv", int)
dof_solref: array("*", "nv", wp.vec2)
dof_solimp: array("*", "nv", vec5)
@@ -1183,6 +1232,9 @@ class Model:
dof_armature: array("*", "nv", float)
dof_damping: array("*", "nv", float)
dof_invweight0: array("*", "nv", float)
tree_bodynum: array("ntree", int)
tree_dofadr: array("ntree", int)
tree_dofnum: array("ntree", int)
geom_type: array("ngeom", int)
geom_contype: array("ngeom", int)
geom_conaffinity: array("ngeom", int)
@@ -1218,10 +1270,11 @@ class Model:
cam_poscom0: array("*", "ncam", wp.vec3)
cam_pos0: array("*", "ncam", wp.vec3)
cam_mat0: array("*", "ncam", wp.mat33)
cam_fovy: array("ncam", float)
cam_projection: array("ncam", int)
cam_fovy: array("*", "ncam", float)
cam_resolution: array("ncam", wp.vec2i)
cam_sensorsize: array("ncam", wp.vec2)
cam_intrinsic: array("ncam", wp.vec4)
cam_intrinsic: array("*", "ncam", wp.vec4)
light_mode: array("nlight", int)
light_bodyid: array("nlight", int)
light_targetbodyid: array("nlight", int)
@@ -1251,6 +1304,9 @@ class Model:
flex_stiffness: array("nflexelem", 21, float)
flex_bending: array("nflexedge", 17, float)
flex_damping: array("nflex", float)
flexedge_J_rownnz: array("nflexedge", int)
flexedge_J_rowadr: array("nflexedge", int)
flexedge_J_colind: wp.array(dtype=int)
mesh_vertadr: array("nmesh", int)
mesh_vertnum: array("nmesh", int)
mesh_faceadr: array("nmesh", int)
@@ -1275,7 +1331,7 @@ class Model:
hfield_ncol: array("nhfield", int)
hfield_adr: array("nhfield", int)
hfield_data: array("nhfielddata", float)
mat_texid: array("nmat", 10, int)
mat_texid: array("*", "nmat", 10, int)
mat_texrepeat: array("*", "nmat", wp.vec2)
mat_rgba: array("*", "nmat", wp.vec4)
pair_dim: array("npair", int)
@@ -1355,6 +1411,7 @@ class Model:
M_colind: array("nC", int)
mapM2M: array("nC", int)
# warp only fields:
nbranch: int
nv_pad: int
nacttrnbody: int
nsensorcollision: int
@@ -1365,9 +1422,13 @@ class Model:
nmaxpyramid: int
nmaxpolygon: int
nmaxmeshdeg: int
is_sparse: bool
has_fluid: bool
has_sdf_geom: bool
block_dim: BlockDim
body_tree: tuple[wp.array(dtype=int), ...]
body_branches: wp.array(dtype=int)
body_branch_start: wp.array(dtype=int)
mocap_bodyid: array("nmocap", int)
body_fluid_ellipsoid: array("nbody", bool)
jnt_limited_slide_hinge_adr: wp.array(dtype=int)
@@ -1422,9 +1483,10 @@ class Model:
qLD_updates: tuple[wp.array(dtype=wp.vec3i), ...]
qM_fullm_i: wp.array(dtype=int)
qM_fullm_j: wp.array(dtype=int)
qM_mulm_i: wp.array(dtype=int)
qM_mulm_j: wp.array(dtype=int)
qM_madr_ij: wp.array(dtype=int)
# Gather-based sparse mul_m indices (thread per DOF, no atomics)
qM_mulm_rowadr: wp.array(dtype=int) # start address for each row [nv+1]
qM_mulm_col: wp.array(dtype=int) # column index to gather from
qM_mulm_madr: wp.array(dtype=int) # matrix address to read
class ContactType(enum.IntFlag):
@@ -1492,26 +1554,9 @@ class Constraint:
aref: reference pseudo-acceleration (nworld, njmax)
frictionloss: frictionloss (friction) (nworld, njmax)
force: constraint force in constraint space (nworld, njmax)
Jaref: Jac*qacc - aref (nworld, njmax)
Ma: M*qacc (nworld, nv)
grad: gradient of master cost (nworld, nv_pad)
grad_dot: dot(grad, grad) (nworld,)
Mgrad: M / grad (nworld, nv_pad)
search: linesearch vector (nworld, nv)
search_dot: dot(search, search) (nworld,)
gauss: Gauss Cost (nworld,)
cost: constraint + Gauss cost (nworld,)
prev_cost: cost from previous iter (nworld,)
state: constraint state (nworld, njmax_pad)
mv: qM @ search (nworld, nv)
jv: efc_J @ search (nworld, njmax)
quad: quadratic cost coefficients (nworld, njmax, 3)
quad_gauss: quadratic cost Gauss coefficients (nworld, 3)
alpha: line search step size (nworld,)
prev_grad: previous grad (nworld, nv)
prev_Mgrad: previous Mgrad (nworld, nv)
beta: Polak-Ribiere beta (nworld,)
done: solver done (nworld,)
warp only fields:
Ma: M*qacc (nworld, nv)
"""
type: array("nworld", "njmax", int)
@@ -1524,26 +1569,8 @@ class Constraint:
aref: array("nworld", "njmax", float)
frictionloss: array("nworld", "njmax", float)
force: array("nworld", "njmax", float)
Jaref: array("nworld", "njmax", float)
Ma: array("nworld", "nv", float)
grad: array("nworld", "nv_pad", float)
grad_dot: array("nworld", float)
Mgrad: array("nworld", "nv_pad", float)
search: array("nworld", "nv", float)
search_dot: array("nworld", float)
gauss: array("nworld", float)
cost: array("nworld", float)
prev_cost: array("nworld", float)
state: array("nworld", "njmax_pad", int)
mv: array("nworld", "nv", float)
jv: array("nworld", "njmax", float)
quad: array("nworld", "njmax", wp.vec3)
quad_gauss: array("nworld", wp.vec3)
alpha: array("nworld", float)
prev_grad: array("nworld", "nv", float)
prev_Mgrad: array("nworld", "nv", float)
beta: array("nworld", float)
done: array("nworld", bool)
Ma: array("nworld", "nv", float)
@dataclasses.dataclass
@@ -1590,7 +1617,7 @@ class Data:
cdof: com-based motion axis of each dof (rot:lin) (nworld, nv, 6)
cinert: com-based body inertia and mass (nworld, nbody, 10)
flexvert_xpos: cartesian flex vertex positions (nworld, nflexvert, 3)
flexedge_J: edge length Jacobian (nworld, nflexedge, nv)
flexedge_J: edge length Jacobian (nworld, 1, nflexedge*6)
flexedge_length: flex edge lengths (nworld, nflexedge, 1)
ten_wrapadr: start address of tendon's path (nworld, ntendon)
ten_wrapnum: number of wrap points in path (nworld, ntendon)
@@ -1606,7 +1633,7 @@ class Data:
qLD: L'*D*L factorization of M (nworld, nv, nv) if dense
(nworld, 1, nC) if sparse
qLDiagInv: 1/diag(D) (nworld, nv)
flexedge_velocity: flex edge velocities (nworld, nflexedge,)
flexedge_velocity: flex edge velocities (nworld, nflexedge)
ten_velocity: tendon velocities (nworld, ntendon)
actuator_velocity: actuator velocities (nworld, nu)
cvel: com-based velocity (rot:lin) (nworld, nbody, 6)
@@ -1636,18 +1663,9 @@ class Data:
warp only fields:
nworld: number of worlds
naconmax: maximum number of contacts (shared across all worlds)
naccdmax: maximum number of contacts for CCD (all worlds)
njmax: maximum number of constraints per world
nacon: number of detected contacts (across all worlds) (1,)
ne_connect: number of equality connect constraints (nworld,)
ne_weld: number of equality weld constraints (nworld,)
ne_jnt: number of equality joint constraints (nworld,)
ne_ten: number of equality tendon constraints (nworld,)
ne_flex: number of flex edge equality constraints (nworld,)
nsolving: number of unconverged worlds (1,)
subtree_bodyvel: subtree body velocity (ang, vel) (nworld, nbody, 6)
collision_pair: collision pairs from broadphase (naconmax, 2)
collision_pairid: ids from broadphase (naconmax, 2)
collision_worldid: collision world ids from broadphase (naconmax,)
ncollision: collision count from broadphase (1,)
"""
@@ -1690,7 +1708,7 @@ class Data:
cdof: array("nworld", "nv", wp.spatial_vector)
cinert: array("nworld", "nbody", vec10)
flexvert_xpos: array("nworld", "nflexvert", wp.vec3)
flexedge_J: array("nworld", "nflexedge", "nv", float)
flexedge_J: wp.array3d(dtype=float)
flexedge_length: array("nworld", "nflexedge", float)
ten_wrapadr: array("nworld", "ntendon", int)
ten_wrapnum: array("nworld", "ntendon", int)
@@ -1732,18 +1750,131 @@ class Data:
# warp only fields:
nworld: int
naconmax: int
naccdmax: int
njmax: int
nacon: array(1, int)
ne_connect: array("nworld", int)
ne_weld: array("nworld", int)
ne_jnt: array("nworld", int)
ne_ten: array("nworld", int)
ne_flex: array("nworld", int)
nsolving: array(1, int)
subtree_bodyvel: array("nworld", "nbody", wp.spatial_vector)
# warp only: collision driver
collision_pair: array("naconmax", wp.vec2i)
collision_pairid: array("naconmax", wp.vec2i)
collision_worldid: array("naconmax", int)
ncollision: array(1, int)
@dataclasses.dataclass
class CollisionContext:
"""Collision driver intermediate arrays.
Attributes:
collision_pair: collision pairs from broadphase (naconmax, 2)
collision_pairid: ids from broadphase (naconmax, 2)
collision_worldid: collision world ids from broadphase (naconmax,)
"""
collision_pair: wp.array
collision_pairid: wp.array
collision_worldid: wp.array
@dataclasses.dataclass
class RenderContext:
"""Context for rendering.
Attributes:
nrender: number of actively rendering cameras
cam_res: camera resolution for actively rendering cameras
cam_id_map: camera id map
use_textures: whether to use textures
use_shadows: whether to use shadows
bvh_ngeom: number of geometries in the BVH
enabled_geom_ids: enabled geometry ids
mesh_registry: mesh BVH id to warp mesh mapping
mesh_bvh_id: mesh BVH ids
mesh_bounds_size: mesh bounds size
mesh_texcoord: mesh texture coordinates
mesh_texcoord_offsets: mesh texture coordinate offsets
mesh_facetexcoord: mesh face texture coordinates
textures: textures
textures_registry: texture registry
hfield_registry: hfield BVH id to warp mesh mapping
hfield_bvh_id: hfield BVH ids
hfield_bounds_size: hfield bounds size
flex_mesh: flex mesh
flex_rgba: flex rgba
flex_bvh_id: flex BVH id
flex_face_point: flex face points
flex_faceadr: flex face addresses
flex_nface: number of flex faces
flex_nwork: total flex work items for refit
flex_group_root: flex group roots
flex_elemdataadr: flex element data addresses
flex_shell: flex shell data
flex_shelldataadr: flex shell data addresses
flex_radius: flex radius
flex_workadr: flex work item addresses for refit
flex_worknum: flex work item counts for refit
flex_render_smooth: whether to render flex meshes smoothly
bvh: scene BVH
bvh_id: scene BVH id
lower: lower bounds
upper: upper bounds
group: groups
group_root: group roots
ray: rays
rgb_data: RGB data
rgb_adr: RGB addresses
rgb_size: per-camera RGB buffer sizes
depth_data: depth data
depth_adr: depth addresses
depth_size: per-camera depth buffer sizes
render_rgb: per-camera RGB render flags
render_depth: per-camera depth render flags
znear: near plane distance
total_rays: total number of rays
"""
nrender: int
cam_res: array("ncam", wp.vec2i)
cam_id_map: array("ncam", int)
use_textures: bool
use_shadows: bool
background_color: wp.uint32
bvh_ngeom: int
enabled_geom_ids: array("*", int)
mesh_registry: dict
mesh_bvh_id: array("nmesh", wp.uint64)
mesh_bounds_size: array("nmesh", wp.vec3)
mesh_texcoord: array("*", wp.vec2)
mesh_texcoord_offsets: array("nmesh", int)
mesh_facetexcoord: array("nmeshface", wp.vec3i)
# TODO(team): remove after mjwarp depends on warp-lang >= 1.12 in pyproject.toml
textures: array("*", TEXTURE_DTYPE)
textures_registry: list[TEXTURE_DTYPE]
hfield_registry: dict
hfield_bvh_id: array("nhfield", wp.uint64)
hfield_bounds_size: array("nhfield", wp.vec3)
flex_mesh: wp.Mesh
flex_rgba: array("nflex", wp.vec4)
flex_bvh_id: wp.uint64
flex_face_point: array("*", wp.vec3)
flex_faceadr: array("nflex", int)
flex_nface: int
flex_nwork: int
flex_group_root: array("nworld", int)
flex_elemdataadr: array("nflex", int)
flex_shell: array("*", int)
flex_shelldataadr: array("nflex", int)
flex_radius: array("nflex", float)
flex_workadr: array("nflex", int)
flex_worknum: array("nflex", int)
flex_render_smooth: bool
bvh: wp.Bvh
bvh_id: wp.uint64
lower: array("*", wp.vec3)
upper: array("*", wp.vec3)
group: array("*", int)
group_root: array("*", int)
ray: array("*", wp.vec3)
rgb_data: array("*", wp.uint32)
rgb_adr: array("ncam", int)
depth_data: array("*", wp.float32)
depth_adr: array("ncam", int)
render_rgb: array("ncam", bool)
render_depth: array("ncam", bool)
znear: float
total_rays: int
+15 -14
View File
@@ -20,6 +20,7 @@ from typing import Tuple
import warp as wp
from mujoco.mjx.third_party.mujoco_warp._src import math
from mujoco.mjx.third_party.mujoco_warp._src.types import MJ_MAXVAL
from mujoco.mjx.third_party.mujoco_warp._src.types import MJ_MINVAL
from mujoco.mjx.third_party.mujoco_warp._src.types import GeomType
from mujoco.mjx.third_party.mujoco_warp._src.types import WrapType
@@ -105,13 +106,13 @@ def wrap_circle(end: wp.vec4, side: wp.vec2, radius: float) -> Tuple[float, wp.v
Args:
end: Two 2D points.
side: Optional 2D side point, no side point: wp.vec2(wp.inf).
side: Optional 2D side point, no side point: wp.vec2(MJ_MAXVAL).
radius: Circle radius.
Returns:
Length of circular wrap or -1.0 if no wrap, pair of 2D wrap points.
"""
valid_side = wp.norm_l2(side) < wp.inf
valid_side = wp.norm_l2(side) < MJ_MAXVAL
end0 = wp.vec2(end[0], end[1])
end1 = wp.vec2(end[2], end[3])
@@ -122,13 +123,13 @@ def wrap_circle(end: wp.vec4, side: wp.vec2, radius: float) -> Tuple[float, wp.v
# either point inside circle or circle too small: no wrap
if (sqlen0 < sqrad) or (sqlen1 < sqrad) or (radius < MJ_MINVAL):
return -1.0, wp.vec2(wp.inf), wp.vec2(wp.inf)
return -1.0, wp.vec2(MJ_MAXVAL), wp.vec2(MJ_MAXVAL)
# points too close: no wrap
dif = end1 - end0
dd = wp.dot(dif, dif)
if dd < MJ_MINVAL:
return -1.0, wp.vec2(wp.inf), wp.vec2(wp.inf)
return -1.0, wp.vec2(MJ_MAXVAL), wp.vec2(MJ_MAXVAL)
# find nearest point on line segment to origin: a * dif + d0
a = -wp.dot(dif, end0) / dd
@@ -137,7 +138,7 @@ def wrap_circle(end: wp.vec4, side: wp.vec2, radius: float) -> Tuple[float, wp.v
# check for intersection and side
tmp = a * dif + end0
if (wp.dot(tmp, tmp) > sqrad) and (not valid_side or wp.dot(side, tmp) >= 0.0):
return -1.0, wp.vec2(wp.inf), wp.vec2(wp.inf)
return -1.0, wp.vec2(MJ_MAXVAL), wp.vec2(MJ_MAXVAL)
sqrt0 = wp.sqrt(sqlen0 - sqrad)
sqrt1 = wp.sqrt(sqlen1 - sqrad)
@@ -191,7 +192,7 @@ def wrap_circle(end: wp.vec4, side: wp.vec2, radius: float) -> Tuple[float, wp.v
# check for intersection
if is_intersect(end0, pnt0, end1, pnt1):
return -1.0, wp.vec2(wp.inf), wp.vec2(wp.inf)
return -1.0, wp.vec2(MJ_MAXVAL), wp.vec2(MJ_MAXVAL)
# return curve length
return length_circle(pnt0, pnt1, ind, radius), pnt0, pnt1
@@ -230,7 +231,7 @@ def wrap_inside(
# either point inside circle or circle too small: no wrap
if (len0 <= radius) or (len1 <= radius) or (radius < MJ_MINVAL) or (len0 < MJ_MINVAL) or (len1 < MJ_MINVAL):
return -1.0, wp.vec2(wp.inf), wp.vec2(wp.inf)
return -1.0, wp.vec2(MJ_MAXVAL), wp.vec2(MJ_MAXVAL)
# segment-circle intersection: no wrap
if dd > MJ_MINVAL:
@@ -241,7 +242,7 @@ def wrap_inside(
if (a > 0.0) and (a < 1.0):
tmp = end0 + a * dif
if wp.norm_l2(tmp) <= radius:
return -1.0, wp.vec2(wp.inf), wp.vec2(wp.inf)
return -1.0, wp.vec2(MJ_MAXVAL), wp.vec2(MJ_MAXVAL)
# prepare default in case of numerical failure: average
pnt = 0.5 * (end0 + end1)
@@ -335,14 +336,14 @@ def wrap(
mat: Orientation of geom.
radius: Geom radius.
geomtype: Wrap type (mjtWrap).
side: 3D position for sidesite, no side point: wp.vec3(wp.inf).
side: 3D position for sidesite, no side point: wp.vec3(MJ_MAXVAL).
Returns:
Length of circular wrap else -1.0 if no wrap, pair of 3D wrap points.
"""
# check object type
if geomtype != WrapType.SPHERE and geomtype != WrapType.CYLINDER:
return wp.inf, wp.vec3(wp.inf), wp.vec3(wp.inf)
return MJ_MAXVAL, wp.vec3(MJ_MAXVAL), wp.vec3(MJ_MAXVAL)
# map sites to wrap object's local frame
matT = wp.transpose(mat)
@@ -351,7 +352,7 @@ def wrap(
# too close to origin: return
if (wp.norm_l2(p0) < MJ_MINVAL) or (wp.norm_l2(p1) < MJ_MINVAL):
return -1.0, wp.vec3(wp.inf), wp.vec3(wp.inf)
return -1.0, wp.vec3(MJ_MAXVAL), wp.vec3(MJ_MAXVAL)
# construct 2D frame for circle wrap
if geomtype == WrapType.SPHERE:
@@ -399,7 +400,7 @@ def wrap(
)
# handle sidesite
valid_side = wp.norm_l2(side) < wp.inf
valid_side = wp.norm_l2(side) < MJ_MAXVAL
if valid_side:
# side point: apply same projection as x0, x1
@@ -414,7 +415,7 @@ def wrap(
sidepnt_proj, _ = math.normalize_with_norm(sidepnt_proj)
sidepnt_proj *= radius
else:
sidepnt_proj = wp.vec2(wp.inf)
sidepnt_proj = wp.vec2(MJ_MAXVAL)
# apply inside wrap
if valid_side and wp.norm_l2(sidepnt) < radius:
@@ -424,7 +425,7 @@ def wrap(
# no wrap: return
if wlen < 0.0:
return -1.0, wp.vec3(wp.inf), wp.vec3(wp.inf)
return -1.0, wp.vec3(MJ_MAXVAL), wp.vec3(MJ_MAXVAL)
# reconstruct 3D points in local frame: res
res0 = axis0 * pnt0[0] + axis1 * pnt0[1]
+1 -78
View File
@@ -15,7 +15,6 @@
import functools
import inspect
from typing import Callable, Optional
import warp as wp
@@ -119,82 +118,6 @@ def event_scope(fn, name: str = ""):
return wrapper
# @nested_kernel decorator to automatically set up modules based on nested
# function names
def nested_kernel(
f: Optional[Callable] = None,
*,
enable_backward: Optional[bool] = None,
module: Optional[wp.Module] = None,
):
"""Decorator to register a Warp kernel from a Python function.
The function must be defined with type annotations for all arguments.
The function must not return anything.
Example::
@nested_kernel
def my_kernel(a: wp.array(dtype=float), b: wp.array(dtype=float)):
tid = wp.tid()
b[tid] = a[tid] + 1.0
@nested_kernel(enable_backward=False)
def my_kernel_no_backward(a: wp.array(dtype=float, ndim=2), x: float):
# the backward pass will not be generated
i, j = wp.tid()
a[i, j] = x
@nested_kernel(module="unique")
def my_kernel_unique_module(a: wp.array(dtype=float), b: wp.array(dtype=float)):
# the kernel will be registered in new unique module created just for this
# kernel and its dependent functions and structs
tid = wp.tid()
b[tid] = a[tid] + 1.0
@neste_kernel(enable_backward=False, module=None)
def my_kernel_with_args(a: wp.array(dtype=float), b: wp.array(dtype=float)):
# can now use arguments even when module=None
tid = wp.tid()
b[tid] = a[tid] + 1.0
Args:
f: The function to be registered as a kernel.
enable_backward: If False, the backward pass will not be generated.
module: The :class:`warp.Module` to which the kernel belongs. Alternatively,
if a string `"unique"` is provided, the kernel is assigned to a new module
named after the kernel name and hash. If None, the module is inferred from
the function's module.
Returns:
The registered kernel.
"""
def decorator(func):
if module is None:
# create a module name based on the name of the nested function
# get the qualified name, e.g. "main.<locals>.nested_kernel"
qualname = func.__qualname__
parts = [part for part in qualname.split(".") if part != "<locals>"]
outer_functions = parts[:-1]
module_name = wp.get_module(".".join([func.__module__] + outer_functions))
else:
module_name = module
return wp.kernel(func, enable_backward=enable_backward, module=module_name)
# Handle both @kernel and @kernel(...) usage patterns
if f is None:
# Called with arguments: @kernel(enable_backward=False)
return decorator
else:
# Called without arguments: @kernel
return decorator(f)
_KERNEL_CACHE = {}
@@ -221,4 +144,4 @@ def check_toolkit_driver():
wp.init()
if wp.get_device().is_cuda:
if not wp.is_conditional_graph_supported():
RuntimeError("Minimum supported CUDA version: 12.4.")
raise RuntimeError("Minimum supported CUDA version: 12.4.")
+1 -1
View File
@@ -54,7 +54,7 @@ dev = [
"ruff",
"pygls>=1.0.0,<2.0.0",
"lsprotocol>=2023.0.1,<2024.0.0",
"mujoco>=3.3.7.dev0",
"mujoco>=3.4.1.dev0",
"warp-lang>=1.11.0.dev0",
]
# TODO(team): cpu and cuda JAX optional dependencies are temporary, remove after we land MJX:Warp
+39 -30
View File
@@ -24,6 +24,7 @@ Example:
import copy
import enum
import logging
import shutil
import sys
import time
from typing import Sequence
@@ -51,10 +52,11 @@ class EngineOptions(enum.IntEnum):
C = 1
_CLEAR_KERNEL_CACHE = flags.DEFINE_bool("clear_kernel_cache", False, "Clear kernel cache (to calculate full JIT time)")
_CLEAR_WARP_CACHE = flags.DEFINE_bool("clear_warp_cache", False, "Clear warp caches (kernel, LTO, CUDA compute)")
_ENGINE = flags.DEFINE_enum_class("engine", EngineOptions.WARP, EngineOptions, "Simulation engine")
_NCONMAX = flags.DEFINE_integer("nconmax", None, "Maximum number of contacts.")
_NJMAX = flags.DEFINE_integer("njmax", None, "Maximum number of constraints per world.")
_NCCDMAX = flags.DEFINE_integer("nccdmax", None, "Maximum number of CCD contacts per world.")
_OVERRIDE = flags.DEFINE_multi_string("override", [], "Model overrides (notation: foo.bar = baz)", short_name="o")
_KEYFRAME = flags.DEFINE_integer("keyframe", 0, "keyframe to initialize simulation.")
_DEVICE = flags.DEFINE_string("device", None, "override the default Warp device")
@@ -134,27 +136,37 @@ def _main(argv: Sequence[str]) -> None:
else:
wp.config.quiet = flags.FLAGS["verbosity"].value < 1
wp.init()
if _CLEAR_KERNEL_CACHE.value:
wp.set_device(_DEVICE.value)
if _CLEAR_WARP_CACHE.value:
wp.clear_kernel_cache()
wp.clear_lto_cache()
# Clear CUDA compute cache for truly cold start JIT
compute_cache = epath.Path("~/.nv/ComputeCache").expanduser()
if compute_cache.exists():
shutil.rmtree(compute_cache)
compute_cache.mkdir()
with wp.ScopedDevice(_DEVICE.value):
m = mjw.put_model(mjm)
override_model(m, _OVERRIDE.value)
broadphase, filter = mjw.BroadphaseType(m.opt.broadphase).name, mjw.BroadphaseFilter(m.opt.broadphase_filter).name
solver, cone = mjw.SolverType(m.opt.solver).name, mjw.ConeType(m.opt.cone).name
integrator = mjw.IntegratorType(m.opt.integrator).name
iterations, ls_iterations = m.opt.iterations, m.opt.ls_iterations
ls_str = f"{'parallel' if m.opt.ls_parallel else 'iterative'} linesearch iterations: {ls_iterations}"
print(
f" nbody: {m.nbody} nv: {m.nv} ngeom: {m.ngeom} nu: {m.nu} is_sparse: {m.opt.is_sparse}\n"
f" broadphase: {broadphase} broadphase_filter: {filter}\n"
f" solver: {solver} cone: {cone} iterations: {iterations} {ls_str}\n"
f" integrator: {integrator} graph_conditional: {m.opt.graph_conditional}"
)
d = mjw.put_data(mjm, mjd, nconmax=_NCONMAX.value, njmax=_NJMAX.value)
print(f"Data\n nworld: {d.nworld} nconmax: {d.naconmax / d.nworld} njmax: {d.njmax}\n")
graph = _compile_step(m, d)
print(f"MuJoCo Warp simulating with dt = {m.opt.timestep.numpy()[0]:.3f}...")
override_model(mjm, _OVERRIDE.value)
m = mjw.put_model(mjm)
override_model(m, _OVERRIDE.value)
d = mjw.put_data(mjm, mjd, nconmax=_NCONMAX.value, njmax=_NJMAX.value, nccdmax=_NCCDMAX.value)
graph = _compile_step(m, d) if wp.get_device().is_cuda else None
if graph is None:
mjw.step(m, d) # warmup step
print("Running Warp unoptimized on CPU.")
broadphase, filter = mjw.BroadphaseType(m.opt.broadphase).name, mjw.BroadphaseFilter(m.opt.broadphase_filter).name
solver, cone = mjw.SolverType(m.opt.solver).name, mjw.ConeType(m.opt.cone).name
integrator = mjw.IntegratorType(m.opt.integrator).name
iterations, ls_iterations = m.opt.iterations, m.opt.ls_iterations
ls_str = f"{'parallel' if m.opt.ls_parallel else 'iterative'} linesearch iterations: {ls_iterations}"
print(
f" nbody: {m.nbody} nv: {m.nv} ngeom: {m.ngeom} nu: {m.nu} is_sparse: {m.is_sparse}\n"
f" broadphase: {broadphase} broadphase_filter: {filter}\n"
f" solver: {solver} cone: {cone} iterations: {iterations} {ls_str}\n"
f" integrator: {integrator} graph_conditional: {m.opt.graph_conditional}"
)
print(f"Data\n nworld: {d.nworld} nconmax: {int(d.naconmax / d.nworld)} njmax: {d.njmax}\n")
print(f"MuJoCo Warp simulating with dt = {m.opt.timestep.numpy()[0]:.3f}...")
with mujoco.viewer.launch_passive(mjm, mjd, key_callback=key_callback) as viewer:
opt = copy.copy(mjm.opt)
@@ -175,22 +187,19 @@ def _main(argv: Sequence[str]) -> None:
wp.copy(d.qpos, wp.array([mjd.qpos.astype(np.float32)]))
wp.copy(d.qvel, wp.array([mjd.qvel.astype(np.float32)]))
wp.copy(d.time, wp.array([mjd.time], dtype=wp.float32))
# if the user changed an option in the MuJoCo Simulate UI, go ahead and recompile the step
# TODO: update memory tied to option max iterations
if mjm.opt != opt:
opt = copy.copy(mjm.opt)
m = mjw.put_model(mjm)
graph = _compile_step(m, d)
if _VIEWER_GLOBAL_STATE["running"]:
wp.capture_launch(graph)
wp.synchronize()
elif _VIEWER_GLOBAL_STATE["step_once"]:
graph = _compile_step(m, d) if wp.get_device().is_cuda else None
if _VIEWER_GLOBAL_STATE["running"] or _VIEWER_GLOBAL_STATE["step_once"]:
_VIEWER_GLOBAL_STATE["step_once"] = False
wp.capture_launch(graph)
wp.synchronize()
if graph is None:
mjw.step(m, d)
else:
wp.capture_launch(graph)
wp.synchronize()
mjw.get_data_into(mjd, mjm, d)
viewer.sync()
+20 -34
View File
@@ -42,6 +42,7 @@ _e = mjwarp.Constraint(
**{f.name: None for f in dataclasses.fields(mjwarp.Constraint) if f.init}
)
@ffi.format_args_for_warp
def _collision_shim(
# Model
@@ -112,10 +113,8 @@ def _collision_shim(
opt__sdf_initpoints: int,
opt__sdf_iterations: int,
# Data
naccdmax: int,
naconmax: int,
collision_pair: wp.array(dtype=wp.vec2i),
collision_pairid: wp.array(dtype=wp.vec2i),
collision_worldid: wp.array(dtype=int),
geom_xmat: wp.array2d(dtype=wp.mat33),
geom_xpos: wp.array2d(dtype=wp.vec3),
nacon: wp.array(dtype=int),
@@ -203,9 +202,6 @@ def _collision_shim(
_m.pair_solreffriction = pair_solreffriction
_m.plugin = plugin
_m.plugin_attr = plugin_attr
_d.collision_pair = collision_pair
_d.collision_pairid = collision_pairid
_d.collision_worldid = collision_worldid
_d.contact.dim = contact__dim
_d.contact.dist = contact__dist
_d.contact.frame = contact__frame
@@ -221,6 +217,7 @@ def _collision_shim(
_d.contact.worldid = contact__worldid
_d.geom_xmat = geom_xmat
_d.geom_xpos = geom_xpos
_d.naccdmax = naccdmax
_d.nacon = nacon
_d.naconmax = naconmax
_d.ncollision = ncollision
@@ -230,9 +227,6 @@ def _collision_shim(
def _collision_jax_impl(m: types.Model, d: types.Data):
output_dims = {
'collision_pair': d._impl.collision_pair.shape,
'collision_pairid': d._impl.collision_pairid.shape,
'collision_worldid': d._impl.collision_worldid.shape,
'nacon': d._impl.nacon.shape,
'ncollision': d._impl.ncollision.shape,
'contact__dim': d._impl.contact__dim.shape,
@@ -251,13 +245,10 @@ def _collision_jax_impl(m: types.Model, d: types.Data):
}
jf = ffi.jax_callable_variadic_tuple(
_collision_shim,
num_outputs=18,
num_outputs=15,
output_dims=output_dims,
vmap_method=None,
in_out_argnames={
'collision_pair',
'collision_pairid',
'collision_worldid',
'nacon',
'ncollision',
'contact__dim',
@@ -364,10 +355,8 @@ def _collision_jax_impl(m: types.Model, d: types.Data):
m.opt.enableflags,
m.opt._impl.sdf_initpoints,
m.opt._impl.sdf_iterations,
d._impl.naccdmax,
d._impl.naconmax,
d._impl.collision_pair,
d._impl.collision_pairid,
d._impl.collision_worldid,
d.geom_xmat,
d.geom_xpos,
d._impl.nacon,
@@ -387,24 +376,21 @@ def _collision_jax_impl(m: types.Model, d: types.Data):
d._impl.contact__worldid,
)
d = d.tree_replace({
'_impl.collision_pair': out[0],
'_impl.collision_pairid': out[1],
'_impl.collision_worldid': out[2],
'_impl.nacon': out[3],
'_impl.ncollision': out[4],
'_impl.contact__dim': out[5],
'_impl.contact__dist': out[6],
'_impl.contact__frame': out[7],
'_impl.contact__friction': out[8],
'_impl.contact__geom': out[9],
'_impl.contact__geomcollisionid': out[10],
'_impl.contact__includemargin': out[11],
'_impl.contact__pos': out[12],
'_impl.contact__solimp': out[13],
'_impl.contact__solref': out[14],
'_impl.contact__solreffriction': out[15],
'_impl.contact__type': out[16],
'_impl.contact__worldid': out[17],
'_impl.nacon': out[0],
'_impl.ncollision': out[1],
'_impl.contact__dim': out[2],
'_impl.contact__dist': out[3],
'_impl.contact__frame': out[4],
'_impl.contact__friction': out[5],
'_impl.contact__geom': out[6],
'_impl.contact__geomcollisionid': out[7],
'_impl.contact__includemargin': out[8],
'_impl.contact__pos': out[9],
'_impl.contact__solimp': out[10],
'_impl.contact__solref': out[11],
'_impl.contact__solreffriction': out[12],
'_impl.contact__type': out[13],
'_impl.contact__worldid': out[14],
})
return d
File diff suppressed because it is too large Load Diff
+13 -3
View File
@@ -42,10 +42,13 @@ _e = mjwarp.Constraint(
**{f.name: None for f in dataclasses.fields(mjwarp.Constraint) if f.init}
)
@ffi.format_args_for_warp
def _kinematics_shim(
# Model
nworld: int,
body_branch_start: wp.array(dtype=int),
body_branches: wp.array(dtype=int),
body_ipos: wp.array2d(dtype=wp.vec3),
body_iquat: wp.array2d(dtype=wp.quat),
body_jntadr: wp.array(dtype=int),
@@ -55,7 +58,6 @@ def _kinematics_shim(
body_pos: wp.array2d(dtype=wp.vec3),
body_quat: wp.array2d(dtype=wp.quat),
body_rootid: wp.array(dtype=int),
body_tree: tuple[wp.array(dtype=int), ...],
body_weldid: wp.array(dtype=int),
geom_bodyid: wp.array(dtype=int),
geom_pos: wp.array2d(dtype=wp.vec3),
@@ -64,6 +66,8 @@ def _kinematics_shim(
jnt_pos: wp.array2d(dtype=wp.vec3),
jnt_qposadr: wp.array(dtype=int),
jnt_type: wp.array(dtype=int),
nbody: int,
nbranch: int,
ngeom: int,
nsite: int,
qpos0: wp.array2d(dtype=float),
@@ -90,6 +94,8 @@ def _kinematics_shim(
_m.opt = _o
_d.efc = _e
_d.contact = _c
_m.body_branch_start = body_branch_start
_m.body_branches = body_branches
_m.body_ipos = body_ipos
_m.body_iquat = body_iquat
_m.body_jntadr = body_jntadr
@@ -99,7 +105,6 @@ def _kinematics_shim(
_m.body_pos = body_pos
_m.body_quat = body_quat
_m.body_rootid = body_rootid
_m.body_tree = body_tree
_m.body_weldid = body_weldid
_m.geom_bodyid = geom_bodyid
_m.geom_pos = geom_pos
@@ -108,6 +113,8 @@ def _kinematics_shim(
_m.jnt_pos = jnt_pos
_m.jnt_qposadr = jnt_qposadr
_m.jnt_type = jnt_type
_m.nbody = nbody
_m.nbranch = nbranch
_m.ngeom = ngeom
_m.nsite = nsite
_m.qpos0 = qpos0
@@ -208,6 +215,8 @@ def _kinematics_jax_impl(m: types.Model, d: types.Data):
)
out = jf(
d.qpos.shape[0],
m._impl.body_branch_start,
m._impl.body_branches,
m.body_ipos,
m.body_iquat,
m.body_jntadr,
@@ -217,7 +226,6 @@ def _kinematics_jax_impl(m: types.Model, d: types.Data):
m.body_pos,
m.body_quat,
m.body_rootid,
m._impl.body_tree,
m.body_weldid,
m.geom_bodyid,
m.geom_pos,
@@ -226,6 +234,8 @@ def _kinematics_jax_impl(m: types.Model, d: types.Data):
m.jnt_pos,
m.jnt_qposadr,
m.jnt_type,
m.nbody,
m._impl.nbranch,
m.ngeom,
m.nsite,
m.qpos0,
+70 -121
View File
@@ -72,7 +72,7 @@ class BlockDim:
energy_vel_kinetic: int
euler_dense: int
linesearch_iterative: int
mul_m_dense: int
qderiv_actuator_dense: int
ray: int
segmented_sort: int
tendon_velocity: int
@@ -93,7 +93,7 @@ class BlockDim:
class StatisticWarp(PyTreeNode):
"""Derived fields from Statistic."""
meaninertia: float
meaninertia: jax.Array
class OptionWarp(PyTreeNode):
"""Derived fields from Option."""
@@ -104,9 +104,7 @@ class OptionWarp(PyTreeNode):
contact_sensor_maxmatch: int
graph_conditional: bool
graph_mode: GraphMode
has_fluid: bool
impratio_invsqrt: jax.Array
is_sparse: bool
ls_parallel: bool
ls_parallel_min_step: float
run_collision_detection: bool
@@ -120,8 +118,11 @@ class ModelWarp(PyTreeNode):
M_rownnz: np.ndarray
actuator_trntype_body_adr: np.ndarray
block_dim: BlockDim
body_branch_start: np.ndarray
body_branches: np.ndarray
body_fluid_ellipsoid: np.ndarray
body_tree: Tuple[np.ndarray, ...]
cam_projection: np.ndarray
collision_sensor_adr: np.ndarray
dof_tri_col: np.ndarray
dof_tri_row: np.ndarray
@@ -146,11 +147,16 @@ class ModelWarp(PyTreeNode):
flex_vertadr: np.ndarray
flex_vertbodyid: np.ndarray
flex_vertnum: np.ndarray
flexedge_J_colind: np.ndarray
flexedge_J_rowadr: np.ndarray
flexedge_J_rownnz: np.ndarray
flexedge_invweight0: np.ndarray
flexedge_length0: np.ndarray
geom_pair_type_count: Tuple[int, ...]
geom_plugin_index: np.ndarray
has_fluid: bool
has_sdf_geom: bool
is_sparse: bool
jnt_limited_ball_adr: np.ndarray
jnt_limited_slide_hinge_adr: np.ndarray
light_active: jax.Array
@@ -169,6 +175,7 @@ class ModelWarp(PyTreeNode):
mesh_polyvertnum: np.ndarray
mocap_bodyid: np.ndarray
nacttrnbody: int
nbranch: int
nflex: int
nflexedge: int
nflexelem: int
@@ -185,6 +192,7 @@ class ModelWarp(PyTreeNode):
nsensorcollision: int
nsensorcontact: int
nsensortaxel: int
ntree: int
nv_pad: int
nxn_geom_pair: np.ndarray
nxn_geom_pair_filtered: np.ndarray
@@ -198,9 +206,9 @@ class ModelWarp(PyTreeNode):
qLD_updates: Tuple[np.ndarray, ...]
qM_fullm_i: np.ndarray
qM_fullm_j: np.ndarray
qM_madr_ij: np.ndarray
qM_mulm_i: np.ndarray
qM_mulm_j: np.ndarray
qM_mulm_col: np.ndarray
qM_mulm_madr: np.ndarray
qM_mulm_rowadr: np.ndarray
qM_tiles: Tuple[TileSet, ...]
rangefinder_sensor_adr: np.ndarray
sensor_acc_adr: np.ndarray
@@ -228,6 +236,9 @@ class ModelWarp(PyTreeNode):
tendon_jnt_adr: np.ndarray
tendon_limited_adr: np.ndarray
tendon_site_pair_adr: np.ndarray
tree_bodynum: np.ndarray
tree_dofadr: np.ndarray
tree_dofnum: np.ndarray
wrap_geom_adr: np.ndarray
wrap_jnt_adr: np.ndarray
wrap_pulley_scale: np.ndarray
@@ -242,9 +253,6 @@ class DataWarp(PyTreeNode):
cfrc_ext: jax.Array
cfrc_int: jax.Array
cinert: jax.Array
collision_pair: jax.Array
collision_pairid: jax.Array
collision_worldid: jax.Array
contact__dim: jax.Array
contact__dist: jax.Array
contact__efc_address: jax.Array
@@ -262,31 +270,13 @@ class DataWarp(PyTreeNode):
crb: jax.Array
efc__D: jax.Array
efc__J: jax.Array
efc__Jaref: jax.Array
efc__Ma: jax.Array
efc__Mgrad: jax.Array
efc__alpha: jax.Array
efc__aref: jax.Array
efc__beta: jax.Array
efc__cost: jax.Array
efc__done: jax.Array
efc__force: jax.Array
efc__frictionloss: jax.Array
efc__gauss: jax.Array
efc__grad: jax.Array
efc__grad_dot: jax.Array
efc__id: jax.Array
efc__jv: jax.Array
efc__margin: jax.Array
efc__mv: jax.Array
efc__pos: jax.Array
efc__prev_Mgrad: jax.Array
efc__prev_cost: jax.Array
efc__prev_grad: jax.Array
efc__quad: jax.Array
efc__quad_gauss: jax.Array
efc__search: jax.Array
efc__search_dot: jax.Array
efc__state: jax.Array
efc__type: jax.Array
efc__vel: jax.Array
@@ -297,20 +287,15 @@ class DataWarp(PyTreeNode):
flexvert_xpos: jax.Array
light_xdir: jax.Array
light_xpos: jax.Array
naccdmax: int
nacon: jax.Array
naconmax: int
ncollision: jax.Array
ne: jax.Array
ne_connect: jax.Array
ne_flex: jax.Array
ne_jnt: jax.Array
ne_ten: jax.Array
ne_weld: jax.Array
nefc: jax.Array
nf: jax.Array
njmax: int
nl: jax.Array
nsolving: jax.Array
nworld: int
qLD: jax.Array
qLDiagInv: jax.Array
@@ -319,7 +304,6 @@ class DataWarp(PyTreeNode):
qfrc_spring: jax.Array
solver_niter: jax.Array
subtree_angmom: jax.Array
subtree_bodyvel: jax.Array
subtree_linvel: jax.Array
ten_J: jax.Array
ten_velocity: jax.Array
@@ -329,9 +313,6 @@ class DataWarp(PyTreeNode):
wrap_xpos: jax.Array
shape = property(lambda self: self.cacc.shape)
DATA_NON_VMAP = {
'collision_pair',
'collision_pairid',
'collision_worldid',
'contact__dim',
'contact__dist',
'contact__efc_address',
@@ -346,11 +327,11 @@ DATA_NON_VMAP = {
'contact__solreffriction',
'contact__type',
'contact__worldid',
'naccdmax',
'nacon',
'naconmax',
'ncollision',
'njmax',
'nsolving',
'nworld',
}
@@ -394,9 +375,6 @@ _NDIM = {
'cfrc_ext': 3,
'cfrc_int': 3,
'cinert': 3,
'collision_pair': 2,
'collision_pairid': 2,
'collision_worldid': 1,
'contact__dim': 1,
'contact__dist': 1,
'contact__efc_address': 2,
@@ -416,31 +394,13 @@ _NDIM = {
'cvel': 3,
'efc__D': 2,
'efc__J': 3,
'efc__Jaref': 2,
'efc__Ma': 2,
'efc__Mgrad': 2,
'efc__alpha': 1,
'efc__aref': 2,
'efc__beta': 1,
'efc__cost': 1,
'efc__done': 1,
'efc__force': 2,
'efc__frictionloss': 2,
'efc__gauss': 1,
'efc__grad': 2,
'efc__grad_dot': 1,
'efc__id': 2,
'efc__jv': 2,
'efc__margin': 2,
'efc__mv': 2,
'efc__pos': 2,
'efc__prev_Mgrad': 2,
'efc__prev_cost': 1,
'efc__prev_grad': 2,
'efc__quad': 3,
'efc__quad_gauss': 2,
'efc__search': 2,
'efc__search_dot': 1,
'efc__state': 2,
'efc__type': 2,
'efc__vel': 2,
@@ -456,20 +416,15 @@ _NDIM = {
'light_xpos': 3,
'mocap_pos': 3,
'mocap_quat': 3,
'naccdmax': 0,
'nacon': 1,
'naconmax': 0,
'ncollision': 1,
'ne': 1,
'ne_connect': 1,
'ne_flex': 1,
'ne_jnt': 1,
'ne_ten': 1,
'ne_weld': 1,
'nefc': 1,
'nf': 1,
'njmax': 0,
'nl': 1,
'nsolving': 1,
'nworld': 0,
'qLD': 3,
'qLDiagInv': 2,
@@ -495,7 +450,6 @@ _NDIM = {
'site_xpos': 3,
'solver_niter': 1,
'subtree_angmom': 3,
'subtree_bodyvel': 3,
'subtree_com': 3,
'subtree_linvel': 3,
'ten_J': 3,
@@ -549,7 +503,7 @@ _NDIM = {
'block_dim__energy_vel_kinetic': 0,
'block_dim__euler_dense': 0,
'block_dim__linesearch_iterative': 0,
'block_dim__mul_m_dense': 0,
'block_dim__qderiv_actuator_dense': 0,
'block_dim__ray': 0,
'block_dim__segmented_sort': 0,
'block_dim__tendon_velocity': 0,
@@ -557,6 +511,8 @@ _NDIM = {
'block_dim__update_gradient_JTDAJ_sparse': 0,
'block_dim__update_gradient_cholesky': 0,
'block_dim__update_gradient_cholesky_blocked': 0,
'body_branch_start': 1,
'body_branches': 1,
'body_conaffinity': 1,
'body_contype': 1,
'body_dofadr': 1,
@@ -579,15 +535,17 @@ _NDIM = {
'body_rootid': 1,
'body_subtreemass': 2,
'body_tree': -1,
'body_treeid': 1,
'body_weldid': 1,
'cam_bodyid': 1,
'cam_fovy': 1,
'cam_intrinsic': 2,
'cam_fovy': 2,
'cam_intrinsic': 3,
'cam_mat0': 4,
'cam_mode': 1,
'cam_pos': 3,
'cam_pos0': 3,
'cam_poscom0': 3,
'cam_projection': 1,
'cam_quat': 3,
'cam_resolution': 2,
'cam_sensorsize': 2,
@@ -603,6 +561,7 @@ _NDIM = {
'dof_parentid': 1,
'dof_solimp': 3,
'dof_solref': 3,
'dof_treeid': 1,
'dof_tri_col': 1,
'dof_tri_row': 1,
'eq_active0': 1,
@@ -635,6 +594,9 @@ _NDIM = {
'flex_vertadr': 1,
'flex_vertbodyid': 1,
'flex_vertnum': 1,
'flexedge_J_colind': 1,
'flexedge_J_rowadr': 1,
'flexedge_J_rownnz': 1,
'flexedge_invweight0': 1,
'flexedge_length0': 1,
'geom_aabb': 4,
@@ -661,12 +623,14 @@ _NDIM = {
'geom_solmix': 2,
'geom_solref': 3,
'geom_type': 1,
'has_fluid': 0,
'has_sdf_geom': 0,
'hfield_adr': 1,
'hfield_data': 1,
'hfield_ncol': 1,
'hfield_nrow': 1,
'hfield_size': 2,
'is_sparse': 0,
'jnt_actfrclimited': 1,
'jnt_actfrcrange': 3,
'jnt_actgravcomp': 1,
@@ -697,7 +661,7 @@ _NDIM = {
'light_type': 2,
'mapM2M': 1,
'mat_rgba': 3,
'mat_texid': 2,
'mat_texid': 3,
'mat_texrepeat': 3,
'mesh_face': 2,
'mesh_faceadr': 1,
@@ -724,6 +688,7 @@ _NDIM = {
'na': 0,
'nacttrnbody': 0,
'nbody': 0,
'nbranch': 0,
'ncam': 0,
'neq': 0,
'nexclude': 0,
@@ -765,6 +730,7 @@ _NDIM = {
'nsensortaxel': 0,
'nsite': 0,
'ntendon': 0,
'ntree': 0,
'nu': 0,
'nv': 0,
'nv_pad': 0,
@@ -785,10 +751,8 @@ _NDIM = {
'opt__enableflags': 0,
'opt__graph_conditional': 0,
'opt__gravity': 2,
'opt__has_fluid': 0,
'opt__impratio_invsqrt': 1,
'opt__integrator': 0,
'opt__is_sparse': 0,
'opt__iterations': 0,
'opt__ls_iterations': 0,
'opt__ls_parallel': 0,
@@ -817,9 +781,9 @@ _NDIM = {
'qLD_updates': -1,
'qM_fullm_i': 1,
'qM_fullm_j': 1,
'qM_madr_ij': 1,
'qM_mulm_i': 1,
'qM_mulm_j': 1,
'qM_mulm_col': 1,
'qM_mulm_madr': 1,
'qM_mulm_rowadr': 1,
'qM_tiles': -1,
'qpos0': 2,
'qpos_spring': 2,
@@ -856,7 +820,7 @@ _NDIM = {
'site_quat': 3,
'site_size': 2,
'site_type': 1,
'stat__meaninertia': 0,
'stat__meaninertia': 1,
'taxel_sensorid': 1,
'taxel_vertadr': 1,
'ten_wrapadr_site': 1,
@@ -883,6 +847,9 @@ _NDIM = {
'tendon_solref_fri': 3,
'tendon_solref_lim': 3,
'tendon_stiffness': 2,
'tree_bodynum': 1,
'tree_dofadr': 1,
'tree_dofnum': 1,
'wrap_geom_adr': 1,
'wrap_jnt_adr': 1,
'wrap_objid': 1,
@@ -902,10 +869,8 @@ _NDIM = {
'enableflags': 0,
'graph_conditional': 0,
'gravity': 2,
'has_fluid': 0,
'impratio_invsqrt': 1,
'integrator': 0,
'is_sparse': 0,
'iterations': 0,
'ls_iterations': 0,
'ls_parallel': 0,
@@ -921,7 +886,7 @@ _NDIM = {
'viscosity': 1,
'wind': 2,
},
'Statistic': {'meaninertia': 0},
'Statistic': {'meaninertia': 1},
}
_BATCH_DIM = {
'Data': {
@@ -939,9 +904,6 @@ _BATCH_DIM = {
'cfrc_ext': True,
'cfrc_int': True,
'cinert': True,
'collision_pair': False,
'collision_pairid': False,
'collision_worldid': False,
'contact__dim': False,
'contact__dist': False,
'contact__efc_address': False,
@@ -961,31 +923,13 @@ _BATCH_DIM = {
'cvel': True,
'efc__D': True,
'efc__J': True,
'efc__Jaref': True,
'efc__Ma': True,
'efc__Mgrad': True,
'efc__alpha': True,
'efc__aref': True,
'efc__beta': True,
'efc__cost': True,
'efc__done': True,
'efc__force': True,
'efc__frictionloss': True,
'efc__gauss': True,
'efc__grad': True,
'efc__grad_dot': True,
'efc__id': True,
'efc__jv': True,
'efc__margin': True,
'efc__mv': True,
'efc__pos': True,
'efc__prev_Mgrad': True,
'efc__prev_cost': True,
'efc__prev_grad': True,
'efc__quad': True,
'efc__quad_gauss': True,
'efc__search': True,
'efc__search_dot': True,
'efc__state': True,
'efc__type': True,
'efc__vel': True,
@@ -1001,20 +945,15 @@ _BATCH_DIM = {
'light_xpos': True,
'mocap_pos': True,
'mocap_quat': True,
'naccdmax': False,
'nacon': False,
'naconmax': False,
'ncollision': False,
'ne': True,
'ne_connect': True,
'ne_flex': True,
'ne_jnt': True,
'ne_ten': True,
'ne_weld': True,
'nefc': True,
'nf': True,
'njmax': False,
'nl': True,
'nsolving': False,
'nworld': False,
'qLD': True,
'qLDiagInv': True,
@@ -1040,7 +979,6 @@ _BATCH_DIM = {
'site_xpos': True,
'solver_niter': True,
'subtree_angmom': True,
'subtree_bodyvel': True,
'subtree_com': True,
'subtree_linvel': True,
'ten_J': True,
@@ -1094,7 +1032,7 @@ _BATCH_DIM = {
'block_dim__energy_vel_kinetic': False,
'block_dim__euler_dense': False,
'block_dim__linesearch_iterative': False,
'block_dim__mul_m_dense': False,
'block_dim__qderiv_actuator_dense': False,
'block_dim__ray': False,
'block_dim__segmented_sort': False,
'block_dim__tendon_velocity': False,
@@ -1102,6 +1040,8 @@ _BATCH_DIM = {
'block_dim__update_gradient_JTDAJ_sparse': False,
'block_dim__update_gradient_cholesky': False,
'block_dim__update_gradient_cholesky_blocked': False,
'body_branch_start': False,
'body_branches': False,
'body_conaffinity': False,
'body_contype': False,
'body_dofadr': False,
@@ -1124,15 +1064,17 @@ _BATCH_DIM = {
'body_rootid': False,
'body_subtreemass': True,
'body_tree': False,
'body_treeid': False,
'body_weldid': False,
'cam_bodyid': False,
'cam_fovy': False,
'cam_intrinsic': False,
'cam_fovy': True,
'cam_intrinsic': True,
'cam_mat0': True,
'cam_mode': False,
'cam_pos': True,
'cam_pos0': True,
'cam_poscom0': True,
'cam_projection': False,
'cam_quat': True,
'cam_resolution': False,
'cam_sensorsize': False,
@@ -1148,6 +1090,7 @@ _BATCH_DIM = {
'dof_parentid': False,
'dof_solimp': True,
'dof_solref': True,
'dof_treeid': False,
'dof_tri_col': False,
'dof_tri_row': False,
'eq_active0': False,
@@ -1180,6 +1123,9 @@ _BATCH_DIM = {
'flex_vertadr': False,
'flex_vertbodyid': False,
'flex_vertnum': False,
'flexedge_J_colind': False,
'flexedge_J_rowadr': False,
'flexedge_J_rownnz': False,
'flexedge_invweight0': False,
'flexedge_length0': False,
'geom_aabb': True,
@@ -1206,12 +1152,14 @@ _BATCH_DIM = {
'geom_solmix': True,
'geom_solref': True,
'geom_type': False,
'has_fluid': False,
'has_sdf_geom': False,
'hfield_adr': False,
'hfield_data': False,
'hfield_ncol': False,
'hfield_nrow': False,
'hfield_size': False,
'is_sparse': False,
'jnt_actfrclimited': False,
'jnt_actfrcrange': True,
'jnt_actgravcomp': False,
@@ -1242,7 +1190,7 @@ _BATCH_DIM = {
'light_type': True,
'mapM2M': False,
'mat_rgba': True,
'mat_texid': False,
'mat_texid': True,
'mat_texrepeat': True,
'mesh_face': False,
'mesh_faceadr': False,
@@ -1269,6 +1217,7 @@ _BATCH_DIM = {
'na': False,
'nacttrnbody': False,
'nbody': False,
'nbranch': False,
'ncam': False,
'neq': False,
'nexclude': False,
@@ -1310,6 +1259,7 @@ _BATCH_DIM = {
'nsensortaxel': False,
'nsite': False,
'ntendon': False,
'ntree': False,
'nu': False,
'nv': False,
'nv_pad': False,
@@ -1330,10 +1280,8 @@ _BATCH_DIM = {
'opt__enableflags': False,
'opt__graph_conditional': False,
'opt__gravity': True,
'opt__has_fluid': False,
'opt__impratio_invsqrt': True,
'opt__integrator': False,
'opt__is_sparse': False,
'opt__iterations': False,
'opt__ls_iterations': False,
'opt__ls_parallel': False,
@@ -1362,9 +1310,9 @@ _BATCH_DIM = {
'qLD_updates': False,
'qM_fullm_i': False,
'qM_fullm_j': False,
'qM_madr_ij': False,
'qM_mulm_i': False,
'qM_mulm_j': False,
'qM_mulm_col': False,
'qM_mulm_madr': False,
'qM_mulm_rowadr': False,
'qM_tiles': False,
'qpos0': True,
'qpos_spring': True,
@@ -1401,7 +1349,7 @@ _BATCH_DIM = {
'site_quat': True,
'site_size': False,
'site_type': False,
'stat__meaninertia': False,
'stat__meaninertia': True,
'taxel_sensorid': False,
'taxel_vertadr': False,
'ten_wrapadr_site': False,
@@ -1428,6 +1376,9 @@ _BATCH_DIM = {
'tendon_solref_fri': True,
'tendon_solref_lim': True,
'tendon_stiffness': True,
'tree_bodynum': False,
'tree_dofadr': False,
'tree_dofnum': False,
'wrap_geom_adr': False,
'wrap_jnt_adr': False,
'wrap_objid': False,
@@ -1447,10 +1398,8 @@ _BATCH_DIM = {
'enableflags': False,
'graph_conditional': False,
'gravity': True,
'has_fluid': False,
'impratio_invsqrt': True,
'integrator': False,
'is_sparse': False,
'iterations': False,
'ls_iterations': False,
'ls_parallel': False,
@@ -1466,5 +1415,5 @@ _BATCH_DIM = {
'viscosity': True,
'wind': True,
},
'Statistic': {'meaninertia': False},
'Statistic': {'meaninertia': True},
}