Skip to content

Commit 2a55add

Browse files
authored
Merge branch 'main' into jumyungc/vbd-cable-isotropic-conditioning
2 parents 3b8b9b9 + 1a1dcef commit 2a55add

7 files changed

Lines changed: 313 additions & 41 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
### Changed
5555

5656
- Decide collider visibility from USD `purpose` and visibility rather than from a bound render material. A collider whose `purpose` resolves to `default` is viewport geometry and is drawn; mark it `guide` to state that it is collision-only. Previously an unrelated visual elsewhere in the scene could make a collider vanish. `force_show_colliders` and `hide_collision_shapes` are unchanged.
57+
- Require `warp-lang>=1.16.0`; upgrade Warp to version 1.16.0 or later.
5758
- Disable the implicit positive Dahl-friction defaults in `SolverVBD.register_custom_attributes()` (deprecated in 1.3.0): `vbd:dahl_eps_max` and `vbd:dahl_tau` now default to zero, and Dahl cable friction is enabled only where both are authored positive. Pass `dahl_defaults_enabled=True` to temporarily restore the old defaults; the compatibility mode will be removed in a future release.
5859
- Keep the authored render mesh when `ModelBuilder.add_usd()` approximates a collider. `physics:approximation` is scoped to collision, so a Mesh that is both render geometry and a collider now imports as an approximated collision shape plus a visual shape carrying the original topology, instead of replacing the render mesh with the approximation. This raises `Model.shape_count` for such prims: iterate on `ShapeFlags.COLLIDE_SHAPES` rather than assuming one shape per collider prim. The visual shape adds no mass and no collision, appends after the originals so existing shape indices and `path_shape_map` entries are unchanged, and is skipped when `load_visual_shapes=False`.
5960
- Compile tiled camera render kernels with CUDA fast math by default for faster rendering; set `SensorTiledCamera.render_config.enable_fast_math = False` for bit-exact, IEEE-precise output.
@@ -104,6 +105,7 @@
104105

105106
### Fixed
106107

108+
- Fix MPR returning scale-dependent, excessively deep contacts for small convex shapes on large mesh triangles while preserving triangle-specific shared-edge manifold witnesses.
107109
- Make deterministic collision pipelines cover hydroelastic contact generation and reduction, including unique reduced-contact sort keys and overflow-safe fixed-point pressure accumulation.
108110
- Fix `SolverMuJoCo` retaining an invalid external-contact cache when its first step is captured in a CUDA graph. (#3767)
109111
- Convert `newton:mimicCoef0` from degrees to radians when the mimic follower joint is angular. Assets authored against the old behavior need the value rescaled to degrees.

asv.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"build_command": ["python -m build --wheel -o {build_cache_dir} {build_dir}"],
1717
"install_command": [
1818
"in-dir={env_dir} python -m pip install {wheel_file}[dev] --extra-index-url=https://pypi.nvidia.com/",
19-
"in-dir={env_dir} python -m pip install warp-lang==1.16.0.dev20260716 mujoco==3.11.0 mujoco-warp==3.11.0 --extra-index-url=https://pypi.nvidia.com/",
19+
"in-dir={env_dir} python -m pip install warp-lang==1.16.0 mujoco==3.11.0 mujoco-warp==3.11.0 --extra-index-url=https://pypi.nvidia.com/",
2020
"python -m pip install torch==2.10.0+cu130 --index-url https://download.pytorch.org/whl/cu130"
2121
]
2222
}

docs/guide/release.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,16 @@ Pre-release planning
7878
- Select release dependency versions and confirm their availability on
7979
public PyPI: warp-lang, mujoco, mujoco-warp, newton-usd-schemas. Land
8080
release-ready versions on ``main`` before the branch cut when possible.
81+
* - ☐
82+
- When the stable Warp version for the upcoming Newton release is
83+
published before the branch cut, stabilize it on ``main``: replace the
84+
dev or RC requirement in ``pyproject.toml`` and the pin in
85+
``asv.conf.json`` with the stable version and regenerate ``uv.lock``.
86+
Keep both the NVIDIA ``warp-lang`` package-index override and the ASV
87+
``--extra-index-url=https://pypi.nvidia.com/`` option on ``main`` so it
88+
is ready for the next dev-nightly update. Cut the upcoming release
89+
branch after this change; do not apply the new Warp minor version to
90+
previously released Newton branches.
8191
* - ☐
8292
- Set timeline: code freeze → RC1 → testing window → GA.
8393
* - ☐

newton/_src/geometry/mpr.py

Lines changed: 77 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -230,33 +230,53 @@ def geometric_center(
230230
center_b_local = 0.5 * (min_b + max_b)
231231

232232
center_b_world = position_b + wp.quat_rotate(orientation_b, center_b_local)
233+
center_b_to_a = center_a - center_b_world
233234

234235
if geom_a.shape_type == int(GeoTypeEx.TRIANGLE) or geom_a.shape_type == int(GeoTypeEx.TRIANGLE_PRISM):
235-
# Project shape B's center onto the triangle for a starting
236-
# point near the contact region — this dramatically improves
237-
# MPR convergence for large triangles.
238-
#
239-
# Blend 1% toward the centroid so the point is strictly in the
240-
# face interior. This does NOT prevent an MPR degeneracy (MPR
241-
# works fine from an edge point); it improves *manifold quality*.
242-
# When shape B projects onto a shared mesh edge, both adjacent
243-
# triangles get the same v0, producing MPR witness points biased
244-
# toward the edge. The manifold builder (multicontact.py) uses
245-
# these witness points as its center for perturbed support
246-
# mapping, so edge-biased centers cause overlapping contact
247-
# polygons across the two triangles instead of distinct ones —
248-
# resulting in asymmetric force distribution and spurious torque.
249-
# The 1% nudge gives each triangle a unique v0 pulled toward its
250-
# own interior, yielding well-separated manifold centers.
236+
# Start near the local contact region. A small centroid bias keeps
237+
# adjacent triangles' manifold witnesses distinct at a shared
238+
# edge, but bound it by the normal center-to-plane distance so a
239+
# large face cannot turn the initial MPR ray nearly tangential.
251240
tri_a = wp.vec3(0.0, 0.0, 0.0)
252241
tri_b = geom_a.scale
253242
tri_c = geom_a.auxiliary
243+
face_normal = wp.cross(tri_b - tri_a, tri_c - tri_a)
244+
face_normal_length_sq = wp.length_sq(face_normal)
254245
proj = closest_point_on_triangle(center_b_world, tri_a, tri_b, tri_c)
255-
centroid = (tri_a + tri_b + tri_c) / 3.0
256-
center_a = proj + 0.01 * (centroid - proj)
246+
247+
if face_normal_length_sq >= 1.0e-20:
248+
face_normal_length = wp.sqrt(face_normal_length_sq)
249+
face_normal_unit = face_normal / face_normal_length
250+
signed_plane_distance = wp.dot(center_b_world - tri_a, face_normal_unit)
251+
plane_proj = center_b_world - signed_plane_distance * face_normal_unit
252+
253+
# Reconstruct face-region offsets directly. Subtracting two
254+
# large, nearby positions can otherwise leave a spurious
255+
# tangential component in the initial MPR ray.
256+
inside_face = (
257+
wp.dot(wp.cross(tri_b - tri_a, plane_proj - tri_a), face_normal) >= 0.0
258+
and wp.dot(wp.cross(tri_c - tri_b, plane_proj - tri_b), face_normal) >= 0.0
259+
and wp.dot(wp.cross(tri_a - tri_c, plane_proj - tri_c), face_normal) >= 0.0
260+
)
261+
if inside_face:
262+
proj = plane_proj
263+
center_b_to_a = -signed_plane_distance * face_normal_unit
264+
else:
265+
center_b_to_a = proj - center_b_world
266+
267+
centroid = (tri_a + tri_b + tri_c) / 3.0
268+
to_centroid = centroid - proj
269+
to_centroid -= wp.dot(to_centroid, face_normal_unit) * face_normal_unit
270+
distance_to_centroid = wp.length(to_centroid)
271+
if distance_to_centroid > 1.0e-12:
272+
nudge_distance = 0.01 * wp.min(distance_to_centroid, wp.abs(signed_plane_distance))
273+
center_b_to_a += to_centroid * (nudge_distance / distance_to_centroid)
274+
275+
else:
276+
center_b_to_a = proj - center_b_world
257277

258278
center.B = center_b_world
259-
center.BtoA = center_a - center_b_world
279+
center.BtoA = center_b_to_a
260280

261281
return center
262282

@@ -335,20 +355,44 @@ def solve_mpr_core(
335355

336356
normal = v0.BtoA
337357
if wp.length_sq(normal) < NUMERIC_EPSILON:
338-
# Centers coincide — probe three orthogonal directions and
339-
# pick the one with the largest Minkowski support, giving
340-
# MPR the most room to find a valid portal.
341-
best_dot = float(-1.0e30)
342-
best_dir = wp.vec3(1.0, 0.0, 0.0)
343-
for axis_idx in range(3):
344-
probe = wp.vec3(0.0, 0.0, 0.0)
345-
probe[axis_idx] = 1.0
346-
sv = minkowski_support(geom_a, geom_b, probe, orientation_b, position_b, extend, data_provider)
347-
d = wp.dot(sv.BtoA, probe)
348-
if d > best_dot:
349-
best_dot = d
350-
best_dir = probe
351-
v0.BtoA = best_dir * 1e-05
358+
used_triangle_fallback = bool(False)
359+
if geom_a.shape_type == int(GeoTypeEx.TRIANGLE) or geom_a.shape_type == int(GeoTypeEx.TRIANGLE_PRISM):
360+
tri_a = wp.vec3(0.0, 0.0, 0.0)
361+
tri_b = geom_a.scale
362+
tri_c = geom_a.auxiliary
363+
face_normal = wp.cross(tri_b - tri_a, tri_c - tri_a)
364+
face_normal_length_sq = wp.length_sq(face_normal)
365+
if face_normal_length_sq >= 1.0e-20:
366+
face_normal = face_normal / wp.sqrt(face_normal_length_sq)
367+
proj = closest_point_on_triangle(v0.B, tri_a, tri_b, tri_c)
368+
centroid = (tri_a + tri_b + tri_c) / 3.0
369+
to_centroid = centroid - proj
370+
to_centroid -= wp.dot(to_centroid, face_normal) * face_normal
371+
to_centroid_length_sq = wp.length_sq(to_centroid)
372+
373+
# Use the face normal for coincident triangle/convex
374+
# centers, retaining a small triangle-specific bias.
375+
fallback_dir = -face_normal
376+
if wp.dot(v0.B - proj, face_normal) < 0.0:
377+
fallback_dir = face_normal
378+
if to_centroid_length_sq > 1.0e-20:
379+
fallback_dir += 0.01 * to_centroid / wp.sqrt(to_centroid_length_sq)
380+
v0.BtoA = wp.normalize(fallback_dir) * 1.0e-5
381+
used_triangle_fallback = True
382+
383+
if not used_triangle_fallback:
384+
# Probe three axes and use the direction with most support.
385+
best_dot = float(-1.0e30)
386+
best_dir = wp.vec3(1.0, 0.0, 0.0)
387+
for axis_idx in range(3):
388+
probe = wp.vec3(0.0, 0.0, 0.0)
389+
probe[axis_idx] = 1.0
390+
sv = minkowski_support(geom_a, geom_b, probe, orientation_b, position_b, extend, data_provider)
391+
d = wp.dot(sv.BtoA, probe)
392+
if d > best_dot:
393+
best_dot = d
394+
best_dir = probe
395+
v0.BtoA = best_dir * 1e-05
352396

353397
normal = -v0.BtoA
354398

0 commit comments

Comments
 (0)