Collision Shape
The collision shape Collision Shape defines the actual physical shape that a rigidbody uses to respond to collisions. The physics system uses the Shape to determine whether two objects intersect, thereby producing collision effects.
TIP
Starting from @orillusion/physics@0.3, we recommend directly using the native Ammo Shape to manage collision shapes.
Collision Shape Utility
To simplify the process of creating collision shapes, the CollisionShapeUtil utility class provides convenient methods for building physical shapes, covering a variety of common collision shapes. This utility class encapsulates the complex process of generating physical shapes into a series of easy-to-call methods, enabling developers to quickly and efficiently generate matching collision shapes for model objects.
Built-in Shapes
Currently, the physical shape creation methods provided by CollisionShapeUtil are shown in the table below:
| Function Name | Return Type | Description |
|---|---|---|
| createStaticPlaneShape | Ammo.btStaticPlaneShape | Creates a static plane collision shape, suitable for an infinite and stationary plane, such as a ground or wall |
| createBoxShape | Ammo.btBoxShape | Box collision shape |
| createSphereShape | Ammo.btSphereShape | Sphere collision shape |
| createCapsuleShape | Ammo.btCapsuleShape | Capsule collision shape |
| createCylinderShape | Ammo.btCylinderShape | Cylinder collision shape |
| createConeShape | Ammo.btConeShape | Cone collision shape |
| createCompoundShape | Ammo.btCompoundShape | Compound shape, combining multiple child shapes into one complex collision shape |
| createHeightfieldTerrainShape | Ammo.btHeightfieldTerrainShape | Heightfield shape, suitable for terrain collision detection |
| createConvexHullShape | Ammo.btConvexHullShape | Convex hull shape, suitable for fast collision detection of dynamic objects |
| createConvexTriangleMeshShape | Ammo.btConvexTriangleMeshShape | Convex triangle mesh shape, suitable for dynamic objects requiring complex geometry representation |
| createBvhTriangleMeshShape | Ammo.btBvhTriangleMeshShape | Bounding Volume Hierarchy BVH mesh shape, suitable for static objects requiring complex geometry representation |
| createGImpactMeshShape | Ammo.btGImpactMeshShape | GImpact mesh shape, suitable for complex triangle mesh collision detection, especially for dynamic objects |
| createShapeFromObject | Ammo.btCollisionShape | Creates a collision shape matching the geometry type of an Object3D |
Complex Structure Support
CollisionShapeUtil provides two APIs that support handling nested objects. These APIs can automatically generate appropriate collision shapes or extract geometry data, suitable for complex structures created by the engine, without the need to manually configure each child object individually.
| Function Name | Return Type | Description |
|---|---|---|
| createCompoundShapeFromObject | Ammo.btCompoundShape | Automatically creates a compound collision shape based on the geometry types of the passed-in Object3D and its child objects |
| getAllMeshVerticesAndIndices | { vertices:Float32Array; indices: Uint16Array; } | Returns all vertex and index data of the Object3D and its child objects, transformed by the world transformation matrix, which can be used to create high-precision mesh collision shapes |
Basic Usage
The process of creating collision shapes has been simplified. In most cases, you only need to pass in an Object3D to generate a collision shape. The following is example code for creating basic collision shapes using CollisionShapeUtil:
import { Object3D, MeshRenderer, CylinderGeometry, LitMaterial } from '@orillusion/core';
import { CollisionShapeUtil } from '@orillusion/physics';
// Create a cone
const coneObject = new Object3D();
let mr = coneObject.addComponent(MeshRenderer);
mr.geometry = new CylinderGeometry(0.01, 1, 5);
mr.material = new LitMaterial();
// For simple geometry types such as box, sphere, cone, and cylinder, the generic method can be used to create the collision shape
let coneShape1 = CollisionShapeUtil.createShapeFromObject(coneObject);
// Or create the cone shape by computing the local bounding box
let coneShape2 = CollisionShapeUtil.createConeShape(coneObject);
// Or specify the shape dimensions
let coneShape3 = CollisionShapeUtil.createConeShape(null, 1, 5);Meanwhile, the construction process for complex collision shapes has also been simplified. To meet custom requirements, developers can pass in vertices and indices to generate custom collision shapes:
const object = await engine.res.loadGltf('model.glb');
// Create a BVH mesh shape using the model's own vertices and indices
let bvhMeshShape = CollisionShapeUtil.createBvhTriangleMeshShape(object);
// Or manually pass in vertices and indices
const vertices = [...]
const indices = [...]
const vertices = new Float32Array(vertices);
const indices = new Uint16Array(data.indices);
let bvhMeshShape = CollisionShapeUtil.createBvhTriangleMeshShape(object, vertices, indices);In addition, based on TerrainGeometry or PlaneGeometry, you can create a heightfield collision shape suitable for simulating terrain:
import { TerrainGeometry } from '@orillusion/geometry';
// Load textures and create terrain geometry
let heightTexture = await engine.res.loadTexture('height.png');
let terrainGeometry = new TerrainGeometry(100, 100, 60, 60);
terrainGeometry.setHeight(heightTexture as BitmapTexture2D, 50);
const terrain = new Object3D();
let mr = terrain.addComponent(MeshRenderer);
mr.geometry = terrainGeometry;
mr.material = new LitMaterial();
// Create terrain collision shape
let terrainShape = CollisionShapeUtil.createHeightfieldTerrainShape(terrain);Through the operations above, we can create a variety of collision shapes to suit different physical needs. However, to achieve a complete physics simulation, collision shapes alone are not enough. To obtain realistic physical effects, they must also be used in combination with a Rigidbody, thereby enabling complete physical interaction and simulation.
Example
Different collision shapes are suitable for various physical scenarios. The following example demonstrates how to use CollisionShapeUtil to generate corresponding collision shapes for various geometries, and applies these shapes in the physics system in combination with a Rigidbody.
import { Engine3D, LitMaterial, MeshRenderer, BoxGeometry, Object3D, Scene3D, View3D, Object3DUtil, Vector3, AtmosphericComponent, DirectLight, SphereGeometry, CameraUtil, HoverCameraController, BitmapTexture2D, Color, CylinderGeometry, TorusGeometry, ComponentBase } from "@orillusion/core";
import { TerrainGeometry } from "@orillusion/geometry";
import { Ammo, CollisionShapeUtil, Physics, Rigidbody } from "@orillusion/physics";
class Sample_MultipleShapes {
scene: Scene3D;
terrain: Object3D;
gui: dat.GUI;
engine: Engine3D;
async run() {
// init physics and engine
await Physics.init({ useDrag: true });
this.engine = await Engine3D.init({
renderLoop: () => Physics.update(),
// shadow settings
setting: {
shadow: {
shadowBias: 0.01,
shadowSize: 1024 * 4,
csmMargin: 0.1,
csmScatteringExp: 0.8,
csmAreaScale: 0.1,
updateFrameRate: 1
}
}
});
this.scene = new Scene3D();
// Setup camera
let camera = CameraUtil.createCamera3DObject(this.scene);
camera.perspective(60, this.engine.aspect, 0.1, 800.0);
let hoverCtrl = camera.object3D.addComponent(HoverCameraController);
hoverCtrl.setCamera(0, -25, 100);
hoverCtrl.dragSmooth = 4;
// Create directional light
let lightObj3D = new Object3D();
lightObj3D.localRotation = new Vector3(-35, -143, 92);
let light = lightObj3D.addComponent(DirectLight);
light.lightColor = Color.COLOR_WHITE;
light.castShadow = true;
light.enableCSM = true;
light.intensity = 2.2;
this.scene.addChild(light.object3D);
// init sky
let atmosphericSky = this.scene.addComponent(AtmosphericComponent);
atmosphericSky.sunY = 0.6;
// Setup view
let view = new View3D();
view.camera = camera;
view.scene = this.scene;
this.engine.startRenderView(view);
// init terrain and create static planes
await this.initTerrain();
this.createStaticPlanes();
this.scene.addComponent(BoxGenerator);
}
async initTerrain() {
// Load textures
let bitmapTexture = await this.engine.res.loadTexture('https://cdn.orillusion.com/terrain/test01/bitmap.png');
let heightTexture = await this.engine.res.loadTexture('https://cdn.orillusion.com/terrain/test01/height.png');
const width = 100;
const height = 100;
const terrainMaxHeight = 60;
const segment = 60
// Create terrain geometry
let terrainGeometry = new TerrainGeometry(width, height, segment, segment);
terrainGeometry.setHeight(heightTexture as BitmapTexture2D, terrainMaxHeight);
let terrain = new Object3D();
let mr = terrain.addComponent(MeshRenderer);
mr.geometry = terrainGeometry;
let mat = new LitMaterial();
mat.baseMap = bitmapTexture;
mat.metallic = 0;
mat.roughness = 1.3;
mr.material = mat;
this.terrain = terrain;
this.scene.addChild(terrain);
// Add rigidbody to terrain
let terrainRb = terrain.addComponent(Rigidbody);
terrainRb.shape = Rigidbody.collisionShape.createHeightfieldTerrainShape(terrain);
terrainRb.mass = 0; // Static rigidbody
terrainRb.margin = 0.05;
terrainRb.isDisableDebugVisible = true;
terrainRb.friction = 1;
}
// Create static planes for boundaries
createStaticPlanes() {
// Create bottom static plane
let staticFloorBottom = Object3DUtil.GetPlane(this.engine.context3D, this.engine.res.whiteTexture);
staticFloorBottom.y = -500;
staticFloorBottom.transform.enable = false;
this.scene.addChild(staticFloorBottom);
let bottomRb = staticFloorBottom.addComponent(Rigidbody);
bottomRb.shape = CollisionShapeUtil.createStaticPlaneShape();
bottomRb.mass = 0;
// Create top static plane
let staticFloorTop = Object3DUtil.GetPlane(this.engine.context3D, this.engine.res.whiteTexture);
staticFloorTop.y = 100;
staticFloorTop.transform.enable = false;
this.scene.addChild(staticFloorTop);
let topRb = staticFloorTop.addComponent(Rigidbody);
topRb.shape = CollisionShapeUtil.createStaticPlaneShape(Vector3.DOWN);
topRb.mass = 0;
}
}
class BoxGenerator extends ComponentBase {
private lastTime: number = performance.now(); // Save last time
public container: Object3D;
public interval: number = 1000; // Interval for adding shapes
public totalShapes: number = 30; // Maximum number of shapes
async start() {
this.container = new Object3D();
this.object3D.addChild(this.container);
}
// Update loop
public onUpdate(): void {
let now: number = performance.now();
if (now - this.lastTime > this.interval) {
if (this.container.numChildren >= this.totalShapes) {
let index = Math.floor(now / this.interval) % this.totalShapes;
let shapeObject = this.container.getChildByIndex(index) as Object3D;
shapeObject.localPosition.set(Math.random() * 60 - 60 / 2, 40, Math.random() * 60 - 60 / 2);
shapeObject.getComponent(Rigidbody).updateTransform(shapeObject.localPosition, null, true);
} else {
this.addRandomShape();
}
this.lastTime = now; // Save current time
}
}
private addRandomShape(): void {
const shapeObject = new Object3D();
let mr = shapeObject.addComponent(MeshRenderer);
let mat = new LitMaterial();
mat.baseColor = Color.random();
let size = 1 + Math.random() / 2;
let height = 1 + Math.random() * (3 - 1);
let radius = 0.5 + Math.random() / 2;
const segments = 32;
let shape: Ammo.btCollisionShape;
let shapeType = Math.floor(Math.random() * 6); // Six basic shapes
switch (shapeType) {
case 0: // Box shape
mr.geometry = new BoxGeometry(size, size, size);
mr.material = mat;
shape = CollisionShapeUtil.createBoxShape(shapeObject);
break;
case 1: // Sphere shape
mr.geometry = new SphereGeometry(radius, segments, segments);
mr.material = mat;
shape = CollisionShapeUtil.createSphereShape(shapeObject);
break;
case 2: // Cylinder shape
mr.geometry = new CylinderGeometry(radius, radius, height, segments, segments);
mr.materials = [mat, mat, mat];
shape = CollisionShapeUtil.createCylinderShape(shapeObject);
break;
case 3: // Cone shape
mr.geometry = new CylinderGeometry(0.01, radius, height, segments, segments);
mr.materials = [mat, mat, mat];
shape = CollisionShapeUtil.createConeShape(shapeObject);
break;
case 4: // Capsule shape
mr.geometry = new CylinderGeometry(radius, radius, height, segments, segments);
mr.material = mat;
const { r, g, b } = mat.baseColor;
let topSphere = Object3DUtil.GetSingleSphere(radius, r, g, b);
topSphere.y = height / 2;
let bottomSphere = topSphere.clone();
bottomSphere.y = -height / 2;
shapeObject.addChild(topSphere);
shapeObject.addChild(bottomSphere);
shape = CollisionShapeUtil.createCapsuleShape(shapeObject);
break;
case 5: // Torus shape (convex hull shape)
mr.geometry = new TorusGeometry(radius, size / 5, segments / 2, segments / 2);
mr.material = mat;
shape = CollisionShapeUtil.createConvexHullShape(shapeObject);
break;
default:
break;
}
const posRange = 60;
shapeObject.x = Math.random() * posRange - posRange / 2;
shapeObject.y = 40;
shapeObject.z = Math.random() * posRange - posRange / 2;
shapeObject.localRotation = new Vector3(Math.random() * 360, Math.random() * 360, Math.random() * 360);
this.container.addChild(shapeObject);
// Add rigidbody to shape
let rigidbody = shapeObject.addComponent(Rigidbody);
rigidbody.shape = shape;
rigidbody.mass = Math.random() * 10 + 0.1;
rigidbody.rollingFriction = 0.5;
rigidbody.damping = [0.1, 0.1];
// Enable continuous collision detection (CCD)
const maxDimension = Math.max(size, height, radius);
const ccdMotionThreshold = maxDimension * 0.1; // Set motion threshold to 10% of max dimension
const ccdSweptSphereRadius = maxDimension * 0.05; // Set swept sphere radius to 5% of max dimension
rigidbody.ccdSettings = [ccdMotionThreshold, ccdSweptSphereRadius];
}
}
new Sample_MultipleShapes().run();More physics examples

