diff --git a/packages/core/src/physics/DynamicCollider.ts b/packages/core/src/physics/DynamicCollider.ts index 380a3b927f..5c8725c604 100644 --- a/packages/core/src/physics/DynamicCollider.ts +++ b/packages/core/src/physics/DynamicCollider.ts @@ -359,6 +359,15 @@ export class DynamicCollider extends Collider { this._phasedActiveInScene && (this._nativeCollider).addForce(force); } + /** + * Apply a force to the DynamicCollider at a given position in world space. + * @param force - The force to apply, in world space + * @param position - The position where the force is applied, in world space + */ + applyForceAtPosition(force: Vector3, position: Vector3): void { + this._phasedActiveInScene && (this._nativeCollider).addForceAtPosition(force, position); + } + /** * Apply a torque to the DynamicCollider. * @param torque - The force make the collider rotate diff --git a/packages/design/src/physics/IDynamicCollider.ts b/packages/design/src/physics/IDynamicCollider.ts index f729382d87..1900c37b5a 100644 --- a/packages/design/src/physics/IDynamicCollider.ts +++ b/packages/design/src/physics/IDynamicCollider.ts @@ -146,6 +146,13 @@ export interface IDynamicCollider extends ICollider { */ addForce(force: Vector3): void; + /** + * Apply a force to the dynamic collider at a world-space position. + * @param force - The force make the collider move + * @param position - The world-space position where the force is applied + */ + addForceAtPosition(force: Vector3, position: Vector3): void; + /** * Apply a torque to the dynamic collider. * @param torque - The force make the collider rotate diff --git a/packages/physics-lite/src/LiteDynamicCollider.ts b/packages/physics-lite/src/LiteDynamicCollider.ts index dae27bf3ab..6a7e37a08b 100644 --- a/packages/physics-lite/src/LiteDynamicCollider.ts +++ b/packages/physics-lite/src/LiteDynamicCollider.ts @@ -49,6 +49,13 @@ export class LiteDynamicCollider extends LiteCollider implements IDynamicCollide throw "Physics-lite don't support addForce. Use Physics-PhysX instead!"; } + /** + * {@inheritDoc IDynamicCollider.addForceAtPosition } + */ + addForceAtPosition(force: Vector3, position: Vector3): void { + throw "Physics-lite don't support addForceAtPosition. Use Physics-PhysX instead!"; + } + /** * {@inheritDoc IDynamicCollider.addTorque } */ diff --git a/packages/physics-physx/libs/physx.release.simd.wasm b/packages/physics-physx/libs/physx.release.simd.wasm index f52b48110a..84cbbc001b 100755 Binary files a/packages/physics-physx/libs/physx.release.simd.wasm and b/packages/physics-physx/libs/physx.release.simd.wasm differ diff --git a/packages/physics-physx/libs/physx.release.wasm b/packages/physics-physx/libs/physx.release.wasm index 3efb26a288..9144b8181b 100755 Binary files a/packages/physics-physx/libs/physx.release.wasm and b/packages/physics-physx/libs/physx.release.wasm differ diff --git a/packages/physics-physx/src/PhysXDynamicCollider.ts b/packages/physics-physx/src/PhysXDynamicCollider.ts index 31510ae2de..207795ec32 100644 --- a/packages/physics-physx/src/PhysXDynamicCollider.ts +++ b/packages/physics-physx/src/PhysXDynamicCollider.ts @@ -215,14 +215,21 @@ export class PhysXDynamicCollider extends PhysXCollider implements IDynamicColli * {@inheritDoc IDynamicCollider.addForce } */ addForce(force: Vector3) { - this._pxActor.addForce({ x: force.x, y: force.y, z: force.z }); + this._pxActor.addForce(force); + } + + /** + * {@inheritDoc IDynamicCollider.addForceAtPosition } + */ + addForceAtPosition(force: Vector3, position: Vector3) { + this._pxActor.addForceAtPos(force, position); } /** * {@inheritDoc IDynamicCollider.addTorque } */ addTorque(torque: Vector3) { - this._pxActor.addTorque({ x: torque.x, y: torque.y, z: torque.z }); + this._pxActor.addTorque(torque); } /** diff --git a/packages/physics-physx/src/PhysXPhysics.ts b/packages/physics-physx/src/PhysXPhysics.ts index 6c5d788aa1..8bcf8d510a 100644 --- a/packages/physics-physx/src/PhysXPhysics.ts +++ b/packages/physics-physx/src/PhysXPhysics.ts @@ -67,10 +67,10 @@ export class PhysXPhysics implements IPhysics { this._runTimeMode = runtimeMode; this._wasmSIMDModeUrl = runtimeUrls?.wasmSIMDModeUrl ?? - "https://mdn.alipayobjects.com/rms/afts/file/A*iHrYQKBrgTAAAAAAQ4AAAAgAehQnAQ/physx.release.simd.js"; + "https://mdn.alipayobjects.com/rms/uri/file/as/apwallet/1781696156399/suyi/physx.release.simd.js"; this._wasmModeUrl = runtimeUrls?.wasmModeUrl ?? - "https://mdn.alipayobjects.com/rms/afts/file/A*DFuvR6Mv5C0AAAAAQ4AAAAgAehQnAQ/physx.release.js"; + "https://mdn.alipayobjects.com/rms/uri/file/as/apwallet/1781696156399/suyi/physx.release.js"; } /** diff --git a/tests/src/core/physics/DynamicCollider.test.ts b/tests/src/core/physics/DynamicCollider.test.ts index ecde06ef3d..0ed815b9ca 100644 --- a/tests/src/core/physics/DynamicCollider.test.ts +++ b/tests/src/core/physics/DynamicCollider.test.ts @@ -10,10 +10,14 @@ import { PlaneColliderShape } from "@galacean/engine-core"; import { WebGLEngine } from "@galacean/engine"; -import { PhysXPhysics } from "@galacean/engine-physics-physx"; +import { PhysXPhysics, PhysXRuntimeMode } from "@galacean/engine-physics-physx"; import { Vector3 } from "@galacean/engine-math"; import { vi, describe, beforeAll, beforeEach, expect, it } from "vitest"; +const physXWasmModeUrl = new URL("../../../../packages/physics-physx/libs/physx.release.js", import.meta.url).href; +const physXWasmSIMDModeUrl = new URL("../../../../packages/physics-physx/libs/physx.release.simd.js", import.meta.url) + .href; + describe("DynamicCollider", function () { let engine: Engine; let rootEntity: Entity; @@ -53,7 +57,13 @@ describe("DynamicCollider", function () { } beforeAll(async function () { - engine = await WebGLEngine.create({ canvas: document.createElement("canvas"), physics: new PhysXPhysics() }); + engine = await WebGLEngine.create({ + canvas: document.createElement("canvas"), + physics: new PhysXPhysics(PhysXRuntimeMode.Auto, { + wasmModeUrl: physXWasmModeUrl, + wasmSIMDModeUrl: physXWasmSIMDModeUrl + }) + }); rootEntity = engine.sceneManager.activeScene.createRootEntity("root"); }); @@ -290,6 +300,57 @@ describe("DynamicCollider", function () { expect(formatValue(boxCollider.angularVelocity.y)).eq(200); }); + it("applyForceAtPosition delegates world-space force and position to the native collider", function () { + const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0)); + const boxCollider = box.getComponent(DynamicCollider); + const nativeCollider = (boxCollider as any)._nativeCollider; + const force = new Vector3(1, 2, 3); + const position = new Vector3(4, 5, 6); + const addForceAtPositionSpy = vi.spyOn(nativeCollider, "addForceAtPosition").mockImplementation(() => {}); + + boxCollider.applyForceAtPosition(force, position); + + expect(addForceAtPositionSpy).toHaveBeenCalledOnce(); + expect(addForceAtPositionSpy).toHaveBeenCalledWith(force, position); + }); + + it("applyForceAtPosition produces the same motion as an equivalent force and torque", function () { + const setupBox = () => { + const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0)); + const collider = box.getComponent(DynamicCollider); + collider.mass = 1; + collider.useGravity = false; + collider.centerOfMass = new Vector3(0, 0, 0); + collider.inertiaTensor = new Vector3(1, 1, 1); + return collider; + }; + + const force = new Vector3(1, 0, 0); + const position = new Vector3(0, 1, 0); + + const reference = setupBox(); + reference.applyForce(force); + reference.applyTorque(new Vector3(0, 0, -1)); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1); + const referenceLinearVelocity = reference.linearVelocity.clone(); + const referenceAngularVelocity = reference.angularVelocity.clone(); + + rootEntity.clearChildren(); + + const target = setupBox(); + target.applyForceAtPosition(force, position); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1); + + expect(formatValue(target.linearVelocity.x)).eq(formatValue(referenceLinearVelocity.x)); + expect(formatValue(target.linearVelocity.y)).eq(formatValue(referenceLinearVelocity.y)); + expect(formatValue(target.linearVelocity.z)).eq(formatValue(referenceLinearVelocity.z)); + expect(formatValue(target.angularVelocity.x)).eq(formatValue(referenceAngularVelocity.x)); + expect(formatValue(target.angularVelocity.y)).eq(formatValue(referenceAngularVelocity.y)); + expect(formatValue(target.angularVelocity.z)).eq(formatValue(referenceAngularVelocity.z)); + }); + it("maxDepenetrationVelocity", function () { const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0)); const box2 = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0)); diff --git a/tests/src/core/physics/PhysicsScene.test.ts b/tests/src/core/physics/PhysicsScene.test.ts index bc215b1315..30348c9d0a 100644 --- a/tests/src/core/physics/PhysicsScene.test.ts +++ b/tests/src/core/physics/PhysicsScene.test.ts @@ -138,6 +138,34 @@ describe("Physics Test", () => { engineLite.run(); }); + it("dynamic force APIs report unsupported behavior", () => { + const root = engineLite.sceneManager.activeScene.createRootEntity("root"); + const collider = root.addComponent(DynamicCollider); + const force = new Vector3(1, 0, 0); + const unsupportedOperations: Array<[string, () => unknown]> = [ + ["addForce", () => collider.applyForce(force)], + ["addForceAtPosition", () => collider.applyForceAtPosition(force, new Vector3(0, 1, 0))], + ["addTorque", () => collider.applyTorque(force)], + ["putToSleep", () => collider.sleep()], + ["wakeUp", () => collider.wakeUp()], + ["isSleeping", () => collider.isSleeping()] + ]; + + for (const [method, operation] of unsupportedOperations) { + let error: unknown; + + try { + operation(); + } catch (caughtError) { + error = caughtError; + } + + expect(error).eq(`Physics-lite don't support ${method}. Use Physics-PhysX instead!`); + } + + root.destroy(); + }); + it("removeShape", () => { const scene = engineLite.sceneManager.activeScene; const root = scene.createRootEntity("root");