Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support get contact points and get closest point on shape from a point #2458

Merged
merged 15 commits into from
Dec 18, 2024
3 changes: 2 additions & 1 deletion packages/core/src/physics/CharacterController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -166,6 +166,7 @@ export class CharacterController extends Collider {
(<ICharacterController>this._nativeCollider).getWorldPosition(this.entity.transform.worldPosition);
}

@ignoreClone
private _setUpDirection(): void {
(<ICharacterController>this._nativeCollider).setUpDirection(this._upDirection);
}
Expand Down
53 changes: 53 additions & 0 deletions packages/core/src/physics/Collision.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,58 @@
import { Vector3 } from "@galacean/engine-math";
import { ColliderShape } from "./shape";
import { ICollision } from "@galacean/engine-design";

/**
* Describes a contact point where the collision occurs.
*/
export interface ContactPoint {
/** The position of the contact point between the shapes, in world space. */
readonly position: 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: Vector3;
/** The impulse applied at the contact point, in world space. Divide by the simulation time step to get a force value. */
readonly impulse: Vector3;
/** The separation of the shapes at the contact point. A negative separation denotes a penetration. */
readonly separation: number;
}

export class Collision {
/** @internal */
_nativeCollision: ICollision;

/** The shape be collided. */
shape: ColliderShape;

/**
* Get count of contact points.
*/
get contactCount(): number {
return this._nativeCollision.contactCount;
}

/**
* Get contact points.
* @param outContacts - The result of contact points
* @returns The result of contact points
*/
getContacts(outContacts: ContactPoint[]): ContactPoint[] {
const { shape0Id, shape1Id } = this._nativeCollision;
const nativeContactPoints = this._nativeCollision.getContacts();
for (let i = 0, n = nativeContactPoints.size(); i < n; i++) {
const nativeContractPoint = nativeContactPoints.get(i);
const { position, normal, impulse, separation } = nativeContractPoint;
let factor = 1;
if (shape0Id > shape1Id) {
factor = -1;
}
const contact: ContactPoint = {
position: new Vector3(position.x, position.y, position.z),
normal: new Vector3(normal.x, normal.y, normal.z).scale(factor),
impulse: new Vector3(impulse.x, impulse.y, impulse.z).scale(factor),
separation: separation
};
outContacts.push(contact);
}
return outContacts;
}
}
35 changes: 19 additions & 16 deletions packages/core/src/physics/PhysicsScene.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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);
},
Expand All @@ -46,7 +48,6 @@ export class PhysicsScene {

shape2.collider.entity._scripts.forEach(
(element: Script) => {
let collision = PhysicsScene._collision;
collision.shape = shape1;
element.onCollisionEnter(collision);
},
Expand All @@ -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);
},
Expand All @@ -74,7 +77,6 @@ export class PhysicsScene {

shape2.collider.entity._scripts.forEach(
(element: Script) => {
let collision = PhysicsScene._collision;
collision.shape = shape1;
element.onCollisionExit(collision);
},
Expand All @@ -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);
},
Expand All @@ -101,7 +105,6 @@ export class PhysicsScene {

shape2.collider.entity._scripts.forEach(
(element: Script) => {
let collision = PhysicsScene._collision;
collision.shape = shape1;
element.onCollisionStay(collision);
},
Expand Down
49 changes: 48 additions & 1 deletion packages/core/src/physics/shape/ColliderShape.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IColliderShape } from "@galacean/engine-design";
import { PhysicsMaterial } from "../PhysicsMaterial";
import { Vector3 } from "@galacean/engine-math";
import { MathUtil, Matrix, Quaternion, Vector3 } from "@galacean/engine-math";
import { Collider } from "../Collider";
import { deepClone, ignoreClone } from "../../clone/CloneManager";
import { ICustomClone } from "../../clone/ComponentCloner";
Expand All @@ -11,6 +11,9 @@ import { Engine } from "../../Engine";
*/
export abstract class ColliderShape implements ICustomClone {
private static _idGenerator: number = 0;
private static _tempWorldPos: Vector3 = new Vector3();
private static _tempWorldRot: Quaternion = new Quaternion();
private static _tempMatrix: Matrix = new Matrix();

/** @internal */
@ignoreClone
Expand Down Expand Up @@ -133,6 +136,50 @@ 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 - The point
* @param outClosestPoint - The result of the closest point on the shape
* @returns The distance between the point and the shape
*/
getDistanceAndClosestPointFromPoint(point: Vector3, outClosestPoint: Vector3): number {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#include "PxPhysicsAPI.h"

using namespace physx;

PxVec3 getClosestPointOnShape(PxShape* shape, const PxVec3& point) {
    PxGeometryHolder geomHolder = shape->getGeometry(); // 获取 PxShape 的几何信息

    if (!geomHolder.any().isValid()) {
        throw std::runtime_error("Invalid geometry in shape.");
    }

    PxTransform pose = shape->getActor()->getGlobalPose() * shape->getLocalPose(); // 获取 PxShape 的全局变换

    PxVec3 closestPoint;
    PxReal distance = PxGeometryQuery::pointDistance(point, geomHolder.any(), pose, &closestPoint);

    return closestPoint; // 返回最近点
}

const collider = this._collider;
if (collider.enabled === false || collider.entity._isActiveInHierarchy === false) {
console.warn("The collider is not active in scene.");
return -1;
}
const tempQuat = ColliderShape._tempWorldRot;
const tempPos = ColliderShape._tempWorldPos;
Vector3.transformCoordinate(this._position, collider.entity.transform.worldMatrix, tempPos);

const rotation = this._rotation;
Quaternion.rotationEuler(
MathUtil.degreeToRadian(rotation.x),
MathUtil.degreeToRadian(rotation.y),
MathUtil.degreeToRadian(rotation.z),
tempQuat
);
Quaternion.multiply(this._collider.entity.transform.rotationQuaternion, tempQuat, tempQuat);

const res = this._nativeShape.pointDistance(tempPos, tempQuat, point);
const distance = res.distance;
if (distance > 0) {
outClosestPoint.copyFrom(res.closestPoint);
} else {
outClosestPoint.copyFrom(point);
}

const m = ColliderShape._tempMatrix;
Matrix.invert(collider.entity.transform.worldMatrix, m);
Vector3.transformCoordinate(outClosestPoint, m, outClosestPoint);

outClosestPoint.subtract(this._position);

Quaternion.invert(tempQuat, tempQuat);
Vector3.transformByQuat(outClosestPoint, tempQuat, outClosestPoint);
return Math.sqrt(distance);
}

/**
* @internal
*/
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/physics/shape/PlaneColliderShape.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Vector3 } from "@galacean/engine-math";
import { PhysicsScene } from "../PhysicsScene";
import { ColliderShape } from "./ColliderShape";

Expand All @@ -9,4 +10,9 @@
super();
this._nativeShape = PhysicsScene._nativePhysics.createPlaneColliderShape(this._id, this._material._nativeMaterial);
}

override getDistanceAndClosestPointFromPoint(point: Vector3, closestPoint: Vector3): number {
console.error("PlaneColliderShape is not support getDistanceAndClosestPointFromPoint");
return -1;
}

Check warning on line 17 in packages/core/src/physics/shape/PlaneColliderShape.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/physics/shape/PlaneColliderShape.ts#L15-L17

Added lines #L15 - L17 were not covered by tests
}
37 changes: 37 additions & 0 deletions packages/design/src/physics/ICollision.ts
Original file line number Diff line number Diff line change
@@ -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(): PhysXVectorPxContactPairPoint;
}

interface PhysXVectorPxContactPairPoint {
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;
}
7 changes: 4 additions & 3 deletions packages/design/src/physics/IPhysics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions packages/design/src/physics/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
25 changes: 24 additions & 1 deletion packages/design/src/physics/shape/IColliderShape.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Vector3 } from "@galacean/engine-math";
import { Quaternion, Vector3 } from "@galacean/engine-math";
import { IPhysicsMaterial } from "../IPhysicsMaterial";

/**
Expand Down Expand Up @@ -41,8 +41,31 @@ export interface IColliderShape {
*/
setIsTrigger(value: boolean): void;

/**
* Get the distance between a point and the shape.
* @param position - The position in world space
* @param rotation - The rotation in world space
* @param point - The point
* @returns The distance information
*/
pointDistance(position: Vector3, rotation: Quaternion, point: Vector3): IPointDistanceInfo;
/**
* Decrements the reference count of a shape and releases it if the new reference count is zero.
*/
destroy(): void;
}

/**
* Distance information of a point to the shape.
*/
export interface IPointDistanceInfo {
/**
* The distance between the point and the shape.
*/
distance: number;

/**
* The closest point on the shape.
*/
closestPoint: Vector3;
}
2 changes: 1 addition & 1 deletion packages/design/src/physics/shape/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export type { IColliderShape } from "./IColliderShape";
export type { IColliderShape, IPointDistanceInfo } from "./IColliderShape";
export type { IBoxColliderShape } from "./IBoxColliderShape";
export type { ICapsuleColliderShape } from "./ICapsuleColliderShape";
export type { ISphereColliderShape } from "./ISphereColliderShape";
Expand Down
Loading
Loading