mirror of
https://github.com/galacean/engine.git
synced 2026-06-02 16:52:48 +08:00
Support collision group and fix the position of characterController synchronization late issue (#2614)
* feat: support collision group --------- Co-authored-by: GuoLei1990 <gl3336563@163.com>
This commit is contained in:
134
e2e/case/litePhysics-collision-group.ts
Normal file
134
e2e/case/litePhysics-collision-group.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* @title LitePhysics Collision Group
|
||||
* @category Physics
|
||||
*/
|
||||
import {
|
||||
WebGLEngine,
|
||||
SphereColliderShape,
|
||||
DynamicCollider,
|
||||
BoxColliderShape,
|
||||
Vector3,
|
||||
MeshRenderer,
|
||||
PointLight,
|
||||
PrimitiveMesh,
|
||||
Camera,
|
||||
Script,
|
||||
StaticCollider,
|
||||
ColliderShape,
|
||||
PBRMaterial,
|
||||
Entity,
|
||||
Layer
|
||||
} from "@galacean/engine";
|
||||
|
||||
import { LitePhysics } from "@galacean/engine-physics-lite";
|
||||
import { initScreenshot, updateForE2E } from "./.mockForE2E";
|
||||
class MoveScript extends Script {
|
||||
onUpdate() {
|
||||
this.entity.transform.position.y -= 0.1;
|
||||
}
|
||||
|
||||
onTriggerEnter(other: ColliderShape) {
|
||||
// Change color to green when collision occurs
|
||||
(this.entity.getComponent(MeshRenderer).getMaterial() as PBRMaterial).baseColor.set(0, 1, 0, 1);
|
||||
}
|
||||
}
|
||||
// Create a sphere with physics
|
||||
function createPhysicsSphere(
|
||||
rootEntity: Entity,
|
||||
name: string,
|
||||
position: Vector3,
|
||||
radius: number,
|
||||
color: Vector3,
|
||||
collisionLayer: Layer
|
||||
) {
|
||||
const sphereEntity = rootEntity.createChild(name);
|
||||
sphereEntity.transform.setPosition(position.x, position.y, position.z);
|
||||
sphereEntity.addComponent(MoveScript);
|
||||
|
||||
// Add visual representation
|
||||
const sphereMtl = new PBRMaterial(rootEntity.engine);
|
||||
const sphereRenderer = sphereEntity.addComponent(MeshRenderer);
|
||||
sphereMtl.baseColor.set(color.x, color.y, color.z, 1.0);
|
||||
sphereMtl.metallic = 0.0;
|
||||
sphereMtl.roughness = 0.5;
|
||||
sphereRenderer.mesh = PrimitiveMesh.createSphere(rootEntity.engine, radius);
|
||||
sphereRenderer.setMaterial(sphereMtl);
|
||||
|
||||
// Add physics
|
||||
const physicsSphere = new SphereColliderShape();
|
||||
physicsSphere.radius = radius;
|
||||
|
||||
const sphereCollider = sphereEntity.addComponent(DynamicCollider);
|
||||
sphereCollider.collisionLayer = collisionLayer;
|
||||
sphereCollider.addShape(physicsSphere);
|
||||
|
||||
return sphereEntity;
|
||||
}
|
||||
|
||||
WebGLEngine.create({ canvas: "canvas", physics: new LitePhysics() }).then((engine) => {
|
||||
engine.canvas.resizeByClientSize();
|
||||
const scene = engine.sceneManager.activeScene;
|
||||
const rootEntity = scene.createRootEntity("root");
|
||||
|
||||
// Set up ambient lighting
|
||||
scene.ambientLight.diffuseSolidColor.set(1, 1, 1, 1);
|
||||
scene.ambientLight.diffuseIntensity = 1.2;
|
||||
|
||||
// Set up camera
|
||||
const cameraEntity = rootEntity.createChild("camera");
|
||||
const camera = cameraEntity.addComponent(Camera);
|
||||
cameraEntity.transform.setPosition(0, 3, 15);
|
||||
cameraEntity.transform.lookAt(new Vector3(0, 0, 0));
|
||||
|
||||
// Add point light
|
||||
const light = rootEntity.createChild("light");
|
||||
light.transform.setPosition(0, 10, 0);
|
||||
const pointLight = light.addComponent(PointLight);
|
||||
pointLight.intensity = 1.5;
|
||||
|
||||
const groundEntity = rootEntity.createChild("ground");
|
||||
|
||||
// 设置立方体的位置和大小
|
||||
groundEntity.transform.setPosition(0, 1, 0);
|
||||
// groundEntity.isActive = false;
|
||||
|
||||
// Visual representation of the ground cube
|
||||
const groundMtl = new PBRMaterial(engine);
|
||||
groundMtl.baseColor.set(0.5, 0.5, 0.5, 1.0);
|
||||
groundMtl.roughness = 0.7;
|
||||
|
||||
const cubeSize = new Vector3(10, 0.2, 10);
|
||||
const groundRenderer = groundEntity.addComponent(MeshRenderer);
|
||||
groundRenderer.mesh = PrimitiveMesh.createCuboid(engine, cubeSize.x, cubeSize.y, cubeSize.z);
|
||||
groundRenderer.setMaterial(groundMtl);
|
||||
|
||||
// Physics for the ground cube
|
||||
const groundCollider = groundEntity.addComponent(StaticCollider);
|
||||
const groundShape = new BoxColliderShape();
|
||||
groundShape.size = cubeSize;
|
||||
groundCollider.addShape(groundShape);
|
||||
|
||||
groundCollider.collisionLayer = Layer.Layer3;
|
||||
|
||||
const sphere1 = createPhysicsSphere(
|
||||
rootEntity,
|
||||
"RedSphere",
|
||||
new Vector3(-2, 5, 0),
|
||||
0.5,
|
||||
new Vector3(1, 0, 0),
|
||||
Layer.Layer1
|
||||
);
|
||||
|
||||
const sphere2 = createPhysicsSphere(
|
||||
rootEntity,
|
||||
"BlueSphere",
|
||||
new Vector3(2, 5, 0),
|
||||
0.5,
|
||||
new Vector3(0, 0, 1),
|
||||
Layer.Layer2
|
||||
);
|
||||
|
||||
scene.physics.setColliderLayerCollision(Layer.Layer2, Layer.Layer3, false);
|
||||
updateForE2E(engine, 1000, 38);
|
||||
initScreenshot(engine, camera);
|
||||
});
|
||||
146
e2e/case/physx-collision-group.ts
Normal file
146
e2e/case/physx-collision-group.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* @title Physx Collision Group
|
||||
* @category Physics
|
||||
*/
|
||||
import {
|
||||
WebGLEngine,
|
||||
SphereColliderShape,
|
||||
DynamicCollider,
|
||||
BoxColliderShape,
|
||||
Vector3,
|
||||
MeshRenderer,
|
||||
PointLight,
|
||||
PrimitiveMesh,
|
||||
Camera,
|
||||
Script,
|
||||
StaticCollider,
|
||||
ColliderShape,
|
||||
PBRMaterial,
|
||||
Entity,
|
||||
Layer
|
||||
} from "@galacean/engine";
|
||||
|
||||
import { PhysXPhysics } from "@galacean/engine-physics-physx";
|
||||
import { initScreenshot, updateForE2E } from "./.mockForE2E";
|
||||
|
||||
class CheckScript extends Script {
|
||||
onTriggerEnter(other: ColliderShape) {
|
||||
console.log("onTriggerEnter", other);
|
||||
// Change color to green when collision occurs
|
||||
(this.entity.getComponent(MeshRenderer).getMaterial() as PBRMaterial).baseColor.set(0, 1, 0, 1);
|
||||
}
|
||||
|
||||
onContactEnter(other: ColliderShape) {
|
||||
console.log("onContactEnter", other);
|
||||
// Change color to green when collision occurs
|
||||
(this.entity.getComponent(MeshRenderer).getMaterial() as PBRMaterial).baseColor.set(0, 1, 0, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Create a sphere with physics
|
||||
function createPhysicsSphere(
|
||||
rootEntity: Entity,
|
||||
name: string,
|
||||
position: Vector3,
|
||||
radius: number,
|
||||
color: Vector3,
|
||||
collisionLayer: number
|
||||
) {
|
||||
const sphereEntity = rootEntity.createChild(name);
|
||||
sphereEntity.transform.setPosition(position.x, position.y, position.z);
|
||||
|
||||
// Add visual representation
|
||||
const sphereMtl = new PBRMaterial(rootEntity.engine);
|
||||
const sphereRenderer = sphereEntity.addComponent(MeshRenderer);
|
||||
sphereMtl.baseColor.set(color.x, color.y, color.z, 1.0);
|
||||
sphereMtl.metallic = 0.0;
|
||||
sphereMtl.roughness = 0.5;
|
||||
sphereRenderer.mesh = PrimitiveMesh.createSphere(rootEntity.engine, radius);
|
||||
sphereRenderer.setMaterial(sphereMtl);
|
||||
|
||||
// Add physics
|
||||
const physicsSphere = new SphereColliderShape();
|
||||
physicsSphere.radius = radius;
|
||||
physicsSphere.material.bounciness = 0.8;
|
||||
|
||||
const sphereCollider = sphereEntity.addComponent(DynamicCollider);
|
||||
sphereCollider.collisionLayer = collisionLayer;
|
||||
sphereEntity.addComponent(CheckScript);
|
||||
sphereCollider.addShape(physicsSphere);
|
||||
|
||||
return sphereEntity;
|
||||
}
|
||||
|
||||
WebGLEngine.create({ canvas: "canvas", physics: new PhysXPhysics() }).then((engine) => {
|
||||
engine.canvas.resizeByClientSize();
|
||||
const scene = engine.sceneManager.activeScene;
|
||||
const rootEntity = scene.createRootEntity("root");
|
||||
|
||||
// Set up ambient lighting
|
||||
scene.ambientLight.diffuseSolidColor.set(1, 1, 1, 1);
|
||||
scene.ambientLight.diffuseIntensity = 1.2;
|
||||
|
||||
// Set up camera
|
||||
const cameraEntity = rootEntity.createChild("camera");
|
||||
const camera = cameraEntity.addComponent(Camera);
|
||||
// 调整相机位置以便更好地观察穿透效果
|
||||
cameraEntity.transform.setPosition(0, 3, 15);
|
||||
cameraEntity.transform.lookAt(new Vector3(0, 0, 0));
|
||||
|
||||
// Add point light
|
||||
const light = rootEntity.createChild("light");
|
||||
light.transform.setPosition(0, 10, 0);
|
||||
const pointLight = light.addComponent(PointLight);
|
||||
pointLight.intensity = 1.5;
|
||||
|
||||
// 创建立方体作为地面
|
||||
const groundEntity = rootEntity.createChild("ground");
|
||||
|
||||
// 设置立方体的位置和大小
|
||||
groundEntity.transform.setPosition(0, 1, 0);
|
||||
|
||||
// Visual representation of the ground cube
|
||||
const groundMtl = new PBRMaterial(engine);
|
||||
groundMtl.baseColor.set(0.5, 0.5, 0.5, 1.0);
|
||||
groundMtl.roughness = 0.7;
|
||||
// 设置半透明以便能看到穿透的球体
|
||||
groundMtl.baseColor.a = 0.5;
|
||||
|
||||
const cubeSize = new Vector3(10, 0.2, 10);
|
||||
const groundRenderer = groundEntity.addComponent(MeshRenderer);
|
||||
groundRenderer.mesh = PrimitiveMesh.createCuboid(engine, cubeSize.x, cubeSize.y, cubeSize.z);
|
||||
groundRenderer.setMaterial(groundMtl);
|
||||
|
||||
// Physics for the ground cube
|
||||
const groundCollider = groundEntity.addComponent(StaticCollider);
|
||||
const groundShape = new BoxColliderShape();
|
||||
groundShape.size = cubeSize;
|
||||
groundCollider.addShape(groundShape);
|
||||
|
||||
groundCollider.collisionLayer = Layer.Layer3;
|
||||
|
||||
// 创建可以碰撞的红色球体
|
||||
const sphere1 = createPhysicsSphere(
|
||||
rootEntity,
|
||||
"RedSphere",
|
||||
new Vector3(-2, 5, 0),
|
||||
0.5,
|
||||
new Vector3(1, 0, 0),
|
||||
Layer.Layer1
|
||||
);
|
||||
|
||||
// 创建可以穿透的蓝色球体
|
||||
const sphere2 = createPhysicsSphere(
|
||||
rootEntity,
|
||||
"BlueSphere",
|
||||
new Vector3(2, 5, 0),
|
||||
0.5,
|
||||
new Vector3(0, 0, 1),
|
||||
Layer.Layer2
|
||||
);
|
||||
|
||||
scene.physics.setColliderLayerCollision(Layer.Layer2, Layer.Layer3, false);
|
||||
|
||||
updateForE2E(engine, 110);
|
||||
initScreenshot(engine, camera);
|
||||
});
|
||||
@@ -214,6 +214,16 @@ export const E2E_CONFIG = {
|
||||
category: "Physics",
|
||||
caseFileName: "physx-collision",
|
||||
threshold: 0.1
|
||||
},
|
||||
"LitePhysics Collision Group": {
|
||||
category: "Physics",
|
||||
caseFileName: "litePhysics-collision-group",
|
||||
threshold: 0.1
|
||||
},
|
||||
"PhysXPhysics Collision Group": {
|
||||
category: "Physics",
|
||||
caseFileName: "physx-collision-group",
|
||||
threshold: 0.1
|
||||
}
|
||||
},
|
||||
Particle: {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:98e836afe6c51a5b510e7d7d462a79de8a7e7c720b81cdac10ffae4bf3e0bdca
|
||||
size 37799
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:fbd1f39578c6f1f92eaf6910ac91a05f76015e5c22742bf7690a885cd82bfc7e
|
||||
size 40667
|
||||
@@ -81,13 +81,12 @@ export class CharacterController extends Collider {
|
||||
constructor(entity: Entity) {
|
||||
super(entity);
|
||||
(<ICharacterController>this._nativeCollider) = PhysicsScene._nativePhysics.createCharacterController();
|
||||
|
||||
this._setUpDirection = this._setUpDirection.bind(this);
|
||||
//@ts-ignore
|
||||
this._upDirection._onValueChanged = this._setUpDirection;
|
||||
|
||||
// sync world position to physical space
|
||||
this._onUpdate();
|
||||
// Sync world position to physical space
|
||||
(<ICharacterController>this._nativeCollider).setWorldPosition(this.entity.transform.worldPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { ICollider, IStaticCollider } from "@galacean/engine-design";
|
||||
import { BoolUpdateFlag } from "../BoolUpdateFlag";
|
||||
import { deepClone, ignoreClone } from "../clone/CloneManager";
|
||||
import { ICustomClone } from "../clone/ComponentCloner";
|
||||
import { Component } from "../Component";
|
||||
import { DependentMode, dependentComponents } from "../ComponentsDependencies";
|
||||
import { Entity } from "../Entity";
|
||||
import { Layer } from "../Layer";
|
||||
import { Transform } from "../Transform";
|
||||
import { deepClone, ignoreClone } from "../clone/CloneManager";
|
||||
import { ColliderShape } from "./shape/ColliderShape";
|
||||
import { ICustomClone } from "../clone/ComponentCloner";
|
||||
import { ColliderShapeChangeFlag } from "./enums/ColliderShapeChangeFlag";
|
||||
|
||||
/**
|
||||
* Base class for all colliders.
|
||||
@@ -24,6 +26,7 @@ export class Collider extends Component implements ICustomClone {
|
||||
protected _updateFlag: BoolUpdateFlag;
|
||||
@deepClone
|
||||
protected _shapes: ColliderShape[] = [];
|
||||
protected _collisionLayerIndex: number = 0;
|
||||
|
||||
/**
|
||||
* The shapes of this collider.
|
||||
@@ -32,6 +35,26 @@ export class Collider extends Component implements ICustomClone {
|
||||
return this._shapes;
|
||||
}
|
||||
|
||||
/**
|
||||
* The collision layer of this collider, only support single layer.
|
||||
*
|
||||
* @defaultValue `Layer.Layer0`
|
||||
*/
|
||||
get collisionLayer(): Layer {
|
||||
return (1 << this._collisionLayerIndex) as Layer;
|
||||
}
|
||||
|
||||
set collisionLayer(value: Layer) {
|
||||
// Check if value is a single layer (power of 2)
|
||||
const index = Math.log2(value);
|
||||
if (!Number.isInteger(index)) {
|
||||
throw new Error("Collision layer must be a single layer (Layer.Layer0 to Layer.Layer31)");
|
||||
}
|
||||
|
||||
this._collisionLayerIndex = index;
|
||||
this._nativeCollider.setCollisionLayer(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@@ -52,7 +75,7 @@ export class Collider extends Component implements ICustomClone {
|
||||
}
|
||||
this._shapes.push(shape);
|
||||
this._addNativeShape(shape);
|
||||
this._handleShapesChanged();
|
||||
this._handleShapesChanged(ColliderShapeChangeFlag.Count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +88,7 @@ export class Collider extends Component implements ICustomClone {
|
||||
if (index !== -1) {
|
||||
this._shapes.splice(index, 1);
|
||||
this._removeNativeShape(shape);
|
||||
this._handleShapesChanged();
|
||||
this._handleShapesChanged(ColliderShapeChangeFlag.Count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +101,7 @@ export class Collider extends Component implements ICustomClone {
|
||||
this._removeNativeShape(shapes[i]);
|
||||
}
|
||||
shapes.length = 0;
|
||||
this._handleShapesChanged();
|
||||
this._handleShapesChanged(ColliderShapeChangeFlag.Count);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,12 +152,17 @@ export class Collider extends Component implements ICustomClone {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_handleShapesChanged(): void {}
|
||||
_handleShapesChanged(changeType: ColliderShapeChangeFlag): void {
|
||||
if (changeType & ColliderShapeChangeFlag.Count) {
|
||||
this._setCollisionLayer();
|
||||
}
|
||||
}
|
||||
|
||||
protected _syncNative(): void {
|
||||
for (let i = 0, n = this.shapes.length; i < n; i++) {
|
||||
this._addNativeShape(this.shapes[i]);
|
||||
}
|
||||
this._setCollisionLayer();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,4 +189,8 @@ export class Collider extends Component implements ICustomClone {
|
||||
shape._collider = null;
|
||||
this._nativeCollider.removeShape(shape._nativeShape);
|
||||
}
|
||||
|
||||
private _setCollisionLayer(): void {
|
||||
this._nativeCollider.setCollisionLayer(this._collisionLayerIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Quaternion, Vector3 } from "@galacean/engine-math";
|
||||
import { ignoreClone } from "../clone/CloneManager";
|
||||
import { Entity } from "../Entity";
|
||||
import { Collider } from "./Collider";
|
||||
import { ColliderShapeChangeFlag } from "./enums/ColliderShapeChangeFlag";
|
||||
import { PhysicsScene } from "./PhysicsScene";
|
||||
|
||||
/**
|
||||
@@ -420,7 +421,9 @@ export class DynamicCollider extends Collider {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
override _handleShapesChanged(): void {
|
||||
override _handleShapesChanged(changeType: ColliderShapeChangeFlag): void {
|
||||
super._handleShapesChanged(changeType);
|
||||
|
||||
if (this._automaticCenterOfMass || this._automaticInertiaTensor) {
|
||||
this._setMassAndUpdateInertia();
|
||||
}
|
||||
|
||||
@@ -231,6 +231,38 @@ export class PhysicsScene {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether two colliders can collide with each other.
|
||||
* @param layer1 - The first collision layer
|
||||
* @param layer2 - The second collision layer
|
||||
* @returns Whether the colliders should collide
|
||||
*/
|
||||
getColliderLayerCollision(layer1: Layer, layer2: Layer): boolean {
|
||||
const index1 = Math.log2(layer1);
|
||||
const index2 = Math.log2(layer2);
|
||||
if (!Number.isInteger(index1) || !Number.isInteger(index1)) {
|
||||
throw new Error("Collision layer must be a single layer (Layer.Layer0 to Layer.Layer31)");
|
||||
}
|
||||
|
||||
return PhysicsScene._nativePhysics.getColliderLayerCollision(index1, index2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether two colliders can collide with each other.
|
||||
* @param layer1 - The first collision layer
|
||||
* @param layer2 - The second collision layer
|
||||
* @param isCollide - Whether the colliders should collide
|
||||
*/
|
||||
setColliderLayerCollision(layer1: Layer, layer2: Layer, isCollide: boolean): void {
|
||||
const index1 = Math.log2(layer1);
|
||||
const index2 = Math.log2(layer2);
|
||||
if (!Number.isInteger(index1) || !Number.isInteger(index1)) {
|
||||
throw new Error("Collision layer must be a single layer (Layer.Layer0 to Layer.Layer31)");
|
||||
}
|
||||
|
||||
PhysicsScene._nativePhysics.setColliderLayerCollision(index1, index2, isCollide);
|
||||
}
|
||||
|
||||
/**
|
||||
* Casts a ray through the Scene and returns the first hit.
|
||||
* @param ray - The ray
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export enum ColliderShapeChangeFlag {
|
||||
Property = 0x1,
|
||||
Count = 0x2,
|
||||
Both = Property | Count
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { Collider } from "../Collider";
|
||||
import { deepClone, ignoreClone } from "../../clone/CloneManager";
|
||||
import { ICustomClone } from "../../clone/ComponentCloner";
|
||||
import { Engine } from "../../Engine";
|
||||
import { ColliderShapeChangeFlag } from "../enums/ColliderShapeChangeFlag";
|
||||
|
||||
/**
|
||||
* Abstract class for collider shapes.
|
||||
@@ -52,6 +53,7 @@ export abstract class ColliderShape implements ICustomClone {
|
||||
|
||||
/**
|
||||
* Contact offset for this shape, the value must be greater than or equal to 0.
|
||||
* @defaultValue 0.02
|
||||
*/
|
||||
get contactOffset(): number {
|
||||
return this._contactOffset;
|
||||
@@ -183,18 +185,18 @@ export abstract class ColliderShape implements ICustomClone {
|
||||
this._nativeShape.setIsTrigger(this._isTrigger);
|
||||
this._nativeShape.setMaterial(this._material._nativeMaterial);
|
||||
|
||||
this._collider?._handleShapesChanged();
|
||||
this._collider?._handleShapesChanged(ColliderShapeChangeFlag.Property);
|
||||
}
|
||||
|
||||
@ignoreClone
|
||||
private _setPosition(): void {
|
||||
this._nativeShape.setPosition(this._position);
|
||||
this._collider?._handleShapesChanged();
|
||||
this._collider?._handleShapesChanged(ColliderShapeChangeFlag.Property);
|
||||
}
|
||||
|
||||
@ignoreClone
|
||||
private _setRotation(): void {
|
||||
this._nativeShape.setRotation(this._rotation);
|
||||
this._collider?._handleShapesChanged();
|
||||
this._collider?._handleShapesChanged(ColliderShapeChangeFlag.Property);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,12 @@ export interface ICollider {
|
||||
*/
|
||||
removeShape(shape: IColliderShape): void;
|
||||
|
||||
/**
|
||||
* Set the collision group of the collider.
|
||||
* @param layer - The layer of the collider which the collider belongs to
|
||||
*/
|
||||
setCollisionLayer(layer: number): void;
|
||||
|
||||
/**
|
||||
* Deletes the collider.
|
||||
*/
|
||||
|
||||
@@ -119,19 +119,35 @@ export interface IPhysics {
|
||||
|
||||
/**
|
||||
* Create fixed joint.
|
||||
* @param collider - Affector of joint
|
||||
* @param collider - collider of joint
|
||||
*/
|
||||
createFixedJoint(collider: ICollider): IFixedJoint;
|
||||
|
||||
/**
|
||||
* Create hinge joint.
|
||||
* @param collider - Affector of joint
|
||||
* @param collider - collider of joint
|
||||
*/
|
||||
createHingeJoint(collider: ICollider): IHingeJoint;
|
||||
|
||||
/**
|
||||
* Create spring joint
|
||||
* @param collider - Affector of joint
|
||||
* @param collider - collider of joint
|
||||
*/
|
||||
createSpringJoint(collider: ICollider): ISpringJoint;
|
||||
|
||||
/**
|
||||
* Get whether two collision layers can collide with each other.
|
||||
* @param layer1 - The first collision layer
|
||||
* @param layer2 - The second collision layer
|
||||
* @returns Whether the layers should collide
|
||||
*/
|
||||
getColliderLayerCollision(layer1: number, layer2: number): boolean;
|
||||
|
||||
/**
|
||||
* Set whether two collision layers can collide with each other.
|
||||
* @param layer1 - The first collision layer
|
||||
* @param layer2 - The second collision layer
|
||||
* @param isCollide - Whether the layers should collide
|
||||
*/
|
||||
setColliderLayerCollision(layer1: number, layer2: number, isCollide: boolean): void;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { ICollider } from "@galacean/engine-design";
|
||||
import { Quaternion, Ray, Vector3 } from "@galacean/engine";
|
||||
import { Layer, Quaternion, Ray, Vector3 } from "@galacean/engine";
|
||||
import { LiteHitResult } from "./LiteHitResult";
|
||||
import { LiteColliderShape } from "./shape/LiteColliderShape";
|
||||
import { LiteTransform } from "./LiteTransform";
|
||||
import { LitePhysicsScene } from "./LitePhysicsScene";
|
||||
import { LitePhysics } from "./LitePhysics";
|
||||
|
||||
/**
|
||||
* Abstract class of physical collider.
|
||||
@@ -11,6 +12,7 @@ import { LitePhysicsScene } from "./LitePhysicsScene";
|
||||
export abstract class LiteCollider implements ICollider {
|
||||
/** @internal */
|
||||
abstract readonly _isStaticCollider: boolean;
|
||||
private _litePhysics: LitePhysics;
|
||||
|
||||
/** @internal */
|
||||
_scene: LitePhysicsScene;
|
||||
@@ -18,9 +20,12 @@ export abstract class LiteCollider implements ICollider {
|
||||
_shapes: LiteColliderShape[] = [];
|
||||
/** @internal */
|
||||
_transform: LiteTransform = new LiteTransform();
|
||||
/** @internal */
|
||||
_collisionLayer: number;
|
||||
|
||||
protected constructor() {
|
||||
protected constructor(litePhysics: LitePhysics) {
|
||||
this._transform.owner = this;
|
||||
this._litePhysics = litePhysics;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,6 +72,13 @@ export abstract class LiteCollider implements ICollider {
|
||||
outRotation.set(rotationQuaternion.x, rotationQuaternion.y, rotationQuaternion.z, rotationQuaternion.w);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc ICollider.setCollisionLayer }
|
||||
*/
|
||||
setCollisionLayer(collisionLayer: Layer): void {
|
||||
this._litePhysics.setColliderLayer(this, collisionLayer);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc ICollider.destroy }
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { LiteCollider } from "./LiteCollider";
|
||||
import { IDynamicCollider } from "@galacean/engine-design";
|
||||
import { Logger, Quaternion, Vector3 } from "@galacean/engine";
|
||||
import { LitePhysics } from "./LitePhysics";
|
||||
|
||||
/**
|
||||
* A dynamic collider can act with self-defined movement or physical force
|
||||
@@ -13,8 +14,8 @@ export class LiteDynamicCollider extends LiteCollider implements IDynamicCollide
|
||||
* @param position - The global position
|
||||
* @param rotation - The global rotation
|
||||
*/
|
||||
constructor(position: Vector3, rotation: Quaternion) {
|
||||
super();
|
||||
constructor(litePhysics: LitePhysics, position: Vector3, rotation: Quaternion) {
|
||||
super(litePhysics);
|
||||
this._transform.setPosition(position.x, position.y, position.z);
|
||||
this._transform.setRotationQuaternion(rotation.x, rotation.y, rotation.z, rotation.w);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Quaternion, Vector3 } from "@galacean/engine";
|
||||
import { Quaternion, Vector3, Layer } from "@galacean/engine";
|
||||
import {
|
||||
IBoxColliderShape,
|
||||
ICapsuleColliderShape,
|
||||
ICharacterController,
|
||||
ICollider,
|
||||
ICollision,
|
||||
IDynamicCollider,
|
||||
IFixedJoint,
|
||||
@@ -25,6 +26,8 @@ import { LiteSphereColliderShape } from "./shape/LiteSphereColliderShape";
|
||||
import { LitePhysicsManager } from "./LitePhysicsManager";
|
||||
|
||||
export class LitePhysics implements IPhysics {
|
||||
private _layerCollisionMatrix: boolean[] = [];
|
||||
|
||||
/**
|
||||
* {@inheritDoc IPhysics.initialize }
|
||||
*/
|
||||
@@ -52,6 +55,7 @@ export class LitePhysics implements IPhysics {
|
||||
onTriggerPersist?: (obj1: number, obj2: number) => void
|
||||
): LitePhysicsScene {
|
||||
return new LitePhysicsScene(
|
||||
this,
|
||||
onContactBegin,
|
||||
onContactEnd,
|
||||
onContactPersist,
|
||||
@@ -65,14 +69,14 @@ export class LitePhysics implements IPhysics {
|
||||
* {@inheritDoc IPhysics.createStaticCollider }
|
||||
*/
|
||||
createStaticCollider(position: Vector3, rotation: Quaternion): IStaticCollider {
|
||||
return new LiteStaticCollider(position, rotation);
|
||||
return new LiteStaticCollider(this, position, rotation);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc IPhysics.createDynamicCollider }
|
||||
*/
|
||||
createDynamicCollider(position: Vector3, rotation: Quaternion): IDynamicCollider {
|
||||
return new LiteDynamicCollider(position, rotation);
|
||||
return new LiteDynamicCollider(this, position, rotation);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -148,4 +152,46 @@ export class LitePhysics implements IPhysics {
|
||||
createSpringJoint(collider: LiteCollider): ISpringJoint {
|
||||
throw "Physics-lite don't support CapsuleColliderShape. Use Physics-PhysX instead!";
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc IPhysics.setColliderLayer }
|
||||
*/
|
||||
setColliderLayer(collider: LiteCollider, layer: Layer): void {
|
||||
collider._collisionLayer = layer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc IPhysics.getColliderLayerCollision }
|
||||
*/
|
||||
getColliderLayerCollision(layer1: number, layer2: number): boolean {
|
||||
const index = this._getColliderLayerIndex(layer1, layer2);
|
||||
if (index > -1) {
|
||||
return this._layerCollisionMatrix[index] ?? true;
|
||||
}
|
||||
// If either layer is Layer.Nothing, they cant collide
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc IPhysics.setColliderLayerCollision }
|
||||
*/
|
||||
setColliderLayerCollision(layer1: number, layer2: number, collide: boolean): void {
|
||||
const index = this._getColliderLayerIndex(layer1, layer2);
|
||||
if (index > -1) {
|
||||
this._layerCollisionMatrix[index] = collide;
|
||||
}
|
||||
}
|
||||
|
||||
private _getColliderLayerIndex(layer1: number, layer2: number): number {
|
||||
if (layer1 === 32 || layer2 === 32) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const min = Math.min(layer1, layer2);
|
||||
const max = Math.max(layer1, layer2);
|
||||
|
||||
// Calculate a unique index for the layer pair using the triangular number formula
|
||||
// This ensures that each layer combination maps to a unique index in the collision matrix
|
||||
return (max * (max + 1)) / 2 + min;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { LiteStaticCollider } from "./LiteStaticCollider";
|
||||
import { LiteBoxColliderShape } from "./shape/LiteBoxColliderShape";
|
||||
import { LiteColliderShape } from "./shape/LiteColliderShape";
|
||||
import { LiteSphereColliderShape } from "./shape/LiteSphereColliderShape";
|
||||
import { LitePhysics } from "./LitePhysics";
|
||||
|
||||
/**
|
||||
* A manager is a collection of colliders and constraints which can interact.
|
||||
@@ -32,8 +33,10 @@ export class LitePhysicsScene implements IPhysicsScene {
|
||||
private _currentEvents: DisorderedArray<TriggerEvent> = new DisorderedArray<TriggerEvent>();
|
||||
private _eventMap: Record<number, Record<number, TriggerEvent>> = {};
|
||||
private _eventPool: TriggerEvent[] = [];
|
||||
private _physics: LitePhysics;
|
||||
|
||||
constructor(
|
||||
physics: LitePhysics,
|
||||
onContactEnter?: (collision: ICollision) => void,
|
||||
onContactExit?: (collision: ICollision) => void,
|
||||
onContactStay?: (collision: ICollision) => void,
|
||||
@@ -41,6 +44,7 @@ export class LitePhysicsScene implements IPhysicsScene {
|
||||
onTriggerExit?: (obj1: number, obj2: number) => void,
|
||||
onTriggerStay?: (obj1: number, obj2: number) => void
|
||||
) {
|
||||
this._physics = physics;
|
||||
this._onContactEnter = onContactEnter;
|
||||
this._onContactExit = onContactExit;
|
||||
this._onContactStay = onContactStay;
|
||||
@@ -216,7 +220,14 @@ export class LitePhysicsScene implements IPhysicsScene {
|
||||
if (myShape instanceof LiteBoxColliderShape) {
|
||||
LitePhysicsScene._updateWorldBox(myShape, this._box);
|
||||
for (let j = 0, len = colliders.length; j < len; j++) {
|
||||
const colliderShape = colliders[j]._shapes;
|
||||
const collider = colliders[j];
|
||||
const colliderShape = collider._shapes;
|
||||
|
||||
// Skip collision check if layers can't collide
|
||||
if (!this._checkColliderCollide(collider, myCollider)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let k = 0, len = colliderShape.length; k < len; k++) {
|
||||
const shape = colliderShape[k];
|
||||
const index1 = shape._id;
|
||||
@@ -243,7 +254,14 @@ export class LitePhysicsScene implements IPhysicsScene {
|
||||
} else if (myShape instanceof LiteSphereColliderShape) {
|
||||
LitePhysicsScene._upWorldSphere(myShape, this._sphere);
|
||||
for (let j = 0, len = colliders.length; j < len; j++) {
|
||||
const colliderShape = colliders[j]._shapes;
|
||||
const collider = colliders[j];
|
||||
const colliderShape = collider._shapes;
|
||||
|
||||
// Skip collision check if layers can't collide
|
||||
if (!this._checkColliderCollide(collider, myCollider)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let k = 0, len = colliderShape.length; k < len; k++) {
|
||||
const shape = colliderShape[k];
|
||||
const index1 = shape._id;
|
||||
@@ -345,6 +363,17 @@ export class LitePhysicsScene implements IPhysicsScene {
|
||||
|
||||
return isHit;
|
||||
}
|
||||
|
||||
private _checkColliderCollide(collider1: LiteCollider, collider2: LiteCollider): boolean {
|
||||
const group1 = collider1._collisionLayer;
|
||||
const group2 = collider2._collisionLayer;
|
||||
|
||||
if (group1 === group2) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this._physics.getColliderLayerCollision(group1, group2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { IStaticCollider } from "@galacean/engine-design";
|
||||
import { LiteCollider } from "./LiteCollider";
|
||||
import { Quaternion, Vector3 } from "@galacean/engine";
|
||||
import { LitePhysics } from "./LitePhysics";
|
||||
|
||||
/**
|
||||
* A static collider component that will not move.
|
||||
@@ -14,8 +15,8 @@ export class LiteStaticCollider extends LiteCollider implements IStaticCollider
|
||||
* @param position - The global position
|
||||
* @param rotation - The global rotation
|
||||
*/
|
||||
constructor(position: Vector3, rotation: Quaternion) {
|
||||
super();
|
||||
constructor(litePhysics: LitePhysics, position: Vector3, rotation: Quaternion) {
|
||||
super(litePhysics);
|
||||
this._transform.setPosition(position.x, position.y, position.z);
|
||||
this._transform.setRotationQuaternion(rotation.x, rotation.y, rotation.z, rotation.w);
|
||||
}
|
||||
|
||||
@@ -88,6 +88,8 @@ export class PhysXCharacterController implements ICharacterController {
|
||||
* {@inheritDoc ICharacterController.addShape }
|
||||
*/
|
||||
addShape(shape: PhysXColliderShape): void {
|
||||
// Add shape should sync latest position and world scale to pxController
|
||||
this._updateShapePosition(shape._position, shape._worldScale);
|
||||
// When CharacterController is disabled, set shape property need check pxController whether exist because of this._pxManager is null and won't create pxController
|
||||
this._pxManager && this._createPXController(this._pxManager, shape);
|
||||
this._shape = shape;
|
||||
@@ -106,6 +108,17 @@ export class PhysXCharacterController implements ICharacterController {
|
||||
this._scene?._removeColliderShape(shape._id);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc ICollider.setCollisionLayer }
|
||||
*/
|
||||
setCollisionLayer(layer: number): void {
|
||||
const actor = this._pxController?.getActor();
|
||||
|
||||
if (actor) {
|
||||
this._physXPhysics._physX.setGroup(actor, layer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc ICharacterController.destroy }
|
||||
*/
|
||||
@@ -136,6 +149,8 @@ export class PhysXCharacterController implements ICharacterController {
|
||||
|
||||
this._pxController = pxManager._getControllerManager().createController(desc);
|
||||
this._pxController.setUUID(shape._id);
|
||||
|
||||
this._updateNativePosition();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -156,7 +171,7 @@ export class PhysXCharacterController implements ICharacterController {
|
||||
this._updateNativePosition();
|
||||
}
|
||||
|
||||
private _updateNativePosition() {
|
||||
private _updateNativePosition(): void {
|
||||
const worldPosition = this._worldPosition;
|
||||
if (this._pxController && worldPosition) {
|
||||
Vector3.add(worldPosition, this._shapeScaledPosition, PhysXCharacterController._tempVec);
|
||||
|
||||
@@ -61,6 +61,13 @@ export abstract class PhysXCollider implements ICollider {
|
||||
outRotation.set(transform.rotation.x, transform.rotation.y, transform.rotation.z, transform.rotation.w);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc ICollider.setCollisionLayer }
|
||||
*/
|
||||
setCollisionLayer(layer: number): void {
|
||||
this._physXPhysics._physX.setGroup(this._pxActor, layer);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc ICollider.destroy }
|
||||
*/
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Quaternion, Vector3, version } from "@galacean/engine";
|
||||
import { Quaternion, Vector3, Layer } from "@galacean/engine";
|
||||
import {
|
||||
IBoxColliderShape,
|
||||
ICapsuleColliderShape,
|
||||
ICharacterController,
|
||||
ICollider,
|
||||
ICollision,
|
||||
IDynamicCollider,
|
||||
IFixedJoint,
|
||||
@@ -92,7 +93,7 @@ export class PhysXPhysics implements IPhysics {
|
||||
if (runtimeMode == PhysXRuntimeMode.JavaScript) {
|
||||
script.src = `https://mdn.alipayobjects.com/rms/afts/file/A*PXxaQrGL0XsAAAAAAAAAAAAAARQnAQ/physx.release.downgrade.js`;
|
||||
} else if (runtimeMode == PhysXRuntimeMode.WebAssembly) {
|
||||
script.src = `https://mdn.alipayobjects.com/rms/afts/file/A*0Qq8Rob3_5oAAAAAAAAAAAAAARQnAQ/physx.release.js`;
|
||||
script.src = `https://mdn.alipayobjects.com/rms/afts/file/A*H4ElTYwBxwgAAAAAAAAAAAAAARQnAQ/physx.release.js`;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -147,7 +148,7 @@ export class PhysXPhysics implements IPhysics {
|
||||
onTriggerEnd?: (obj1: number, obj2: number) => void,
|
||||
onTriggerStay?: (obj1: number, obj2: number) => void
|
||||
): IPhysicsScene {
|
||||
const manager = new PhysXPhysicsScene(
|
||||
const scene = new PhysXPhysicsScene(
|
||||
this,
|
||||
physicsManager,
|
||||
onContactBegin,
|
||||
@@ -157,7 +158,7 @@ export class PhysXPhysics implements IPhysics {
|
||||
onTriggerEnd,
|
||||
onTriggerStay
|
||||
);
|
||||
return manager;
|
||||
return scene;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -248,6 +249,20 @@ export class PhysXPhysics implements IPhysics {
|
||||
return new PhysXSpringJoint(this, collider);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc IPhysics.getColliderLayerCollision }
|
||||
*/
|
||||
getColliderLayerCollision(layer1: number, layer2: number): boolean {
|
||||
return this._physX.getGroupCollisionFlag(layer1, layer2);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc IPhysics.setColliderLayerCollision }
|
||||
*/
|
||||
setColliderLayerCollision(layer1: number, layer2: number, isCollide: boolean): void {
|
||||
this._physX.setGroupCollisionFlag(layer1, layer2, isCollide);
|
||||
}
|
||||
|
||||
private _init(physX: any): void {
|
||||
const version = physX.PX_PHYSICS_VERSION;
|
||||
const defaultErrorCallback = new physX.PxDefaultErrorCallback();
|
||||
|
||||
@@ -31,16 +31,10 @@ export abstract class PhysXColliderShape implements IColliderShape {
|
||||
_controllers: DisorderedArray<PhysXCharacterController> = new DisorderedArray<PhysXCharacterController>();
|
||||
/** @internal */
|
||||
_contractOffset: number = 0.02;
|
||||
|
||||
protected _physXPhysics: PhysXPhysics;
|
||||
protected _worldScale: Vector3 = new Vector3(1, 1, 1);
|
||||
protected _position: Vector3 = new Vector3();
|
||||
protected _rotation: Vector3 = new Vector3();
|
||||
protected _axis: Quaternion = null;
|
||||
protected _physXRotation: Quaternion = new Quaternion();
|
||||
|
||||
private _shapeFlags: ShapeFlag = ShapeFlag.SCENE_QUERY_SHAPE | ShapeFlag.SIMULATION_SHAPE;
|
||||
|
||||
/** @internal */
|
||||
_worldScale: Vector3 = new Vector3(1, 1, 1);
|
||||
/** @internal */
|
||||
_position: Vector3 = new Vector3();
|
||||
/** @internal */
|
||||
_pxMaterial: any;
|
||||
/** @internal */
|
||||
@@ -50,6 +44,13 @@ export abstract class PhysXColliderShape implements IColliderShape {
|
||||
/** @internal */
|
||||
_id: number;
|
||||
|
||||
protected _physXPhysics: PhysXPhysics;
|
||||
protected _rotation: Vector3 = new Vector3();
|
||||
protected _axis: Quaternion = null;
|
||||
protected _physXRotation: Quaternion = new Quaternion();
|
||||
|
||||
private _shapeFlags: ShapeFlag = ShapeFlag.SCENE_QUERY_SHAPE | ShapeFlag.SIMULATION_SHAPE;
|
||||
|
||||
constructor(physXPhysics: PhysXPhysics) {
|
||||
this._physXPhysics = physXPhysics;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,8 @@ import {
|
||||
PlaneColliderShape,
|
||||
DynamicCollider,
|
||||
Script,
|
||||
ControllerCollisionFlag
|
||||
ControllerCollisionFlag,
|
||||
Layer
|
||||
} from "@galacean/engine-core";
|
||||
import { WebGLEngine } from "@galacean/engine-rhi-webgl";
|
||||
import { PhysXPhysics } from "@galacean/engine-physics-physx";
|
||||
@@ -322,4 +323,124 @@ describe("CharacterController", function () {
|
||||
roleEntity.isActive = false;
|
||||
controller.destroy();
|
||||
});
|
||||
|
||||
it("collision group", () => {
|
||||
const controller = roleEntity.getComponent(CharacterController);
|
||||
|
||||
const obstacleEntity = rootEntity.createChild("obstacle");
|
||||
obstacleEntity.transform.position = new Vector3(0, 0, 2);
|
||||
|
||||
const obstacleCollider = obstacleEntity.addComponent(StaticCollider);
|
||||
const triggerShape = new BoxColliderShape();
|
||||
triggerShape.size = new Vector3(1, 1, 1);
|
||||
triggerShape.isTrigger = true;
|
||||
obstacleCollider.addShape(triggerShape);
|
||||
|
||||
class TriggerDetectionScript extends Script {
|
||||
triggerEntered = false;
|
||||
|
||||
onTriggerEnter() {
|
||||
this.triggerEntered = true;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.triggerEntered = false;
|
||||
}
|
||||
}
|
||||
|
||||
const triggerScript = obstacleEntity.addComponent(TriggerDetectionScript);
|
||||
|
||||
roleEntity.layer = Layer.Layer1;
|
||||
obstacleEntity.layer = Layer.Layer2;
|
||||
|
||||
controller.move(new Vector3(0, 0, 2), 0.0001, 0.1);
|
||||
|
||||
// @ts-ignore
|
||||
engine.sceneManager.activeScene.physics._update(1);
|
||||
|
||||
expect(triggerScript.triggerEntered).toBe(true);
|
||||
|
||||
roleEntity.transform.position = new Vector3(0, 0, 0);
|
||||
triggerScript.reset();
|
||||
|
||||
engine.sceneManager.activeScene.physics.setColliderLayerCollision(Layer.Layer1, Layer.Layer2, false);
|
||||
|
||||
controller.move(new Vector3(0, 0, 2), 0.0001, 0.1);
|
||||
|
||||
// @ts-ignore
|
||||
engine.sceneManager.activeScene.physics._update(1);
|
||||
|
||||
expect(triggerScript.triggerEntered).toBe(false);
|
||||
|
||||
// 恢复默认设置
|
||||
engine.sceneManager.activeScene.physics.setColliderLayerCollision(1, 2, true);
|
||||
});
|
||||
|
||||
it("should handle manual collision group setting with trigger", () => {
|
||||
const controller = roleEntity.getComponent(CharacterController);
|
||||
|
||||
const obstacleEntity = rootEntity.createChild("obstacle");
|
||||
obstacleEntity.transform.position = new Vector3(0, 0, 2);
|
||||
|
||||
const obstacleCollider = obstacleEntity.addComponent(StaticCollider);
|
||||
const triggerShape = new BoxColliderShape();
|
||||
triggerShape.size = new Vector3(1, 1, 1);
|
||||
triggerShape.isTrigger = true;
|
||||
obstacleCollider.addShape(triggerShape);
|
||||
|
||||
class TriggerDetectionScript extends Script {
|
||||
triggerEntered = false;
|
||||
|
||||
onTriggerEnter() {
|
||||
this.triggerEntered = true;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.triggerEntered = false;
|
||||
}
|
||||
}
|
||||
|
||||
const triggerScript = obstacleEntity.addComponent(TriggerDetectionScript);
|
||||
|
||||
roleEntity.layer = Layer.Layer1;
|
||||
obstacleEntity.layer = Layer.Layer2;
|
||||
|
||||
controller.move(new Vector3(0, 0, 2), 0.0001, 0.1);
|
||||
|
||||
// @ts-ignore
|
||||
engine.sceneManager.activeScene.physics._update(1);
|
||||
|
||||
expect(triggerScript.triggerEntered).toBe(true);
|
||||
|
||||
roleEntity.transform.position = new Vector3(0, 0, 0);
|
||||
triggerScript.reset();
|
||||
|
||||
controller.collisionLayer = Layer.Layer10;
|
||||
|
||||
engine.sceneManager.activeScene.physics.setColliderLayerCollision(Layer.Layer10, Layer.Layer2, false);
|
||||
|
||||
controller.move(new Vector3(0, 0, 2), 0.0001, 0.1);
|
||||
|
||||
// @ts-ignore
|
||||
engine.sceneManager.activeScene.physics._update(1);
|
||||
|
||||
expect(triggerScript.triggerEntered).toBe(false);
|
||||
|
||||
engine.sceneManager.activeScene.physics.setColliderLayerCollision(Layer.Layer10, Layer.Layer2, true);
|
||||
|
||||
// 恢复默认设置
|
||||
engine.sceneManager.activeScene.physics.setColliderLayerCollision(Layer.Layer1, Layer.Layer2, true);
|
||||
});
|
||||
|
||||
it("keep entity position when disabled", () => {
|
||||
roleEntity.transform.position = new Vector3(0, 0, 3);
|
||||
// @ts-ignore
|
||||
engine.sceneManager.activeScene.physics._update(1);
|
||||
const controller = roleEntity.getComponent(CharacterController);
|
||||
controller.enabled = false;
|
||||
controller.enabled = true;
|
||||
// @ts-ignore
|
||||
controller._syncWorldPositionFromPhysicalSpace();
|
||||
expect(roleEntity.transform.position.z).eq(3);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import {
|
||||
BoxColliderShape,
|
||||
Collision,
|
||||
DynamicCollider,
|
||||
Entity,
|
||||
Layer,
|
||||
PlaneColliderShape,
|
||||
Script,
|
||||
SphereColliderShape,
|
||||
@@ -9,8 +11,9 @@ import {
|
||||
} from "@galacean/engine-core";
|
||||
import { Vector3 } from "@galacean/engine-math";
|
||||
import { PhysXPhysics } from "@galacean/engine-physics-physx";
|
||||
import { LitePhysics } from "@galacean/engine-physics-lite";
|
||||
import { WebGLEngine } from "@galacean/engine-rhi-webgl";
|
||||
import { vi, describe, beforeAll, beforeEach, expect, it } from "vitest";
|
||||
import { vi, describe, beforeAll, beforeEach, expect, it, afterEach } from "vitest";
|
||||
|
||||
class CollisionScript extends Script {
|
||||
onTriggerEnter = vi.fn(CollisionScript.prototype.onTriggerEnter);
|
||||
@@ -38,6 +41,22 @@ class MoveScript extends Script {
|
||||
}
|
||||
}
|
||||
|
||||
class CollisionDetectionScript extends Script {
|
||||
collisionDetected = false;
|
||||
|
||||
onTriggerEnter() {
|
||||
this.collisionDetected = true;
|
||||
}
|
||||
|
||||
onCollisionEnter(other: Collision) {
|
||||
this.collisionDetected = true;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.collisionDetected = false;
|
||||
}
|
||||
}
|
||||
|
||||
describe("physics collider test", function () {
|
||||
let engine: WebGLEngine;
|
||||
let rootEntity: Entity;
|
||||
@@ -362,3 +381,165 @@ describe("physics collider test", function () {
|
||||
expect(collider.shapes.length).eq(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Collider Layer Collision Tests", () => {
|
||||
describe("LitePhysics Layer Collision", () => {
|
||||
let engine: WebGLEngine;
|
||||
let rootEntity: Entity;
|
||||
let physics: LitePhysics;
|
||||
|
||||
beforeAll(async () => {
|
||||
physics = new LitePhysics();
|
||||
engine = await WebGLEngine.create({
|
||||
canvas: document.createElement("canvas"),
|
||||
physics
|
||||
});
|
||||
rootEntity = engine.sceneManager.activeScene.createRootEntity("root");
|
||||
});
|
||||
|
||||
it("should respect collision group settings", () => {
|
||||
const entity1 = rootEntity.createChild("entity1");
|
||||
const entity2 = rootEntity.createChild("entity2");
|
||||
|
||||
entity1.transform.position = new Vector3(0, 0, 0);
|
||||
entity2.transform.position = new Vector3(0, 0, 0);
|
||||
|
||||
const collider1 = entity1.addComponent(DynamicCollider);
|
||||
const shape1 = new BoxColliderShape();
|
||||
shape1.size = new Vector3(1, 1, 1);
|
||||
collider1.addShape(shape1);
|
||||
|
||||
const collider2 = entity2.addComponent(DynamicCollider);
|
||||
const shape2 = new BoxColliderShape();
|
||||
shape2.size = new Vector3(1, 1, 1);
|
||||
collider2.addShape(shape2);
|
||||
|
||||
entity1.layer = Layer.Layer1;
|
||||
entity2.layer = Layer.Layer2;
|
||||
|
||||
const script = entity1.addComponent(CollisionDetectionScript);
|
||||
|
||||
// @ts-ignore
|
||||
engine.sceneManager.activeScene.physics._update(1);
|
||||
expect(script.collisionDetected).toBe(true);
|
||||
|
||||
script.reset();
|
||||
|
||||
engine.sceneManager.activeScene.physics.setColliderLayerCollision(Layer.Layer1, Layer.Layer2, false);
|
||||
|
||||
// @ts-ignore
|
||||
engine.sceneManager.activeScene.physics._update(1);
|
||||
|
||||
expect(script.collisionDetected).toBe(false);
|
||||
|
||||
engine.sceneManager.activeScene.physics.setColliderLayerCollision(Layer.Layer1, Layer.Layer2, true);
|
||||
});
|
||||
|
||||
it("should handle manual collision group setting in LitePhysics", () => {
|
||||
const entity1 = rootEntity.createChild("entity1");
|
||||
const entity2 = rootEntity.createChild("entity2");
|
||||
|
||||
entity1.transform.position = new Vector3(0, 0, 0);
|
||||
entity2.transform.position = new Vector3(0, 0, 0);
|
||||
|
||||
const collider1 = entity1.addComponent(DynamicCollider);
|
||||
const shape1 = new BoxColliderShape();
|
||||
shape1.size = new Vector3(1, 1, 1);
|
||||
collider1.addShape(shape1);
|
||||
|
||||
const collider2 = entity2.addComponent(StaticCollider);
|
||||
const shape2 = new BoxColliderShape();
|
||||
shape2.size = new Vector3(1, 1, 1);
|
||||
shape2.isTrigger = true;
|
||||
collider2.addShape(shape2);
|
||||
|
||||
const script = entity2.addComponent(CollisionDetectionScript);
|
||||
|
||||
entity1.layer = Layer.Layer1;
|
||||
entity2.layer = Layer.Layer2;
|
||||
|
||||
// @ts-ignore
|
||||
engine.sceneManager.activeScene.physics._update(1);
|
||||
expect(script.collisionDetected).toBe(true);
|
||||
|
||||
script.reset();
|
||||
|
||||
collider1.collisionLayer = Layer.Layer10;
|
||||
|
||||
engine.sceneManager.activeScene.physics.setColliderLayerCollision(Layer.Layer10, Layer.Layer2, false);
|
||||
|
||||
// @ts-ignore
|
||||
engine.sceneManager.activeScene.physics._update(1);
|
||||
|
||||
expect(script.collisionDetected).toBe(false);
|
||||
|
||||
// 恢复默认设置
|
||||
engine.sceneManager.activeScene.physics.setColliderLayerCollision(Layer.Layer10, Layer.Layer2, true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
const entities = rootEntity.children;
|
||||
for (let i = entities.length - 1; i >= 0; i--) {
|
||||
entities[i].destroy();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("PhysXPhysics Layer Collision", () => {
|
||||
let engine: WebGLEngine;
|
||||
let rootEntity: Entity;
|
||||
|
||||
beforeAll(async () => {
|
||||
engine = await WebGLEngine.create({
|
||||
canvas: document.createElement("canvas"),
|
||||
physics: new PhysXPhysics()
|
||||
});
|
||||
rootEntity = engine.sceneManager.activeScene.createRootEntity("root");
|
||||
});
|
||||
|
||||
it("should respect collision group settings", () => {
|
||||
const entity1 = rootEntity.createChild("entity1");
|
||||
const entity2 = rootEntity.createChild("entity2");
|
||||
|
||||
entity1.transform.position = new Vector3(0, 0, 0);
|
||||
entity2.transform.position = new Vector3(0, 0, 0);
|
||||
|
||||
const collider1 = entity1.addComponent(DynamicCollider);
|
||||
const shape1 = new BoxColliderShape();
|
||||
shape1.size = new Vector3(1, 1, 1);
|
||||
collider1.addShape(shape1);
|
||||
|
||||
const collider2 = entity2.addComponent(StaticCollider);
|
||||
const shape2 = new BoxColliderShape();
|
||||
shape2.size = new Vector3(1, 1, 1);
|
||||
collider2.addShape(shape2);
|
||||
|
||||
entity1.layer = Layer.Layer1;
|
||||
entity2.layer = Layer.Layer2;
|
||||
|
||||
const script = entity1.addComponent(CollisionDetectionScript);
|
||||
|
||||
// @ts-ignore
|
||||
engine.sceneManager.activeScene.physics._update(1);
|
||||
expect(script.collisionDetected).toBe(true);
|
||||
|
||||
script.reset();
|
||||
|
||||
engine.sceneManager.activeScene.physics.setColliderLayerCollision(Layer.Layer1, Layer.Layer2, false);
|
||||
|
||||
// @ts-ignore
|
||||
engine.sceneManager.activeScene.physics._update(1);
|
||||
|
||||
expect(script.collisionDetected).toBe(false);
|
||||
|
||||
engine.sceneManager.activeScene.physics.setColliderLayerCollision(Layer.Layer1, Layer.Layer2, true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
const entities = rootEntity.children;
|
||||
for (let i = entities.length - 1; i >= 0; i--) {
|
||||
entities[i].destroy();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
Entity,
|
||||
HitResult,
|
||||
Layer,
|
||||
PhysicsScene,
|
||||
Script,
|
||||
SphereColliderShape,
|
||||
StaticCollider
|
||||
@@ -114,13 +115,16 @@ function setColliderProps(entity: Entity, isDynamic: boolean, isTrigger: boolean
|
||||
describe("Physics Test", () => {
|
||||
describe("LitePhysics", () => {
|
||||
let engineLite: WebGLEngine;
|
||||
|
||||
let physics: LitePhysics;
|
||||
let physicsScene: PhysicsScene;
|
||||
beforeAll(async () => {
|
||||
physics = new LitePhysics();
|
||||
// Init engine with LitePhysics.
|
||||
engineLite = await WebGLEngine.create({
|
||||
canvas: document.createElement("canvas"),
|
||||
physics: new LitePhysics()
|
||||
physics
|
||||
});
|
||||
physicsScene = engineLite.sceneManager.activeScene.physics;
|
||||
|
||||
const rootEntityLitePhysics = engineLite.sceneManager.activeScene.createRootEntity("root_camera");
|
||||
|
||||
@@ -342,6 +346,34 @@ describe("Physics Test", () => {
|
||||
expect(collisionTestScript.onTriggerExit).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
describe("Collision Group Tests", () => {
|
||||
it("should set and get collision group settings correctly", () => {
|
||||
physicsScene.setColliderLayerCollision(Layer.Layer0, Layer.Layer1, true);
|
||||
expect(physicsScene.getColliderLayerCollision(Layer.Layer0, Layer.Layer1)).to.eq(true);
|
||||
physicsScene.setColliderLayerCollision(Layer.Layer0, Layer.Layer2, false);
|
||||
expect(physicsScene.getColliderLayerCollision(Layer.Layer0, Layer.Layer2)).to.eq(false);
|
||||
physicsScene.setColliderLayerCollision(Layer.Layer1, Layer.Layer2, true);
|
||||
expect(physicsScene.getColliderLayerCollision(Layer.Layer1, Layer.Layer2)).to.eq(true);
|
||||
});
|
||||
|
||||
it("should handle edge cases in collision group matrix", () => {
|
||||
const maxGroup = Layer.Layer31;
|
||||
|
||||
physicsScene.setColliderLayerCollision(maxGroup, Layer.Layer0, false);
|
||||
expect(physicsScene.getColliderLayerCollision(maxGroup, Layer.Layer0)).to.eq(false);
|
||||
physicsScene.setColliderLayerCollision(maxGroup, Layer.Layer0, true);
|
||||
expect(physicsScene.getColliderLayerCollision(maxGroup, Layer.Layer0)).to.eq(true);
|
||||
});
|
||||
|
||||
it("should handle invalid collision groups correctly", () => {
|
||||
const invalidGroup = -1;
|
||||
// @ts-ignore
|
||||
expect(() => physicsScene.setColliderLayerCollision(invalidGroup, Layer.Layer0, false)).to.throw();
|
||||
// @ts-ignore
|
||||
expect(() => physicsScene.setColliderLayerCollision(invalidGroup, Layer.Layer0, true)).to.throw();
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
const root = engineLite.sceneManager.activeScene.findEntityByName("root");
|
||||
root?.destroy();
|
||||
@@ -350,6 +382,7 @@ describe("Physics Test", () => {
|
||||
|
||||
describe("PhysXPhysics", () => {
|
||||
let enginePhysX: WebGLEngine;
|
||||
let physicsScene: PhysicsScene;
|
||||
|
||||
beforeAll(async () => {
|
||||
// Init engine with PhysXPhysics.
|
||||
@@ -357,6 +390,8 @@ describe("Physics Test", () => {
|
||||
canvas: document.createElement("canvas"),
|
||||
physics: new PhysXPhysics()
|
||||
});
|
||||
physicsScene = enginePhysX.sceneManager.activeScene.physics;
|
||||
|
||||
const rootEntityPhysX = enginePhysX.sceneManager.activeScene.createRootEntity("root_camera");
|
||||
|
||||
const cameraEntityPhysX = rootEntityPhysX.createChild("camera");
|
||||
@@ -498,6 +533,34 @@ describe("Physics Test", () => {
|
||||
root.destroy();
|
||||
});
|
||||
|
||||
describe("Collision Group Tests", () => {
|
||||
it("should set and get collision group settings correctly", () => {
|
||||
physicsScene.setColliderLayerCollision(Layer.Layer0, Layer.Layer1, true);
|
||||
expect(physicsScene.getColliderLayerCollision(Layer.Layer0, Layer.Layer1)).to.eq(true);
|
||||
physicsScene.setColliderLayerCollision(Layer.Layer0, Layer.Layer2, false);
|
||||
expect(physicsScene.getColliderLayerCollision(Layer.Layer0, Layer.Layer2)).to.eq(false);
|
||||
physicsScene.setColliderLayerCollision(Layer.Layer1, Layer.Layer2, true);
|
||||
expect(physicsScene.getColliderLayerCollision(Layer.Layer1, Layer.Layer2)).to.eq(true);
|
||||
});
|
||||
|
||||
it("should handle edge cases in collision group matrix", () => {
|
||||
const maxGroup = Layer.Layer31;
|
||||
|
||||
physicsScene.setColliderLayerCollision(maxGroup, Layer.Layer0, false);
|
||||
expect(physicsScene.getColliderLayerCollision(maxGroup, Layer.Layer0)).to.eq(false);
|
||||
physicsScene.setColliderLayerCollision(maxGroup, Layer.Layer0, true);
|
||||
expect(physicsScene.getColliderLayerCollision(maxGroup, Layer.Layer0)).to.eq(true);
|
||||
});
|
||||
|
||||
it("should handle invalid collision groups correctly", () => {
|
||||
const invalidGroup = -1;
|
||||
// @ts-ignore
|
||||
expect(() => physicsScene.setColliderLayerCollision(invalidGroup, Layer.Layer0, false)).to.throw();
|
||||
// @ts-ignore
|
||||
expect(() => physicsScene.setColliderLayerCollision(invalidGroup, Layer.Layer0, true)).to.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Collision Test", () => {
|
||||
it("Dynamic Trigger vs Dynamic Trigger", () => {
|
||||
const physicsMgr = enginePhysX.physicsManager;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { defineProject } from "vitest/config";
|
||||
|
||||
export default defineProject({
|
||||
server: {
|
||||
port: 51204
|
||||
},
|
||||
optimizeDeps: {
|
||||
exclude: [
|
||||
"@galacean/engine",
|
||||
|
||||
Reference in New Issue
Block a user