Fix/shaderlab#2983
Conversation
…tin functions texture(sampler2D, vec2) returns GVec4 which was incorrectly resolved to the sampler type instead of vec4, causing "No overload function type found" when passing the result to user-defined functions like decode32(vec4). Add resolveGenericReturnType() to correctly map GSampler* → GVec4: sampler2D/sampler3D/samplerCube → vec4 isampler2D/isampler3D/... → ivec4 usampler2D/usampler3D/... → uvec4
…exture2DLod signatures - Simplify resolveGenericReturnType: remove genericParamType param, only check if return type is GVec4 - Fix textureCube/textureCubeLod return type: SAMPLER_CUBE → VEC4 - Add missing texture2DLod builtin function registration - Add texture2DLod test cases to texture-generic.shader
* feat: implement HorizontalBillboard render mode
… type When a builtin generic function (e.g. normalize) receives TypeAny args, resolvedReturnType stays TypeAny. Previously the else branch returned the raw EGenType enum value (200), which is neither a concrete type nor a wildcard, causing downstream user-function overload matching to fail.
…tin functions texture(sampler2D, vec2) returns GVec4 which was incorrectly resolved to the sampler type instead of vec4, causing "No overload function type found" when passing the result to user-defined functions like decode32(vec4). Add resolveGenericReturnType() to correctly map GSampler* → GVec4: sampler2D/sampler3D/samplerCube → vec4 isampler2D/isampler3D/... → ivec4 usampler2D/usampler3D/... → uvec4
…exture2DLod signatures - Simplify resolveGenericReturnType: remove genericParamType param, only check if return type is GVec4 - Fix textureCube/textureCubeLod return type: SAMPLER_CUBE → VEC4 - Add missing texture2DLod builtin function registration - Add texture2DLod test cases to texture-generic.shader
… type When a builtin generic function (e.g. normalize) receives TypeAny args, resolvedReturnType stays TypeAny. Previously the else branch returned the raw EGenType enum value (200), which is neither a concrete type nor a wildcard, causing downstream user-function overload matching to fail.
…ing CodeGen When #define values contain struct member access like `o.v_uv` (where `o` is a Varyings/Attributes/MRT struct variable), the CodeGen now correctly transforms them to just the property name (e.g. `v_uv`), matching the behavior of direct struct member access in regular code. Closes #2944.
…uct-access Verify the actual CodeGen output instead of just checking GLSL compilation: - #define values with struct member access are correctly transformed - varying/attribute declarations are emitted for referenced properties
…ions Add assertions for macro usage in expressions (not just #define transformation): - Macro as RHS in multiplication, as LHS in assignment - Macro as function argument in dot(), texture2D() - Multiple varying properties (v_uv, v_normal) referenced via #define
…ransform Build a combined _globalStructVarMap in visitShaderProgram by scanning both vertex and fragment entry functions, so global #define values like `attr.POSITION` or `o.v_uv` are correctly transformed in all stages. Rewrite define-struct-access tests to use snapshot file comparison against expected/ GLSL outputs for clearer verification.
…cro as builtin arg
…cro scanning - Suppress `uniform` output for global struct-typed variables (e.g. `Varyings o;`) - Register global struct vars in both per-function and cross-stage maps - Unify macro member access scanning into callback-based _forEachMacroMemberAccess - Add registerStructVar() encapsulation in VisitorContext - Add Cocos VSOutput pattern test (global-varying-var)
…version GLES100 visitJumpStatement converted `return expr;` to `gl_FragColor = expr` without a trailing semicolon, causing WebGL compilation errors. Only triggered when fragment entry returns vec4 (Cocos pattern), not void (standard Galacean).
…eprocessor #if !0 and similar expressions now work correctly, matching C/GLSL preprocessor behavior.
…atrix The viewMatrix getter intentionally ignores the camera entity's world scale (using Matrix.rotationTranslation), but _getInvViewProjMat() was using the full entity.transform.worldMatrix which includes inherited scale. This inconsistency causes screenPointToRay to produce incorrect world-space rays when the camera inherits scale from a parent entity (e.g. UICanvas in ScreenSpaceCamera mode with camera as a child of canvas). Closes #2748 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Verify that screenPointToRay and viewport-world round-trip produce correct results when the camera inherits non-identity scale from a parent entity. Without the fix, the round-trip deviates by the inherited scale factor (e.g. 105 -> 107.5 at scale 1.5). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ReflectionParser 解析 IComponentRef(entityPath + componentType)时, 目标组件可能在 GLB clone 的子 entity 上而非 clone 根 entity 自身。 getComponents 找不到时 fallback 到 getComponentsIncludeChildren。
CloneManager: 当 source 和 target 属性是同类型 Object 实例(如 Vector4)时, 自动升级为 DeepClone,避免 prefab 模板的引用覆盖克隆体的独立实例。 新增 Map/Set 类型的 deep clone 支持。 ModelMesh: throw string 改为 throw Error 以获得正确堆栈。
AnimatorState.speed is part of the shared AnimatorController asset. Modifying it at runtime pollutes all Animator instances sharing the same controller, causing animation speed corruption after cloning. - Add speed field to AnimatorStatePlayData, initialized from AnimatorState.speed on reset - Add proxy properties (name/clip/wrapMode/transitions/addStateMachineScript) - Change speed calculation to playData.speed * animator.speed - findAnimatorState now returns per-instance AnimatorStatePlayData - Export AnimatorStatePlayData for consumer code
When sizeMode is set to Automatic, the UITransform size is automatically synchronized to the sprite's natural dimensions when the sprite changes. This matches Cocos Creator's Sprite.SizeMode.TRIMMED behavior. - Add SpriteSizeMode enum (Custom / Automatic) - Add sizeMode property to Image with getter/setter - Sync UITransform.size in set sprite and _onSpriteChange - Export SpriteSizeMode from component index
Four independent bugs all share the same root pattern — "JS field is the source of truth, native handle synced via setter side-effects" — fails whenever a code path bypasses the setter: - **R0** kinematic + CCD warning: setRigidBodyFlag(eENABLE_CCD,true) on a kinematic actor prints a PhysX warning and the flag becomes undefined when switching back to dynamic. Cache `_isKinematic` and `_collisionDetectionMode`; apply CCD flags only in dynamic state; on kinematic→dynamic transition, reapply the cached mode. - **R5** addForce/addTorque silently ignored on sleeping actors: PhysX wasm wrapper omits the `autowake` parameter (C++ default true, wasm default false). Explicitly `wakeUp()` before adding force/torque; guard kinematic to avoid triggering a separate PhysX warning. - **R6** MeshColliderShape async cook & prefab clone: `_cookMesh` can fail transiently when mesh data is not yet accessible; the cloned shape's `_nativeShape` is `@ignoreClone` so prefab instantiation produces a useless shape. Add `_pendingNativeShapeCreation` flag; retry every physics tick via new `ColliderShape._onPhysicsUpdate` hook (driven by `Collider._onUpdate`); override `_cloneTo` to cook a fresh native shape from already-cloned buffers. - **R8** PhysicsMaterial clone bypasses setters: CloneManager deep-copies `_bounciness/_friction/_combine` fields directly without invoking setters, so the target's freshly-constructed PxMaterial keeps its default values while JS fields show source values. Result: PhysX uses default `b=0` while logs and `material.bounciness` read source values — fully invisible divergence. Mark `_nativeMaterial` `@ignoreClone`; add `_cloneTo` → `_syncNative()` that re-writes all 5 fields to native via setter API. Side fix: PhysicsMaterial constructor was passing `bounceCombine` and `frictionCombine` to `createPhysicsMaterial` in reverse order — the bug was hidden because R8 kept native stuck at default Average (both reversed arguments are 0) and users only ever read JS fields. Fixed in the same patch. Test: `PhysicsMaterial.test.ts > cloned collider shape material keeps native values` verifies a cloned dynamic body bounces off a wall by simulating 40 frames and asserting vx flips negative — fails without the R8 fix.
Add Unity-style applyForceAtPosition(force, worldPosition) to apply a force at an arbitrary world-space point, producing both linear acceleration through the center of mass and angular acceleration about it. Internally decomposes into the textbook (F, τ = r × F) pair: worldCoM = entity.worldPos + entity.worldRot · localCoM (from native) r = worldPosition - worldCoM addForce(F); addTorque(r × F) Equivalent to Cocos `RigidBody.applyForce(force, relativePoint)` (Bullet's implementation is the same cross product); enables direct migration of billiards-style "hit at an offset point" gameplay without manual r × F math. Pure TS-layer composition over existing addForce/addTorque — no design interface change, no native binding change (physX.js untouched). Tests (6 cases, orthogonal coverage of all worldCoM computation terms): 1. force at CoM → only linear acceleration 2. r ≠ 0 produces torque = r × F, validated via differential vs applyTorque(τ) 3. CoM offset + force at world CoM → r = 0 → no torque 4. Entity rotated 90° + CoM offset → worldRot correctly transforms localCoM 5. (position + rotation + CoM offset + r ≠ 0) — full stress test catching any missing term in worldCoM = pos + rot·localCoM 6. After entity.clone() — defends prefab instantiation path (R6/R8 territory)
… root cause Previous commit 273d181 claimed "PhysX wasm wrapper's addForce doesn't pass the autowake parameter, so calls on sleeping actors are silently ignored". This claim is incorrect — the wasm binding explicitly passes autowake=true: body.addForce(force, PxForceMode::eFORCE, true); PhysX 4.1.1 doc confirms: "If true, this call wakes up the actor if it is sleeping." Two new tests verify autowake works as documented in wasm: - "applyForce on sleeping actor must wake up and apply force" → sleep() → applyForce (no explicit wakeUp) → force is applied, actor wakes - "applyForce after kinematic→dynamic switch (mimic billiards game break flow)" → reproduces the exact game scenario where 'force seemed lost' originally Both pass without the explicit wakeUp() call. The original 'applyForce lost' symptom in the billiards game was actually caused by other concurrent bugs (R1 collisionMatrix, R8 PhysicsMaterial, fixedTimeStep mismatch) that were each masking the real cause, not by sleeping-actor behavior. Keep the `_isKinematic` guard: PhysX makes addForce a no-op on kinematic actors (documented), but the guard avoids the wasm boundary cross.
…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.
…lone After fixing R0/R5/R6/R8 in commits 273d181 and 9c32650, run a RED-GREEN verification on each: temporarily revert the fix, run the test, confirm it fails; restore the fix, confirm it passes. Outcome reshaped what we thought the root causes were. R6 (MeshColliderShape clone) — real bug fix, RED verified `R6: cloned MeshColliderShape rebuilds its native PhysX shape` — With `_cloneTo` override removed, `clonedShape._nativeShape` is null and the cloned ground entity is not physically present (sphere falls through). With the fix, sphere lands on the cloned ground (sphereY > -1). R0 (kinematic + CCD) — defensive, not a bug fix `R0: CCD mode survives kinematic toggle (PhysX rejects CCD on kinematic)` and `R0: setCollisionDetectionMode in kinematic state defers application` — Both pass even with the R0 fix reverted. PhysX 4.1.1 already manages the CCD↔kinematic flag relation correctly on its own. R0's real value is: 1. Suppressing the PhysX warning that fires when CCD-on actors switch to kinematic (or vice-versa). 2. Preserving user intent — `collisionDetectionMode` getter returns the last user-set mode even while temporarily kinematic; on dynamic restore, CCD is reapplied without the user having to remember. These tests are kept as contract tests, not red-green regression tests. R5's wakeUp call was already removed in 9c32650 after its red test showed PhysX wasm `addForce(force, eFORCE, autowake=true)` wakes sleeping actors on its own — no separate test needed here. Verification: full physics suite 216/216 passing.
GuoLei1990
left a comment
There was a problem hiding this comment.
总结
本轮 PR 已经历大规模 rebase,diff 收敛到 221 个文件(packages/ 141 个,tests/ 36 个,e2e/ 21 个)。物理/UI clone 等子模块独立改动已包含在内,并有配套测试。主要新增内容:GLTFSceneParser 始终包装 GLTF_ROOT、AnimatorStatePlayData per-instance speed、UI RectMask2D / Mask 新组件、CloneManager 自动深克隆推断。
问题
[P0] e2e/config.ts 删除后 e2e/tests/index.spec.ts 仍然 import "../config" — CI 必然报错
PR diff 将 e2e/config.ts 整体删除(-518 行),但 e2e/tests/index.spec.ts(SHA 未变)仍有:
import { E2E_CONFIG } from "../config";
// ...
Object.entries(E2E_CONFIG).forEach(...)合并后 e2e/tests/ 目录下找不到 ../config,playwright 跑 e2e suite 时会立即 import 失败,测试无法启动。e2e/global-setup.ts 虽然有 try-catch 降级(文件不存在时打印警告并继续),但 tests/index.spec.ts 没有任何降级,是硬依赖。
请二选一:
- 保留
e2e/config.ts(或迁移为新格式),并更新tests/index.spec.ts的 import 路径 - 如果 e2e 测试体系已整体迁移,同步删除
tests/index.spec.ts或重写为不依赖 config 的格式
[P1] UIPointerEventEmitter._bubble — 冒泡结束后 currentTarget 未清空(已提 4 轮,持续未修复)
private _bubble(path: Entity[], pointer: Pointer, fireEvent: FireEvent): void {
const length = path.length;
if (length <= 0) return;
const eventData = this._createEventData(pointer, path[0]);
for (let i = 0; i < length; i++) {
eventData.currentTarget = path[i];
fireEvent(path[i], eventData);
}
// 缺少:eventData.currentTarget = null;
}循环结束后 currentTarget 停留在 path[length - 1]。DOM 标准在 dispatch 结束后将 currentTarget 设为 null,请在循环后补一行:
eventData.currentTarget = null;[P1] Collider._onUpdate — _onPhysicsUpdate 每帧对所有 shape 无条件调用(已提 2 轮,持续未修复)
// packages/core/src/physics/Collider.ts
for (let i = 0, n = shapes.length; i < n; i++) {
shapes[i]._onPhysicsUpdate();
}基类 ColliderShape._onPhysicsUpdate() 是空函数,仅 MeshColliderShape 且 _pendingNativeShapeCreation=true 时才有实际逻辑。每帧对每个 shape 执行虚调用是可避免的热路径开销。
最低成本修复:在 Collider 上加一个标记 _hasPendingShapeUpdate,仅在 MeshColliderShape 设置 _pendingNativeShapeCreation = true 时置为 true,创建成功后清除,_onUpdate 用该标记做 guard:
if (this._hasPendingShapeUpdate) {
for (let i = 0, n = shapes.length; i < n; i++) {
shapes[i]._onPhysicsUpdate();
}
}[P1] findAnimatorState 返回类型从 AnimatorState 改为 AnimatorStatePlayData — Breaking API Change,且"state 未在播放"时返回值语义不明确
// 新实现
findAnimatorState(stateName: string, layerIndex: number = -1): AnimatorStatePlayData {
const { state, layerIndex: foundLayer } = this._getAnimatorStateInfo(stateName, layerIndex);
if (!state || foundLayer < 0) return null;
// ...
if (layerData.srcPlayData.state === state) return layerData.srcPlayData;
if (layerData.destPlayData.state === state) return layerData.destPlayData;
// State exists in controller but not currently playing — return srcPlayData initialized with the state
return layerData.srcPlayData; // ← 当 state 未在播放时,返回当前正在播放的 srcPlayData!
}当目标 state 存在于 controller 但当前未播放时(比如用户通过 findAnimatorState("Idle") 想预先修改 speed),返回的是当前正在播放的 srcPlayData(其 .state 可能是完全不同的动画)。这不仅语义错误,还会让用户误修改了正在播放的 state 的 speed。
建议:未在播放时返回 null,并在 JSDoc 中说明这一点;若需要支持预修改未播放状态的 speed,应提供独立 API。
另外,返回类型从 AnimatorState 改为 AnimatorStatePlayData 是 breaking change,需要 changelog 标注。
简化建议
DynamicCollider.applyForceAtPosition 缺少 isKinematic guard(已提 1 轮)
applyForceAtPosition(force: Vector3, position: Vector3): void {
if (!this._phasedActiveInScene) return;
const nativeCollider = <IDynamicCollider>this._nativeCollider;
const localCoM = DynamicCollider._tempVector3;
nativeCollider.getCenterOfMass(localCoM); // ← kinematic 时这里仍会执行
// ...
nativeCollider.addForce(force); // PhysX 层 _isKinematic guard 会拦截
nativeCollider.addTorque(torque); // 同上
}getCenterOfMass 跨 WASM 边界的调用在 kinematic actor 上是无意义的。建议和 addForce/addTorque 保持一致,在开头加 _isKinematic guard:
if (!this._phasedActiveInScene || this._isKinematic) return;[P2] PointerEventData.target / currentTarget 类型声明不含 null(已提 4 轮,持续未修复)
target: Entity = null;
currentTarget: Entity = null;应改为:
target: Entity | null = null;
currentTarget: Entity | null = null;
Please check if the PR fulfills these requirements
What kind of change does this PR introduce? (Bug fix, feature, docs update, ...)
What is the current behavior? (You can also link to an open issue here)
What is the new behavior (if this is a feature change)?
Does this PR introduce a breaking change? (What changes might users need to make in their application due to this PR?)
Other information: