Repository navigation
feat(physics): add continuous collision detection for fast circles - #717
Merged
Merged
Conversation
Adds createContinuousCollisionEcsSystem, registered right after createEulerIntegrationEcsSystem. It sweeps each fast dynamic circle from where this tick's collision detection saw it (position.world) to where integration moved it (position.local) against static colliders, and moves position.local back to the first contact when the step would sink the circle more than a tenth of its radius into a surface. Adds the public sweepCircleCircle/sweepCirclePolygon/sweepCircleTerrain primitives and SweepHit, registers the system in every demo pipeline and the physics guides, and records the implementation's deviations from the design doc. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GPKzFGs2aYn7pN8vMzcQ6o
Resolves the CHANGELOG conflict (keeps both the CCD Added entry and dev's Changed entries) and updates the terrain sweep and CCD system tests for TerrainCollider's slab now extending toward -y. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GPKzFGs2aYn7pN8vMzcQ6o
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
stormmuller
enabled auto-merge (squash)
October 6, 2026 20:52
Resolves the CHANGELOG and physics index conflicts with collision filtering, sensors and per-entity contacts, keeping both sides. Adapts createContinuousCollisionEcsSystem to colliders owning their aabb: it now queries [position, collider], never sweeps sensors or sweeps against them, and skips target pairs whose category/mask exclude each other. Registers the system in the new sensors demo and updates the CCD guide. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GPKzFGs2aYn7pN8vMzcQ6o
3 of 5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements Phases 1 and 2 of
design/continuous-collision-detection.md.sweepCircleCircle,sweepCirclePolygonandsweepCircleTerrain, plus aSweepHittype (point,normal,t). They live insrc/physics/ccd/and are exported from@forge-game-engine/forge/physics. Each one sweeps a ray from the circle's center against the target's Minkowski sum with the circle. Only hits that enter the shape on its real boundary count: front faces within their span, and corner rounds within their normal cone. A sweep that starts already touching a feature ignores that feature, since narrow phase already handles that contact. Terrain is swept against its one-sided surface chain, the same one narrow phase uses, so a fast wheel doesn't catch on the tops of neighbouring columns.createContinuousCollisionEcsSystem(): registered right aftercreateEulerIntegrationEcsSystem. It sweeps each fast dynamic circle fromposition.world(where this tick's collision detection saw it) toposition.local(where integration moved it), against static colliders whosecollider.aabboverlaps the path. It follows the same rules as discrete collision: sensors are never swept and never stop anything, and pairs whosecategory/maskexclude each other are skipped. It only acts when the step would leave the circle more than 0.1·r inside a surface. In that case it movesposition.localback to the first contact, leaving the circle 0.01·r inside so the next tick's narrow phase reports the contact. Velocity is left alone, and the next tick's collision resolution handles the contact./demo. The physics guides list it, and there's a new Continuous Collision Detection guide.Deviations from the design doc
The
solution-revieweragent approved these. They are also recorded in the doc's new §11.RigidBodyEcsComponentthatcreateEulerIntegrationEcsSystemthen reads and clears, which gives that field two writers. Instead, the system runs after integration and rewindsposition.local. Box2D'sb2SolveContinuousand Avian's swept CCD are structured the same way. Euler integration is unchanged.continuousDetectionflag and no configurable threshold. Sensors and category/mask filtering already decide which pairs collide, and CCD honours them; any other opt-out could only let a body tunnel.Car demo finding
I probed the live Car demo under sustained throttle. CCD removes every landing that goes from no contact to deep in one tick: without it, 11-19 units at 750-1500 units/s downward, which matches the doc's diagnosis.
The demo's deepest penetrations (50-80 units) are a different problem: wheels that are already touching the ground sink further over several ticks. The joint systems run after
createCollisionResolutionEcsSystemand get the last word on velocity, so they drive the wheel back into the ground. As an experiment, running contact resolution after the joints dropped the worst depth to about 12 units. The fix is a coupled contact and joint solver, as in Box2D, which needs its own design. It is not part of this PR.Related issue(s)
Implements
design/continuous-collision-detection.md.Verification checklist
npm run check-typespasses with 0 errorsnpm testpasses (1940 tests). New tests cover each sweep primitive and the system (including sensors and category/mask filtering), plus a full-pipeline regression test: the deepest wheel landing on the Car demo's terrain is 28 units without CCD and 9.5 with it. Another test checks that CCD doesn't slow a fast wheel rolling over bumpy terrain.npm run lintpasses with 0 errorsnpm run cspellpasses with 0 errorsnpm run check-exportspassesindex.ts/documentation-site/docs/docsis updatednpm run build, docsnpm run typecheckandnpm run build, then the physics demos (car, sensors, physics, brick-breaker, rolling-ball, newtons-cradle and the rest) loaded in Chromium with no page errors.Changelog
## [Unreleased]inCHANGELOG.md🤖 Generated with Claude Code
https://claude.ai/code/session_01GPKzFGs2aYn7pN8vMzcQ6o