Skip to content

Commit 6383d5e

Browse files
authored
Explicit Contacts instantiation with Model.contacts() and CollisionPipeline.contacts() (newton-physics#1445)
1 parent 51ce35e commit 6383d5e

File tree

63 files changed

+426
-415
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+426
-415
lines changed

docs/concepts/collisions.rst

Lines changed: 61 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,15 @@ Basic usage:
1919
.. code-block:: python
2020
2121
# Default: creates CollisionPipeline with EXPLICIT broad phase (precomputed pairs)
22-
contacts = model.collide(state)
22+
contacts = model.contacts()
23+
model.collide(state, contacts)
2324
24-
# Or create a pipeline explicitly to choose broad phase mode
25+
# Or create a pipeline explicitly for more control
2526
from newton import CollisionPipeline, BroadPhaseMode
2627
27-
pipeline = CollisionPipeline.from_model(
28-
model,
29-
broad_phase_mode=BroadPhaseMode.SAP,
30-
)
31-
contacts = model.collide(state, collision_pipeline=pipeline)
28+
pipeline = CollisionPipeline(model, broad_phase_mode=BroadPhaseMode.SAP)
29+
contacts = pipeline.contacts()
30+
pipeline.collide(state, contacts)
3231
3332
.. _Supported Shape Types:
3433

@@ -347,15 +346,16 @@ Broad Phase and Shape Compatibility
347346
from newton import CollisionPipeline, BroadPhaseMode
348347
349348
# Default: EXPLICIT (precomputed pairs)
350-
pipeline = CollisionPipeline.from_model(model)
349+
pipeline = CollisionPipeline(model)
351350
352351
# NxN for small scenes
353-
pipeline = CollisionPipeline.from_model(model, broad_phase_mode=BroadPhaseMode.NXN)
352+
pipeline = CollisionPipeline(model, broad_phase_mode=BroadPhaseMode.NXN)
354353
355354
# SAP for larger scenes
356-
pipeline = CollisionPipeline.from_model(model, broad_phase_mode=BroadPhaseMode.SAP)
355+
pipeline = CollisionPipeline(model, broad_phase_mode=BroadPhaseMode.SAP)
357356
358-
contacts = model.collide(state, collision_pipeline=pipeline)
357+
contacts = pipeline.contacts()
358+
pipeline.collide(state, contacts)
359359
360360
.. _Shape Compatibility:
361361

@@ -492,7 +492,7 @@ The primary algorithm for convex shape pairs. Uses support mapping functions to
492492

493493
**Multi-contact Generation**
494494

495-
For shape pairs, multiple contact points are generated for stable stacking and resting contacts. The collision pipeline estimates buffer sizes based on the model; you can override with ``rigid_contact_max`` when creating the pipeline via :meth:`CollisionPipeline.from_model`.
495+
For shape pairs, multiple contact points are generated for stable stacking and resting contacts. The collision pipeline estimates buffer sizes based on the model; you can override this value with ``rigid_contact_max`` when instantiating the pipeline.
496496

497497
.. _Mesh Collisions:
498498

@@ -562,7 +562,7 @@ For hydroelastic and SDF-based contacts, use :class:`~newton.SDFHydroelasticConf
562562
moment_matching=False, # Match friction moments (experimental)
563563
)
564564
565-
pipeline = CollisionPipeline.from_model(model, sdf_hydroelastic_config=config)
565+
pipeline = CollisionPipeline(model, sdf_hydroelastic_config=config)
566566
567567
**Understanding betas:**
568568

@@ -727,7 +727,7 @@ For performance, you can run collision detection less often than simulation subs
727727
for frame in range(num_frames):
728728
for substep in range(sim_substeps):
729729
if substep % collide_every_n_substeps == 0:
730-
contacts = model.collide(state, collision_pipeline=pipeline)
730+
pipeline.collide(state, contacts)
731731
solver.step(state_0, state_1, control, contacts, dt=sim_dt)
732732
state_0, state_1 = state_1, state_0
733733
@@ -740,8 +740,10 @@ Soft contacts are generated automatically when particles are present. They use a
740740
# Add particles
741741
builder.add_particle(pos=wp.vec3(0, 0, 1), vel=wp.vec3(0, 0, 0), mass=1.0)
742742
743-
# Soft contact margin is set at collision time
744-
contacts = model.collide(state, soft_contact_margin=0.01)
743+
# Set soft contact margin
744+
pipeline = CollisionPipeline(model, soft_contact_margin=0.01)
745+
contacts = pipeline.contacts()
746+
pipeline.collide(state, contacts)
745747
746748
# Access soft contact data
747749
n_soft = contacts.soft_contact_count.numpy()[0]
@@ -756,10 +758,6 @@ Contact Data
756758
The :class:`~newton.Contacts` class stores the results from the collision detection step
757759
and is consumed by the solver :meth:`~newton.solvers.SolverBase.step` method for contact handling.
758760

759-
.. note::
760-
Contact forces are not part of the :class:`~newton.Contacts` class - it only stores geometric
761-
contact information. See :class:`~newton.SensorContact` for computing contact forces.
762-
763761
**Rigid contacts:**
764762

765763
.. list-table::
@@ -800,11 +798,23 @@ and is consumed by the solver :meth:`~newton.solvers.SolverBase.step` method for
800798
* - ``soft_contact_normal``
801799
- Contact normal.
802800

801+
**Extended contact attributes** (see :ref:`extended_contact_attributes`):
802+
803+
.. list-table::
804+
:header-rows: 1
805+
:widths: 22 78
806+
807+
* - Attribute
808+
- Description
809+
* - :attr:`~newton.Contacts.force`
810+
- Contact spatial forces (used by :class:`~newton.sensors.SensorContact`)
811+
803812
Example usage:
804813

805814
.. code-block:: python
806815
807-
contacts = model.collide(state)
816+
contacts = model.contacts()
817+
model.collide(state, contacts)
808818
809819
n = contacts.rigid_contact_count.numpy()[0]
810820
points0 = contacts.rigid_contact_point0.numpy()[:n]
@@ -815,23 +825,36 @@ Example usage:
815825
shape0 = contacts.rigid_contact_shape0.numpy()[:n]
816826
shape1 = contacts.rigid_contact_shape1.numpy()[:n]
817827
818-
.. _Collide Method:
828+
.. _Creating Contacts:
819829

820-
Model.collide() Parameters
821-
--------------------------
830+
Creating and Populating Contacts
831+
--------------------------------
822832

823-
The :meth:`Model.collide` method accepts the following parameters:
833+
:meth:`~newton.Model.contacts` creates a :class:`~newton.Contacts` buffer using a default
834+
:class:`~newton.CollisionPipeline` (EXPLICIT broad phase, cached on first call).
835+
:meth:`~newton.Model.collide` populates it:
824836

825-
.. list-table::
826-
:header-rows: 1
827-
:widths: 30 70
837+
.. code-block:: python
828838
829-
* - Parameter
830-
- Description
831-
* - ``state``
832-
- Current simulation state (required).
833-
* - ``collision_pipeline``
834-
- Optional :class:`~newton.CollisionPipeline`. If None, creates/reuses a default pipeline (EXPLICIT broad phase). For options like ``rigid_contact_max``, ``soft_contact_margin``, ``requires_grad``, create the pipeline via :meth:`CollisionPipeline.from_model` and pass it here.
839+
contacts = model.contacts()
840+
model.collide(state, contacts)
841+
842+
The contacts buffer can be reused across steps — ``collide`` clears it each time.
843+
844+
For control over broad phase mode, contact limits, or hydroelastic configuration, create a
845+
:class:`~newton.CollisionPipeline` directly:
846+
847+
.. code-block:: python
848+
849+
from newton import CollisionPipeline, BroadPhaseMode
850+
851+
pipeline = CollisionPipeline(
852+
model,
853+
broad_phase_mode=BroadPhaseMode.SAP,
854+
rigid_contact_max=50000,
855+
)
856+
contacts = pipeline.contacts()
857+
pipeline.collide(state, contacts)
835858
836859
.. _Hydroelastic Contacts:
837860

@@ -988,7 +1011,7 @@ Performance
9881011
- Use positive collision groups to reduce candidate pairs
9891012
- Use world indices for parallel simulations (essential for RL with many environments)
9901013
- Contact reduction is enabled by default for mesh-heavy scenes
991-
- Pass ``rigid_contact_max`` to :meth:`CollisionPipeline.from_model` to limit memory in complex scenes
1014+
- Pass ``rigid_contact_max`` to :class:`~newton.CollisionPipeline` to limit memory in complex scenes
9921015

9931016
See Also
9941017
--------
@@ -1008,12 +1031,14 @@ See Also
10081031
10091032
**API Reference:**
10101033

1034+
- :meth:`~newton.Model.contacts` - Create a contacts buffer (default pipeline)
1035+
- :meth:`~newton.Model.collide` - Run collision detection (default pipeline)
10111036
- :class:`~newton.CollisionPipeline` - Collision pipeline with configurable broad phase
1037+
- :meth:`~newton.CollisionPipeline.contacts` - Allocate a contacts buffer for a custom pipeline
10121038
- :class:`~newton.BroadPhaseMode` - Broad phase algorithm selection
10131039
- :class:`~newton.Contacts` - Contact data container
10141040
- :class:`~newton.GeoType` - Shape geometry types
10151041
- :class:`~newton.ModelBuilder.ShapeConfig` - Shape configuration options
1016-
- :meth:`~newton.Model.collide` - Collision detection method
10171042
- :class:`~newton.geometry.SDFHydroelasticConfig` - Hydroelastic contact configuration
10181043

10191044
**Model attributes:**

docs/migration.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,21 @@ The :meth:`newton.ModelBuilder.add_joint_free()` method now initializes the posi
157157
The universal and compound joints have been removed in favor of the more general D6 joint.
158158

159159

160+
Collisions
161+
----------
162+
163+
+-----------------------------------------------+--------------------------------------------------------------+
164+
| **warp.sim** | **Newton** |
165+
+-----------------------------------------------+--------------------------------------------------------------+
166+
| ``contacts = model.collide(state)`` | ``contacts = model.contacts()`` |
167+
| | |
168+
| | ``model.collide(state, contacts)`` |
169+
+-----------------------------------------------+--------------------------------------------------------------+
170+
171+
:meth:`~newton.Model.contacts` allocates the contacts buffer and :meth:`~newton.Model.collide` populates it in place.
172+
The buffer can be reused across steps. For more control, create a :class:`~newton.CollisionPipeline` directly.
173+
174+
160175
Renderers
161176
---------
162177

docs/tutorials/00_introduction.ipynb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -246,10 +246,10 @@
246246
"# The control object is not used in this example, but we create it for completeness\n",
247247
"control = model.control()\n",
248248
"\n",
249-
"# Perform initial collision detection\n",
250-
"contacts = model.collide(state_0)\n",
249+
"# Allocate Contacts buffer\n",
250+
"contacts = model.contacts()\n",
251251
"\n",
252-
"print(\"State and control objects created\")"
252+
"print(\"State, Contacts and Control objects created\")"
253253
]
254254
},
255255
{
@@ -337,7 +337,7 @@
337337
" # viewer.apply_forces(state_0)\n",
338338
"\n",
339339
" # 3. Detect collisions\n",
340-
" contacts = model.collide(state_0)\n",
340+
" model.collide(state_0, contacts)\n",
341341
"\n",
342342
" # 4. Step the simulation by one physics timestep\n",
343343
" solver.step(state_in=state_0, state_out=state_1, control=control, contacts=contacts, dt=sim_dt)\n",

newton/_src/sim/builder.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,11 @@ class ModelBuilder:
107107
state_0, state_1 = model.state(), model.state()
108108
control = model.control()
109109
solver = SolverXPBD(model)
110+
contacts = model.contacts()
110111
111112
for i in range(10):
112113
state_0.clear_forces()
113-
contacts = model.collide(state_0)
114+
model.collide(state_0, contacts)
114115
solver.step(state_0, state_1, control, contacts, dt=1.0 / 60.0)
115116
state_0, state_1 = state_1, state_0
116117

0 commit comments

Comments
 (0)