Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 35 additions & 28 deletions packages/physics-physx/src/PhysXDynamicCollider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export class PhysXDynamicCollider extends PhysXCollider implements IDynamicColli
private static _tempTranslation = new Vector3();
private static _tempRotation = new Quaternion();

private _isKinematic = false;

constructor(physXPhysics: PhysXPhysics, position: Vector3, rotation: Quaternion) {
super(physXPhysics);
const transform = this._transform(position, rotation);
Expand Down Expand Up @@ -160,30 +162,11 @@ export class PhysXDynamicCollider extends PhysXCollider implements IDynamicColli
* {@inheritDoc IDynamicCollider.setCollisionDetectionMode }
*/
setCollisionDetectionMode(value: number): void {
const physX = this._physXPhysics._physX;

switch (value) {
case CollisionDetectionMode.Continuous:
this._pxActor.setRigidBodyFlag(physX.PxRigidBodyFlag.eENABLE_CCD, true);
this._pxActor.setRigidBodyFlag(physX.PxRigidBodyFlag.eENABLE_CCD_FRICTION, false);
this._pxActor.setRigidBodyFlag(physX.PxRigidBodyFlag.eENABLE_SPECULATIVE_CCD, false);
break;
case CollisionDetectionMode.ContinuousDynamic:
this._pxActor.setRigidBodyFlag(physX.PxRigidBodyFlag.eENABLE_CCD, true);
this._pxActor.setRigidBodyFlag(physX.PxRigidBodyFlag.eENABLE_CCD_FRICTION, true);
this._pxActor.setRigidBodyFlag(physX.PxRigidBodyFlag.eENABLE_SPECULATIVE_CCD, false);
break;
case CollisionDetectionMode.ContinuousSpeculative:
this._pxActor.setRigidBodyFlag(physX.PxRigidBodyFlag.eENABLE_CCD, false);
this._pxActor.setRigidBodyFlag(physX.PxRigidBodyFlag.eENABLE_CCD_FRICTION, false);
this._pxActor.setRigidBodyFlag(physX.PxRigidBodyFlag.eENABLE_SPECULATIVE_CCD, true);
break;
case CollisionDetectionMode.Discrete:
this._pxActor.setRigidBodyFlag(physX.PxRigidBodyFlag.eENABLE_CCD, false);
this._pxActor.setRigidBodyFlag(physX.PxRigidBodyFlag.eENABLE_CCD_FRICTION, false);
this._pxActor.setRigidBodyFlag(physX.PxRigidBodyFlag.eENABLE_SPECULATIVE_CCD, false);
break;
}
const effectiveMode =
this._isKinematic && value !== CollisionDetectionMode.ContinuousSpeculative
? CollisionDetectionMode.Discrete
: value;
this._applyCollisionDetectionFlags(effectiveMode);
}

/**
Expand All @@ -197,11 +180,15 @@ export class PhysXDynamicCollider extends PhysXCollider implements IDynamicColli
* {@inheritDoc IDynamicCollider.setIsKinematic }
*/
setIsKinematic(value: boolean): void {
if (value) {
this._pxActor.setRigidBodyFlag(this._physXPhysics._physX.PxRigidBodyFlag.eKINEMATIC, true);
} else {
this._pxActor.setRigidBodyFlag(this._physXPhysics._physX.PxRigidBodyFlag.eKINEMATIC, false);
if (this._isKinematic === value) return;
const physX = this._physXPhysics._physX;
// PhysX rejects swept CCD when eKINEMATIC is raised.
if (value && this._pxActor.getRigidBodyFlags(physX.PxRigidBodyFlag.eENABLE_CCD)) {
this._pxActor.setRigidBodyFlag(physX.PxRigidBodyFlag.eENABLE_CCD, false);
this._pxActor.setRigidBodyFlag(physX.PxRigidBodyFlag.eENABLE_CCD_FRICTION, false);
}
this._pxActor.setRigidBodyFlag(physX.PxRigidBodyFlag.eKINEMATIC, value);
this._isKinematic = value;
}

/**
Expand Down Expand Up @@ -271,4 +258,24 @@ export class PhysXDynamicCollider extends PhysXCollider implements IDynamicColli
wakeUp(): void {
return this._pxActor.wakeUp();
}

private _applyCollisionDetectionFlags(value: number): void {
const physX = this._physXPhysics._physX;
const enableCCD = value === CollisionDetectionMode.Continuous || value === CollisionDetectionMode.ContinuousDynamic;
// PhysX rejects enabling swept and speculative CCD at the same time.
if (enableCCD) {
this._pxActor.setRigidBodyFlag(physX.PxRigidBodyFlag.eENABLE_SPECULATIVE_CCD, false);
}
this._pxActor.setRigidBodyFlag(physX.PxRigidBodyFlag.eENABLE_CCD, enableCCD);
this._pxActor.setRigidBodyFlag(
physX.PxRigidBodyFlag.eENABLE_CCD_FRICTION,
value === CollisionDetectionMode.ContinuousDynamic
);
if (!enableCCD) {
this._pxActor.setRigidBodyFlag(
physX.PxRigidBodyFlag.eENABLE_SPECULATIVE_CCD,
value === CollisionDetectionMode.ContinuousSpeculative
);
}
}
}
67 changes: 67 additions & 0 deletions tests/src/core/physics/DynamicCollider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,21 @@ describe("DynamicCollider", function () {
return Math.round(value * 100000) / 100000;
}

function getPhysXRigidBody(collider: DynamicCollider) {
const nativeCollider = collider._nativeCollider as any;
return {
actor: nativeCollider._pxActor,
flags: nativeCollider._physXPhysics._physX.PxRigidBodyFlag
};
}

function expectFlagDisabledBeforeEnabled(calls: unknown[][], disabledFlag: unknown, enabledFlag: unknown) {
const disableCallIndex = calls.findIndex(([flag, enabled]) => flag === disabledFlag && enabled === false);
const enableCallIndex = calls.findIndex(([flag, enabled]) => flag === enabledFlag && enabled === true);
expect(disableCallIndex).toBeGreaterThanOrEqual(0);
expect(enableCallIndex).toBeGreaterThan(disableCallIndex);
}

beforeAll(async function () {
engine = await WebGLEngine.create({
canvas: document.createElement("canvas"),
Expand Down Expand Up @@ -503,6 +518,58 @@ describe("DynamicCollider", function () {
).toBeTruthy();
});

it("defers swept CCD across kinematic transitions", function () {
const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0));
const boxCollider = box.getComponent(DynamicCollider);
const { actor, flags } = getPhysXRigidBody(boxCollider);
const setRigidBodyFlag = vi.spyOn(actor, "setRigidBodyFlag");
const ccdFlag = () => actor.getRigidBodyFlags(flags.eENABLE_CCD);

boxCollider.collisionDetectionMode = CollisionDetectionMode.Continuous;
expect(ccdFlag()).toBeTruthy();

setRigidBodyFlag.mockClear();
boxCollider.isKinematic = true;
expect(ccdFlag()).toBeFalsy();
expectFlagDisabledBeforeEnabled(setRigidBodyFlag.mock.calls, flags.eENABLE_CCD, flags.eKINEMATIC);

setRigidBodyFlag.mockClear();
boxCollider.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;
expect(setRigidBodyFlag).not.toHaveBeenCalledWith(flags.eENABLE_CCD, true);
expect(ccdFlag()).toBeFalsy();
expect(boxCollider.collisionDetectionMode).toEqual(CollisionDetectionMode.ContinuousDynamic);

boxCollider.isKinematic = false;
expect(ccdFlag()).toBeTruthy();
expect(actor.getRigidBodyFlags(flags.eENABLE_CCD_FRICTION)).toBeTruthy();
});

it("switches between swept and speculative CCD without overlapping flags", function () {
const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0));
const boxCollider = box.getComponent(DynamicCollider);
const { actor, flags } = getPhysXRigidBody(boxCollider);
const setRigidBodyFlag = vi.spyOn(actor, "setRigidBodyFlag");
const ccdFlag = () => actor.getRigidBodyFlags(flags.eENABLE_CCD);
const speculativeCCDFlag = () => actor.getRigidBodyFlags(flags.eENABLE_SPECULATIVE_CCD);

boxCollider.collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative;
expect(speculativeCCDFlag()).toBeTruthy();

setRigidBodyFlag.mockClear();
boxCollider.collisionDetectionMode = CollisionDetectionMode.Continuous;
expectFlagDisabledBeforeEnabled(setRigidBodyFlag.mock.calls, flags.eENABLE_SPECULATIVE_CCD, flags.eENABLE_CCD);
expect(ccdFlag()).toBeTruthy();
expect(speculativeCCDFlag()).toBeFalsy();

setRigidBodyFlag.mockClear();
boxCollider.collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative;
expectFlagDisabledBeforeEnabled(setRigidBodyFlag.mock.calls, flags.eENABLE_CCD, flags.eENABLE_SPECULATIVE_CCD);
expect(speculativeCCDFlag()).toBeTruthy();

boxCollider.isKinematic = true;
expect(speculativeCCDFlag()).toBeTruthy();
});

it("sleep", function () {
const box = addBox(new Vector3(2, 2, 2), DynamicCollider, new Vector3(0, 0, 0));
const boxCollider = box.getComponent(DynamicCollider);
Expand Down
Loading