Skip to content

fix(physics): make mesh rebuilds atomic and scale PhysX defaults#3042

Draft
luzhuang wants to merge 1 commit into
dev/2.0from
fix/physics-mesh-defaults
Draft

fix(physics): make mesh rebuilds atomic and scale PhysX defaults#3042
luzhuang wants to merge 1 commit into
dev/2.0from
fix/physics-mesh-defaults

Conversation

@luzhuang

@luzhuang luzhuang commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Problem

This PR fixes several ownership and default-value problems in the physics shape lifecycle:

  • MeshColliderShape and Collider both participated in native shape attachment. Replacing or clearing a mesh could therefore leave duplicate attachments, stale native geometry, or a "ghost" collider after mesh = null.
  • Mesh recooking was modeled as an in-place setMeshData transaction. That is not a valid PhysX transaction boundary: PhysX 4's PxShape::setGeometry reports validation through the error callback, returns no success value, and cannot change the geometry type between convex and triangle mesh. A failed update could not be rolled back reliably.
  • A replacement shape was initially built at unit scale and scaled later, which could cook or validate the wrong geometry before the actual world scale was applied.
  • Cloning a mesh shape did not establish a complete native shape and explicit mesh-resource ownership.
  • tolerancesScale was read lazily from a caller-owned mutable object, while Core eagerly overwrote backend defaults with fixed contact-offset and sleep-threshold values.
  • Shape cloning created a temporary default physics material and then lost ownership of it.
  • PhysicsMaterial passed friction and bounce combine modes to the native factory in the wrong order.
  • Recreating a PhysX character controller reset its native contact offset to PhysX's hard-coded descriptor default (0.1), losing both the configured value and the backend-scaled default.

Solution

Atomic mesh-shape rebuild

  • Build a complete native mesh-shape candidate first, using the final world scale, cooking flags, trigger state, local pose, contact offset, and material.
  • Commit the public mesh state only after candidate creation succeeds. Cooking or validation failure keeps the previous public state and native collision shape intact.
  • Keep native attachment ownership in Collider: it removes the previous native shape, attaches the candidate, destroys the previous shape, and emits one shape-change notification.
  • Clearing mesh now performs the same owned replacement with null, so the old native shape is detached and destroyed immediately.
  • Remove the misleading setMeshData mutation API and the second attachment-state flag. Convex/triangle transitions are represented by rebuilding the native shape, which matches the PhysX API contract.
  • Reuse cached CPU mesh data for later convex/triangle switches after uploadData(true), and retain/release ModelMesh references explicitly when cloning.
  • Do not add per-frame scanning or retry paths; an unchanged invalid input remains unchanged until the caller supplies new state.

Scaled PhysX defaults

  • Validate and snapshot tolerancesScale.length and tolerancesScale.speed in the PhysX backend constructor, so later caller mutations cannot change initialized physics behavior.
  • Initialize both PxPhysics and PxCookingParams from the same scale and release the temporary Embind wrapper after the native objects have copied it.
  • Derive the backend contact-offset default as 0.02 * length and sleep-threshold default as 5e-5 * speed^2.
  • Core leaves these values unset until explicitly assigned, allowing each backend's scaled defaults to remain authoritative.
  • Restore a character controller's contact offset at its single native-controller creation boundary. This is applied immediately after creation rather than through PxControllerDesc, because Core permits 0 while PhysX rejects a descriptor contact offset that is not strictly positive.

Material ownership

  • Cloned collider shapes share the source PhysicsMaterial, preserving existing assignment semantics, and explicitly destroy the constructor-created temporary material.
  • Pass friction and bounce combine modes to the backend in the declared IPhysics.createPhysicsMaterial order.

Scope

This PR is based directly on dev/2.0 and contains one commit with only the mesh-shape lifecycle, scaled-default, and material-ownership changes described above. It does not include #3025 or #3041.

Related work remains separate:

Verification

  • pnpm -F @galacean/engine-design run b:types
  • pnpm -F @galacean/engine-core run b:types
  • pnpm -F @galacean/engine-physics-physx run b:types
  • pnpm b:module
  • Targeted Chromium suite on dev/2.0: 5 files, 101 tests passed
    • mesh replacement/rollback, clone ownership, missing CPU data, mesh = null, and post-upload convex switching
    • native tolerancesScale readback and scaled Core defaults
    • material clone lifetime
    • character-controller recreation, including contactOffset = 0
  • pre-commit ESLint and Prettier checks
  • git diff --check

@coderabbitai

coderabbitai Bot commented Jun 17, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8ad7ead8-0400-488f-82ae-25d5fecd69ba

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/physics-mesh-defaults

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

首轮 review @ 0ad694262(stacked PR 3/3,--comment

⚠️ HEAD 校准:gh 一度缓存的 headRefOid=dbe20e696(含 _pendingNativeShapeCreation/_onPhysicsUpdate 每帧重试机制)是旧 rebase 版;当前真实分支 tip 是 0ad694262(commit 2 "keep mesh recooking transactional"),已整体删掉重试机制、改成事务式。本轮按真 tip 0ad694262 审。

总结

一组 mesh collider / 物理材质 / 默认值的修复,6 个子项:clone 重建 native shape、recooking 事务化、cook-null 视为终止失败(不再每帧重试)、PhysX tolerancesScale 选项 + scaled 默认 contactOffset/sleepThreshold、未显式设置时不覆盖 PhysX 默认、physics-lite 材质写入降级为一次性提示。整体方向正确,事务式重写比旧的"每帧重试"版本干净得多(cook-null 在 PhysX 侧基本是几何确定性失败,每帧重 cook 是热路径开销 + 每帧刷错 —— 删掉是对的,与描述一致)。

重点发现

[P2] PhysicsMaterial 构造期 friction/bounce combine 参数顺序——本 PR 修了一个真实潜伏 bug,但 PR 描述未提及。

createPhysicsMaterial 接口签名是 (staticFriction, dynamicFriction, bounciness, frictionCombine, bounceCombine)(design/IPhysics.ts:76-82,frictionCombine 在前)。dev/2.0 与本 PR base 上,PhysicsMaterial 构造器传的是 (..., this._bounceCombine, this._frictionCombine) —— 顺序反了:PhysX 侧 setFrictionCombineMode(bounceCombine) / setRestitutionCombineMode(frictionCombine)构造时把摩擦与弹性的 combine 模式装反。本 PR 改成 (this._frictionCombine, this._bounceCombine),修对了。

  • 之所以一直没暴露:两者默认都是 Average(PhysicsMaterial.ts:13-14),只有用户把 frictionCombine / bounceCombine 设成不同值时才显形,且只在构造期(单独 setter 路径一直是对的)。
  • 这是个货真价实的正确性修复,值得在 PR 描述里单列一条(当前 6 条 summary 没有这一项)。混在大 PR 里又不提,影响 changelog / 回滚判断。建议补进描述,或单独成一句 commit message 点明。

[P2] LitePhysicsMaterialconsole.log 报"不支持",与 physics-lite 既有惯例不一致。

新代码把 5 个 setter 从 throw 改成一次性 console.log("Physics-lite don't support physics material...")(LitePhysicsMaterial.ts:66)。改成非抛出是对的(否则 clone _syncNative 重写会崩),但报告通道选错:同包 LiteDynamicCollider 对同类"不支持,请用 PhysX"用的是 Logger.error(...)(LiteDynamicCollider.ts:27/34/42…)。console.log 既不符合 Logger 惯例、严重度也偏低("不支持某能力"应当是 warn/error 级,不是 log 级)。建议改成 Logger.warn(保留一次性去重),与既有 lite 不支持提示对齐。参见 cr 原则"不支持应尽早清晰报错,不静默"。

核对通过项

  • 事务式 recooking 正确(MeshColliderShape.ts set mesh/set cookingFlags、PhysXMeshColliderShape _cookMesh/setMeshData/_updateGeometry):先 cook 新 geometry,setGeometry 包 try/catch,失败则清新资源 + 回滚 _mesh/_positions/_indices + refCount,成功才 release 旧资源。失败不丢已工作的 mesh,公开 mesh/cookingFlags 状态也回滚。无悬挂 native。
  • 重试机制已彻底移除、无悬挂引用_onPhysicsUpdate(base no-op + override)、Collider._onUpdate 的 per-shape 循环、_pendingNativeShapeCreation 字段全删干净(grep 物理目录零残留),_createNativeShape/_updateNativeShapeData 改返 bool。cook-null 直接 return false,终止、不每帧重 cook。
  • clone 重建路径正确PhysicsMaterial._nativeMaterial/MeshColliderShape._nativeShape@ignoreClone,靠 _cloneTo 钩子重建——material _cloneTo_syncNative() 把 5 个属性重写到克隆 native(且 _syncNative 用对了 setFrictionCombine/setBounceCombine 顺序),mesh _cloneTo 用已 deepClone 的 _positions 重 cook。_cloneTo 由 cloner 同步直调(与 #3036 判例一致),clone 测试(sphere 落在克隆地面)从公开 clone() 入口守住这条链。
  • scaled 默认值公式 + 边界校验contactOffset = 0.02 * lengthsleepThreshold = 5e-5 * speed²(PhysXPhysics.ts:354-356),与 PhysX 自身 tolerancesScale 缩放约定一致(接触偏移线性于长度单位、休眠能量阈值二次于速度);_assertPositiveFinite系统边界(用户传入 options)对 length/speed 校验 + 抛错,符合"边界防御"。构造器重载(typeof === "object" 检测 options)向后兼容,干净。
  • _contractOffset → _contactOffset 拼写修复跨 3 处(PhysXColliderShape 字段+setter、PhysXCharacterController 读取)一致修齐,无遗漏。
  • 可选默认字段下沉ColliderShape._contactOffset/DynamicCollider._sleepThresholdnumber | undefined,getter ?? getDefault?.() ?? 硬编码、setter 仅显式设值时 sync native):让 PhysX 按 world scale 管默认而不强制覆盖用户值,符合"shared asset + override 透传"心法。

测试

does not retry non-convex mesh creation on non-kinematic dynamic colliders 名字带旧"retry"措辞(机制已删),但测试本身仍有效:验证终止失败只 console.error 一次、随后 3 tick 不再刷错——守住"终止失败不每帧重报"。可顺手把名字里的 "retry" 去掉以免误导,非阻塞。tolerancesScale drives PhysX default...(length=2→0.04 / speed=20→0.02)公式断言正确。material clone 测试从公开 clone 驱动、跑 40 tick 看克隆体按摩擦回弹,守住 material 同步。

结论

无 P0/P1,两个 [P2](material 参数顺序修复未在描述点明 + lite 用 console.log 不合惯例)均不阻塞,--comment stacked PR,建议随 #3025#3041 → 本 PR 栈顺序合并。补一下 PR 描述里漏的 combine 顺序修复 + lite 改 Logger.warn 即可。

@GuoLei1990 GuoLei1990 marked this pull request as draft July 1, 2026 08:49
@luzhuang luzhuang changed the title fix(physics): rebuild mesh shapes and scaled defaults fix(physics): make mesh rebuilds atomic and scale PhysX defaults Jul 13, 2026
@luzhuang luzhuang force-pushed the fix/physics-mesh-defaults branch from fbffe68 to e6cac5d Compare July 13, 2026 08:08
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Jul 13, 2026
@luzhuang luzhuang changed the base branch from fix/physics-kinematic-sync to dev/2.0 July 13, 2026 08:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants