Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions e2e/case/litePhysics-collision-group.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 146 additions & 0 deletions e2e/case/physx-collision-group.ts
Original file line number Diff line number Diff line change
@@ -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);
});
10 changes: 10 additions & 0 deletions e2e/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions e2e/fixtures/originImage/Physics_physx-collision-group.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion packages/core/src/physics/CharacterController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ 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;
Expand Down
38 changes: 35 additions & 3 deletions packages/core/src/physics/Collider.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
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";

/**
* Base class for all colliders.
Expand All @@ -24,6 +25,7 @@ export class Collider extends Component implements ICustomClone {
protected _updateFlag: BoolUpdateFlag;
@deepClone
protected _shapes: ColliderShape[] = [];
protected _collisionLayerIndex: number = 0;

/**
* The shapes of this collider.
Expand All @@ -32,6 +34,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
*/
Expand Down Expand Up @@ -128,13 +150,19 @@ export class Collider extends Component implements ICustomClone {

/**
* @internal
* @remarks
* When shapes are updated, we need to reset the collision layer because the underlying PhysX
* will call the shape methods when setting the collision layer.
*/
_handleShapesChanged(): void {}
_handleShapesChanged(): void {
this._setCollisionLayer();
}

protected _syncNative(): void {
for (let i = 0, n = this.shapes.length; i < n; i++) {
this._addNativeShape(this.shapes[i]);
}
this._setCollisionLayer();
}

/**
Expand All @@ -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);
}
}
Loading
Loading