diff --git a/packages/core/src/physics/CharacterController.ts b/packages/core/src/physics/CharacterController.ts index a4d1b49ccc..d96eae2048 100644 --- a/packages/core/src/physics/CharacterController.ts +++ b/packages/core/src/physics/CharacterController.ts @@ -5,7 +5,7 @@ import { Collider } from "./Collider"; import { PhysicsScene } from "./PhysicsScene"; import { ControllerNonWalkableMode } from "./enums/ControllerNonWalkableMode"; import { ColliderShape } from "./shape"; -import { deepClone } from "../clone/CloneManager"; +import { deepClone, ignoreClone } from "../clone/CloneManager"; /** * The character controllers. @@ -166,6 +166,7 @@ export class CharacterController extends Collider { (this._nativeCollider).getWorldPosition(this.entity.transform.worldPosition); } + @ignoreClone private _setUpDirection(): void { (this._nativeCollider).setUpDirection(this._upDirection); } diff --git a/packages/core/src/physics/Collision.ts b/packages/core/src/physics/Collision.ts index db6497bf10..5638a35980 100644 --- a/packages/core/src/physics/Collision.ts +++ b/packages/core/src/physics/Collision.ts @@ -1,5 +1,47 @@ +import { ContactPoint } from "./ContactPoint"; import { ColliderShape } from "./shape"; +import { ICollision } from "@galacean/engine-design"; +/** + * Collision information between two shapes when they collide. + */ export class Collision { + /** @internal */ + _nativeCollision: ICollision; + + /** The target shape be collided. */ shape: ColliderShape; + + /** + * Count of contact points. + */ + get contactCount(): number { + return this._nativeCollision.contactCount; + } + + /** + * Get contact points. + * @param outContacts - The result of contact points + * @returns The actual count of contact points + * + * @remarks To optimize performance, the engine does not modify the length of the array you pass. + * You need to obtain the actual number of contact points from the function's return value. + */ + getContacts(outContacts: ContactPoint[]): number { + const nativeCollision = this._nativeCollision; + const factor = nativeCollision.shape0Id < nativeCollision.shape1Id ? 1 : -1; + + const nativeContactPoints = nativeCollision.getContacts(); + const length = nativeContactPoints.size(); + for (let i = 0; i < length; i++) { + const nativeContractPoint = nativeContactPoints.get(i); + + const contact = (outContacts[i] ||= new ContactPoint()); + contact.position.copyFrom(nativeContractPoint.position); + contact.normal.copyFrom(nativeContractPoint.normal).scale(factor); + contact.impulse.copyFrom(nativeContractPoint.impulse).scale(factor); + contact.separation = nativeContractPoint.separation; + } + return length; + } } diff --git a/packages/core/src/physics/ContactPoint.ts b/packages/core/src/physics/ContactPoint.ts new file mode 100644 index 0000000000..ac24ab3ae8 --- /dev/null +++ b/packages/core/src/physics/ContactPoint.ts @@ -0,0 +1,15 @@ +import { Vector3 } from "@galacean/engine-math"; + +/** + * Describes a contact point where the collision occurs. + */ +export class ContactPoint { + /** The position of the contact point between the shapes, in world space. */ + readonly position = new Vector3(); + /** The normal of the contacting surfaces at the contact point. The normal direction points from the second shape to the first shape. */ + readonly normal = new Vector3(); + /** The impulse applied at the contact point, in world space. Divide by the simulation time step to get a force value. */ + readonly impulse = new Vector3(); + /** The separation of the shapes at the contact point. A negative separation denotes a penetration. */ + separation: number; +} diff --git a/packages/core/src/physics/PhysicsScene.ts b/packages/core/src/physics/PhysicsScene.ts index 251532dad7..dea373a767 100644 --- a/packages/core/src/physics/PhysicsScene.ts +++ b/packages/core/src/physics/PhysicsScene.ts @@ -1,4 +1,4 @@ -import { ICharacterController, ICollider, IPhysics, IPhysicsScene } from "@galacean/engine-design"; +import { ICharacterController, ICollider, IPhysics, IPhysicsScene, ICollision } from "@galacean/engine-design"; import { MathUtil, Ray, Vector3 } from "@galacean/engine-math"; import { Layer } from "../Layer"; import { Scene } from "../Scene"; @@ -28,14 +28,16 @@ export class PhysicsScene { private _gravity: Vector3 = new Vector3(0, -9.81, 0); private _nativePhysicsScene: IPhysicsScene; - private _onContactEnter = (obj1: number, obj2: number) => { + private _onContactEnter = (nativeCollision: ICollision) => { const physicalObjectsMap = Engine._physicalObjectsMap; - const shape1 = physicalObjectsMap[obj1]; - const shape2 = physicalObjectsMap[obj2]; + const { shape0Id, shape1Id } = nativeCollision; + const shape1 = physicalObjectsMap[shape0Id]; + const shape2 = physicalObjectsMap[shape1Id]; + const collision = PhysicsScene._collision; + collision._nativeCollision = nativeCollision; shape1.collider.entity._scripts.forEach( (element: Script) => { - let collision = PhysicsScene._collision; collision.shape = shape2; element.onCollisionEnter(collision); }, @@ -46,7 +48,6 @@ export class PhysicsScene { shape2.collider.entity._scripts.forEach( (element: Script) => { - let collision = PhysicsScene._collision; collision.shape = shape1; element.onCollisionEnter(collision); }, @@ -56,14 +57,16 @@ export class PhysicsScene { ); }; - private _onContactExit = (obj1: number, obj2: number) => { + private _onContactExit = (nativeCollision: ICollision) => { const physicalObjectsMap = Engine._physicalObjectsMap; - const shape1 = physicalObjectsMap[obj1]; - const shape2 = physicalObjectsMap[obj2]; + const { shape0Id, shape1Id } = nativeCollision; + const shape1 = physicalObjectsMap[shape0Id]; + const shape2 = physicalObjectsMap[shape1Id]; + const collision = PhysicsScene._collision; + collision._nativeCollision = nativeCollision; shape1.collider.entity._scripts.forEach( (element: Script) => { - let collision = PhysicsScene._collision; collision.shape = shape2; element.onCollisionExit(collision); }, @@ -74,7 +77,6 @@ export class PhysicsScene { shape2.collider.entity._scripts.forEach( (element: Script) => { - let collision = PhysicsScene._collision; collision.shape = shape1; element.onCollisionExit(collision); }, @@ -83,14 +85,16 @@ export class PhysicsScene { } ); }; - private _onContactStay = (obj1: number, obj2: number) => { + private _onContactStay = (nativeCollision: ICollision) => { const physicalObjectsMap = Engine._physicalObjectsMap; - const shape1 = physicalObjectsMap[obj1]; - const shape2 = physicalObjectsMap[obj2]; + const { shape0Id, shape1Id } = nativeCollision; + const shape1 = physicalObjectsMap[shape0Id]; + const shape2 = physicalObjectsMap[shape1Id]; + const collision = PhysicsScene._collision; + collision._nativeCollision = nativeCollision; shape1.collider.entity._scripts.forEach( (element: Script) => { - let collision = PhysicsScene._collision; collision.shape = shape2; element.onCollisionStay(collision); }, @@ -101,7 +105,6 @@ export class PhysicsScene { shape2.collider.entity._scripts.forEach( (element: Script) => { - let collision = PhysicsScene._collision; collision.shape = shape1; element.onCollisionStay(collision); }, diff --git a/packages/core/src/physics/index.ts b/packages/core/src/physics/index.ts index b7fbe00d86..537bd367d6 100644 --- a/packages/core/src/physics/index.ts +++ b/packages/core/src/physics/index.ts @@ -5,6 +5,8 @@ export { HitResult } from "./HitResult"; export { PhysicsMaterial } from "./PhysicsMaterial"; export { PhysicsScene } from "./PhysicsScene"; export { StaticCollider } from "./StaticCollider"; +export { Collision } from "./Collision"; +export { ContactPoint } from "./ContactPoint"; export * from "./enums"; export * from "./joint"; export * from "./shape"; diff --git a/packages/core/src/physics/shape/ColliderShape.ts b/packages/core/src/physics/shape/ColliderShape.ts index 79e6dc4239..7701561635 100644 --- a/packages/core/src/physics/shape/ColliderShape.ts +++ b/packages/core/src/physics/shape/ColliderShape.ts @@ -80,7 +80,7 @@ export abstract class ColliderShape implements ICustomClone { } /** - * The local rotation of this ColliderShape. + * The local rotation of this ColliderShape, in radians. */ get rotation(): Vector3 { return this._rotation; @@ -133,6 +133,30 @@ export abstract class ColliderShape implements ICustomClone { Engine._physicalObjectsMap[this._id] = this; } + /** + * Get the distance and the closest point on the shape from a point. + * @param point - Location in world space you want to find the closest point to + * @param outClosestPoint - The closest point on the shape in world space + * @returns The distance between the point and the shape + */ + getClosestPoint(point: Vector3, outClosestPoint: Vector3): number { + const collider = this._collider; + if (collider.enabled === false || collider.entity._isActiveInHierarchy === false) { + console.warn("The collider is not active in scene."); + return -1; + } + + const res = this._nativeShape.pointDistance(point); + const distance = res.w; + if (distance > 0) { + outClosestPoint.set(res.x, res.y, res.z); + } else { + outClosestPoint.copyFrom(point); + } + + return Math.sqrt(distance); + } + /** * @internal */ diff --git a/packages/core/src/physics/shape/PlaneColliderShape.ts b/packages/core/src/physics/shape/PlaneColliderShape.ts index 151ec3d10a..82e7f77a58 100644 --- a/packages/core/src/physics/shape/PlaneColliderShape.ts +++ b/packages/core/src/physics/shape/PlaneColliderShape.ts @@ -1,3 +1,4 @@ +import { Vector3 } from "@galacean/engine-math"; import { PhysicsScene } from "../PhysicsScene"; import { ColliderShape } from "./ColliderShape"; @@ -9,4 +10,9 @@ export class PlaneColliderShape extends ColliderShape { super(); this._nativeShape = PhysicsScene._nativePhysics.createPlaneColliderShape(this._id, this._material._nativeMaterial); } + + override getClosestPoint(point: Vector3, closestPoint: Vector3): number { + console.error("PlaneColliderShape is not support getClosestPoint"); + return -1; + } } diff --git a/packages/design/src/physics/ICollision.ts b/packages/design/src/physics/ICollision.ts new file mode 100644 index 0000000000..c0dd650797 --- /dev/null +++ b/packages/design/src/physics/ICollision.ts @@ -0,0 +1,37 @@ +/** + * Interface of collision. + */ +export interface ICollision { + /** The unique id of the first collider. */ + shape0Id: number; + /** The unique id of the second collider. */ + shape1Id: number; + /** Count of contact points. */ + contactCount: number; + /** Get contact points. */ + getContacts(): VectorContactPairPoint; +} + +interface VectorContactPairPoint { + size(): number; + get(index: number): IContactPoint; +} + +interface IContactPoint { + position: { + x: number; + y: number; + z: number; + }; + normal: { + x: number; + y: number; + z: number; + }; + impulse: { + x: number; + y: number; + z: number; + }; + separation: number; +} diff --git a/packages/design/src/physics/IPhysics.ts b/packages/design/src/physics/IPhysics.ts index 80f7aab202..4ddaa242fc 100644 --- a/packages/design/src/physics/IPhysics.ts +++ b/packages/design/src/physics/IPhysics.ts @@ -8,6 +8,7 @@ import { IPhysicsScene } from "./IPhysicsScene"; import { IStaticCollider } from "./IStaticCollider"; import { IFixedJoint, IHingeJoint, ISpringJoint } from "./joints"; import { IBoxColliderShape, ICapsuleColliderShape, IPlaneColliderShape, ISphereColliderShape } from "./shape"; +import { ICollision } from "./ICollision"; /** * The interface of physics creation. @@ -36,9 +37,9 @@ export interface IPhysics { */ createPhysicsScene( physicsManager: IPhysicsManager, - onContactEnter?: (obj1: number, obj2: number) => void, - onContactExit?: (obj1: number, obj2: number) => void, - onContactStay?: (obj1: number, obj2: number) => void, + onContactEnter?: (collision: ICollision) => void, + onContactExit?: (collision: ICollision) => void, + onContactStay?: (collision: ICollision) => void, onTriggerEnter?: (obj1: number, obj2: number) => void, onTriggerExit?: (obj1: number, obj2: number) => void, onTriggerStay?: (obj1: number, obj2: number) => void diff --git a/packages/design/src/physics/index.ts b/packages/design/src/physics/index.ts index abe6ec7c8f..88404cfb8b 100644 --- a/packages/design/src/physics/index.ts +++ b/packages/design/src/physics/index.ts @@ -6,5 +6,6 @@ export type { IPhysicsMaterial } from "./IPhysicsMaterial"; export type { IPhysicsScene } from "./IPhysicsScene"; export type { IPhysicsManager } from "./IPhysicsManager"; export type { IStaticCollider } from "./IStaticCollider"; +export type { ICollision } from "./ICollision"; export * from "./joints/index"; export * from "./shape/index"; diff --git a/packages/design/src/physics/shape/IColliderShape.ts b/packages/design/src/physics/shape/IColliderShape.ts index 0320a208fe..dd3df38eda 100644 --- a/packages/design/src/physics/shape/IColliderShape.ts +++ b/packages/design/src/physics/shape/IColliderShape.ts @@ -1,4 +1,4 @@ -import { Vector3 } from "@galacean/engine-math"; +import { Quaternion, Vector3, Vector4 } from "@galacean/engine-math"; import { IPhysicsMaterial } from "../IPhysicsMaterial"; /** @@ -41,6 +41,13 @@ export interface IColliderShape { */ setIsTrigger(value: boolean): void; + /** + * Get the distance between a point and the shape. + * @param point - Location in world space you want to find the closest point to + * @returns The x, y, and z components of the Vector4 represent the closest point on the shape in world space, + * and the w component represents the distance between the point and the shape + */ + pointDistance(point: Vector3): Vector4; /** * Decrements the reference count of a shape and releases it if the new reference count is zero. */ diff --git a/packages/physics-lite/src/LiteDynamicCollider.ts b/packages/physics-lite/src/LiteDynamicCollider.ts index 2848398797..0660cb18a2 100644 --- a/packages/physics-lite/src/LiteDynamicCollider.ts +++ b/packages/physics-lite/src/LiteDynamicCollider.ts @@ -1,6 +1,6 @@ import { LiteCollider } from "./LiteCollider"; import { IDynamicCollider } from "@galacean/engine-design"; -import { Quaternion, Vector3 } from "@galacean/engine"; +import { Logger, Quaternion, Vector3 } from "@galacean/engine"; /** * A dynamic collider can act with self-defined movement or physical force @@ -23,14 +23,14 @@ export class LiteDynamicCollider extends LiteCollider implements IDynamicCollide * {@inheritDoc IDynamicCollider.getInertiaTensor } */ getInertiaTensor(out: Vector3): Vector3 { - console.error("Physics-lite don't support getInertiaTensor. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support getInertiaTensor. Use Physics-PhysX instead!"); return out; } /** * {@inheritDoc IDynamicCollider.getCenterOfMass } */ getCenterOfMass(out: Vector3): Vector3 { - console.error("Physics-lite don't support getCenterOfMass. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support getCenterOfMass. Use Physics-PhysX instead!"); return out; } @@ -38,7 +38,7 @@ export class LiteDynamicCollider extends LiteCollider implements IDynamicCollide * {@inheritDoc IDynamicCollider.setMassAndUpdateInertia } */ setMassAndUpdateInertia(mass: number): void { - console.error("Physics-lite don't support setMassAndUpdateInertia. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support setMassAndUpdateInertia. Use Physics-PhysX instead!"); } /** @@ -87,14 +87,14 @@ export class LiteDynamicCollider extends LiteCollider implements IDynamicCollide * {@inheritDoc IDynamicCollider.setAngularDamping } */ setAngularDamping(value: number): void { - console.error("Physics-lite don't support setAngularDamping. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support setAngularDamping. Use Physics-PhysX instead!"); } /** * {@inheritDoc IDynamicCollider.getAngularVelocity } */ getAngularVelocity(out: Vector3): Vector3 { - console.error("Physics-lite don't support getAngularVelocity. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support getAngularVelocity. Use Physics-PhysX instead!"); return out; } @@ -102,42 +102,42 @@ export class LiteDynamicCollider extends LiteCollider implements IDynamicCollide * {@inheritDoc IDynamicCollider.setAngularVelocity } */ setAngularVelocity(value: Vector3): void { - console.error("Physics-lite don't support setAngularVelocity. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support setAngularVelocity. Use Physics-PhysX instead!"); } /** * {@inheritDoc IDynamicCollider.setCenterOfMass } */ setCenterOfMass(value: Vector3): void { - console.error("Physics-lite don't support setCenterOfMass. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support setCenterOfMass. Use Physics-PhysX instead!"); } /** * {@inheritDoc IDynamicCollider.setCollisionDetectionMode } */ setCollisionDetectionMode(value: number): void { - console.error("Physics-lite don't support setCollisionDetectionMode. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support setCollisionDetectionMode. Use Physics-PhysX instead!"); } /** * {@inheritDoc IDynamicCollider.setConstraints } */ setConstraints(flags: number): void { - console.error("Physics-lite don't support setConstraints. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support setConstraints. Use Physics-PhysX instead!"); } /** * {@inheritDoc IDynamicCollider.setInertiaTensor } */ setInertiaTensor(value: Vector3): void { - console.error("Physics-lite don't support setInertiaTensor. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support setInertiaTensor. Use Physics-PhysX instead!"); } /** * {@inheritDoc IDynamicCollider.setIsKinematic } */ setIsKinematic(value: boolean): void { - console.error("Physics-lite don't support setIsKinematic. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support setIsKinematic. Use Physics-PhysX instead!"); } /** @@ -150,14 +150,14 @@ export class LiteDynamicCollider extends LiteCollider implements IDynamicCollide * {@inheritDoc IDynamicCollider.setLinearDamping } */ setLinearDamping(value: number): void { - console.error("Physics-lite don't support setLinearDamping. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support setLinearDamping. Use Physics-PhysX instead!"); } /** * {@inheritDoc IDynamicCollider.getLinearVelocity } */ getLinearVelocity(out: Vector3): Vector3 { - console.error("Physics-lite don't support getLinearVelocity. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support getLinearVelocity. Use Physics-PhysX instead!"); return out; } @@ -165,21 +165,21 @@ export class LiteDynamicCollider extends LiteCollider implements IDynamicCollide * {@inheritDoc IDynamicCollider.setLinearVelocity } */ setLinearVelocity(value: Vector3): void { - console.error("Physics-lite don't support setLinearVelocity. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support setLinearVelocity. Use Physics-PhysX instead!"); } /** * {@inheritDoc IDynamicCollider.setMass } */ setMass(value: number): void { - console.error("Physics-lite don't support setMass. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support setMass. Use Physics-PhysX instead!"); } /** * {@inheritDoc IDynamicCollider.setMaxAngularVelocity } */ setMaxAngularVelocity(value: number): void { - console.error("Physics-lite don't support setMaxAngularVelocity. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support setMaxAngularVelocity. Use Physics-PhysX instead!"); } /** @@ -193,21 +193,21 @@ export class LiteDynamicCollider extends LiteCollider implements IDynamicCollide * {@inheritDoc IDynamicCollider.setMaxDepenetrationVelocity } */ setMaxDepenetrationVelocity(value: number): void { - console.error("Physics-lite don't support setMaxDepenetrationVelocity. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support setMaxDepenetrationVelocity. Use Physics-PhysX instead!"); } /** * {@inheritDoc IDynamicCollider.setSleepThreshold } */ setSleepThreshold(value: number): void { - console.error("Physics-lite don't support setSleepThreshold. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support setSleepThreshold. Use Physics-PhysX instead!"); } /** * {@inheritDoc IDynamicCollider.setSolverIterations } */ setSolverIterations(value: number): void { - console.error("Physics-lite don't support setSolverIterations. Use Physics-PhysX instead!"); + Logger.error("Physics-lite don't support setSolverIterations. Use Physics-PhysX instead!"); } /** diff --git a/packages/physics-lite/src/LitePhysics.ts b/packages/physics-lite/src/LitePhysics.ts index 5422c6a126..b666f857f0 100644 --- a/packages/physics-lite/src/LitePhysics.ts +++ b/packages/physics-lite/src/LitePhysics.ts @@ -3,6 +3,7 @@ import { IBoxColliderShape, ICapsuleColliderShape, ICharacterController, + ICollision, IDynamicCollider, IFixedJoint, IHingeJoint, @@ -43,9 +44,9 @@ export class LitePhysics implements IPhysics { */ createPhysicsScene( physicsManager: LitePhysicsManager, - onContactBegin?: (obj1: number, obj2: number) => void, - onContactEnd?: (obj1: number, obj2: number) => void, - onContactPersist?: (obj1: number, obj2: number) => void, + onContactBegin?: (collision: ICollision) => void, + onContactEnd?: (collision: ICollision) => void, + onContactPersist?: (collision: ICollision) => void, onTriggerBegin?: (obj1: number, obj2: number) => void, onTriggerEnd?: (obj1: number, obj2: number) => void, onTriggerPersist?: (obj1: number, obj2: number) => void diff --git a/packages/physics-lite/src/LitePhysicsScene.ts b/packages/physics-lite/src/LitePhysicsScene.ts index d1732f150d..13e1bb11ca 100644 --- a/packages/physics-lite/src/LitePhysicsScene.ts +++ b/packages/physics-lite/src/LitePhysicsScene.ts @@ -1,5 +1,5 @@ import { BoundingBox, BoundingSphere, CollisionUtil, DisorderedArray, Ray, Vector3 } from "@galacean/engine"; -import { ICharacterController, IPhysicsScene } from "@galacean/engine-design"; +import { ICharacterController, ICollision, IPhysicsScene } from "@galacean/engine-design"; import { LiteCollider } from "./LiteCollider"; import { LiteDynamicCollider } from "./LiteDynamicCollider"; import { LiteHitResult } from "./LiteHitResult"; @@ -17,9 +17,9 @@ export class LitePhysicsScene implements IPhysicsScene { private static _currentHit: LiteHitResult = new LiteHitResult(); private static _hitResult: LiteHitResult = new LiteHitResult(); - private readonly _onContactEnter?: (obj1: number, obj2: number) => void; - private readonly _onContactExit?: (obj1: number, obj2: number) => void; - private readonly _onContactStay?: (obj1: number, obj2: number) => void; + private readonly _onContactEnter?: (collision: ICollision) => void; + private readonly _onContactExit?: (collision: ICollision) => void; + private readonly _onContactStay?: (collision: ICollision) => void; private readonly _onTriggerEnter?: (obj1: number, obj2: number) => void; private readonly _onTriggerExit?: (obj1: number, obj2: number) => void; private readonly _onTriggerStay?: (obj1: number, obj2: number) => void; @@ -34,9 +34,9 @@ export class LitePhysicsScene implements IPhysicsScene { private _eventPool: TriggerEvent[] = []; constructor( - onContactEnter?: (obj1: number, obj2: number) => void, - onContactExit?: (obj1: number, obj2: number) => void, - onContactStay?: (obj1: number, obj2: number) => void, + onContactEnter?: (collision: ICollision) => void, + onContactExit?: (collision: ICollision) => void, + onContactStay?: (collision: ICollision) => void, onTriggerEnter?: (obj1: number, obj2: number) => void, onTriggerExit?: (obj1: number, obj2: number) => void, onTriggerStay?: (obj1: number, obj2: number) => void diff --git a/packages/physics-lite/src/shape/LiteBoxColliderShape.ts b/packages/physics-lite/src/shape/LiteBoxColliderShape.ts index 39a09b624b..af41c8305e 100644 --- a/packages/physics-lite/src/shape/LiteBoxColliderShape.ts +++ b/packages/physics-lite/src/shape/LiteBoxColliderShape.ts @@ -1,4 +1,4 @@ -import { BoundingBox, Ray, Vector3 } from "@galacean/engine"; +import { BoundingBox, Matrix, Quaternion, Ray, Vector3, Vector4 } from "@galacean/engine"; import { IBoxColliderShape } from "@galacean/engine-design"; import { LiteHitResult } from "../LiteHitResult"; import { LitePhysicsMaterial } from "../LitePhysicsMaterial"; @@ -9,6 +9,8 @@ import { LiteColliderShape } from "./LiteColliderShape"; */ export class LiteBoxColliderShape extends LiteColliderShape implements IBoxColliderShape { private static _tempBox: BoundingBox = new BoundingBox(); + private static _tempMatrix: Matrix = new Matrix(); + private static _tempInvMatrix: Matrix = new Matrix(); private _halfSize: Vector3 = new Vector3(); private _sizeScale: Vector3 = new Vector3(1, 1, 1); @@ -55,6 +57,50 @@ export class LiteBoxColliderShape extends LiteColliderShape implements IBoxColli this._setBondingBox(); } + /** + * {@inheritDoc IColliderShape.pointDistance } + */ + override pointDistance(point: Vector3): Vector4 { + const position = LiteColliderShape._tempPos; + const rotation = LiteColliderShape._tempRot; + this._transform.worldMatrix.decompose(position, rotation, LiteColliderShape._tempScale); + const { position: shapePosition } = this._transform; + const m = LiteBoxColliderShape._tempMatrix; + const invM = LiteBoxColliderShape._tempInvMatrix; + const p = LiteColliderShape._tempPoint; + const scale = this._sizeScale; + const boundingBox = LiteBoxColliderShape._tempBox; + + const { _boxMin, _boxMax } = this; + p.copyFrom(_boxMin); + p.subtract(shapePosition); + p.divide(scale); + boundingBox.min.copyFrom(p); + p.copyFrom(_boxMax); + p.subtract(shapePosition); + p.divide(scale); + boundingBox.max.copyFrom(p); + + Matrix.affineTransformation(scale, rotation, position, m); + Matrix.invert(m, invM); + Vector3.transformCoordinate(point, invM, p); + const min = boundingBox.min; + const max = boundingBox.max; + p.x = Math.max(min.x, Math.min(p.x, max.x)); + p.y = Math.max(min.y, Math.min(p.y, max.y)); + p.z = Math.max(min.z, Math.min(p.z, max.z)); + Vector3.transformCoordinate(p, m, p); + + const res = LiteColliderShape._tempVector4; + if (Vector3.equals(p, point)) { + res.set(point.x, point.y, point.z, 0); + } else { + res.set(p.x, p.y, p.z, Vector3.distanceSquared(p, point)); + } + + return res; + } + /** * @internal */ diff --git a/packages/physics-lite/src/shape/LiteColliderShape.ts b/packages/physics-lite/src/shape/LiteColliderShape.ts index c1f2b42d97..a205753972 100644 --- a/packages/physics-lite/src/shape/LiteColliderShape.ts +++ b/packages/physics-lite/src/shape/LiteColliderShape.ts @@ -1,4 +1,4 @@ -import { Matrix, Ray, Vector3 } from "@galacean/engine"; +import { Matrix, Quaternion, Ray, Vector3, Vector4 } from "@galacean/engine"; import { IColliderShape, IPhysicsMaterial } from "@galacean/engine-design"; import { LiteCollider } from "../LiteCollider"; import { LiteHitResult } from "../LiteHitResult"; @@ -9,8 +9,13 @@ import { LiteUpdateFlag } from "../LiteUpdateFlag"; * Abstract class for collider shapes. */ export abstract class LiteColliderShape implements IColliderShape { + protected static _tempPos = new Vector3(); + protected static _tempRot = new Quaternion(); + protected static _tempScale = new Vector3(); + protected static _tempPoint = new Vector3(); + protected static _tempVector4 = new Vector4(); + private static _ray = new Ray(); - private static _tempPoint = new Vector3(); /** @internal */ _id: number; @@ -27,6 +32,8 @@ export abstract class LiteColliderShape implements IColliderShape { /** @internal */ _inverseWorldMatFlag: LiteUpdateFlag; + private _rotation: Vector3 = new Vector3(); + protected constructor() { this._transform.owner = this; this._inverseWorldMatFlag = this._transform.registerWorldChangeFlag(); @@ -36,7 +43,10 @@ export abstract class LiteColliderShape implements IColliderShape { * {@inheritDoc IColliderShape.setRotation } */ setRotation(rotation: Vector3): void { - console.log("Physics-lite don't support setRotation. Use Physics-PhysX instead!"); + if (rotation !== this._rotation) { + this._rotation.copyFrom(rotation); + Quaternion.rotationEuler(rotation.x, rotation.y, rotation.z, this._transform.rotationQuaternion); + } } /** @@ -85,6 +95,11 @@ export abstract class LiteColliderShape implements IColliderShape { console.log("Physics-lite don't support setIsTrigger. Use Physics-PhysX instead!"); } + /** + * {@inheritDoc IColliderShape.pointDistance } + */ + abstract pointDistance(point: Vector3): Vector4; + /** * {@inheritDoc IColliderShape.destroy } */ diff --git a/packages/physics-lite/src/shape/LiteSphereColliderShape.ts b/packages/physics-lite/src/shape/LiteSphereColliderShape.ts index 50052823a5..9bc4a42cd9 100644 --- a/packages/physics-lite/src/shape/LiteSphereColliderShape.ts +++ b/packages/physics-lite/src/shape/LiteSphereColliderShape.ts @@ -1,6 +1,6 @@ import { ISphereColliderShape } from "@galacean/engine-design"; import { LiteColliderShape } from "./LiteColliderShape"; -import { BoundingSphere, Quaternion, Ray, Vector3 } from "@galacean/engine"; +import { BoundingSphere, Quaternion, Ray, Vector3, Vector4 } from "@galacean/engine"; import { LiteHitResult } from "../LiteHitResult"; import { LitePhysicsMaterial } from "../LitePhysicsMaterial"; @@ -44,6 +44,29 @@ export class LiteSphereColliderShape extends LiteColliderShape implements ISpher this._maxScale = Math.max(Math.abs(scale.x), Math.abs(scale.y), Math.abs(scale.z)); } + /** + * {@inheritDoc IColliderShape.pointDistance } + */ + override pointDistance(point: Vector3): Vector4 { + const position = LiteColliderShape._tempPos; + this._transform.worldMatrix.decompose(position, LiteColliderShape._tempRot, LiteColliderShape._tempScale); + const p = LiteColliderShape._tempPoint; + Vector3.subtract(point, position, p); + const direction = p.normalize(); + + Vector3.scale(direction, this.worldRadius, p); + p.add(position); + + const res = LiteColliderShape._tempVector4; + if (Vector3.equals(p, point)) { + res.set(point.x, point.y, point.z, 0); + } else { + res.set(p.x, p.y, p.z, Vector3.distanceSquared(p, point)); + } + + return res; + } + /** * @internal */ diff --git a/packages/physics-physx/src/PhysXPhysics.ts b/packages/physics-physx/src/PhysXPhysics.ts index f061405803..9f3c68b714 100644 --- a/packages/physics-physx/src/PhysXPhysics.ts +++ b/packages/physics-physx/src/PhysXPhysics.ts @@ -3,6 +3,7 @@ import { IBoxColliderShape, ICapsuleColliderShape, ICharacterController, + ICollision, IDynamicCollider, IFixedJoint, IHingeJoint, @@ -91,7 +92,7 @@ export class PhysXPhysics implements IPhysics { if (runtimeMode == PhysXRuntimeMode.JavaScript) { script.src = `https://mdn.alipayobjects.com/rms/afts/file/A*rnDeR58NNGoAAAAAAAAAAAAAARQnAQ/physx.release.js.js`; } else if (runtimeMode == PhysXRuntimeMode.WebAssembly) { - script.src = `https://mdn.alipayobjects.com/rms/afts/file/A*rP-bRKBDf0YAAAAAAAAAAAAAARQnAQ/physx.release.js`; + script.src = `https://mdn.alipayobjects.com/rms/afts/file/A*04GyRKeSJw4AAAAAAAAAAAAAARQnAQ/physx.release.js`; } }); @@ -139,9 +140,9 @@ export class PhysXPhysics implements IPhysics { */ createPhysicsScene( physicsManager: PhysXPhysicsManager, - onContactBegin?: (obj1: number, obj2: number) => void, - onContactEnd?: (obj1: number, obj2: number) => void, - onContactStay?: (obj1: number, obj2: number) => void, + onContactBegin?: (collision: ICollision) => void, + onContactEnd?: (collision: ICollision) => void, + onContactStay?: (collision: ICollision) => void, onTriggerBegin?: (obj1: number, obj2: number) => void, onTriggerEnd?: (obj1: number, obj2: number) => void, onTriggerStay?: (obj1: number, obj2: number) => void diff --git a/packages/physics-physx/src/PhysXPhysicsScene.ts b/packages/physics-physx/src/PhysXPhysicsScene.ts index 805001bb91..8535227224 100644 --- a/packages/physics-physx/src/PhysXPhysicsScene.ts +++ b/packages/physics-physx/src/PhysXPhysicsScene.ts @@ -1,5 +1,5 @@ import { Ray, Vector3, DisorderedArray } from "@galacean/engine"; -import { IPhysicsScene } from "@galacean/engine-design"; +import { ICollision, IPhysicsScene } from "@galacean/engine-design"; import { PhysXCharacterController } from "./PhysXCharacterController"; import { PhysXCollider } from "./PhysXCollider"; import { PhysXPhysics } from "./PhysXPhysics"; @@ -22,12 +22,12 @@ export class PhysXPhysicsScene implements IPhysicsScene { private _pxScene: any; - private readonly _onContactEnter?: (obj1: number, obj2: number) => void; - private readonly _onContactExit?: (obj1: number, obj2: number) => void; - private readonly _onContactStay?: (obj1: number, obj2: number) => void; - private readonly _onTriggerEnter?: (obj1: number, obj2: number) => void; - private readonly _onTriggerExit?: (obj1: number, obj2: number) => void; - private readonly _onTriggerStay?: (obj1: number, obj2: number) => void; + private readonly _onContactEnter?: (collision: ICollision) => void; + private readonly _onContactExit?: (collision: ICollision) => void; + private readonly _onContactStay?: (collision: ICollision) => void; + private readonly _onTriggerEnter?: (index1: number, index2: number) => void; + private readonly _onTriggerExit?: (index1: number, index2: number) => void; + private readonly _onTriggerStay?: (index1: number, index2: number) => void; private _currentEvents: DisorderedArray = new DisorderedArray(); @@ -36,9 +36,9 @@ export class PhysXPhysicsScene implements IPhysicsScene { constructor( physXPhysics: PhysXPhysics, physicsManager: PhysXPhysicsManager, - onContactEnter?: (obj1: number, obj2: number) => void, - onContactExit?: (obj1: number, obj2: number) => void, - onContactStay?: (obj1: number, obj2: number) => void, + onContactEnter?: (collision: ICollision) => void, + onContactExit?: (collision: ICollision) => void, + onContactStay?: (collision: ICollision) => void, onTriggerEnter?: (obj1: number, obj2: number) => void, onTriggerExit?: (obj1: number, obj2: number) => void, onTriggerStay?: (obj1: number, obj2: number) => void @@ -60,14 +60,14 @@ export class PhysXPhysicsScene implements IPhysicsScene { this._onTriggerStay = onTriggerStay; const triggerCallback = { - onContactBegin: (index1, index2) => { - this._onContactEnter(index1, index2); + onContactBegin: (collision) => { + this._onContactEnter(collision); }, - onContactEnd: (index1, index2) => { - this._onContactExit(index1, index2); + onContactEnd: (collision) => { + this._onContactExit(collision); }, - onContactPersist: (index1, index2) => { - this._onContactStay(index1, index2); + onContactPersist: (collision) => { + this._onContactStay(collision); }, onTriggerBegin: (index1, index2) => { const event = index1 < index2 ? this._getTrigger(index1, index2) : this._getTrigger(index2, index1); diff --git a/packages/physics-physx/src/shape/PhysXColliderShape.ts b/packages/physics-physx/src/shape/PhysXColliderShape.ts index 170bba49e1..a2565ade4d 100644 --- a/packages/physics-physx/src/shape/PhysXColliderShape.ts +++ b/packages/physics-physx/src/shape/PhysXColliderShape.ts @@ -1,4 +1,4 @@ -import { Quaternion, Vector3, DisorderedArray } from "@galacean/engine"; +import { Quaternion, Vector3, DisorderedArray, Vector4 } from "@galacean/engine"; import { IColliderShape } from "@galacean/engine-design"; import { PhysXCharacterController } from "../PhysXCharacterController"; import { PhysXPhysics } from "../PhysXPhysics"; @@ -20,6 +20,7 @@ export enum ShapeFlag { * Abstract class for collider shapes. */ export abstract class PhysXColliderShape implements IColliderShape { + protected static _tempVector4 = new Vector4(); static readonly halfSqrt: number = 0.70710678118655; static transform = { translation: new Vector3(), @@ -58,7 +59,7 @@ export abstract class PhysXColliderShape implements IColliderShape { */ setRotation(value: Vector3): void { this._rotation = value; - Quaternion.rotationYawPitchRoll(value.x, value.y, value.z, this._physXRotation); + Quaternion.rotationYawPitchRoll(value.y, value.x, value.z, this._physXRotation); this._axis && Quaternion.multiply(this._physXRotation, this._axis, this._physXRotation); this._physXRotation.normalize(); this._setLocalPose(); @@ -125,6 +126,17 @@ export abstract class PhysXColliderShape implements IColliderShape { this._setShapeFlags(this._shapeFlags); } + /** + * {@inheritDoc IColliderShape.pointDistance } + */ + pointDistance(point: Vector3): Vector4 { + const info = this._pxGeometry.pointDistance(this._pxShape.getGlobalPose(), point); + const closestPoint = info.closestPoint; + const res = PhysXColliderShape._tempVector4; + res.set(closestPoint.x, closestPoint.y, closestPoint.z, info.distance); + return res; + } + /** * {@inheritDoc IColliderShape.destroy } */ diff --git a/tests/src/core/physics/ColliderShape.test.ts b/tests/src/core/physics/ColliderShape.test.ts index ed1a650344..12fd2fc17f 100644 --- a/tests/src/core/physics/ColliderShape.test.ts +++ b/tests/src/core/physics/ColliderShape.test.ts @@ -12,22 +12,33 @@ import { Vector3 } from "@galacean/engine-math"; import { WebGLEngine } from "@galacean/engine-rhi-webgl"; import { PhysXPhysics } from "@galacean/engine-physics-physx"; import { describe, beforeAll, beforeEach, expect, it } from "vitest"; +import { LitePhysics } from "@galacean/engine-physics-lite"; -describe("ColliderShape", () => { +describe("ColliderShape PhysX", () => { let dynamicCollider: DynamicCollider; + function formatValue(value: number) { + return Math.round(value * 10000) / 10000; + } + beforeAll(async () => { const engine = await WebGLEngine.create({ canvas: document.createElement("canvas"), physics: new PhysXPhysics() }); engine.run(); const scene = engine.sceneManager.activeScene; + scene.physics.gravity = new Vector3(0, 0, 0); const root = scene.createRootEntity("root"); const roleEntity = root.createChild("role"); + dynamicCollider = roleEntity.addComponent(DynamicCollider); }); beforeEach(() => { + const entity = dynamicCollider.entity; + entity.transform.setPosition(0, 0, 0); + entity.transform.setScale(1, 1, 1); + entity.transform.setRotation(0, 0, 0); dynamicCollider.clearShapes(); }); @@ -240,6 +251,83 @@ describe("ColliderShape", () => { expect(sphereShape.rotation).to.deep.include({ x: 40, y: -182, z: 720 }); }); + it("boxShape getClosestPoint", () => { + const boxShape = new BoxColliderShape(); + boxShape.size.set(1, 2, 3); + boxShape.position.set(2, 3, 4); + boxShape.rotation.set(23, 45, 12); + dynamicCollider.addShape(boxShape); + const entity = dynamicCollider.entity; + const engine = entity.engine; + entity.transform.setPosition(2, 3, 5); + entity.transform.setScale(3, 4, 5); + entity.transform.setRotation(13, -45, 38); + + const point = new Vector3(-9, 7, 6); + const closestPoint = new Vector3(); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + let distance = boxShape.getClosestPoint(point, closestPoint); + expect(formatValue(distance)).to.eq(10.492); + expect(formatValue(closestPoint.x)).to.eq(-16.0876); + expect(formatValue(closestPoint.y)).to.eq(10.7095); + expect(formatValue(closestPoint.z)).to.eq(12.7889); + + entity.transform.setScale(1, 1, 1); + entity.transform.setRotation(0, 0, 0); + + point.set(4, 6, 9); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + distance = boxShape.getClosestPoint(point, closestPoint); + expect(distance).to.eq(0); + expect(formatValue(closestPoint.x)).to.eq(4); + expect(formatValue(closestPoint.y)).to.eq(6); + expect(formatValue(closestPoint.z)).to.eq(9); + }); + + it("sphereShape getClosestPoint", () => { + const sphereShape = new SphereColliderShape(); + sphereShape.radius = 2; + sphereShape.position.set(2, 3, 4); + dynamicCollider.addShape(sphereShape); + const entity = dynamicCollider.entity; + const engine = entity.engine; + entity.transform.setPosition(2, 3, 5); + entity.transform.setScale(3, 4, 5); + entity.transform.setRotation(13, -45, 38); + + const point = new Vector3(14, 8, 10); + const closestPoint = new Vector3(); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + const distance = sphereShape.getClosestPoint(point, closestPoint); + expect(formatValue(distance)).to.eq(21.2571); + expect(formatValue(closestPoint.x)).to.eq(-6.2337); + expect(formatValue(closestPoint.y)).to.eq(10.2538); + expect(formatValue(closestPoint.z)).to.eq(16.1142); + + entity.transform.setScale(1, 1, 1); + entity.transform.setRotation(0, 0, 0); + point.set(4, 6, 9); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + const distance2 = sphereShape.getClosestPoint(point, closestPoint); + expect(distance2).to.eq(0); + expect(closestPoint).to.deep.include({ x: 4, y: 6, z: 9 }); + }); + + it("getClosestPoint with collider disabled", () => { + const sphereShape = new BoxColliderShape(); + dynamicCollider.addShape(sphereShape); + dynamicCollider.enabled = false; + + const point = new Vector3(2, 0, 0); + const closestPoint = new Vector3(); + const distance = sphereShape.getClosestPoint(point, closestPoint); + expect(distance).to.eq(-1); + }); + it("clone", () => { // SphereColliderShape const sphereShape = new SphereColliderShape(); @@ -270,3 +358,221 @@ describe("ColliderShape", () => { expect((newCollider3.shapes[0] as CapsuleColliderShape).height).to.eq(3); }); }); + +describe("ColliderShape Lite", () => { + let dynamicCollider: DynamicCollider; + + function formatValue(value: number) { + return Math.round(value * 10000) / 10000; + } + + beforeAll(async () => { + const engine = await WebGLEngine.create({ canvas: document.createElement("canvas"), physics: new LitePhysics() }); + engine.run(); + + const scene = engine.sceneManager.activeScene; + const root = scene.createRootEntity("root"); + + const roleEntity = root.createChild("role"); + + dynamicCollider = roleEntity.addComponent(DynamicCollider); + }); + + beforeEach(() => { + const entity = dynamicCollider.entity; + entity.transform.setPosition(0, 0, 0); + entity.transform.setScale(1, 1, 1); + entity.transform.setRotation(0, 0, 0); + dynamicCollider.clearShapes(); + }); + + it("BoxColliderShape", () => { + const boxShape = new BoxColliderShape(); + dynamicCollider.addShape(boxShape); + + // Test that set size works correctly. + boxShape.size = new Vector3(1, 2, 3); + expect(boxShape.size).to.deep.include({ x: 1, y: 2, z: 3 }); + + // Test that set trigger works correctly. + boxShape.isTrigger = true; + expect(boxShape.isTrigger).to.eq(true); + + // Test that set contactOffset works correctly. + let contactOffset = boxShape.contactOffset; + boxShape.contactOffset = contactOffset; + expect(boxShape.contactOffset).to.eq(contactOffset); + + contactOffset = 2.4; + boxShape.contactOffset = contactOffset; + expect(boxShape.contactOffset).to.eq(contactOffset); + + contactOffset = 0; + boxShape.contactOffset = contactOffset; + expect(boxShape.contactOffset).to.eq(contactOffset); + + contactOffset = 2.7; + boxShape.contactOffset = contactOffset; + expect(boxShape.contactOffset).to.eq(contactOffset); + + // Test that set material works correctly. + const material = new PhysicsMaterial(); + boxShape.material = material; + expect(boxShape.material).to.eq(material); + + // Test that set position works correctly. + boxShape.position = new Vector3(1, 2, -1); + expect(boxShape.position).to.deep.include({ x: 1, y: 2, z: -1 }); + + // Test that set rotation works correctly. + boxShape.rotation = new Vector3(40, -182, 720); + expect(boxShape.rotation).to.deep.include({ x: 40, y: -182, z: 720 }); + }); + + it("SphereColliderShape", () => { + const sphereShape = new SphereColliderShape(); + dynamicCollider.addShape(sphereShape); + + // Test that set radius works correctly. + let radius = sphereShape.radius; + expect(sphereShape.radius).to.eq(radius); + + radius *= 0.5; + sphereShape.radius = radius; + expect(sphereShape.radius).to.eq(radius); + + radius *= 4; + sphereShape.radius = radius; + expect(sphereShape.radius).to.eq(radius); + + // Test that set trigger works correctly. + sphereShape.isTrigger = true; + expect(sphereShape.isTrigger).to.eq(true); + + // Test that set contactOffset works correctly. + let contactOffset = sphereShape.contactOffset; + sphereShape.contactOffset = contactOffset; + expect(sphereShape.contactOffset).to.eq(contactOffset); + + contactOffset = 2.4; + sphereShape.contactOffset = contactOffset; + expect(sphereShape.contactOffset).to.eq(contactOffset); + + contactOffset = 0; + sphereShape.contactOffset = contactOffset; + expect(sphereShape.contactOffset).to.eq(contactOffset); + + contactOffset = 2.7; + sphereShape.contactOffset = contactOffset; + expect(sphereShape.contactOffset).to.eq(contactOffset); + + // Test that set material works correctly. + const material = new PhysicsMaterial(); + sphereShape.material = material; + expect(sphereShape.material).to.eq(material); + + // Test that set position works correctly. + sphereShape.position = new Vector3(1, 2, -1); + expect(sphereShape.position).to.deep.include({ x: 1, y: 2, z: -1 }); + + // Test that set rotation works correctly. + sphereShape.rotation = new Vector3(40, -182, 720); + expect(sphereShape.rotation).to.deep.include({ x: 40, y: -182, z: 720 }); + }); + + it("boxShape getClosestPoint", () => { + const boxShape = new BoxColliderShape(); + boxShape.size.set(1, 2, 3); + boxShape.position.set(2, 3, 4); + boxShape.rotation.set(23, 45, 12); + dynamicCollider.addShape(boxShape); + const entity = dynamicCollider.entity; + const engine = entity.engine; + entity.transform.setPosition(2, 3, 5); + entity.transform.setScale(3, 4, 5); + entity.transform.setRotation(13, -45, 38); + + const point = new Vector3(-9, 7, 6); + const closestPoint = new Vector3(); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + let distance = boxShape.getClosestPoint(point, closestPoint); + expect(formatValue(distance)).to.eq(10.492); + expect(formatValue(closestPoint.x)).to.eq(-16.0876); + expect(formatValue(closestPoint.y)).to.eq(10.7095); + expect(formatValue(closestPoint.z)).to.eq(12.7889); + + entity.transform.setScale(1, 1, 1); + entity.transform.setRotation(0, 0, 0); + + point.set(4, 6, 9); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + distance = boxShape.getClosestPoint(point, closestPoint); + expect(distance).to.eq(0); + expect(formatValue(closestPoint.x)).to.eq(4); + expect(formatValue(closestPoint.y)).to.eq(6); + expect(formatValue(closestPoint.z)).to.eq(9); + }); + + it("sphereShape getClosestPoint", () => { + const sphereShape = new SphereColliderShape(); + sphereShape.radius = 2; + sphereShape.position.set(2, 3, 4); + dynamicCollider.addShape(sphereShape); + const entity = dynamicCollider.entity; + const engine = entity.engine; + entity.transform.setPosition(2, 3, 5); + entity.transform.setScale(3, 4, 5); + entity.transform.setRotation(13, -45, 38); + + const point = new Vector3(14, 8, 10); + const closestPoint = new Vector3(); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + const distance = sphereShape.getClosestPoint(point, closestPoint); + expect(formatValue(distance)).to.eq(21.2571); + expect(formatValue(closestPoint.x)).to.eq(-6.2337); + expect(formatValue(closestPoint.y)).to.eq(10.2538); + expect(formatValue(closestPoint.z)).to.eq(16.1142); + + entity.transform.setScale(1, 1, 1); + entity.transform.setRotation(0, 0, 0); + point.set(4, 6, 9); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1 / 60); + const distance2 = sphereShape.getClosestPoint(point, closestPoint); + expect(distance2).to.eq(0); + expect(closestPoint).to.deep.include({ x: 4, y: 6, z: 9 }); + }); + + it("getClosestPoint with collider disabled", () => { + const sphereShape = new BoxColliderShape(); + dynamicCollider.addShape(sphereShape); + dynamicCollider.enabled = false; + + const point = new Vector3(2, 0, 0); + const closestPoint = new Vector3(); + const distance = sphereShape.getClosestPoint(point, closestPoint); + expect(distance).to.eq(-1); + }); + + it("clone", () => { + // SphereColliderShape + const sphereShape = new SphereColliderShape(); + sphereShape.radius = 2; + dynamicCollider.addShape(sphereShape); + const newCollider = dynamicCollider.entity.clone().getComponent(DynamicCollider); + expect(newCollider.shapes.length).to.eq(1); + expect((newCollider.shapes[0] as SphereColliderShape).radius).to.eq(2); + + // BoxColliderShape + dynamicCollider.clearShapes(); + const boxShape = new BoxColliderShape(); + boxShape.size = new Vector3(1, 2, 3); + dynamicCollider.addShape(boxShape); + const newCollider2 = dynamicCollider.entity.clone().getComponent(DynamicCollider); + expect(newCollider2.shapes.length).to.eq(1); + expect((newCollider2.shapes[0] as BoxColliderShape).size).to.deep.include({ x: 1, y: 2, z: 3 }); + }); +}); diff --git a/tests/src/core/physics/Collision.test.ts b/tests/src/core/physics/Collision.test.ts new file mode 100644 index 0000000000..63110340ec --- /dev/null +++ b/tests/src/core/physics/Collision.test.ts @@ -0,0 +1,69 @@ +import { BoxColliderShape, DynamicCollider, Entity, Engine, Script, StaticCollider } from "@galacean/engine-core"; +import { Vector3 } from "@galacean/engine-math"; +import { PhysXPhysics } from "@galacean/engine-physics-physx"; +import { WebGLEngine } from "@galacean/engine-rhi-webgl"; +import { Collision } from "packages/core/types/physics/Collision"; +import { describe, beforeAll, beforeEach, expect, it } from "vitest"; + +describe("Collision", function () { + let rootEntity: Entity; + let engine: Engine; + + function addBox(cubeSize: Vector3, type: typeof DynamicCollider | typeof StaticCollider, pos: Vector3) { + const boxEntity = rootEntity.createChild("BoxEntity"); + boxEntity.transform.setPosition(pos.x, pos.y, pos.z); + + const physicsBox = new BoxColliderShape(); + physicsBox.material.dynamicFriction = 0; + physicsBox.material.staticFriction = 0; + physicsBox.size = cubeSize; + const boxCollider = boxEntity.addComponent(type); + boxCollider.addShape(physicsBox); + return boxEntity; + } + + function formatValue(value: number) { + return Math.round(value * 100000) / 100000; + } + + beforeAll(async function () { + engine = await WebGLEngine.create({ canvas: document.createElement("canvas"), physics: new PhysXPhysics() }); + + rootEntity = engine.sceneManager.activeScene.createRootEntity("root"); + }); + + beforeEach(function () { + rootEntity.clearChildren(); + engine.sceneManager.activeScene.physics.gravity = new Vector3(0, -9.81, 0); + }); + + it("collision info", function () { + engine.sceneManager.activeScene.physics.gravity = new Vector3(0, 0, 0); + const box1 = addBox(new Vector3(1, 1, 1), DynamicCollider, new Vector3(-3, 0, 0)); + const box2 = addBox(new Vector3(1, 1, 1), DynamicCollider, new Vector3(0, 0, 0)); + + return new Promise((done) => { + box1.addComponent( + class extends Script { + onCollisionEnter(other: Collision): void { + expect(other.shape).toBe(box2.getComponent(DynamicCollider).shapes[0]); + expect(other.contactCount).toBe(4); + const contacts = []; + other.getContacts(contacts); + expect(contacts.length).toBe(4); + expect(formatValue(contacts[0].position.x)).toBe(-0.27778); + expect(formatValue(contacts[0].separation)).toBe(-0.22222); + expect(formatValue(contacts[0].normal.x)).toBe(-1); + expect(formatValue(contacts[0].impulse.x)).toBe(-2.93748); + + done(); + } + } + ); + + box1.getComponent(DynamicCollider).applyForce(new Vector3(1000, 0, 0)); + // @ts-ignore + engine.sceneManager.activeScene.physics._update(1); + }); + }); +});