Skip to content

Commit 801f2ea

Browse files
authored
[FEATURE] Filter out by default self-collision pairs active in neutral configuration. (#2251)
* Fix RigidEntity init. * Filter out collision pairs that are active in neutral configuration.
1 parent b914823 commit 801f2ea

File tree

11 files changed

+156
-48
lines changed

11 files changed

+156
-48
lines changed

examples/speed_benchmark/franka.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,16 @@
77

88
########################## create a scene ##########################
99
scene = gs.Scene(
10-
show_viewer=False,
10+
rigid_options=gs.options.RigidOptions(
11+
dt=0.01,
12+
),
1113
viewer_options=gs.options.ViewerOptions(
1214
camera_pos=(3.5, -1.0, 2.5),
1315
camera_lookat=(0.0, 0.0, 0.5),
1416
camera_fov=40,
1517
res=(1920, 1080),
1618
),
17-
rigid_options=gs.options.RigidOptions(
18-
dt=0.01,
19-
),
19+
show_viewer=False,
2020
)
2121

2222
########################## entities ##########################

genesis/engine/entities/rigid_entity/rigid_entity.py

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,8 @@ def _build(self):
596596
self._n_qs = self.n_qs
597597
self._n_dofs = self.n_dofs
598598
self._n_geoms = self.n_geoms
599+
self._geoms = self.geoms
600+
self._vgeoms = self.vgeoms
599601
self._is_built = True
600602

601603
verts_start = 0
@@ -614,9 +616,6 @@ def _build(self):
614616
self._n_free_verts = len(self._free_verts_idx_local)
615617
self._n_fixed_verts = len(self._fixed_verts_idx_local)
616618

617-
self._geoms = self.geoms
618-
self._vgeoms = self.vgeoms
619-
620619
self._init_jac_and_IK()
621620

622621
def _init_jac_and_IK(self):
@@ -2993,6 +2992,13 @@ def get_mass(self):
29932992
# ----------------------------------- properties -------------------------------------
29942993
# ------------------------------------------------------------------------------------
29952994

2995+
@property
2996+
def is_built(self):
2997+
"""
2998+
Whether this rigid entity is built.
2999+
"""
3000+
return self._is_built
3001+
29963002
@property
29973003
def visualize_contact(self):
29983004
"""Whether to visualize contact force."""
@@ -3171,22 +3177,14 @@ def geoms(self) -> list[RigidGeom]:
31713177
"""The list of collision geoms (`RigidGeom`) in the entity."""
31723178
if self.is_built:
31733179
return self._geoms
3174-
else:
3175-
geoms = gs.List()
3176-
for link in self._links:
3177-
geoms += link.geoms
3178-
return geoms
3180+
return gs.List(geom for link in self._links for geom in link.geoms)
31793181

31803182
@property
31813183
def vgeoms(self):
31823184
"""The list of visual geoms (`RigidVisGeom`) in the entity."""
31833185
if self.is_built:
31843186
return self._vgeoms
3185-
else:
3186-
vgeoms = gs.List()
3187-
for link in self._links:
3188-
vgeoms += link.vgeoms
3189-
return vgeoms
3187+
return gs.List(vgeom for link in self._links for vgeom in link.vgeoms)
31903188

31913189
@property
31923190
def links(self) -> list[RigidLink]:

genesis/engine/entities/rigid_entity/rigid_equality.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def set_sol_params(self, sol_params):
4242
"""
4343
Set the solver parameters of this equality constraint.
4444
"""
45-
if self.is_built:
45+
if self._solver.is_built:
4646
self._solver.set_sol_params(sol_params, eqs_idx=self._idx, envs_idx=None)
4747
else:
4848
self._sol_params = sol_params
@@ -52,7 +52,7 @@ def sol_params(self):
5252
"""
5353
Returns the solver parameters of this equality constraint.
5454
"""
55-
if self.is_built:
55+
if self._solver.is_built:
5656
return self._solver.get_sol_params(eqs_idx=self._idx, envs_idx=None)[..., 0, :]
5757
return self._sol_params
5858

genesis/engine/entities/rigid_entity/rigid_geom.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ def set_friction(self, friction):
349349
gs.raise_exception("`friction` must be non-negative.")
350350
self._friction = friction
351351

352-
if self.is_built:
352+
if self._solver.is_built:
353353
self._solver.set_geom_friction(friction, self._idx)
354354

355355
# ------------------------------------------------------------------------------------
@@ -400,7 +400,7 @@ def set_sol_params(self, sol_params):
400400
"""
401401
Set the solver parameters of this geometry.
402402
"""
403-
if self.is_built:
403+
if self._solver.is_built:
404404
self._solver.set_sol_params(sol_params, geoms_idx=self._idx, envs_idx=None)
405405
else:
406406
self._sol_params = sol_params
@@ -410,7 +410,7 @@ def sol_params(self):
410410
"""
411411
Get the solver parameters of this geometry.
412412
"""
413-
if self.is_built:
413+
if self._solver.is_built:
414414
return self._solver.get_sol_params(geoms_idx=self._idx, envs_idx=None)[0]
415415
return self._sol_params
416416

genesis/engine/entities/rigid_entity/rigid_joint.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,17 +139,17 @@ def set_sol_params(self, sol_params):
139139
"""
140140
Set the solver parameters of this joint.
141141
"""
142-
if self.is_built:
142+
if self._solver.is_built:
143143
self._solver.set_sol_params(sol_params, joints_idx=self._idx, envs_idx=None)
144144
else:
145145
self._sol_params = sol_params
146146

147147
@property
148148
def sol_params(self):
149149
"""
150-
Retruns the solver parameters of the joint.
150+
Returns the solver parameters of the joint.
151151
"""
152-
if self.is_built:
152+
if self._solver.is_built:
153153
return self._solver.get_sol_params(joints_idx=self._idx, envs_idx=None)[..., 0, :]
154154
return self._sol_params
155155

genesis/engine/sensors/camera.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
SensorOptions,
1818
)
1919

20-
from genesis.utils.geom import pos_lookat_up_to_T, trans_quat_to_T
20+
from genesis.utils.geom import pos_lookat_up_to_T, trans_quat_to_T, transform_by_quat
2121
from genesis.utils.misc import tensor_to_array
2222
from genesis.vis.batch_renderer import BatchRenderer
2323
from genesis.options.renderers import BatchRenderer as BatchRendererOptions
@@ -490,8 +490,6 @@ def _update_camera_pose(self):
490490
link_quat = self._link.get_quat()
491491

492492
# Apply pos directly as offset from link
493-
from genesis.utils.geom import transform_by_quat
494-
495493
pos_world = transform_by_quat(pos, link_quat) + link_pos
496494
pos = pos_world
497495
elif self._link is not None:
@@ -596,8 +594,6 @@ def build(self):
596594
link_quat = self._link.get_quat()
597595

598596
# Apply pos directly as offset from link
599-
from genesis.utils.geom import transform_by_quat
600-
601597
pos_world = transform_by_quat(torch.tensor(pos, dtype=gs.tc_float, device=gs.device), link_quat) + link_pos
602598
pos = pos_world.tolist()
603599

genesis/engine/solvers/rigid/collider_decomp.py

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import numpy as np
55
import torch
6+
import trimesh
67

78
import gstaichi as ti
89

@@ -186,11 +187,13 @@ def _compute_collision_pair_idx(self):
186187
Compute flat indices of all valid collision pairs.
187188
188189
For each pair of geoms, determine if they can collide based on their properties and the solver configuration.
190+
Pairs that are already colliding at the initial configuration (qpos0) are filtered out with a warning.
189191
"""
190192
solver = self._solver
191193
n_geoms = solver.n_geoms_
192194
n_equalities = solver.n_equalities
193195
enable_self_collision = solver._enable_self_collision
196+
enable_neutral_collision = solver._enable_neutral_collision
194197
enable_adjacent_collision = solver._enable_adjacent_collision
195198
batch_links_info = solver._static_rigid_sim_config.batch_links_info
196199

@@ -211,6 +214,18 @@ def _compute_collision_pair_idx(self):
211214
links_is_fixed = links_is_fixed[:, 0]
212215
entities_is_local_collision_mask = solver.entities_info.is_local_collision_mask.to_numpy()
213216

217+
# Compute vertices all geometries, shrunk by 0.1% to avoid false positive when detecting self-collision
218+
geoms_verts: list[np.ndarray] = []
219+
for geom in solver.geoms:
220+
verts = tensor_to_array(geom.get_verts())
221+
verts = verts.reshape((-1, *verts.shape[-2:]))
222+
centroid = verts.mean(axis=1, keepdims=True)
223+
verts = centroid + (1.0 - 1e-3) * (verts - centroid)
224+
geoms_verts.append(verts)
225+
226+
# Track pairs that are colliding in neutral configuration for warning
227+
self_colliding_pairs: list[tuple[int, int]] = []
228+
214229
n_possible_pairs = 0
215230
collision_pair_idx = np.full((n_geoms, n_geoms), fill_value=-1, dtype=gs.np_int)
216231
for i_ga in range(n_geoms):
@@ -224,17 +239,6 @@ def _compute_collision_pair_idx(self):
224239
if i_la == i_lb:
225240
continue
226241

227-
# self collision
228-
if links_root_idx[i_la] == links_root_idx[i_lb]:
229-
if not enable_self_collision:
230-
continue
231-
232-
# adjacent links
233-
if not enable_adjacent_collision and (
234-
links_parent_idx[i_la] == i_lb or links_parent_idx[i_lb] == i_la
235-
):
236-
continue
237-
238242
# Filter out right away weld constraint that have been declared statically and cannot be removed
239243
is_valid = True
240244
for i_eq in range(n_equalities):
@@ -258,9 +262,49 @@ def _compute_collision_pair_idx(self):
258262
if links_is_fixed[i_la] and links_is_fixed[i_lb]:
259263
continue
260264

265+
# self collision
266+
if links_root_idx[i_la] == links_root_idx[i_lb]:
267+
if not enable_self_collision:
268+
continue
269+
270+
# adjacent links
271+
if not enable_adjacent_collision and (
272+
links_parent_idx[i_la] == i_lb or links_parent_idx[i_lb] == i_la
273+
):
274+
continue
275+
276+
# active in neutral configuration (qpos0)
277+
is_self_colliding = False
278+
for i_b in range(1 if not enable_neutral_collision else 0):
279+
mesh_a = trimesh.Trimesh(
280+
vertices=geoms_verts[i_ga][i_b], faces=solver.geoms[i_ga].init_faces, process=False
281+
)
282+
mesh_b = trimesh.Trimesh(
283+
vertices=geoms_verts[i_gb][i_b], faces=solver.geoms[i_gb].init_faces, process=False
284+
)
285+
bounds_a, bounds_b = mesh_a.bounds, mesh_b.bounds
286+
if not ((bounds_a[1] < bounds_b[0]).any() or (bounds_b[1] < bounds_a[0]).any()):
287+
if (mesh_a.is_watertight and mesh_a.contains(mesh_b.vertices).any()) or (
288+
mesh_b.is_watertight and mesh_b.contains(mesh_a.vertices).any()
289+
):
290+
is_self_colliding = True
291+
self_colliding_pairs.append((i_ga, i_gb))
292+
break
293+
if is_self_colliding:
294+
continue
295+
261296
collision_pair_idx[i_ga, i_gb] = n_possible_pairs
262297
n_possible_pairs = n_possible_pairs + 1
263298

299+
# Emit warning for self-collision pairs
300+
if self_colliding_pairs:
301+
pairs = ", ".join((f"({i_ga}, {i_gb})") for i_ga, i_gb in self_colliding_pairs)
302+
gs.logger.warning(
303+
f"Filtered out geometry pairs causing self-collision for the neutral configuration (qpos0): {pairs}. "
304+
"Consider tuning Morph option 'decompose_robot_error_threshold' or specify dedicated collision meshes. "
305+
"This behavior can be disabled by setting Morph option 'enable_neutral_collision=True'."
306+
)
307+
264308
return n_possible_pairs, collision_pair_idx
265309

266310
def _compute_verts_connectivity(self):

genesis/engine/solvers/rigid/rigid_solver_decomp.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ def __init__(self, scene: "Scene", sim: "Simulator", options: RigidOptions) -> N
9393
self._enable_mujoco_compatibility = options.enable_mujoco_compatibility
9494
self._enable_joint_limit = options.enable_joint_limit
9595
self._enable_self_collision = options.enable_self_collision
96+
self._enable_neutral_collision = options.enable_neutral_collision
9697
self._enable_adjacent_collision = options.enable_adjacent_collision
9798
self._disable_constraint = options.disable_constraint
9899
self._max_collision_pairs = options.max_collision_pairs
@@ -132,6 +133,9 @@ def __init__(self, scene: "Scene", sim: "Simulator", options: RigidOptions) -> N
132133

133134
self._options = options
134135

136+
self.collider = None
137+
self.constraint_solver = None
138+
135139
self.qpos: ti.Template | ti.types.NDArray | None = None
136140

137141
self._is_backward: bool = False
@@ -350,12 +354,13 @@ def build(self):
350354

351355
self._init_envs_offset()
352356
self._init_sdf()
353-
self._init_collider()
354-
self._init_constraint_solver()
355357

356358
self._init_invweight_and_meaninertia(force_update=False)
357359
self._func_update_geoms(self._scene._envs_idx, force_update_fixed_geoms=True)
358360

361+
self._init_collider()
362+
self._init_constraint_solver()
363+
359364
# FIXME: when the migration is finished, we will remove the about two lines
360365
self._func_vel_at_point = func_vel_at_point
361366
self._func_apply_coupling_force = func_apply_coupling_force
@@ -639,7 +644,7 @@ def _init_link_fields(self):
639644
self.qpos.from_numpy(init_qpos)
640645
if is_init_qpos_out_of_bounds:
641646
gs.logger.warning(
642-
"Reference robot position exceeds joint limits."
647+
"Neutral robot position (qpos0) exceeds joint limits."
643648
# "Clipping initial position too make sure it is valid."
644649
)
645650

@@ -1833,8 +1838,10 @@ def set_qpos(self, qpos, qs_idx=None, envs_idx=None, *, skip_forward=False):
18331838
qpos = qpos[None]
18341839
kernel_set_qpos(qpos, qs_idx, envs_idx, self._rigid_global_info, self._static_rigid_sim_config)
18351840

1836-
self.collider.reset(envs_idx)
1837-
self.constraint_solver.reset(envs_idx)
1841+
if self.collider is not None:
1842+
self.collider.reset(envs_idx)
1843+
if self.constraint_solver is not None:
1844+
self.constraint_solver.reset(envs_idx)
18381845

18391846
if not skip_forward:
18401847
if not isinstance(envs_idx, torch.Tensor):
@@ -2220,7 +2227,7 @@ def get_sol_params(self, geoms_idx=None, envs_idx=None, *, joints_idx=None, eqs_
22202227
tensor = ti_to_torch(self.joints_info.sol_params, envs_idx, joints_idx, transpose=True, copy=True)
22212228
if self.n_envs == 0 and self._options.batch_joints_info:
22222229
tensor = tensor[0]
2223-
else:
2230+
else: # geoms_idx is not None
22242231
# Never batched
22252232
assert envs_idx is None
22262233
tensor = ti_to_torch(self.geoms_info.sol_params, geoms_idx, transpose=True, copy=True)

genesis/options/solvers.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,9 @@ class RigidOptions(Options):
294294
Whether to enable joint limit. Defaults to True.
295295
enable_self_collision : bool, optional
296296
Whether to enable self collision within each entity. Defaults to True.
297+
enable_neutral_collision : bool, optional
298+
Whether to enable self collision occurring in neutral configuration (qpos0) within each entity. Defaults to
299+
False.
297300
enable_adjacent_collision : bool, optional
298301
Whether to enable collision between successive parent-child body pairs within each entity. Defaults to False.
299302
disable_constraint: bool, optional
@@ -362,6 +365,7 @@ class RigidOptions(Options):
362365
enable_collision: bool = True
363366
enable_joint_limit: bool = True
364367
enable_self_collision: bool = True
368+
enable_neutral_collision: bool = False
365369
enable_adjacent_collision: bool = False
366370
disable_constraint: bool = False
367371
max_collision_pairs: int = 150

genesis/utils/geom.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1535,7 +1535,7 @@ def _tc_z_up_to_R(z, eps: float, up=None, out: torch.Tensor | None = None):
15351535

15361536
# Compute x vectors (first column)
15371537
if up is not None:
1538-
x[:] = torch.cross(up, z, dim=-1)
1538+
x[:] = torch.cross(torch.broadcast_to(up, z.shape), z, dim=-1)
15391539
else:
15401540
up_mask = z[..., 2:].abs() < 1.0 - eps
15411541
torch.where(up_mask, z[..., 1], z[..., 2], out=x[..., 0])

0 commit comments

Comments
 (0)