Skip to content

feat: add dynamic collider force at position#3039

Merged
cptbtptpbcptdtptp merged 3 commits into
dev/2.0from
feat/physics-force-at-position
Jun 25, 2026
Merged

feat: add dynamic collider force at position#3039
cptbtptpbcptdtptp merged 3 commits into
dev/2.0from
feat/physics-force-at-position

Conversation

@luzhuang

@luzhuang luzhuang commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Add DynamicCollider.applyForceAtPosition(force, position) for world-space force-at-position use cases.
  • Extend IDynamicCollider and the PhysX backend to delegate directly to PxRigidBody.addForceAtPos.
  • Update checked-in PhysX wasm runtime generated from feat: expose rigid body force-at-position bindings physX.js#19.
  • Update the default PhysXPhysics CDN runtime URLs to the same fix(README): Official Site Link #19 build so new PhysXPhysics() exposes addForceAtPos without custom runtimeUrls.
  • Add DynamicCollider coverage for native delegation and force+torque motion equivalence.

Related

Runtime Rollout Gate

The default CDN runtime has been uploaded as one zip so physx.release*.js and matching .wasm sidecars share the same CDN directory:

  • https://mdn.alipayobjects.com/rms/uri/file/as/apwallet/1781696156399/suyi/physx.release.js
  • https://mdn.alipayobjects.com/rms/uri/file/as/apwallet/1781696156399/suyi/physx.release.simd.js

Both default CDN JS URLs were smoke-tested in Chromium after wasm initialization: PxRigidDynamic.prototype.addForceAtPos, addImpulseAtPos, and getVelocityAtPos are functions.

Verification

  • npm run b:module
  • npm run b:types
  • npm run lint -- --quiet
  • HEADLESS=true pnpm exec vitest run --config tests/vitest.config.ts tests/src/core/physics/DynamicCollider.test.ts
  • HEADLESS=true pnpm exec vitest --coverage tests/src/core/physics/DynamicCollider.test.ts
  • npm run coverage
  • curl -I -L verified default CDN JS is served as application/x-javascript, wasm as application/wasm, with CORS enabled.
  • Chromium CDN smoke verified both default runtime URLs expose the new PhysX rigid body extension helpers.
  • physX.js release and SIMD release wasm built with default LTO config via emmake make -j1.
  • Node smoke: generated PhysX runtime exposes addForceAtPos and related at-position helpers.

Summary by CodeRabbit

  • New Features

    • Dynamic physics objects now support applying directional force at a specific world-space position, enabling more complex force behaviors for physics simulations. Supported in the PhysX physics engine; the Lite physics engine provides informative error guidance.
  • Tests

    • Comprehensive test cases added to validate force-at-position physics functionality and behavior.

@luzhuang luzhuang force-pushed the feat/physics-force-at-position branch from ef1ef42 to 20629f9 Compare June 17, 2026 09:57
@codecov

codecov Bot commented Jun 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.94%. Comparing base (5d74c1d) to head (b3cbdaf).
⚠️ Report is 8 commits behind head on dev/2.0.

Additional details and impacted files
@@             Coverage Diff             @@
##           dev/2.0    #3039      +/-   ##
===========================================
+ Coverage    79.17%   79.94%   +0.77%     
===========================================
  Files          914      919       +5     
  Lines       101666   102602     +936     
  Branches     11209    11378     +169     
===========================================
+ Hits         80489    82028    +1539     
+ Misses       20990    20393     -597     
+ Partials       187      181       -6     
Flag Coverage Δ
unittests 79.94% <100.00%> (+0.77%) ⬆️

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

☔ View full report in Codecov by Harness.
📢 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.

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds applyForceAtPosition(force, position) to the physics stack: declared on IDynamicCollider, implemented in PhysXDynamicCollider via addForceAtPos, stubbed with an unsupported error in LiteDynamicCollider, and exposed as a public method on DynamicCollider. Default PhysX WASM CDN URLs are updated, and tests cover both backends.

Changes

applyForceAtPosition Feature

Layer / File(s) Summary
Interface contract and backend implementations
packages/design/src/physics/IDynamicCollider.ts, packages/physics-physx/src/PhysXDynamicCollider.ts, packages/physics-lite/src/LiteDynamicCollider.ts, packages/physics-physx/src/PhysXPhysics.ts
Declares addForceAtPosition on IDynamicCollider; implements it in PhysXDynamicCollider forwarding to _pxActor.addForceAtPos; adds a throw stub in LiteDynamicCollider; updates default PhysX WASM CDN URLs from /rms/afts/file/... to /rms/uri/file/as/apwallet/....
Public API integration
packages/core/src/physics/DynamicCollider.ts
Adds DynamicCollider.applyForceAtPosition(force, position) gated on _phasedActiveInScene, delegating to the native collider's addForceAtPosition.
Tests
tests/src/core/physics/DynamicCollider.test.ts, tests/src/core/physics/PhysicsScene.test.ts
Configures PhysX tests with PhysXRuntimeMode.Auto and explicit WASM URLs; adds a spy-based delegation test and a physics-behavior equivalence test for applyForceAtPosition; adds a lite-physics test asserting all unsupported dynamic-force methods throw the expected error message.

Possibly related issues

  • Track PhysX integration follow-ups after #3025 #3038: This PR directly implements the feature tracked in that issue, exposing addForceAtPos through DynamicCollider.applyForceAtPosition across IDynamicCollider, PhysXDynamicCollider, and LiteDynamicCollider with corresponding tests.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🐇 A force applied just where you need,
At a position in the world, it's freed!
PhysX says yes, Lite throws with flair,
The engine hops forward with springtime air.
More physics magic — celebrate! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add dynamic collider force at position' clearly and concisely describes the main change: adding the applyForceAtPosition method to the DynamicCollider class.
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
  • Commit unit tests in branch feat/physics-force-at-position

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.

GuoLei1990

This comment was marked as outdated.

@luzhuang luzhuang force-pushed the feat/physics-force-at-position branch from 20629f9 to 55d2f44 Compare June 17, 2026 11:38

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
packages/physics-lite/src/LiteDynamicCollider.ts (1)

55-56: ⚡ Quick win

Throw an Error object instead of a string literal.

At Line 56, string throws make downstream error handling less reliable (instanceof Error, stack, message shape). Use an Error instance here for consistent runtime behavior.

Proposed fix
   addForceAtPosition(force: Vector3, position: Vector3): void {
-    throw "Physics-lite don't support addForceAtPosition. Use Physics-PhysX instead!";
+    throw new Error("Physics-lite don't support addForceAtPosition. Use Physics-PhysX instead!");
   }
🤖 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 `@packages/physics-lite/src/LiteDynamicCollider.ts` around lines 55 - 56, In
the addForceAtPosition method, replace the string literal being thrown with a
proper Error object instance. Change the throw statement from throwing a plain
string to throwing new Error with the same message text. This ensures proper
error handling downstream with instanceof checks and provides correct stack
trace information.
notes/physics/2026-06-17-physx-force-at-position.md (1)

21-32: ⚡ Quick win

Consider reformatting the Verification section as a pre-merge/release checklist.

The verification section documents steps already taken, which is valuable. However, for maintainers reviewing before merge or release, it would be clearer to present this as an actionable checklist with pre-merge gates explicitly called out.

Suggestion: Add a structured checklist format (e.g., markdown checkbox list or table) that clarifies:

  • Which steps are pre-merge blockers (e.g., CDN runtime validation per the Rollout Gate section).
  • Which steps are verification-only (e.g., coverage runs).
  • Whether each step is a one-time verification or a repeatable gating check.

Example structure:

## Pre-Merge Checklist

- [ ] **CDN runtime**: Default `PhysXPhysics` runtime URLs point to CDN with `PxRigidDynamic.prototype.addForceAtPos` and related helpers.
- [ ] **Local build & smoke test**: Built and smoke-tested local runtime.
- [ ] **Tests**: Run `HEADLESS=true pnpm exec vitest run --config tests/vitest.config.ts tests/src/core/physics/DynamicCollider.test.ts` (expect 27 passed).
- [ ] **Coverage**: Run `npm run coverage` (expect 111 files and 1449 tests passed).
- [ ] **Type build**: Run `npm run b:types` (expect success).
🤖 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 `@notes/physics/2026-06-17-physx-force-at-position.md` around lines 21 - 32,
The Verification section in the file currently documents steps already taken in
a bullet-list format, but it should be restructured as an actionable pre-merge
checklist for future maintainers. Replace the existing Verification section with
a new "Pre-Merge Checklist" section that uses markdown checkboxes and clearly
categorizes each step. Distinguish between pre-merge blockers (such as CDN
runtime validation) and verification-only steps (such as coverage runs). For
each checklist item, include the command to run and expected outcome. This will
make it explicit which gates must pass before merging and which are
informational verifications.
🤖 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.

Nitpick comments:
In `@notes/physics/2026-06-17-physx-force-at-position.md`:
- Around line 21-32: The Verification section in the file currently documents
steps already taken in a bullet-list format, but it should be restructured as an
actionable pre-merge checklist for future maintainers. Replace the existing
Verification section with a new "Pre-Merge Checklist" section that uses markdown
checkboxes and clearly categorizes each step. Distinguish between pre-merge
blockers (such as CDN runtime validation) and verification-only steps (such as
coverage runs). For each checklist item, include the command to run and expected
outcome. This will make it explicit which gates must pass before merging and
which are informational verifications.

In `@packages/physics-lite/src/LiteDynamicCollider.ts`:
- Around line 55-56: In the addForceAtPosition method, replace the string
literal being thrown with a proper Error object instance. Change the throw
statement from throwing a plain string to throwing new Error with the same
message text. This ensures proper error handling downstream with instanceof
checks and provides correct stack trace information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7bf2b4b0-7b20-47d6-a702-e2e2bbc4bee3

📥 Commits

Reviewing files that changed from the base of the PR and between 20629f9 and 55d2f44.

⛔ Files ignored due to path filters (2)
  • 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)
  • notes/physics/2026-06-17-physx-force-at-position.md
  • packages/core/src/physics/DynamicCollider.ts
  • packages/design/src/physics/IDynamicCollider.ts
  • packages/physics-lite/src/LiteDynamicCollider.ts
  • packages/physics-physx/src/PhysXDynamicCollider.ts
  • packages/physics-physx/src/PhysXPhysics.ts
  • tests/src/core/physics/DynamicCollider.test.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/physics-physx/src/PhysXPhysics.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/design/src/physics/IDynamicCollider.ts
  • packages/core/src/physics/DynamicCollider.ts
  • packages/physics-physx/src/PhysXDynamicCollider.ts
  • tests/src/core/physics/DynamicCollider.test.ts

@luzhuang luzhuang force-pushed the feat/physics-force-at-position branch from 55d2f44 to 0beedf2 Compare June 17, 2026 11:44
GuoLei1990

This comment was marked as outdated.

@luzhuang luzhuang force-pushed the feat/physics-force-at-position branch from 0beedf2 to 32582b2 Compare June 17, 2026 11:51

@GuoLei1990 GuoLei1990 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

总结

第三轮(HEAD 32582b2f,相比上轮 55d2f448b)。相比上轮,实质代码逻辑(四层 API / lite throw / PhysX 委托 / CDN URL bump)完全无变化,唯一新增是 tests/src/core/physics/PhysicsScene.test.ts 里一个 lite 「unsupported behavior」测试(notes/ 文件只改了一行测试计数 1449→1450)。这个测试补上了我第一轮指出的 codecov「lite throw 分支未覆盖」缺口,方向正确。

问题

无 P0/P1/P2。

  • [P3] PhysicsScene.test.ts:141 新测试未 root.destroy() 清理 — 新测试 const root = ...createRootEntity("root") + addComponent(DynamicCollider),结尾没有 root.destroy()。本 describe 块内几乎所有其他测试(removeShape 167 行起,及 326/536/570/602/632/662/714/767/809… 全部)都在结尾 root.destroy(),本测试偏离了这一既有惯例。已核实不构成正确性问题:该 collider 没 addShape,lite _update 只遍历 shape(LitePhysicsScene._updateWorldBox / 碰撞检测走 shape 列表),无 shape 的 dynamic collider 不进 broadphase、不会被触碰,残留对后续 removeShape 等共享 engineLite.activeScene 的测试无干扰。建议结尾补 root.destroy() 与文件惯例对齐。不阻塞。

  • [P3] notes/physics/2026-06-17-physx-force-at-position.md(前两轮已提,本轮仍未处理) — CR/工程笔记仍在引擎源码树里。再次核实:notes/dev/2.0 不存在、不在 .gitignore、非既有约定。建议从 PR 移除(信息留 PR 描述 / commit / wiki),或要长期留存就改成 design doc 放进 packages/。不阻塞。

新测试核对(已离线证伪,通过)

  • 6 个断言字符串逐条对齐 lite 实际 throw — 测试映射 sleep()→"putToSleep"wakeUp()→"wakeUp"isSleeping()→"isSleeping",与 LiteDynamicCollider 实际 throw(addForce:49 / addTorque:56 / putToSleep:70 / isSleeping:77 / wakeUp:205 / 新增 addForceAtPosition)逐字一致。
  • 守卫短路风险已排除 — core DynamicCollider.applyForce/applyForceAtPosition/applyTorque_phasedActiveInScene && 守卫(358/366 行),若 collider 非 active 则 native throw 不会触发、errorundefined、断言会 fail。已核实:beforeAll 调了 engineLite.run(),root 经 createRootEntity 默认 active、component 默认 enabled ⇒ _phasedActiveInScene=true ⇒ 守卫不短路。sleep/wakeUp/isSleeping(406/414/421 行)本就无守卫恒 throw。CI 1450 tests passed 佐证。
  • 反向证伪成立 — 若 lite addForceAtPosition 不 throw,errorundefinedexpect(undefined).eq("Physics-lite don't support addForceAtPosition...") 会 fail ⇒ 是守得住的回归测试,非摆设。
  • 非重复 — 全仓此前无任何测试断言 lite throw 字符串(grep "Physics-lite don't support" tests/ 仅此一处),不与既有测试冗余。

其余维度(沿用前两轮结论,代码未变不再展开)

四层对称完整 / 物理等价 + delegation 测试守住接线 / CDN==checked-in 逐字节一致、sidecar 配对正确 / lite throw 字符串同族一致(CodeRabbit 改 Error 对象建议按项目惯例不采纳)—— 均已在前两轮闭环,本轮无变化、不重提。

LGTM。两个 P3 都不阻塞,移除 notes/(并顺手给新测试补 root.destroy())后即可合并。

@GuoLei1990 GuoLei1990 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

总结

第四轮(HEAD b3cbdaf5b,相比上轮 32582b2f)。两个新 commit:

  • 5f304ebc — 把我前三轮反复提的两个 P3 一并修掉:删除 notes/physics/2026-06-17-physx-force-at-position.md、给 PhysicsScene.test.ts 的 lite unsupported 测试补上 root.destroy()
  • b3cbdaf5b(本轮唯一实质代码改动)— perf: avoid vector wrappers for PhysX forces:把 addForce/addTorque/addForceAtPosition 里传给原生绑定的 { x: v.x, y: v.y, z: v.z } 字面量改成直接传引擎 Vector3/Vector3 对象。

方向正确,本轮无 P0/P1/P2/P3,前轮两个 P3 已闭环。

已闭环(前轮提出,本轮确认修复,不再展开)

  • [P3 已修] notes/ CR 笔记 — 已从源码树删除(前三轮均提)。
  • [P3 已修] lite unsupported 测试缺 root.destroy()PhysicsScene.test.ts:165 已补 root.destroy(),与本 describe 块内其它测试惯例对齐。

本轮新增 perf 改动核对(已离线证伪,通过)

this._pxActor.addForce({ x: force.x, y: force.y, z: force.z })this._pxActor.addForce(force)addTorque/addForceAtPos 同改)。逐项核实是正确且与代码库主流一致的简化,非风险:

  • emscripten value_object 绑定按 .x/.y/.z 属性读取,不要求特定类 — 这两行 {x,y,z} 包裹是整个 physx 包里仅有的两处这种写法(grep "{ x:" packages/physics-physx/src/ 只命中 PhysXDynamicCollider.ts:218/225),是 outlier。其余所有传向同一 _pxActor 的调用早就直接传 Vector3/QuaternionsetLinearVelocity(value)(:59)、setAngularVelocity(_tempTranslation)(:83)、setCMassLocalPose(position)(:105)、setKinematicTarget(positionOrRotation, rotation)(:233/241/243)、PhysXJoint.setLocalPose(actor, position, rotation)(:114)、PhysXCollider.setGlobalPose(...)(:52)。本改动是把这 3 个方法对齐到既有主流写法,而非引入新模式。
  • Vector3x/y/z 是 getter(返回 _x/_y/_z,Vector3.ts:327/339/351),native 属性访问命中 getter,与 {x,y,z} 字面量逐字段等价 — 不存在「private 字段读不到」问题。
  • 省一次 per-call 对象字面量分配 — force/torque 可能每帧调用,去掉临时对象是真收益。addForceAtPosition 原本一次调用分配 2 个字面量,现在 0。
  • 真实绑定路径被等价测试守住DynamicCollider.test.ts:317 「produces the same motion as an equivalent force and torque」跑真 PhysX _update(1),断言 linearVelocity/angularVelocity 三分量逐一等于 applyForce + applyTorque((0,0,-1)) 的参考分解(com=(0,0,0)force=(1,0,0)pos=(0,1,0)torque = pos×force = (0,0,-1))。若直接传 Vector3 导致绑定读不到坐标,速度会偏离/归零 → 测试 fail。CI(build×3 OS / e2e 4/4 / lint)全绿佐证绑定按 getter 正确读取。
  • perf commit scope 干净 — 仅 PhysXDynamicCollider.ts 3 行,未重新 touch wasm/js(正确:wasm 绑定在前序 commit 已就位,且按 .x/.y/.z 读取与 JS 对象的类无关)。

其余维度(沿用前三轮结论,代码未变不再展开)

四层(design 接口 / core 公开 API / physx 后端 / lite throw)对称完整 / 物理等价 + delegation 测试守住接线 / CDN==checked-in 逐字节一致、sidecar 配对正确 / lite throw 字符串同族一致(CodeRabbit 改 Error 对象建议按项目惯例不采纳)—— 均已闭环。

LGTM,可合并。

@cptbtptpbcptdtptp cptbtptpbcptdtptp merged commit 9083457 into dev/2.0 Jun 25, 2026
12 checks passed
@GuoLei1990 GuoLei1990 deleted the feat/physics-force-at-position branch June 29, 2026 08:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants