Skip to content

Add a hitBackFaces raycast option to skip mesh collider back faces - #9506

Merged
willeastcott merged 2 commits into
mainfrom
raycast-hit-back-faces
Sep 24, 2026
Merged

willeastcott merged 2 commits into
mainfrom
raycast-hit-back-faces

Conversation

@willeastcott

Copy link
Copy Markdown
Contributor

Fixes #2516

Problem

raycastAll through a mesh collider reports two hits, one on the way in and one on the way out. raycastFirst has the same problem when the ray starts inside a mesh: it hits the inside of the far wall. There was no way to exclude these back-face hits, and it couldn't be done in user code either. Bullet flips a back-face hit's normal to face the ray, so every hit looks front-facing and a dot-product check can't tell them apart.

Box, sphere, convex hull and other primitive colliders never report back-face hits, so only mesh colliders are affected.

Change

raycastFirst and raycastAll on RigidBodyComponentSystem take a new hitBackFaces option:

const hits = app.systems.rigidbody.raycastAll(start, end, { hitBackFaces: false });
  • false sets Bullet's kF_FilterBackfaces flag on the ray callback. The binding for it has been in ammo.js since Expose m_flags in RayResultCallback kripken/ammo.js#368 (2021), and the build shipped with the examples has it.
  • The default is true, so existing behavior is unchanged.
  • An Ammo build without the ray callback flags logs one warning and ignores the option.
  • The option is documented on the PhysicsWorld backend base class, so other backends are expected to honor it. Jolt (mBackFaceModeTriangles) and PhysX (PxHitFlag::eMESH_BOTH_SIDES) both have a native per-query equivalent, although both default to ignoring back faces.
  • The collision group and mask handling shared by both raycast methods moves into one helper.

Tests

New raycast back faces block in test/framework/physics/ammo/ammo-physics-world.test.mjs, run against real Ammo:

  • A ray through a mesh collider hits both faces by default, and the back-face normal is flipped towards the ray start.
  • With hitBackFaces: false the same ray hits only the entry face.
  • raycastFirst from inside a mesh hits the far wall by default and misses with hitBackFaces: false.
  • The option also applies when raycastFirst goes through the filtered raycastAll path.
  • A box collider never reports a back-face hit.
  • An Ammo build with the flags binding hidden warns once and still hits back faces.

Without the source change, the four tests that set hitBackFaces: false fail. The full suite matches the local baseline, whose 37 failures come from the missing canvas binary and are not related to this change. build:types, test:types, lint and the docs build (warnings as errors) all pass.

🤖 Generated with Claude Code

raycastFirst and raycastAll take a new hitBackFaces option. Setting it
to false sets Bullet's kF_FilterBackfaces flag on the ray callback, so
rays skip the triangles of mesh colliders that face away from them: the
far side of a closed mesh, and the first surface a ray starting inside
one meets. It defaults to true, keeping the current behavior.

Ammo builds without ray callback flags warn once and ignore the option.

Fixes #2516

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Public API report

This PR changes the public API surface (+2 / −2), per the docs' rules (@ignore / @Private / undocumented are excluded).

Show API diff
-RigidBodyComponentSystem.raycastAll(start: Vec3, end: Vec3, options?: { filterCallback: Function; filterCollisionGroup: number; filterCollisionMask: number; filterTags: any[]; sort: boolean }): RaycastResult[]
-RigidBodyComponentSystem.raycastFirst(start: Vec3, end: Vec3, options?: { filterCallback: Function; filterCollisionGroup: number; filterCollisionMask: number; filterTags: any[] }): RaycastResult | null
+RigidBodyComponentSystem.raycastAll(start: Vec3, end: Vec3, options?: { filterCallback: Function; filterCollisionGroup: number; filterCollisionMask: number; filterTags: any[]; hitBackFaces: boolean; sort: boolean }): RaycastResult[]
+RigidBodyComponentSystem.raycastFirst(start: Vec3, end: Vec3, options?: { filterCallback: Function; filterCollisionGroup: number; filterCollisionMask: number; filterTags: any[]; hitBackFaces: boolean }): RaycastResult | null

Informational only — this never fails the build.

@github-actions

github-actions Bot commented Sep 23, 2026

Copy link
Copy Markdown

Build size report

This PR changes the size of the minified bundles.

Bundle Minified Gzip Brotli
playcanvas.min.js 2475.3 KB (−66 B, −0.00%) 637.9 KB (+45 B, +0.01%) 495.6 KB (+250 B, +0.05%)
playcanvas.min.mjs 2472.6 KB (−66 B, −0.00%) 636.4 KB (+57 B, +0.01%) 494.8 KB (−106 B, −0.02%)

Copilot AI 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.

Copilot review overview

🟢 Approval recommended

No unresolved blocking issues were identified.

Review effort: Lite
Findings: None

What changed in this PR

Adds optional hitBackFaces filtering for rigid-body raycasts, implemented for Ammo while preserving existing behavior by default.

Changes:

  • Documents the hitBackFaces raycast option.
  • Applies Ammo back-face filtering with compatibility handling.
  • Adds comprehensive regression tests.
File Description
test/​framework/​physics/​ammo/​ammo-physics-world.test.mjs Tests filtering, defaults, primitives, and compatibility behavior.
src/​framework/​physics/​physics-world.js Documents the backend raycast option.
src/​framework/​physics/​ammo/​ammo-physics-world.js Applies back-face filtering and fallback warning.
src/​framework/​components/​rigid-body/​system.js Exposes and documents the public API.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/framework/physics/ammo/ammo-physics-world.js
@LeXXik

LeXXik commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Looks good 👍

@willeastcott
willeastcott merged commit 405897d into main Sep 24, 2026
11 checks passed
@willeastcott
willeastcott deleted the raycast-hit-back-faces branch September 24, 2026 00:39

This branch was successfully deployed

2 active deployments
Preview – engine 47f443fc Deployed Sep 24, 2026 by vercel[bot]
Preview – engine-api-docs 47f443fc Deployed Sep 24, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: physics Physics related issue enhancement Request for a new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Raycast result exclude back collider face

3 participants