Skip to content

Introduce the NarrowPhase class#1014

Merged
nvtw merged 36 commits into
newton-physics:mainfrom
nvtw:dev/tw/narrow_phase_api
Nov 5, 2025
Merged

Introduce the NarrowPhase class#1014
nvtw merged 36 commits into
newton-physics:mainfrom
nvtw:dev/tw/narrow_phase_api

Conversation

@nvtw

@nvtw nvtw commented Oct 30, 2025

Copy link
Copy Markdown
Member

Description

Implemented a narrow phase with the API we agreed upon in the Zurich meeting

Key Changes:

  • Created collision_core.py with shared collision detection functions (GJK/MPR contacts, AABB computation, mesh handling, plane conversion)
  • Created narrow_phase.py with standalone NarrowPhase class for collision detection - the launch method of that class has exactly the API we agreed on in the Zurich meeting
  • Modified CollisionPipelineUnified to use the new NarrowPhase class at the cost of 2 additional data conversion kernel launches
  • Added comprehensive test suite (test_narrow_phase.py) validating contact conventions across all primitive pairs

Newton Migration Guide

Please ensure the migration guide for warp.sim users is up-to-date with the changes made in this PR.

  • The migration guide in docs/migration.rst is up-to date

Before your PR is "Ready for review"

  • Necessary tests have been added and new examples are tested (see newton/tests/test_examples.py)
  • Documentation is up-to-date
  • Code passes formatting and linting checks with pre-commit run -a

Summary by CodeRabbit

  • New Features
    • Enhanced collision detection with multi-contact support, plane-to-finite-proxy casting, mesh–triangle overlap processing, improved axial (cylinder/cone) contacts, and tighter AABB/sphere culling.
  • Refactor
    • Unified NarrowPhase-driven collision flow with new geometry preparation, narrow-phase execution, and conversion stages; updated collision pipeline initialization.
  • Tests
    • Comprehensive narrow-phase test suite covering many shape combinations, resting/self-collisions, and contact/tangent/normal validations.

@nvtw nvtw self-assigned this Oct 30, 2025
@nvtw nvtw marked this pull request as draft October 30, 2025 16:31
@coderabbitai

coderabbitai Bot commented Oct 30, 2025

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds a new collision_core module (GJK/MPR drivers and utilities), a Warp-based NarrowPhase with kernels and orchestrator, integrates NarrowPhase into CollisionPipelineUnified via prepare/convert kernels and updated constructor/buffers, and adds a comprehensive narrow-phase unit test suite.

Changes

Cohort / File(s) Summary
Collision core module
newton/_src/geometry/collision_core.py
New collision core: global flags (ENABLE_MULTI_CONTACT, ENABLE_TILE_BVH_QUERY), pre-instantiated convex solvers, support-map utilities, GJK/MPR contact drivers (compute_gjk_mpr_contacts, find_contacts), tight-AABB / bounding-sphere helpers, infinite-plane ↔ cube proxy conversion, pre-contact culling, mesh-triangle extraction, axial-shape postprocessing, and many Warp-typed public utilities.
Narrow-phase pipeline
newton/_src/geometry/narrow_phase.py
New Warp kernels and orchestrator: write_contact_simple, extract_shape_data, create_narrow_phase_kernel_gjk_mpr / narrow_phase_kernel_gjk_mpr, mesh-triangle overlap finder, triangle/mesh-plane contact processors, plus NarrowPhase class that sequences extraction, per-pair GJK/MPR, mesh/triangle/plane postprocessing and emits unified contact buffers.
Collision pipeline integration
newton/_src/sim/collide_unified.py
Integrates NarrowPhase into unified collision flow: adds prepare_geom_data_kernel, convert_narrow_phase_to_contacts_kernel, updates CollisionPipelineUnified.__init__ signature and internal buffers, and replaces legacy narrow-phase/mesh/plane kernel paths with the NarrowPhase-driven sequence.
Tests: narrow phase
newton/tests/test_narrow_phase.py
New unit test suite validating NarrowPhase across many primitives and scenarios (sphere, box, capsule, cylinder, plane, mesh, multi-pair, self-collision). Tests verify normal/tangent consistency, surface reconstruction, contact counts, and separating vs penetrating behavior.

Sequence Diagram(s)

sequenceDiagram
    participant Pipeline as CollisionPipelineUnified
    participant Prep as prepare_geom_data_kernel
    participant NP as NarrowPhase.launch()
    participant Scale as extract_shape_data
    participant GJK as narrow_phase_kernel_gjk_mpr
    participant MeshOverlap as narrow_phase_find_mesh_triangle_overlaps_kernel
    participant TriProc as narrow_phase_process_mesh_triangle_contacts_kernel
    participant PlaneProc as narrow_phase_process_mesh_plane_contacts_kernel
    participant Conv as convert_narrow_phase_to_contacts_kernel

    Pipeline->>Prep: build geom_data & geom_transform
    Prep-->>Pipeline: geom buffers
    Pipeline->>NP: candidate_pairs + geom types/data/transforms
    NP->>Scale: extract per-shape scale/thickness
    Scale-->>NP: scale_array
    NP->>GJK: run per-pair GJK/MPR kernel
    GJK->>GJK: compute tight AABBs, pre_contact_check
    GJK->>GJK: call collision_core.find_contacts / compute_gjk_mpr_contacts
    GJK-->>NP: contact buffers + mesh/plane work items
    NP->>MeshOverlap: find overlapping mesh triangles (if any)
    MeshOverlap-->>NP: triangle_pairs
    NP->>TriProc: process triangle contacts
    TriProc-->>NP: contact buffers
    NP->>PlaneProc: process mesh-plane contacts
    PlaneProc-->>NP: contact buffers
    NP-->>Pipeline: narrow-phase contact buffers
    Pipeline->>Conv: convert to canonical Contacts
    Conv-->>Pipeline: Contacts arrays
Loading
sequenceDiagram
    participant Caller as find_contacts()
    participant Pre as pre_contact_check()
    participant Bsphere as compute_bounding_sphere_from_aabb()
    participant PlaneCheck as check_infinite_plane_bsphere_overlap()
    participant Convert as convert_infinite_plane_to_cube()
    participant Solver as compute_gjk_mpr_contacts()

    Caller->>Pre: shapes + transforms + margins
    Pre->>Bsphere: compute bounding spheres
    Bsphere-->>Pre: centers & radii
    Pre->>PlaneCheck: plane vs bsphere overlap test
    PlaneCheck-->>Pre: overlap result
    alt overlap OK
        Caller->>Convert: (if infinite plane) create finite cube proxy
        Convert-->>Caller: proxy shape_data, offset
        Caller->>Solver: run GJK/MPR driver (convex/discrete)
        Solver-->>Caller: contact count, normal, contact points, penetrations
    else rejected
        Pre-->>Caller: early-out (no contacts)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~70 minutes

Areas needing extra attention:

  • collision_core.py: solver instantiation, GJK/MPR driver logic, plane↔cube proxy conversion, axial-shape postprocessing and degenerate-case handling.
  • narrow_phase.py: kernel indexing, buffer lifecycle, cumulative-sum partitioning for mesh/plane work, and external AABB handling.
  • collide_unified.py: updated constructor/API, prepare/convert kernels, and correct wiring of NarrowPhase buffers into the collision flow.

Possibly related PRs

Suggested reviewers

  • mmacklin
  • eric-heiden
  • nvlukasz

Pre-merge checks and finishing touches

✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "Add the NarrowPhase class to the public API" directly refers to the NarrowPhase class introduced in narrow_phase.py, which is prominently featured as a key deliverable in the PR objectives. While the changeset includes multiple related additions (collision_core.py with utility functions, modifications to CollisionPipelineUnified, and comprehensive tests), the PR's primary purpose is to expose a standalone narrow-phase collision detection class as a public API. The title accurately captures this main objective and is specific enough that a teammate would understand the PR introduces new public-facing collision detection capabilities centered on the NarrowPhase class.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov

codecov Bot commented Oct 30, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.83721% with 1 line in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
newton/_src/sim/collide_unified.py 95.45% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
newton/_src/geometry/collision_core.py (1)

445-504: Drop the unused plane argument

convert_infinite_plane_to_cube() no longer touches the incoming shape_data, so Ruff quite rightly warns about ARG001. Easiest fix is to delete that parameter (and matching call sites still compile because we’re just passing the plane data through).

-def convert_infinite_plane_to_cube(
-    shape_data: GenericShapeData,
+def convert_infinite_plane_to_cube(
     plane_rotation: wp.quat,
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2df8139 and d486a93.

📒 Files selected for processing (7)
  • newton/__init__.py (2 hunks)
  • newton/_src/geometry/collision_core.py (1 hunks)
  • newton/_src/geometry/narrow_phase.py (1 hunks)
  • newton/_src/sim/__init__.py (2 hunks)
  • newton/_src/sim/collide_api.py (1 hunks)
  • newton/_src/sim/collide_unified.py (5 hunks)
  • newton/tests/test_narrow_phase.py (1 hunks)
🧰 Additional context used
🧠 Learnings (13)
📓 Common learnings
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/geometry.py:16-22
Timestamp: 2025-08-12T17:51:37.474Z
Learning: In the Newton public API refactor, common geometry symbols like ParticleFlags and ShapeFlags are now exposed at the top level in newton/__init__.py rather than through newton.geometry. The newton/geometry.py module is intentionally focused only on broad-phase collision detection classes (BroadPhaseAllPairs, BroadPhaseExplicit, BroadPhaseSAP).
📚 Learning: 2025-08-12T17:51:37.474Z
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/geometry.py:16-22
Timestamp: 2025-08-12T17:51:37.474Z
Learning: In the Newton public API refactor, common geometry symbols like ParticleFlags and ShapeFlags are now exposed at the top level in newton/__init__.py rather than through newton.geometry. The newton/geometry.py module is intentionally focused only on broad-phase collision detection classes (BroadPhaseAllPairs, BroadPhaseExplicit, BroadPhaseSAP).

Applied to files:

  • newton/_src/sim/__init__.py
  • newton/__init__.py
  • newton/tests/test_narrow_phase.py
  • newton/_src/sim/collide_api.py
  • newton/_src/sim/collide_unified.py
  • newton/_src/geometry/narrow_phase.py
  • newton/_src/geometry/collision_core.py
📚 Learning: 2025-08-14T17:38:36.106Z
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/__init__.py:25-29
Timestamp: 2025-08-14T17:38:36.106Z
Learning: The Newton project prefers incremental __all__ building using __all__ += [...] pattern to group exports with their related imports, rather than a single consolidated __all__ at the end of the file.

Applied to files:

  • newton/_src/sim/__init__.py
  • newton/__init__.py
📚 Learning: 2025-08-12T17:58:00.815Z
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/_src/solvers/featherstone/kernels.py:18-20
Timestamp: 2025-08-12T17:58:00.815Z
Learning: In newton/_src/core/__init__.py, the function transform_twist is properly re-exported from the spatial module via "from .spatial import (..., transform_twist, ...)" and included in __all__, making it available for import as "from ...core import transform_twist" from other modules.

Applied to files:

  • newton/_src/sim/__init__.py
  • newton/__init__.py
📚 Learning: 2025-08-12T17:58:16.929Z
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/tests/test_ik.py:25-26
Timestamp: 2025-08-12T17:58:16.929Z
Learning: In the Newton physics engine codebase, it's acceptable for tests to import and use private `_src` internal modules and functions when needed for testing purposes, even though this breaks the public API boundary. This is an intentional project decision for test code.

Applied to files:

  • newton/_src/sim/__init__.py
  • newton/__init__.py
  • newton/tests/test_narrow_phase.py
📚 Learning: 2025-10-10T10:47:41.082Z
Learnt from: nvtw
PR: newton-physics/newton#899
File: newton/tests/test_rigid_contact.py:214-268
Timestamp: 2025-10-10T10:47:41.082Z
Learning: In `newton/tests/test_rigid_contact.py`, using `add_shape_plane(width=0, length=0)` for an infinite plane works correctly with the collision system and does not cause degenerate AABB issues in the test scenario `test_shape_collisions_gjk_mpr_multicontact`.

Applied to files:

  • newton/tests/test_narrow_phase.py
  • newton/_src/sim/collide_unified.py
  • newton/_src/geometry/narrow_phase.py
  • newton/_src/geometry/collision_core.py
📚 Learning: 2025-07-15T21:00:03.709Z
Learnt from: dylanturpin
PR: newton-physics/newton#394
File: newton/tests/test_ik.py:119-119
Timestamp: 2025-07-15T21:00:03.709Z
Learning: In the Newton physics engine codebase, it's acceptable to test private API functions (those prefixed with underscore) in unit and component tests, as the maintainers consider this appropriate for testing internal functionality.

Applied to files:

  • newton/tests/test_narrow_phase.py
📚 Learning: 2025-09-26T05:58:21.013Z
Learnt from: WenchaoHuang
PR: newton-physics/newton#835
File: newton/_src/solvers/style3d/collision/collision.py:49-53
Timestamp: 2025-09-26T05:58:21.013Z
Learning: In newton/_src/solvers/style3d/collision/collision.py, the broad_phase buffers (broad_phase_ee, broad_phase_ef, broad_phase_vf) created with wp.array() don't need zero-initialization because they follow a write-first-read-later pattern: BVH query kernels in frame_begin() populate these buffers completely (write-only operations), and collision kernels only read from them afterward in accumulate_contact_force(), ensuring no undefined reads occur.

Applied to files:

  • newton/tests/test_narrow_phase.py
  • newton/_src/sim/collide_api.py
  • newton/_src/sim/collide_unified.py
  • newton/_src/geometry/narrow_phase.py
  • newton/_src/geometry/collision_core.py
📚 Learning: 2025-10-24T07:56:14.792Z
Learnt from: nvtw
PR: newton-physics/newton#981
File: newton/_src/geometry/collision_convex.py:180-289
Timestamp: 2025-10-24T07:56:14.792Z
Learning: In newton/_src/geometry/collision_convex.py, the single-contact solver (create_solve_convex_single_contact) intentionally returns count=1 even when shapes are separated (signed_distance > contact_threshold). The calling code is responsible for deciding whether to accept the contact based on the signed distance value. This design pushes filtering responsibility to the caller.

Applied to files:

  • newton/_src/sim/collide_unified.py
  • newton/_src/geometry/collision_core.py
📚 Learning: 2025-09-26T05:58:21.013Z
Learnt from: WenchaoHuang
PR: newton-physics/newton#835
File: newton/_src/solvers/style3d/collision/collision.py:49-53
Timestamp: 2025-09-26T05:58:21.013Z
Learning: In newton/_src/solvers/style3d/collision/collision.py, the broad_phase buffers (broad_phase_ee, broad_phase_ef, broad_phase_vf) created with wp.array() don't need zero-initialization because the BVH query kernels that use them always write the count to query_results[0, tid] = 0 first before populating any data, ensuring no undefined reads occur.

Applied to files:

  • newton/_src/sim/collide_unified.py
  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-10-30T07:28:13.077Z
Learnt from: nvtw
PR: newton-physics/newton#984
File: newton/_src/geometry/broad_phase_nxn.py:318-336
Timestamp: 2025-10-30T07:28:13.077Z
Learning: In Newton's BroadPhaseAllPairs class (newton/_src/geometry/broad_phase_nxn.py), device management is the caller's responsibility. The library intentionally does not perform automatic device transfers for precomputed arrays in the launch method, even if there's a device mismatch. Users must ensure that the device specified in __init__ matches the device used in launch.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-08-12T05:17:34.423Z
Learnt from: shi-eric
PR: newton-physics/newton#521
File: newton/examples/example_cloth_hanging.py:36-36
Timestamp: 2025-08-12T05:17:34.423Z
Learning: The Newton migration guide (docs/migration.rst) is specifically for documenting how to migrate existing warp.sim functionality to Newton equivalents. New Newton-only features that didn't exist in warp.sim do not need migration documentation.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-08-12T18:04:06.577Z
Learnt from: nvlukasz
PR: newton-physics/newton#519
File: newton/_src/solvers/featherstone/kernels.py:75-75
Timestamp: 2025-08-12T18:04:06.577Z
Learning: The Newton physics framework requires nightly Warp builds, which means compatibility concerns with older stable Warp versions (like missing functions such as wp.spatial_adjoint) are not relevant for this project.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
🧬 Code graph analysis (7)
newton/_src/sim/__init__.py (1)
newton/_src/sim/collide_api.py (1)
  • CollisionPipelineAPI (160-477)
newton/__init__.py (1)
newton/_src/sim/collide_api.py (1)
  • CollisionPipelineAPI (160-477)
newton/tests/test_narrow_phase.py (2)
newton/_src/geometry/narrow_phase.py (1)
  • NarrowPhase (649-849)
newton/_src/geometry/types.py (1)
  • GeoType (25-67)
newton/_src/sim/collide_api.py (6)
newton/_src/geometry/broad_phase_nxn.py (2)
  • BroadPhaseAllPairs (126-207)
  • BroadPhaseExplicit (210-273)
newton/_src/geometry/broad_phase_sap.py (1)
  • BroadPhaseSAP (298-491)
newton/_src/geometry/narrow_phase.py (1)
  • NarrowPhase (649-849)
newton/_src/sim/contacts.py (1)
  • Contacts (23-93)
newton/_src/sim/model.py (1)
  • Model (152-852)
newton/_src/sim/collide_unified.py (1)
  • compute_shape_aabbs (746-810)
newton/_src/sim/collide_unified.py (1)
newton/_src/geometry/collision_core.py (7)
  • compute_gjk_mpr_contacts (241-363)
  • compute_tight_aabb_from_support (367-427)
  • find_contacts (563-625)
  • find_pair_from_cumulative_index (850-880)
  • get_triangle_shape_from_mesh (884-932)
  • mesh_vs_convex_midphase (750-846)
  • pre_contact_check (629-746)
newton/_src/geometry/narrow_phase.py (3)
newton/_src/geometry/collision_core.py (7)
  • compute_gjk_mpr_contacts (241-363)
  • compute_tight_aabb_from_support (367-427)
  • find_contacts (563-625)
  • find_pair_from_cumulative_index (850-880)
  • get_triangle_shape_from_mesh (884-932)
  • mesh_vs_convex_midphase (750-846)
  • pre_contact_check (629-746)
newton/_src/geometry/support_function.py (3)
  • GenericShapeData (90-109)
  • SupportMapDataProvider (56-64)
  • pack_mesh_ptr (68-75)
newton/_src/geometry/types.py (1)
  • GeoType (25-67)
newton/_src/geometry/collision_core.py (4)
newton/_src/geometry/broad_phase_common.py (1)
  • binary_search (42-50)
newton/_src/geometry/collision_convex.py (4)
  • create_solve_convex_multi_contact (54-177)
  • create_solve_convex_single_contact (180-289)
  • solve_convex_multi_contact (73-175)
  • solve_convex_single_contact (198-287)
newton/_src/geometry/support_function.py (5)
  • GenericShapeData (90-109)
  • GeoTypeEx (51-52)
  • SupportMapDataProvider (56-64)
  • pack_mesh_ptr (68-75)
  • support_map (113-339)
newton/_src/geometry/types.py (1)
  • GeoType (25-67)
🪛 Ruff (0.14.2)
newton/tests/test_narrow_phase.py

279-279: Unpacked variable pairs is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


279-279: Unpacked variable positions is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


279-279: Unpacked variable normals is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


279-279: Unpacked variable tangents is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


302-302: Unpacked variable pairs is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


302-302: Unpacked variable positions is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


302-302: Unpacked variable tangents is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


344-344: Unpacked variable pairs is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


344-344: Unpacked variable positions is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


344-344: Unpacked variable tangents is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


383-383: Unpacked variable pairs is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


383-383: Unpacked variable positions is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


383-383: Unpacked variable normals is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


383-383: Unpacked variable tangents is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


411-411: Unpacked variable pairs is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


411-411: Unpacked variable positions is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


411-411: Unpacked variable penetrations is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


411-411: Unpacked variable tangents is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


439-439: Unpacked variable pairs is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


439-439: Unpacked variable positions is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


439-439: Unpacked variable penetrations is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


439-439: Unpacked variable tangents is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


472-472: Unpacked variable pairs is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


472-472: Unpacked variable tangents is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


519-519: Unpacked variable pairs is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


519-519: Unpacked variable positions is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


519-519: Unpacked variable normals is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


519-519: Unpacked variable penetrations is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


519-519: Unpacked variable tangents is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


543-543: Unpacked variable pairs is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


543-543: Unpacked variable positions is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


543-543: Unpacked variable penetrations is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


543-543: Unpacked variable tangents is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


570-570: Unpacked variable pairs is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


570-570: Unpacked variable positions is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


570-570: Unpacked variable penetrations is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


570-570: Unpacked variable tangents is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


592-592: Unpacked variable pairs is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


592-592: Unpacked variable positions is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


592-592: Unpacked variable normals is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


592-592: Unpacked variable tangents is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


614-614: Unpacked variable pairs is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


614-614: Unpacked variable positions is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


614-614: Unpacked variable normals is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


614-614: Unpacked variable penetrations is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


614-614: Unpacked variable tangents is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


637-637: Unpacked variable pairs is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


637-637: Unpacked variable positions is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


637-637: Unpacked variable normals is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


637-637: Unpacked variable tangents is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


659-659: Unpacked variable pairs is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


659-659: Unpacked variable positions is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


659-659: Unpacked variable normals is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


659-659: Unpacked variable tangents is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


685-685: Unpacked variable pairs is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


685-685: Unpacked variable positions is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


685-685: Unpacked variable tangents is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


716-716: Unpacked variable pairs is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


716-716: Unpacked variable tangents is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


755-755: Unpacked variable pairs is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


755-755: Unpacked variable positions is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


755-755: Unpacked variable penetrations is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


755-755: Unpacked variable tangents is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


785-785: Unpacked variable positions is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


785-785: Unpacked variable normals is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


785-785: Unpacked variable penetrations is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


785-785: Unpacked variable tangents is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


812-812: Unpacked variable pairs is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


812-812: Unpacked variable positions is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


812-812: Unpacked variable normals is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


812-812: Unpacked variable tangents is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


830-830: Unpacked variable pairs is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


830-830: Unpacked variable positions is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


830-830: Unpacked variable normals is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


830-830: Unpacked variable penetrations is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


830-830: Unpacked variable tangents is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


858-858: Unpacked variable contact_pairs is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


858-858: Unpacked variable positions is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


858-858: Unpacked variable penetrations is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


858-858: Unpacked variable tangents is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


882-882: Unpacked variable pairs is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


882-882: Unpacked variable positions is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


882-882: Unpacked variable penetrations is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)

newton/_src/sim/collide_api.py

136-136: Unused function argument: shape_type

(ARG001)


229-229: Avoid specifying long messages outside the exception class

(TRY003)

newton/_src/geometry/narrow_phase.py

61-61: Unused function argument: tid

(ARG001)


188-188: Unused function argument: rigid_contact_margin

(ARG001)


240-240: Unpacked variable scale_a is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


248-248: Unpacked variable scale_b is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


475-475: Unpacked variable scale_b is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


532-532: Unused function argument: geom_types

(ARG001)

newton/_src/geometry/collision_core.py

212-212: Value being cast to int is already an integer

Remove unnecessary int call

(RUF046)


446-446: Unused function argument: shape_data

(ARG001)


643-643: Unused function argument: mesh_id_a

(ARG001)


874-874: Value being cast to int is already an integer

Remove unnecessary int call

(RUF046)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run GPU Benchmarks (Pull Request)

Comment thread newton/_src/geometry/collision_core.py
Comment thread newton/_src/sim/collide_api.py Outdated
@nvtw nvtw changed the title Add the NarrowPhase class to the public API Draft: Add the NarrowPhase class to the public API Oct 31, 2025
@nvtw nvtw force-pushed the dev/tw/narrow_phase_api branch from 02821b1 to 1084f50 Compare October 31, 2025 10:36

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (5)
newton/_src/sim/collide_unified.py (2)

322-348: Remove unused parameter.

The shape_type parameter (line 327) is never used in the kernel body. This adds unnecessary overhead and clutter.

Apply this diff:

 @wp.kernel
 def prepare_geom_data_kernel(
     shape_transform: wp.array(dtype=wp.transform),
     shape_body: wp.array(dtype=int),
-    shape_type: wp.array(dtype=int),
     shape_scale: wp.array(dtype=wp.vec3),
     shape_thickness: wp.array(dtype=float),
     body_q: wp.array(dtype=wp.transform),
     # Outputs
     geom_data: wp.array(dtype=wp.vec4),  # scale xyz, thickness w
     geom_transform: wp.array(dtype=wp.transform),  # world space transform
 ):

And update the launch call at line 546:

         inputs=[
             model.shape_transform,
             model.shape_body,
-            model.shape_type,
             model.shape_scale,
             model.shape_thickness,
             state.body_q,
         ],

358-613: LGTM with minor suggestion.

The refactored CollisionPipelineUnified class correctly integrates the NarrowPhase workflow with proper device management and buffer handling.

Regarding line 419, while the exception message is descriptive, if you prefer to follow the static analysis suggestion strictly, you could extract it to a module-level constant. However, this is a minor style point and the current approach is acceptable.

newton/_src/geometry/narrow_phase.py (3)

50-131: Consider removing unused parameter.

The tid parameter (line 61) is never used in the function body. Unlike the similar write_contact function in collide_unified.py which stores tid in out_tids, this simplified version doesn't have that output array.

If tid is not needed for API compatibility or future use, remove it:

 @wp.func
 def write_contact_simple(
     contact_point_center: wp.vec3,
     contact_normal_a_to_b: wp.vec3,
     contact_distance: float,
     radius_eff_a: float,
     radius_eff_b: float,
     thickness_a: float,
     thickness_b: float,
     shape_a: int,
     shape_b: int,
-    tid: int,
     rigid_contact_margin: float,
     contact_max: int,
     # outputs
     contact_count: wp.array(dtype=int),
     contact_pair: wp.array(dtype=wp.vec2i),
     contact_position: wp.array(dtype=wp.vec3),
     contact_normal: wp.array(dtype=wp.vec3),
     contact_penetration: wp.array(dtype=float),
     contact_tangent: wp.array(dtype=wp.vec3),
 ):

And update all call sites (lines 349, 534, 653).


179-369: Address unused parameter.

The rigid_contact_margin parameter (line 189) is never used in the kernel. Instead, per-geometry cutoff values from geom_cutoff are used (lines 327-330). Consider either:

  1. Removing the parameter if per-geometry cutoff is always preferred
  2. Using it as a fallback when geom_cutoff values are zero

Option 1 - Remove the parameter:

 @wp.kernel(enable_backward=False)
 def narrow_phase_kernel_gjk_mpr(
     candidate_pair: wp.array(dtype=wp.vec2i),
     num_candidate_pair: wp.array(dtype=int),
     geom_types: wp.array(dtype=int),
     geom_data: wp.array(dtype=wp.vec4),
     geom_transform: wp.array(dtype=wp.transform),
     geom_source: wp.array(dtype=wp.uint64),
     geom_cutoff: wp.array(dtype=float),
     geom_collision_radius: wp.array(dtype=float),
-    rigid_contact_margin: float,
     contact_max: int,
     total_num_threads: int,
     # outputs
     ...

And update the launch call in NarrowPhase.launch (line 777).


556-673: Remove unused parameter, but logic is correct.

The geom_types parameter (line 558) is never used in the kernel. The kernel correctly processes mesh-plane collisions by checking each mesh vertex against the infinite plane.

Apply this diff:

 @wp.kernel(enable_backward=False)
 def narrow_phase_process_mesh_plane_contacts_kernel(
-    geom_types: wp.array(dtype=int),
     geom_data: wp.array(dtype=wp.vec4),
     geom_transform: wp.array(dtype=wp.transform),
     geom_source: wp.array(dtype=wp.uint64),
     geom_cutoff: wp.array(dtype=float),  # Per-geometry cutoff distances
     shape_pairs_mesh_plane: wp.array(dtype=wp.vec2i),
     shape_pairs_mesh_plane_cumsum: wp.array(dtype=int),
     shape_pairs_mesh_plane_count: wp.array(dtype=int),
     mesh_plane_vertex_total_count: wp.array(dtype=int),
     contact_max: int,
     total_num_threads: int,
     # outputs
     ...

And update the launch call at line 804.

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d486a93 and 1084f50.

📒 Files selected for processing (3)
  • newton/_src/geometry/narrow_phase.py (1 hunks)
  • newton/_src/sim/collide_unified.py (8 hunks)
  • newton/tests/test_narrow_phase.py (1 hunks)
🧰 Additional context used
🧠 Learnings (11)
📓 Common learnings
Learnt from: nvlukasz
Repo: newton-physics/newton PR: 519
File: newton/geometry.py:16-22
Timestamp: 2025-08-12T17:51:37.474Z
Learning: In the Newton public API refactor, common geometry symbols like ParticleFlags and ShapeFlags are now exposed at the top level in newton/__init__.py rather than through newton.geometry. The newton/geometry.py module is intentionally focused only on broad-phase collision detection classes (BroadPhaseAllPairs, BroadPhaseExplicit, BroadPhaseSAP).
Learnt from: WenchaoHuang
Repo: newton-physics/newton PR: 835
File: newton/_src/solvers/style3d/collision/collision.py:49-53
Timestamp: 2025-09-26T05:58:21.013Z
Learning: In newton/_src/solvers/style3d/collision/collision.py, the broad_phase buffers (broad_phase_ee, broad_phase_ef, broad_phase_vf) created with wp.array() don't need zero-initialization because they follow a write-first-read-later pattern: BVH query kernels in frame_begin() populate these buffers completely (write-only operations), and collision kernels only read from them afterward in accumulate_contact_force(), ensuring no undefined reads occur.
📚 Learning: 2025-08-12T17:51:37.474Z
Learnt from: nvlukasz
Repo: newton-physics/newton PR: 519
File: newton/geometry.py:16-22
Timestamp: 2025-08-12T17:51:37.474Z
Learning: In the Newton public API refactor, common geometry symbols like ParticleFlags and ShapeFlags are now exposed at the top level in newton/__init__.py rather than through newton.geometry. The newton/geometry.py module is intentionally focused only on broad-phase collision detection classes (BroadPhaseAllPairs, BroadPhaseExplicit, BroadPhaseSAP).

Applied to files:

  • newton/_src/sim/collide_unified.py
  • newton/_src/geometry/narrow_phase.py
  • newton/tests/test_narrow_phase.py
📚 Learning: 2025-09-26T05:58:21.013Z
Learnt from: WenchaoHuang
Repo: newton-physics/newton PR: 835
File: newton/_src/solvers/style3d/collision/collision.py:49-53
Timestamp: 2025-09-26T05:58:21.013Z
Learning: In newton/_src/solvers/style3d/collision/collision.py, the broad_phase buffers (broad_phase_ee, broad_phase_ef, broad_phase_vf) created with wp.array() don't need zero-initialization because they follow a write-first-read-later pattern: BVH query kernels in frame_begin() populate these buffers completely (write-only operations), and collision kernels only read from them afterward in accumulate_contact_force(), ensuring no undefined reads occur.

Applied to files:

  • newton/_src/sim/collide_unified.py
  • newton/_src/geometry/narrow_phase.py
  • newton/tests/test_narrow_phase.py
📚 Learning: 2025-09-26T05:58:21.013Z
Learnt from: WenchaoHuang
Repo: newton-physics/newton PR: 835
File: newton/_src/solvers/style3d/collision/collision.py:49-53
Timestamp: 2025-09-26T05:58:21.013Z
Learning: In newton/_src/solvers/style3d/collision/collision.py, the broad_phase buffers (broad_phase_ee, broad_phase_ef, broad_phase_vf) created with wp.array() don't need zero-initialization because the BVH query kernels that use them always write the count to query_results[0, tid] = 0 first before populating any data, ensuring no undefined reads occur.

Applied to files:

  • newton/_src/sim/collide_unified.py
  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-10-24T07:56:14.792Z
Learnt from: nvtw
Repo: newton-physics/newton PR: 981
File: newton/_src/geometry/collision_convex.py:180-289
Timestamp: 2025-10-24T07:56:14.792Z
Learning: In newton/_src/geometry/collision_convex.py, the single-contact solver (create_solve_convex_single_contact) intentionally returns count=1 even when shapes are separated (signed_distance > contact_threshold). The calling code is responsible for deciding whether to accept the contact based on the signed distance value. This design pushes filtering responsibility to the caller.

Applied to files:

  • newton/_src/sim/collide_unified.py
  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-10-10T10:47:41.082Z
Learnt from: nvtw
Repo: newton-physics/newton PR: 899
File: newton/tests/test_rigid_contact.py:214-268
Timestamp: 2025-10-10T10:47:41.082Z
Learning: In `newton/tests/test_rigid_contact.py`, using `add_shape_plane(width=0, length=0)` for an infinite plane works correctly with the collision system and does not cause degenerate AABB issues in the test scenario `test_shape_collisions_gjk_mpr_multicontact`.

Applied to files:

  • newton/_src/sim/collide_unified.py
  • newton/_src/geometry/narrow_phase.py
  • newton/tests/test_narrow_phase.py
📚 Learning: 2025-10-30T07:28:13.077Z
Learnt from: nvtw
Repo: newton-physics/newton PR: 984
File: newton/_src/geometry/broad_phase_nxn.py:318-336
Timestamp: 2025-10-30T07:28:13.077Z
Learning: In Newton's BroadPhaseAllPairs class (newton/_src/geometry/broad_phase_nxn.py), device management is the caller's responsibility. The library intentionally does not perform automatic device transfers for precomputed arrays in the launch method, even if there's a device mismatch. Users must ensure that the device specified in __init__ matches the device used in launch.

Applied to files:

  • newton/_src/sim/collide_unified.py
  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-08-12T18:04:06.577Z
Learnt from: nvlukasz
Repo: newton-physics/newton PR: 519
File: newton/_src/solvers/featherstone/kernels.py:75-75
Timestamp: 2025-08-12T18:04:06.577Z
Learning: The Newton physics framework requires nightly Warp builds, which means compatibility concerns with older stable Warp versions (like missing functions such as wp.spatial_adjoint) are not relevant for this project.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-08-12T05:17:34.423Z
Learnt from: shi-eric
Repo: newton-physics/newton PR: 521
File: newton/examples/example_cloth_hanging.py:36-36
Timestamp: 2025-08-12T05:17:34.423Z
Learning: The Newton migration guide (docs/migration.rst) is specifically for documenting how to migrate existing warp.sim functionality to Newton equivalents. New Newton-only features that didn't exist in warp.sim do not need migration documentation.

Applied to files:

  • newton/_src/geometry/narrow_phase.py
📚 Learning: 2025-08-12T17:58:16.929Z
Learnt from: nvlukasz
Repo: newton-physics/newton PR: 519
File: newton/tests/test_ik.py:25-26
Timestamp: 2025-08-12T17:58:16.929Z
Learning: In the Newton physics engine codebase, it's acceptable for tests to import and use private `_src` internal modules and functions when needed for testing purposes, even though this breaks the public API boundary. This is an intentional project decision for test code.

Applied to files:

  • newton/tests/test_narrow_phase.py
📚 Learning: 2025-07-15T21:00:03.709Z
Learnt from: dylanturpin
Repo: newton-physics/newton PR: 394
File: newton/tests/test_ik.py:119-119
Timestamp: 2025-07-15T21:00:03.709Z
Learning: In the Newton physics engine codebase, it's acceptable to test private API functions (those prefixed with underscore) in unit and component tests, as the maintainers consider this appropriate for testing internal functionality.

Applied to files:

  • newton/tests/test_narrow_phase.py
🧬 Code graph analysis (3)
newton/_src/sim/collide_unified.py (4)
newton/_src/geometry/collision_core.py (1)
  • compute_tight_aabb_from_support (367-427)
newton/_src/geometry/narrow_phase.py (2)
  • NarrowPhase (675-878)
  • launch (676-878)
newton/_src/geometry/support_function.py (2)
  • GenericShapeData (90-109)
  • SupportMapDataProvider (56-64)
newton/_src/sim/contacts.py (1)
  • Contacts (23-93)
newton/_src/geometry/narrow_phase.py (3)
newton/_src/geometry/collision_core.py (7)
  • compute_gjk_mpr_contacts (241-363)
  • compute_tight_aabb_from_support (367-427)
  • find_contacts (563-625)
  • find_pair_from_cumulative_index (850-880)
  • get_triangle_shape_from_mesh (884-932)
  • mesh_vs_convex_midphase (750-846)
  • pre_contact_check (629-746)
newton/_src/geometry/support_function.py (3)
  • GenericShapeData (90-109)
  • SupportMapDataProvider (56-64)
  • pack_mesh_ptr (68-75)
newton/_src/geometry/types.py (1)
  • GeoType (25-67)
newton/tests/test_narrow_phase.py (2)
newton/_src/geometry/narrow_phase.py (2)
  • NarrowPhase (675-878)
  • launch (676-878)
newton/_src/geometry/types.py (1)
  • GeoType (25-67)
🪛 Ruff (0.14.2)
newton/_src/sim/collide_unified.py

326-326: Unused function argument: shape_type

(ARG001)


419-419: Avoid specifying long messages outside the exception class

(TRY003)

newton/_src/geometry/narrow_phase.py

61-61: Unused function argument: tid

(ARG001)


189-189: Unused function argument: rigid_contact_margin

(ARG001)


558-558: Unused function argument: geom_types

(ARG001)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Run GPU Benchmarks (Pull Request)
  • GitHub Check: Run GPU Unit Tests on AWS EC2 (Pull Request)
🔇 Additional comments (11)
newton/_src/sim/collide_unified.py (4)

26-36: LGTM!

The imports are clean and necessary for the refactored collision pipeline using NarrowPhase.


53-151: LGTM!

The write_contact function correctly handles contact data conversion with proper coordinate frame transforms and thread-safe atomic operations.


153-218: LGTM!

The AABB computation correctly uses support functions for tight bounds on convex shapes and conservative bounding spheres for infinite planes and meshes.


615-668: LGTM!

The from_model factory method correctly handles default parameter extraction from the Model and properly configures the collision pipeline for different broad phase modes.

newton/_src/geometry/narrow_phase.py (4)

39-48: LGTM!

Simple utility kernel that correctly extracts scale components from packed geometry data.


133-177: LGTM!

The extract_shape_data helper correctly unpacks geometry data from the narrow phase API arrays and handles special cases like convex meshes.


371-449: LGTM!

The mesh triangle overlap detection correctly uses BVH queries with tiled operations when enabled, and properly handles mesh vs convex shape pairs.


451-554: LGTM!

The triangle contact processing correctly extracts triangle geometry from meshes and generates contacts using GJK/MPR collision detection.

newton/tests/test_narrow_phase.py (3)

41-147: LGTM!

The helper functions provide comprehensive validation for contact conventions including normal directions, contact positions, and surface reconstruction. The validation logic is sound and well-documented.


149-287: LGTM!

The test infrastructure is well-designed with clean helper methods for creating geometry arrays and running the narrow phase. The _create_geometry_arrays method provides a flexible, declarative way to specify test geometries.


288-924: Excellent test coverage!

The test suite comprehensively covers:

  • All major primitive collision pairs (sphere, box, capsule, cylinder, plane)
  • Various contact scenarios (separated, touching, penetrating)
  • Edge cases (self-collision, multiple pairs, rotated geometries)
  • Contact convention validation (unit normals, perpendicular tangents, surface reconstruction)

The tests follow the documented conventions and provide thorough validation of the NarrowPhase API.

Comment thread newton/_src/geometry/narrow_phase.py
Comment thread newton/_src/sim/collide_unified.py
eric-heiden pushed a commit to eric-heiden/newton that referenced this pull request Jan 28, 2026
mmacklin pushed a commit to mmacklin/newton that referenced this pull request Apr 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants