Skip to content

fix(physics-physx): skip initial overlap in raycast/sweep + reuse query callbacks#2998

Merged
cptbtptpbcptdtptp merged 12 commits into
galacean:dev/2.0from
luzhuang:fix/physics-raycast-sweep
May 13, 2026
Merged

fix(physics-physx): skip initial overlap in raycast/sweep + reuse query callbacks#2998
cptbtptpbcptdtptp merged 12 commits into
galacean:dev/2.0from
luzhuang:fix/physics-raycast-sweep

Conversation

@luzhuang
Copy link
Copy Markdown
Contributor

@luzhuang luzhuang commented May 12, 2026

摘要

物理 raycast / sweep 修复。与 #2999 一起从原 #2984/#2983 抽离出来,分别承担物理 / 动画+加载器修复,两个 PR 零文件重叠,独立合入。

修复内容

1. raycast / sweep 跳过 initial overlap (fix(physics-physx))

PhysX raycast / sweep 默认会把「ray 起点已在 collider 内部」当作 hit 返回 distance=0,与 Unity / 大部分引擎语义相反(应当跳过 initial overlap)。修复:

  • 给 raycast/sweep 的 filter data 加 POST_FILTER flag,在 query callback 里过滤掉 distance <= 0 的 initial-overlap hit
  • 拆出独立的 _pxRaycastSweepFilterData(仅 raycast/sweep 启用 POST_FILTER);overlap 仍用原 _pxFilterData,避免 wasm 调用不存在的 postFilter callback

2. 复用 PhysX query callbacks (perf(physics-physx))

每次 raycast/sweep/overlap 调用都新建 PxRaycastCallback / PxSweepCallback / PxOverlapCallback 浪费跨 wasm 边界开销。改为持久化复用 + per-type reentrancy stack,hit 数据每次重置。

3. sweep hit 生命周期收紧 (refactor(physics-physx))

  • 提取 QueryHitType enum 替代 raycast/sweep 内的 magic number 0/2(NONE/BLOCK)
  • 修复 outHitResult 抛错路径下 PxSweepHit.delete() 没被调到的泄漏,移到外层 finally

4. PhysX wasm 二进制 + CDN URL 更新

packages/physics-physx/src/PhysXPhysics.ts 默认 CDN URL 指向包含 fix 的新版 PhysX wasm。packages/physics-physx/libs/e2e/.dev/ 下的 .js / .wasm 同步更新。

测试

tests/src/core/physics/PhysicsScene.test.ts 新增 7 个回归测试:

Initial overlap 跳过(4 个)

  • raycast skips initial overlap when ray origin is inside a collider
  • boxCast skips initial overlap and hits far collider beyond
  • sphereCast skips initial overlap and hits far collider beyond
  • capsuleCast skips initial overlap and hits far collider beyond

Reentrancy / 异常恢复(3 个,覆盖 query callback 复用的并发安全)

  • raycast nested inside another raycast's onRaycast keeps stack ordering
  • sweep nested inside raycast's onRaycast uses independent filter stacks
  • raycast callback throwing leaves the filter stack clean for subsequent calls

PhysicsScene.test.ts 全部 55 个测试通过(48 baseline + 7 新增)。

变更范围

  • packages/physics-physx/src/PhysXPhysics.ts (4 行,CDN URL)
  • packages/physics-physx/src/PhysXPhysicsScene.ts (173 行)
  • packages/physics-physx/libs/physx.release.{js,wasm,simd.js,simd.wasm} (PhysX wasm 二进制)
  • e2e/.dev/physx.release.* (同步 e2e wasm)
  • tests/src/core/physics/PhysicsScene.test.ts (310 行新测试)

零文件触及动画 / loader / entity / shader。

Summary by CodeRabbit

  • Bug Fixes

    • Raycast and sweep operations (box, sphere, capsule) now correctly skip initial-overlap hits when a cast originates inside a collider.
  • Improvements

    • Reduced per-query allocations and improved reentrant behavior for scene queries, increasing stability and performance.
  • New Features

    • Updated default PhysX runtime URLs for WebAssembly and SIMD builds.
  • Tests

    • Added tests for initial-overlap skipping, nested query interactions, and error-recovery in query callbacks.

Review Change Stack

luzhuang added 9 commits May 12, 2026 20:04
PhysX 射线/sweep 起点在碰撞体内部时会返回 distance=0 的命中,
与 Unity 行为不一致。通过启用 POST_FILTER 并在 postFilter 中
过滤 distance<=0 的结果,使 PhysX 跳过 initial overlap 并继续
查找下一个最近命中。

- 启用 POST_FILTER query flag
- raycast 和 sweep callback 添加 postFilter 过滤 distance<=0
- 更新 PhysX wasm CDN URL(对应 QueryBinding.h postFilter 改动)
- 更新本地 wasm 产物
Commit 6ab7437 enabled POST_FILTER on the shared _pxFilterData. But
overlap callbacks (overlapBoxAll/SphereAll/CapsuleAll) only define
preFilter — without postFilter, PhysX wasm crashes attempting to invoke
the missing callback for overlap queries.

Split raycast/sweep filter data from overlap filter data:
- _pxFilterData: STATIC | DYNAMIC | PRE_FILTER (overlap queries)
- _pxRaycastFilterData: STATIC | DYNAMIC | PRE_FILTER | POST_FILTER
  (raycast and _sweepSingle, with their postFilter distance<=0 skip)

Also update 4 existing tests that asserted the pre-fix behavior (ray
origin inside collider returns true with distance=0). New behavior:
initial overlap is skipped and raycast returns false unless there is a
collider beyond.
The previous ray origin (3,3,3) on a (6,6,6) box centered at origin is the
box corner — boundary point whose hit/miss depends on PhysX edge tolerance.
Move the origin strictly inside so the test deterministically verifies
the initial-overlap-skip behavior.
…epFilterData

Field is used by both raycast and _sweepSingle; the new name accurately
reflects scope.
Each raycast/sweep/overlap previously created a JS callback object and
called PxQueryFilterCallback.implement(...) / .delete() per query, which
crosses the wasm boundary twice via embind's emval handle pattern. For
high-frequency game queries (FPS aim, AI vision, character controller
sensors) this dominates query cost.

Maintain three persistent callbacks at scene construction time. The
user-provided filter function is swapped per call via a stack so
reentrancy is preserved (e.g., calling raycast inside another
raycast's onRaycast). Each query type owns its own stack so
cross-type nesting (raycast inside sweep, etc.) doesn't conflict.

try/finally guards stack push/pop against user filters that throw,
so stack state stays clean across exceptions.

Tests cover three reentrancy scenarios: same-type nesting, mixed-type
nesting, and exception propagation.
…ere/capsule

Mirror the existing raycast inside-collider regression for sweep paths.
Each cast originates inside a near collider and must skip it (initial
overlap) to hit a far collider beyond, exercising postFilter on
_pxRaycastSweepFilterData for sweepSingle queries.
…yHitType

- Move PxSweepHit.delete() to a finally block so a throwing outHitResult
  callback can no longer leak the wasm-side hit object.
- Extract QueryHitType.NONE / .BLOCK from the magic 0/2 spread across the
  three persistent query callbacks; mirrors the existing QueryFlag enum
  in this file and removes the inline "eBLOCK : eNONE" comments.
The two postFilter arrows triggered prettier-recommended lint failures.
Collapsing the ternary onto a single parenthesised line matches the
formatter and unblocks CI lint.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: f5b17ed9-afce-419f-a1ee-8197a90f1c89

📥 Commits

Reviewing files that changed from the base of the PR and between 3c95b06 and eb3a7e9.

📒 Files selected for processing (1)
  • packages/physics-physx/src/PhysXPhysicsScene.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/physics-physx/src/PhysXPhysicsScene.ts

Walkthrough

PhysX runtime default URLs updated. PhysXPhysicsScene reuses persistent PxQueryFilterCallback instances with a per-call predicate slot for raycast/sweep/overlap to support reentrancy and skip initial overlaps. Tests updated to assert skip-initial-overlap and cover nested-filter ordering and exception recovery.

Changes

PhysX Query Filter Callback Optimization

Layer / File(s) Summary
Runtime URL Configuration
packages/physics-physx/src/PhysXPhysics.ts
Default WebAssembly runtime URLs updated to new physx.release.simd.js and physx.release.js fallback values.
Persistent Filter Callback Infrastructure
packages/physics-physx/src/PhysXPhysicsScene.ts
Scene-level persistent filter callback, _currentOnQuery predicate slot, shared _pxRaycastSweepFilterData, and QueryHitType enum added.
Raycast Query with Filter Stack
packages/physics-physx/src/PhysXPhysicsScene.ts
raycast() now sets _currentOnQuery, calls raycastSingle with shared filter data and persistent callback, and restores predicate in finally.
Sweep and Overlap Queries with Filter Stacks
packages/physics-physx/src/PhysXPhysicsScene.ts
_sweepSingle() and _overlapMultiple() refactored to use the persistent callback/filter data, persistent sweep hit object, set/restore _currentOnQuery, and remove per-call callback/hit allocations.
Scene Destruction and Resource Cleanup
packages/physics-physx/src/PhysXPhysicsScene.ts
destroy() extended to delete persistent sweep hit, filter-data flags, raycast/sweep filter data, and shared query callback.
Test Suite: Initial Overlap Skip and Reentrancy
tests/src/core/physics/PhysicsScene.test.ts
Updated raycast/boxCast/sphereCast/capsuleCast tests to expect skip-initial-overlap when origin is strictly inside a collider; added tests for skipping-overlap then hitting farther colliders, nested raycast ordering, sweep-inside-raycast stack independence, and callback-exception recovery.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A filter stack dance, now tidy and neat,
Predicates stacked up, then popped in defeat.
Persistent callbacks hum, avoiding new birth,
Nested queries behave, no chaos on Earth.
Tests hop in line—everything’s sweet. 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: skipping initial overlap in raycast/sweep operations and reusing query callbacks for performance.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Copy link
Copy Markdown

codecov Bot commented May 12, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 77.80%. Comparing base (25ba6eb) to head (eb3a7e9).
⚠️ Report is 1 commits behind head on dev/2.0.

Additional details and impacted files
@@             Coverage Diff             @@
##           dev/2.0    #2998      +/-   ##
===========================================
- Coverage    78.14%   77.80%   -0.34%     
===========================================
  Files          900      900              
  Lines        99255    99205      -50     
  Branches     10213    10177      -36     
===========================================
- Hits         77563    77191     -372     
- Misses       21521    21841     +320     
- Partials       171      173       +2     
Flag Coverage Δ
unittests 77.80% <100.00%> (-0.34%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

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)
tests/src/core/physics/PhysicsScene.test.ts (1)

573-817: ⚡ Quick win

Make cleanup failure-safe for the new PhysX regression cases.

These tests use root names that the shared afterEach does not clean up, so any assertion failure before root.destroy() will leak colliders into later cases. Either use the shared "root" name again or wrap each body in try/finally.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/src/core/physics/PhysicsScene.test.ts` around lines 573 - 817, The
tests create per-case roots via root = scene.createRootEntity("<...>") but rely
on a shared afterEach that only cleans up the root named "root", so assertion
failures leak colliders; fix by making cleanup failure-safe: either use the
shared name (createRootEntity("root")) for each test or wrap each test body that
creates a root (e.g., the roots in the "boxCast skips initial overlap...",
"sphereCast skips initial overlap...", "capsuleCast...", and the nested/throw
recovery tests) in try/finally and call root.destroy() from finally to guarantee
teardown even on assertion failures.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/physics-physx/src/PhysXPhysicsScene.ts`:
- Around line 485-488: Allocate the PxSweepHit before mutating the sweep stack:
move the creation of the PxSweepHit instance (new
this._physXPhysics._physX.PxSweepHit()) to occur before pushing the onSweep
predicate onto this._onSweepStack (onSweepStack.push(onSweep)), or alternatively
wrap the push and following logic in a try/finally that always pops the
predicate; update the code around onSweepStack, onSweep, and PxSweepHit so that
an exception during allocation cannot leave a stale entry on _onSweepStack.

In `@tests/src/core/physics/PhysicsScene.test.ts`:
- Around line 751-762: Reformat the nativeScene.boxCast call site to satisfy
Prettier: rewrap the arguments and callback to follow the project's Prettier
rules (consistent indentation and line breaks) for the nativeScene.boxCast
invocation that uses sweepCenter, orientation, halfExtents, direction and the
inline callback that updates innerSweepCalls and innerSweepUuids; run Prettier
(or your editor's format command) on tests/src/core/physics/PhysicsScene.test.ts
to produce the properly wrapped/indented version so CI passes.

---

Nitpick comments:
In `@tests/src/core/physics/PhysicsScene.test.ts`:
- Around line 573-817: The tests create per-case roots via root =
scene.createRootEntity("<...>") but rely on a shared afterEach that only cleans
up the root named "root", so assertion failures leak colliders; fix by making
cleanup failure-safe: either use the shared name (createRootEntity("root")) for
each test or wrap each test body that creates a root (e.g., the roots in the
"boxCast skips initial overlap...", "sphereCast skips initial overlap...",
"capsuleCast...", and the nested/throw recovery tests) in try/finally and call
root.destroy() from finally to guarantee teardown even on assertion failures.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: fb46b6b8-0296-4a4e-a8f9-7078a274fd36

📥 Commits

Reviewing files that changed from the base of the PR and between 25ba6eb and 17ce258.

⛔ Files ignored due to path filters (4)
  • e2e/.dev/physx.release.simd.wasm is excluded by !**/*.wasm
  • e2e/.dev/physx.release.wasm is excluded by !**/*.wasm
  • packages/physics-physx/libs/physx.release.simd.wasm is excluded by !**/*.wasm
  • packages/physics-physx/libs/physx.release.wasm is excluded by !**/*.wasm
📒 Files selected for processing (7)
  • e2e/.dev/physx.release.js
  • e2e/.dev/physx.release.simd.js
  • packages/physics-physx/libs/physx.release.js
  • packages/physics-physx/libs/physx.release.simd.js
  • packages/physics-physx/src/PhysXPhysics.ts
  • packages/physics-physx/src/PhysXPhysicsScene.ts
  • tests/src/core/physics/PhysicsScene.test.ts

Comment thread packages/physics-physx/src/PhysXPhysicsScene.ts Outdated
Comment on lines +751 to +762
nativeScene.boxCast(
sweepCenter,
orientation,
halfExtents,
direction,
100,
(sweepUuid: number) => {
innerSweepCalls++;
innerSweepUuids.push(sweepUuid);
return false; // skip everything in inner sweep
}
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Run Prettier on this boxCast call site.

prettier/prettier is already failing on this block, so CI will stay red until the wrapping here is reformatted.

🧰 Tools
🪛 ESLint

[error] 751-757: Replace ⏎············sweepCenter,⏎············orientation,⏎············halfExtents,⏎············direction,⏎············100,⏎··········· with sweepCenter,·orientation,·halfExtents,·direction,·100,

(prettier/prettier)


[error] 758-758: Delete ··

(prettier/prettier)


[error] 759-759: Delete ··

(prettier/prettier)


[error] 760-760: Delete ··

(prettier/prettier)


[error] 761-762: Replace ············}⏎·········· with ··········}

(prettier/prettier)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/src/core/physics/PhysicsScene.test.ts` around lines 751 - 762, Reformat
the nativeScene.boxCast call site to satisfy Prettier: rewrap the arguments and
callback to follow the project's Prettier rules (consistent indentation and line
breaks) for the nativeScene.boxCast invocation that uses sweepCenter,
orientation, halfExtents, direction and the inline callback that updates
innerSweepCalls and innerSweepUuids; run Prettier (or your editor's format
command) on tests/src/core/physics/PhysicsScene.test.ts to produce the properly
wrapped/indented version so CI passes.

GuoLei1990

This comment was marked as outdated.

GuoLei1990

This comment was marked as outdated.

@luzhuang luzhuang added physics Engine's physical system labels May 12, 2026
GuoLei1990

This comment was marked as outdated.

…aycast

Make _sweepSingle structurally mirror raycast:

- Promote pxSweepHit to a scene-level persistent PhysX object (allocated
  once in the constructor, released in destroy), matching how _pxRaycastHit
  is already handled.
- Collapse the nested try/finally that existed only to dispose the per-call
  PxSweepHit; now a single try/finally guards onSweepStack.pop().

This removes one new+delete pair on every box/sphere/capsule cast (a real
cost across the JS↔wasm boundary), and as a side effect closes the
exception window where `new PxSweepHit()` could throw after onSweepStack
had already been pushed — push now happens immediately before the only
try block, leaving no path to leak a stale predicate.

All 55 physics tests (48 baseline + 7 PR regressions) continue to pass.
…h prev-local

Collapse the three persistent PhysX query filter callbacks (raycast, sweep,
overlap) into a single `_pxQueryCallback`, and replace the three predicate
stacks (`_onRaycastStack` / `_onSweepStack` / `_onOverlapStack`) with a single
`_currentOnQuery` field guarded by a call-stack-local previous-value save in
each query method.

Why this is safe:

- PhysX SDK guarantees `postFilter` is only invoked when the query's filter
  data carries `PxQueryFlag::ePOSTFILTER` (PxQueryFilterCallback /
  PxQueryFilterData doc-comments in PxQueryFiltering.h). Overlap queries
  use `_pxFilterData` which omits POST_FILTER, so the shared callback's
  postFilter is never called for overlaps even though the method exists.
  Raycast and sweep both use `_pxRaycastSweepFilterData` with POST_FILTER
  set, getting the initial-overlap-skip behavior they need.
- Unity's native PhysX wrapper (`Modules/Physics/CastFilter.h`,
  `PhysicsQuery.cpp:OverlapSphereInternal`) follows the exact same pattern:
  a single `CastFilter` class implements both preFilter and postFilter and
  is shared by raycast, sweep, and overlap queries.
- Reentrancy (e.g. raycast nested inside another raycast's onRaycast) is
  preserved via the prev-local save/restore in `try/finally`, recreating
  C++-style RAII without an explicit stack array. The existing nested-call
  and throw-recovery regression tests cover this path.

Net result: -15 lines, one persistent callback instead of three, a single
field instead of three arrays, and a faster preFilter hot path
(`scene._currentOnQuery(idx)` vs `arr[arr.length - 1](idx)`).

All 55 physics tests (48 baseline + 7 PR regressions including nested
filter ordering, sweep-inside-raycast, and throw recovery) continue to
pass.
GuoLei1990

This comment was marked as outdated.

Copy link
Copy Markdown
Member

@GuoLei1990 GuoLei1990 left a comment

Choose a reason for hiding this comment

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

总结

本轮是第四轮增量审查,覆盖最新 commit 8fe796a6(persist PxSweepHit + align _sweepSingle with raycast)。

三轮历史遗留的所有问题均已关闭:

  1. overlap preFilter 返回 BLOCK 可能提前终止多命中查询(P2) — 深入源码确认:overlapMultiple 在调用 PhysX 前强制追加 eNO_BLOCK flag(ExtSceneQueryExt.cpp:156),preFilter 返回 BLOCK 在 overlap 查询中不会提前终止,行为正确。

  2. 两个 filterData 差异缺注释(P2) — 已修复。第 39–46 行有完整注释说明单一 callback 共用的安全性保证及 POST_FILTER 仅由 raycast/sweep filterData 激活的设计意图。

  3. _pxSweepHit 生命周期不对称(P3 简化建议) — 已修复,即本轮新增 commit 的核心改动。

新 commit 质量

perf(physics-physx): persist PxSweepHit and align _sweepSingle with raycast 改动干净:

  • 将 per-call new PxSweepHit() / .delete() 提升为 constructor 分配 / destroy() 释放,与 _pxRaycastHit 对称,消除每次 sweep 的 WASM boundary 往返开销。
  • 嵌套 try/finally 合并为单层,逻辑更清晰。
  • 所有 55 个测试继续通过(commit message 已确认)。

无新问题。可以合并。

Copy link
Copy Markdown
Member

@GuoLei1990 GuoLei1990 left a comment

Choose a reason for hiding this comment

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

已关闭问题清单

所有历史问题均已关闭(前 4 轮):initial overlap 跳过、overlap preFilter BLOCK 问题(深入源码确认不影响多命中查询)、PxSweepHit 生命周期收紧、reentrancy 安全性、POST_FILTER 污染 overlap。

总结

三个独立物理修复方向全部正确:initial overlap 通过 POST_FILTER 回调过滤 distance <= 0 的 hit;query callback 对象持久化复用配合 reentrancy stack;sweep hit 生命周期收紧到 finally 块。回归测试覆盖完整(7 个新测试,含 initial overlap 4 类 + reentrancy 3 类)。

无新问题。LGTM,可合入。

@GuoLei1990 GuoLei1990 mentioned this pull request May 13, 2026
3 tasks
this._pxScene = pxPhysics.createScene(sceneDesc);
sceneDesc.delete();

const scene = this;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

const scene = this; 是冗余 — 箭头函数已经从构造器的 lexical scope capture 了 this,直接 this._currentOnQuery(index) 即可。

-    const scene = this;
-    this._pxQueryCallback = physX.PxQueryFilterCallback.implement({
-      preFilter: (_filterData: any, index: number, _actor: any) =>
-        scene._currentOnQuery(index) ? QueryHitType.BLOCK : QueryHitType.NONE,
+    this._pxQueryCallback = physX.PxQueryFilterCallback.implement({
+      preFilter: (_filterData: any, index: number, _actor: any) =>
+        this._currentOnQuery(index) ? QueryHitType.BLOCK : QueryHitType.NONE,

看着像在绕什么 this 绑定坑,实际上没有。

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

第一性原理核实正确:同文件 73-101 行的 triggerCallback 早就在 physX.PxSimulationEventCallback.implement() 里用 this._bufferContactEvent(...) 等直接调用,证明 emscripten .implement() 不破坏箭头函数 thisconst scene = this 是孤例残留。已 push 修复(eb3a7e930)。

Arrow functions inherit `this` lexically, so the alias was unnecessary —
same constructor's triggerCallback (lines 73-101) uses `this._xxx` directly
inside arrow callbacks passed to physX.PxSimulationEventCallback.implement(),
proving emscripten's .implement() doesn't rebind callback `this`.

Reviewed by @cptbtptpbcptdtptp on galacean#2998.
Copy link
Copy Markdown
Member

@GuoLei1990 GuoLei1990 left a comment

Choose a reason for hiding this comment

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

已关闭问题清单

问题 状态
initial overlap 跳过 已修复(POST_FILTER 过滤 distance <= 0)
overlap preFilter 返回 BLOCK 可能提前终止多命中查询 已关闭(深入 PhysX 源码确认 overlapMultiple 强制 eNO_BLOCK flag,不影响多命中)
query callback 持久化复用 + reentrancy 已修复(_on*Stack 支持嵌套)
PxSweepHit 生命周期不对称 已修复(提升到 constructor 分配)
POST_FILTER 污染 overlap filterData 已修复(拆出独立 _pxRaycastSweepFilterData)
两个 filterData 差异缺注释 已修复

总结

三个独立物理修复:initial overlap 通过 POST_FILTER 回调过滤 distance <= 0 的 hit;query callback 对象持久化复用配合 reentrancy stack;PxSweepHit 生命周期收紧到 constructor/destroy 对称。回归测试覆盖完整(7 个新测试)。

前 4 轮 review 提出的所有问题均已修复或经作者/代码验证关闭。

无新问题。LGTM,可合入。

Copy link
Copy Markdown
Member

@GuoLei1990 GuoLei1990 left a comment

Choose a reason for hiding this comment

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

已关闭问题清单

所有历史问题均已关闭(见前 4 轮 review 记录)。

总结

三个独立物理修复,方向全部正确:

  1. raycast/sweep 通过 POST_FILTER 过滤 distance <= 0 的 hit 跳过 initial overlap,对应 Unity 的语义(起点在碰撞体内时不计为命中)
  2. 持久化 _pxRaycastCallback/_pxSweepCallback/_pxOverlapCallback,配合 _on*Stack 支持 reentrant 查询
  3. 收紧 PxSweepHit 生命周期,对齐 PxRaycastHit 的处理方式

overlap preFilter 返回 BLOCK 不影响多命中查询已通过深入 PhysX 源码确认(overlapMultiple 强制 eNO_BLOCK flag)。

无新问题。LGTM,可合入。

@cptbtptpbcptdtptp
Copy link
Copy Markdown
Collaborator

Follow-up 建议(不阻塞本 PR)

这次 PR 落地后,建议把 query filter 进一步推到 native,分两阶段:

阶段 1:内置 filter native 化(API 零改动)

  • layer mask 通过 PxFilterData.word0/word1 下推(PhysXColliderShape._initializesetQueryFilterDataPhysXCollidercollisionLayer setter 同步更新 shape filter data),删 PhysicsScene._createPreFilter
  • postFilter 在 wasm 端 C++ 闭环(if (distance <= 0) return eNONE),JS 端 postFilter method 直接删,filter flags 也可以去掉 POST_FILTER 之外只保留 group/mask filtering
  • 现有 layer mask / initial overlap 测试全部通过即可,行为等价

阶段 2:暴露用户 callback(按需驱动)

PhysicsScene.raycast 改成 options 对象签名:

PhysicsScene.raycast(ray, distance, {
  layerMask: Layer.Everything,       // → native PxFilterData, fast path
  preFilter?: (shape) => boolean,    // 可选, JS callback
  postFilter?: (hit) => boolean,     // 可选, JS callback
  hit?: HitResult
});

没传 callback 时走阶段 1 的 native fast path;传了就回 JS callback 路径。底层本 PR 的 _currentOnQuery + try/finally 骨架保留作为 hook 点,沿用即可。

拆两个 PR 的理由

  • 阶段 1 是 zero-risk 性能优化,无 API 改动,回归测试足够
  • 阶段 2 涉及 API 扩展需要产品决策(当前用户层 PhysicsScene.raycast 只暴露 layerMask,是否有 design partner 反馈需要自定义 pre/postFilter 的能力?没有的话先做阶段 1 就够了)

收益估算

按 100 query/帧、broadphase 候选 ~20/query、narrowphase hit ~3/query、emval invoke ~300ns 估算:

  • 阶段 1 preFilter 下推:~0.6 ms/帧
  • 阶段 1 postFilter native 化:~90 μs/帧

主要价值是 preFilter 下推;postFilter native 化绝对收益小但改动也小(~10 行 + wasm patch),属于顺手做掉。

前置条件

需要确认 PhysX wasm bindings 暴露程度:

  1. PxFilterDataword0-3 字段是否可读写
  2. 是否能在 wasm 端写 native PxQueryFilterCallback subclass(用于 postFilter 闭环)

如果 (2) 受限,阶段 1 的 postFilter native 化路线就走不通,只能保留 JS postFilter;但 preFilter 下推仍然能做。

设计参考:Unity Physics.Raycast(ray, distance, layerMask, queryTriggerInteraction) —— layer 走 native filter data,TriggerInteraction 走 native flag,自定义过滤只能 post-filter on hit result。

@cptbtptpbcptdtptp cptbtptpbcptdtptp merged commit 373f559 into galacean:dev/2.0 May 13, 2026
11 of 12 checks passed
luzhuang added a commit that referenced this pull request May 14, 2026
…ysX scene queries)

These tests were red on dev/2.0 before any of the recent physics work landed.
Diagnosed and fixed at the root, no test-skipping or assertion weakening.

**LitePhysicsMaterial — 1 failure**

`ColliderShape Lite > clone` broke after R8 added `PhysicsMaterial._cloneTo →
_syncNative()` which now calls every native setter on a freshly-cloned material.
Lite's setters threw "Physics-lite don't support physics material", violating
the no-op-with-log convention already used by `LiteColliderShape.setMaterial /
setIsTrigger / setContactOffset`. Change all 5 setters to silent no-op + a
one-time `console.log` warning, matching the existing convention.

**PhysXPhysicsScene — 7 failures (raycast/boxCast/sphereCast/capsuleCast +
overlapBoxAll/overlapSphereAll/overlapCapsuleAll)**

Two underlying problems both surface in the JS filter-callback layer:

1. `_pxFilterData` had `POST_FILTER` flag enabled but `_overlapMultiple` reused
   it with a callback object that only defined `preFilter`. When PhysX tried to
   invoke `postFilter`, the wasm boundary errored with
   `Emval.toValue(...)[getStringOrSymbol(...)] is not a function`.

2. The 4 cast tests asserted the old "origin-inside-collider returns true with
   distance=0" behavior. The wasm side was already switched to the Unity-style
   "skip initial overlap" design (commit bdf5490 / PR #2998 on dev branch
   ahead of us), but the tests still encoded the rejected behavior.

Mirror PR #2998's JS-side fix already merged on dev:

- Split filter data into `_pxFilterData` (PRE only, for overlap) and
  `_pxRaycastSweepFilterData` (PRE + POST, for raycast/sweep).
- Replace per-call `PxQueryFilterCallback.implement` with a single persistent
  `_pxQueryCallback` defining both `preFilter` and `postFilter`. User callbacks
  dispatch via a `_currentOnQuery` slot saved/restored around each native call
  for reentrancy safety.
- Cache the `PxSweepHit` instance instead of creating per-call.
- Add `QueryHitType` enum.

Update 4 stale test assertions in PhysicsScene.test.ts to match the
already-shipped "skip initial overlap" wasm contract. Use strictly-inside origin
(2.9,2.9,2.9) for the raycast case to avoid the corner-tolerance flake that PR
\#2998 already addressed.

Verification: 11 physics test files, 213/213 passing locally.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

physics Engine's physical system

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants