Skip to content

Bind SDFCollisionSequenceErrorFunction to Python#1530

Open
cdtwigg wants to merge 4 commits into
mainfrom
export-D108807352
Open

Bind SDFCollisionSequenceErrorFunction to Python#1530
cdtwigg wants to merge 4 commits into
mainfrom
export-D108807352

Conversation

@cdtwigg

@cdtwigg cdtwigg commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Summary:
Expose the new SDFCollisionSequenceErrorFunctionT in the pymomentum.solver2 module, matching the existing single-frame SDFCollisionErrorFunction binding. The two-frame swept SDF collision error function can now be constructed from Python with a list of pymomentum.geometry.SDFCollider objects and added to a SequenceSolverFunction via add_sequence_error_function, so motion post-processing that runs in Python can penalize a vertex tunneling through a collider between consecutive frames.

The binding mirrors VertexSequenceErrorFunction (the existing mesh-based, float-templated sequence error function): a lambda py::init returning a shared_ptr, py::keep_alive on the character (the C++ class holds a reference to it), a snake_case kw-only weight, and weight / character properties. The collider list is auto-converted from a Python list[SDFCollider] to std::vector<SDFColliderT<float>> by the geometry module's type caster -- the same mechanism the single-frame binding relies on. The solver2 type stub was regenerated.

Reviewed By: yutingye

Differential Revision: D108807352

cdtwigg added 4 commits June 17, 2026 10:09
Summary:

Add `SDFCollisionSequenceErrorFunctionT`, a two-frame `SequenceErrorFunctionT` that penalizes a vertex sweeping through an SDF collider between consecutive frames. The single-frame `SDFCollisionErrorFunctionT` only sees penetration at the sampled poses, so a vertex moving fast enough to pass entirely through a thin collider (a finger, say) in one timestep is never penalized — it tunnels. This closes that gap for the sequence solver, which is used to post-process existing motion.

How it works:
- For each participating vertex, the segment connecting its position at frame t and t+1 is swept against each collider in the collider's local SDF frame: each endpoint is mapped through its own frame's world-to-collider transform and the two local positions are interpolated linearly. This captures the relative motion when both vertex and collider are joint-driven, keeps the interpolation exactly differentiable (no slerp-derivative approximation), and makes the sphere-trace that locates surface crossings exactly conservative (distances and steps are in the same local units, so a thin feature can't be stepped over).
- The penalty is `w * phiMax^2` per penetrating sub-interval, where `phiMax` is the greatest penetration depth reached along the sweep. It is a max, not an integral, so it depends only on how deep the pass-through gets, never on how long the segment stays inside (no degenerate "stretch the trajectory to dilute the penalty" direction). The deepest point is located exactly by bisecting `d'(s) = 0`; because it is a true interior extremum, the envelope theorem makes the gradient independent of how the deepest point drifts with the parameters, so it reduces to the single-frame contact gradient evaluated there, split across the two frames by the interpolation weights `(1 - s*)` and `s*`. One residual per interval; gradient and Jacobian both finite-difference clean.
- Only sub-intervals bounded by two real surface crossings (entry and exit) are penalized, targeting the pure pass-through case and leaving at-frame penetration to the per-frame error function.
- Derivatives reuse the shared `detail_sdf_collision` chain-rule helpers from the previous diff, evaluated against both frames' skeleton states. The local SDF gradient is mapped to world space including the world-to-collider scale factor, which the derivatives require whenever the collider's joint carries a non-unit scale.

The penetration normal is treated as frozen per evaluation (the standard Gauss-Newton contact linearization), so no SDF second derivatives are needed — only transform derivatives, which the shared helpers already provide.

Known limitation / follow-up: a vertex passing exactly through a collider's medial axis (dead-center through the axis of a thin tube, or perpendicular through a thin sheet) lands on a degenerate point where the SDF normal is undefined and the depth has a non-smooth maximum, so the gradient there is ~0 and gives no signal. This is a measure-zero case for convex colliders (any off-axis pass resolves correctly by deflection); a retract-along-the-entry-normal fallback for it is a planned follow-up. There is also no broadphase culling yet (every participating vertex is swept against every collider) and no pymomentum binding (the single-frame function has one).

Reviewed By: yutingye

Differential Revision: D108807348
…1527)

Summary:

Add a conservative broadphase to `SDFCollisionSequenceErrorFunctionT` that skips any (vertex, collider) pair whose swept segment cannot reach the collider. The segment is already mapped into the collider's local SDF frame for the sweep; if the segment's local axis-aligned bounding box does not overlap the collider's SDF `bounds()`, the pair cannot penetrate and is skipped before the more expensive sphere-trace and deepest-point search. Because both the cull and the detailed test operate in the same local frame, the cull is exact — it only ever skips pairs whose detailed test would have found no penetration — so the error, gradient, and Jacobian are unchanged.

This replaces running the sphere-trace for every (vertex, collider) pair regardless of proximity, which matters once the participating-vertex set or collider count is large. It adds `bounds()` to the (already duck-typed) collider interface this function relies on.

Reviewed By: yutingye

Differential Revision: D108807344
Summary:

Split out from the HOI relative-transform work (was bundled in D105769542). Adds the pybind11 binding for `JointToJointSequenceErrorFunction` in `solver2_sequence_error_functions.cpp`, with `add_constraint(source_joint, reference_joint, ...)`, regenerated `solver2.pyi` stubs, and a `test_solver2` case.

Reviewed By: yutingye

Differential Revision: D108807345
Summary:
Expose the new `SDFCollisionSequenceErrorFunctionT` in the `pymomentum.solver2` module, matching the existing single-frame `SDFCollisionErrorFunction` binding. The two-frame swept SDF collision error function can now be constructed from Python with a list of `pymomentum.geometry.SDFCollider` objects and added to a `SequenceSolverFunction` via `add_sequence_error_function`, so motion post-processing that runs in Python can penalize a vertex tunneling through a collider between consecutive frames.

The binding mirrors `VertexSequenceErrorFunction` (the existing mesh-based, float-templated sequence error function): a lambda `py::init` returning a shared_ptr, `py::keep_alive` on the character (the C++ class holds a reference to it), a snake_case kw-only `weight`, and `weight` / `character` properties. The collider list is auto-converted from a Python `list[SDFCollider]` to `std::vector<SDFColliderT<float>>` by the geometry module's type caster -- the same mechanism the single-frame binding relies on. The solver2 type stub was regenerated.

Reviewed By: yutingye

Differential Revision: D108807352
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Jun 17, 2026
@meta-codesync

meta-codesync Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

@cdtwigg has exported this pull request. If you are a Meta employee, you can view the originating Diff in D108807352.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot. meta-exported

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant