Inner Components
To facilitate the use of the physics system, the engine has encapsulated some commonly used physics components. For additional functionality, users can call the Ammo
object to access more native APIs
.
Rigid Body
A rigid body is an object whose own deformation can be neglected after being subjected to external forces. Although ideal rigid bodies cannot exist in reality, many hard objects can be assumed to be perfect rigid bodies under the condition of speeds much smaller than the speed of light. Based on the characteristics of rigid bodies, the physics system of the engine can simulate the motion and collision logic of objects in the real world, creating realistic animation effects.
The rigid body is an important component in the engine's physics system. After connecting a rigid body, we can make the model object have mass and respond to gravity like a real-world object. For user convenience, we have encapsulated the rigid body component Rigidbody. By adding the component, the rigid body can be added to the object:
import { Object3D } from '@orillusion/core'
import { Rigidbody } from '@orillusion/physics'
let object = new Object3D();
let rigidbody = object.addComponent(Rigidbody);
Basic Operations
Set the mass (unit: kg) for the rigid body:
rigidbody.mass = 50;
If a static rigid body is required, set the mass
to 0
:
rigidbody.mass = 0;
Add a velocity to the rigid body:
rigidbody.velocity = new Vector(10, 0, 0);
If you want to manipulate the native Ammo.js
rigid body, you can get it through the following method:
let bt = rigidbody.btRigidbody;
// native rigidbody API
bt.getCollisionShape();
bt.getWorldTransform();
...
Collision Shape
The Collision Shape
defines the actual physical shape that a rigid body uses for collision responses. The physics system uses the Shape
to determine whether two objects intersect, which in turn produces collision effects.
TIP
Starting from @orillusion/physics@0.3
, we recommend directly using the native Ammo.btShape
to manage collision shapes.
- Box Collider
Parameter | Type | Description |
---|---|---|
size | btVector3 | The size of a box-shaped collision shape. By default, this is specified as half the length along the x, y, and z axes from the center of the object, which corresponds to half the width, height, and depth of the object |
import { Object3D } from '@orillusion/core'
import { Ammo, Rigidbody } from '@orillusion/physics'
let object = new Object3D();
let mr = object.addComponent(MeshRenderer);
mr.geometry = new BoxGeometry(1, 1, 1); // size 1
mr.material = new LitMaterial();
let rigidbody = object.addComponent(Rigidbody);
rigidbody.mass = 10;
// half length of BoxGeometry size
let shape = new Ammo.btBoxShape(new Ammo.btVector3(1/2, 1/2, 1/2));
rigidbody.shape = shape;
- Sphere Collider
Parameter | Type | Description |
---|---|---|
radius | number | The radius of a spherical collision shape |
import { Object3D } from '@orillusion/core'
import { Ammo, Rigidbody } from '@orillusion/physics'
let object = new Object3D();
let mr = object.addComponent(MeshRenderer);
mr.geometry = new SphereGeometry(1, 8, 8); // radius 1
mr.material = new LitMaterial();
let rigidbody = object.addComponent(Rigidbody);
rigidbody.mass = 10;
// shape with radius 1
let shape = new Ammo.btSphereShape(1);
rigidbody.shape = shape;
- Capsule Collider
Parameter | Type | Description |
---|---|---|
radius | number | The radius of the top and bottom hemispheres of a capsule collision shape |
height | number | and the height of the cylindrical middle section |
import { Object3D } from '@orillusion/core'
import { Ammo, Rigidbody } from '@orillusion/physics'
// cylinder
let object = new Object3D();
let mr = object.addComponent(MeshRenderer);
mr.geometry = new CylinderGeometry(1, 1, 5); // radius 1, height 5
// top sphere
let topSphere = new Object3D();
topSphere.y = 2.5;
let mrTop = topSphere.addComponent(MeshRenderer);
mrTop.geometry = new SphereGeometry(1, 8, 8);
object.addChild(topSphere);
// bottom sphere
let bottomSphere = new Object3D();
bottomSphere.y = -2.5;
let mrBottom = bottomSphere.addComponent(MeshRenderer);
mrBottom.geometry = new SphereGeometry(1, 8, 8);
object.addChild(bottomSphere);
mr.material = mrBottom.material = mrTop.material = new LitMaterial();
let rigidbody = object.addComponent(Rigidbody);
rigidbody.mass = 10;
// CapsuleShape with radius 1, body height 5
let shape = new Ammo.btCapsuleShape(1, 5);
rigidbody.shape = shape;
In addition to the common shapes mentioned above,Ammo.js
also provides more complex collision shapes such as btStaticPlaneShape
, btCylinderShape
, btConeShape
, btConvexHullShape
, btConcaveShape
and btHeightfieldTerrainShape
.
Using these collision shapes, we can simulate more complex physical scenarios. Let's proceed with an example to further demonstrate the usage of various shapes in the physics system.
import { Engine3D, LitMaterial, MeshRenderer, BoxGeometry, Object3D, Scene3D, View3D, Object3DUtil, Vector3, AtmosphericComponent, DirectLight, SphereGeometry, CameraUtil, HoverCameraController, BitmapTexture2D, VertexAttributeName, Color, CylinderGeometry, TorusGeometry, ComponentBase } from "@orillusion/core";
import { TerrainGeometry } from "@orillusion/geometry";
import { Graphic3D } from "@orillusion/graphic";
import { Ammo, CollisionShapeUtil, Physics, Rigidbody } from "@orillusion/physics";
class Sample_MultipleShapes {
scene: Scene3D;
terrain: Object3D;
gui: dat.GUI;
async run() {
// init physics and engine
await Physics.init();
await Engine3D.init({
renderLoop: () => Physics.update()
});
// shadow settings
Engine3D.setting.shadow.shadowBias = 0.01;
Engine3D.setting.shadow.shadowSize = 1024 * 4;
Engine3D.setting.shadow.csmMargin = 0.1;
Engine3D.setting.shadow.csmScatteringExp = 0.8;
Engine3D.setting.shadow.csmAreaScale = 0.1;
Engine3D.setting.shadow.updateFrameRate = 1;
this.scene = new Scene3D();
// Setup camera
let camera = CameraUtil.createCamera3DObject(this.scene);
camera.perspective(60, Engine3D.aspect, 0.1, 800.0);
camera.enableCSM = true;
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.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;
Engine3D.startRenderView(view);
// init terrain and create static planes
await this.initTerrain();
this.createStaticPlanes();
this.scene.addComponent(BoxGenerator);
}
async initTerrain() {
// Load textures
let bitmapTexture = await Engine3D.res.loadTexture('https://cdn.orillusion.com/terrain/test01/bitmap.png');
let heightTexture = await Engine3D.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(Engine3D.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(Engine3D.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();
SoftBody and Constraint
Similar to rigid bodies, the engine encapsulates native Ammo.js
objects such as btSoftBody
, btFixedConstraint
, btGeneric6DofConstraint
, btHingeConstraint
and btSliderConstraint
to simulate more complex physical effects like cloth, springs, hinges, and sliders.
Since these complex Ammo
objects contain a large number of physical parameter settings, we cannot cover all the details here. Users can search for Bullet
documentation to understand the physical meanings and usage rules of these APIs
.
Here, we will demonstrate basic usage methods for some components through an example. For more Ammo
physics examples, please refer to the example page.
import { Engine3D, LitMaterial, MeshRenderer, Object3D, Scene3D, View3D, Object3DUtil, Vector3, AtmosphericComponent, DirectLight, CameraUtil, HoverCameraController, PlaneGeometry, GPUCullMode, Color } from "@orillusion/core";
import { Stats } from "@orillusion/stats";
import { ActivationState, CollisionShapeUtil, DebugDrawMode, FixedConstraint, HingeConstraint, Physics, PointToPointConstraint, Rigidbody, SliderConstraint, ClothSoftbody, RopeSoftbody } from "@orillusion/physics";
import dat from "dat.gui";
import { Graphic3D } from "@orillusion/graphic";
/**
* Sample class demonstrating the use of multiple constraints in a physics simulation.
*/
class Sample_MultipleConstraints {
scene: Scene3D;
gui: dat.GUI;
async run() {
// init physics and engine
await Physics.init({ useSoftBody: true, useDrag: true });
await Engine3D.init({ renderLoop: () => Physics.update() });
this.gui = new dat.GUI();
this.scene = new Scene3D();
this.scene.addComponent(Stats);
// 在引擎启动后初始化物理调试功能,需要为调试器传入 graphic3D 对象
const graphic3D = new Graphic3D();
this.scene.addChild(graphic3D);
Physics.initDebugDrawer(graphic3D, {
enable: false,
debugDrawMode: DebugDrawMode.DrawConstraintLimits
})
let camera = CameraUtil.createCamera3DObject(this.scene);
camera.perspective(60, Engine3D.aspect, 0.1, 800.0);
camera.object3D.addComponent(HoverCameraController).setCamera(60, -25, 50);
// create directional light
let light = new Object3D();
light.localRotation = new Vector3(36, -130, 60);
let dl = light.addComponent(DirectLight);
dl.castShadow = true;
dl.intensity = 3;
this.scene.addChild(light);
// init sky
this.scene.addComponent(AtmosphericComponent).sunY = 0.6;
let view = new View3D();
view.camera = camera;
view.scene = this.scene;
this.physicsDebug();
Engine3D.startRenderView(view);
// Create ground, turntable, and chains
this.createGround();
this.createTurntable();
this.createChains();
// Create impactor and softBody
let impactorRb = this.createImpactor();
this.createClothSoftbody(impactorRb);
this.createRopeSoftbody(impactorRb);
}
private physicsDebug() {
let physicsFolder = this.gui.addFolder('PhysicsDebug');
physicsFolder.add(Physics.debugDrawer, 'enable');
physicsFolder.add(Physics.debugDrawer, 'debugMode', Physics.debugDrawer.debugModeList);
physicsFolder.add(Physics, 'isStop');
physicsFolder.add({ hint: "Drag dynamic rigid bodies with the mouse." }, "hint");
physicsFolder.open();
}
private async createGround() {
// Create ground
let ground = Object3DUtil.GetSingleCube(80, 2, 20, 1, 1, 1);
ground.y = -1; // Set ground half-height
this.scene.addChild(ground);
// Add rigidbody to ground
let groundRb = ground.addComponent(Rigidbody);
groundRb.shape = CollisionShapeUtil.createBoxShape(ground);
groundRb.mass = 0;
}
private createImpactor(): Rigidbody {
// Create shelves
const shelfSize = 0.5;
const shelfHeight = 5;
let shelfLeft = Object3DUtil.GetCube();
shelfLeft.localScale = new Vector3(shelfSize, shelfHeight, shelfSize);
shelfLeft.localPosition = new Vector3(-30, shelfHeight / 2, 0);
let shelfRight = shelfLeft.clone();
shelfRight.localPosition = new Vector3(30, shelfHeight / 2, 0);
let shelfTop = Object3DUtil.GetCube();
shelfTop.localScale = new Vector3(60 - shelfSize, shelfSize, shelfSize);
shelfTop.localPosition = new Vector3(0, shelfHeight - shelfSize / 2, 0);
// Add rigidbodies to shelves
let shelfRightRb = this.addBoxShapeRigidBody(shelfRight, 0);
let shelfLeftRb = this.addBoxShapeRigidBody(shelfLeft, 0);
this.addBoxShapeRigidBody(shelfTop, 0);
this.scene.addChild(shelfLeft);
this.scene.addChild(shelfRight);
this.scene.addChild(shelfTop);
// Create slider
let slider = Object3DUtil.GetSingleCube(4, 1, 1, Math.random(), Math.random(), Math.random());
this.scene.addChild(slider);
// Add rigidbody to slider
let sliderRb = this.addBoxShapeRigidBody(slider, 500, true, [0.2, 0]);
// Create Impactor
let impactor = Object3DUtil.GetCube();
impactor.localScale = new Vector3(1, 1, 5);
impactor.localPosition = new Vector3(0, shelfHeight - shelfSize / 2, 3);
this.scene.addChild(impactor);
let impactorRb = this.addBoxShapeRigidBody(impactor, 200, true);
// Create fixed constraint to attach slider to impactor
let fixedConstraint = slider.addComponent(FixedConstraint);
fixedConstraint.targetRigidbody = impactorRb;
fixedConstraint.pivotTarget = new Vector3(0, 0, -3);
// Create slider constraint
let sliderConstraint = shelfTop.addComponent(SliderConstraint);
sliderConstraint.targetRigidbody = sliderRb;
sliderConstraint.lowerLinLimit = -30;
sliderConstraint.upperLinLimit = 30;
sliderConstraint.lowerAngLimit = 0;
sliderConstraint.upperAngLimit = 0;
sliderConstraint.poweredLinMotor = true;
sliderConstraint.maxLinMotorForce = 1;
sliderConstraint.targetLinMotorVelocity = 20;
// Setup slider motor event controller
this.sliderMotorEventController(shelfLeftRb, shelfRightRb, sliderConstraint);
return impactorRb;
}
private sliderMotorEventController(leftRb: Rigidbody, rightRb: Rigidbody, slider: SliderConstraint) {
// Control slider movement based on collision events
const timer = { pauseDuration: 1000 };
leftRb.collisionEvent = () => {
rightRb.enableCollisionEvent = true;
leftRb.enableCollisionEvent = false;
setTimeout(() => {
slider.targetLinMotorVelocity = Math.abs(slider.targetLinMotorVelocity);
setTimeout(() => leftRb.enableCollisionEvent = true, 1000);
}, timer.pauseDuration);
};
rightRb.collisionEvent = () => {
rightRb.enableCollisionEvent = false;
leftRb.enableCollisionEvent = true;
setTimeout(() => {
slider.targetLinMotorVelocity = -Math.abs(slider.targetLinMotorVelocity);
setTimeout(() => rightRb.enableCollisionEvent = true, 1000);
}, timer.pauseDuration);
};
// GUI controls for slider motor
let folder = this.gui.addFolder('Slider Motor Controller');
folder.open();
folder.add(slider, 'poweredLinMotor');
folder.add(slider, 'maxLinMotorForce', 0, 30, 1);
folder.add({ velocity: slider.targetLinMotorVelocity }, 'velocity', 0, 30, 1).onChange(v => {
slider.targetLinMotorVelocity = slider.targetLinMotorVelocity > 0 ? v : -v;
});
folder.add(timer, 'pauseDuration', 0, 3000, 1000);
}
private createTurntable() {
// Create turntable components
const columnWidth = 0.5;
const columnHeight = 4.75 - columnWidth / 2;
const columnDepth = 0.5;
let column = Object3DUtil.GetCube();
column.localScale = new Vector3(columnWidth, columnHeight, columnDepth);
column.localPosition = new Vector3(0, columnHeight / 2, 8);
this.scene.addChild(column);
this.addBoxShapeRigidBody(column, 0); // Add rigidbodies to turntable components
// Create arm compound shape
let armParent = new Object3D();
armParent.localPosition = new Vector3(0, columnHeight + columnWidth / 2, 8);
let armChild1 = Object3DUtil.GetCube();
armChild1.rotationY = 45;
armChild1.localScale = new Vector3(10, 0.5, 0.5);
let armChild2 = armChild1.clone();
armChild2.rotationY = 135;
armParent.addChild(armChild1);
armParent.addChild(armChild2);
this.scene.addChild(armParent);
let armRigidbody = armParent.addComponent(Rigidbody);
armRigidbody.shape = CollisionShapeUtil.createCompoundShapeFromObject(armParent);
armRigidbody.mass = 500;
armRigidbody.activationState = ActivationState.DISABLE_DEACTIVATION;
// Create hinge constraint to attach arm1 to column
let hinge = column.addComponent(HingeConstraint);
hinge.targetRigidbody = armRigidbody;
hinge.pivotSelf.set(0, columnHeight / 2 + columnWidth / 2, 0);
hinge.enableAngularMotor(true, 5, 50);
}
private createChains() {
const chainHeight = 1;
let chainLink = Object3DUtil.GetCube();
chainLink.localScale = new Vector3(0.25, chainHeight, 0.25);
chainLink.localPosition = new Vector3(5, 16, 5);
this.scene.addChild(chainLink);
// Add static rigidbody to the first chain link
let chainRb = this.addBoxShapeRigidBody(chainLink, 0);
let prevRb = chainRb;
// Create chain links and add point-to-point constraints
for (let i = 0; i < 10; i++) {
let link = chainLink.clone();
link.y -= (i + 1) * chainHeight;
this.scene.addChild(link);
let linkRb = this.addBoxShapeRigidBody(link, 1, false, [0.3, 0.3]);
linkRb.isSilent = true; // Disable collision events
let p2p = link.addComponent(PointToPointConstraint);
p2p.targetRigidbody = prevRb;
p2p.pivotTarget.y = -chainHeight / 2;
p2p.pivotSelf.y = chainHeight / 2;
prevRb = linkRb;
}
// Create a sphere and add point-to-point constraint to the last chain link
const sphereRadius = 0.8;
let sphere = Object3DUtil.GetSingleSphere(sphereRadius, 1, 1, 1);
let sphereMaterial = (sphere.getComponent(MeshRenderer).material as LitMaterial);
sphere.localPosition = new Vector3(5, 4.5, 5);
this.scene.addChild(sphere);
let sphereRb = sphere.addComponent(Rigidbody);
sphereRb.shape = CollisionShapeUtil.createSphereShape(sphere);
sphereRb.mass = 2;
sphereRb.damping = [0.3, 0.3];
sphereRb.enablePhysicsTransformSync = true;
// Sphere collision event to change color
let timer: number | null = null;
sphereRb.collisionEvent = () => {
if (timer !== null) clearTimeout(timer);
else sphereMaterial.baseColor = new Color(Color.SALMON);
timer = setTimeout(() => {
sphereMaterial.baseColor = Color.COLOR_WHITE;
timer = null;
}, 1000);
};
let p2p = sphere.addComponent(PointToPointConstraint);
p2p.disableCollisionsBetweenLinkedBodies = true;
p2p.targetRigidbody = prevRb;
p2p.pivotTarget.y = -chainHeight / 2;
p2p.pivotSelf.y = sphereRadius;
}
private createClothSoftbody(anchorRb: Rigidbody) {
const cloth = new Object3D();
let meshRenderer = cloth.addComponent(MeshRenderer);
meshRenderer.geometry = new PlaneGeometry(3, 3, 10, 10, Vector3.X_AXIS); // Set the plane direction to determine the four corners
let material = new LitMaterial();
material.baseMap = Engine3D.res.redTexture;
material.cullMode = GPUCullMode.none;
meshRenderer.material = material;
this.scene.addChild(cloth);
// Add cloth softbody component
let softBody = cloth.addComponent(ClothSoftbody);
softBody.mass = 5;
softBody.margin = 0.1;
softBody.anchorRigidbody = anchorRb; // Anchor rigidbody
softBody.anchorIndices = ['leftTop', 'top', 'rightTop']; // Anchor points
softBody.influence = 1; // Attachment influence
softBody.disableCollision = false; // Enable collision with rigidbody
softBody.anchorPosition = new Vector3(0, -2.1, 0); // Relative position to anchor
softBody.wait().then(btSoftbody => {
// native softbody API
let sbConfig = btSoftbody.get_m_cfg(); // configure softbody parameters
sbConfig.set_kDF(0.2);
sbConfig.set_kDP(0.01);
sbConfig.set_kLF(0.02);
sbConfig.set_kDG(0.001);
});
}
private createRopeSoftbody(headRb: Rigidbody) {
const box = Object3DUtil.GetSingleCube(1, 1, 1, 1, 1, 1);
box.localPosition = new Vector3(0, 10, 0);
this.scene.addChild(box);
let tailRb = this.addBoxShapeRigidBody(box, 1, true, [0.2, 0.2]);
const rope = new Object3D();
let mr = rope.addComponent(MeshRenderer);
let startPos = new Vector3(0, 4.75, 3);
let endPos = new Vector3(0, 10, 0);
mr.geometry = RopeSoftbody.buildRopeGeometry(10, startPos, endPos);
mr.material = new LitMaterial();
mr.material.topology = 'line-list';
this.scene.addChild(rope);
// Add rope softbody component
let softBody = rope.addComponent(RopeSoftbody);
softBody.mass = 1;
softBody.elasticity = 0.1;
softBody.anchorRigidbodyHead = headRb;
softBody.anchorOffsetHead = new Vector3(0, -0.5, 2.1);
softBody.anchorRigidbodyTail = tailRb;
softBody.anchorOffsetTail = new Vector3(0, 0.5, 0);
}
private addBoxShapeRigidBody(obj: Object3D, mass: number, disableHibernation?: boolean, damping?: [number, number]) {
let rigidbody = obj.addComponent(Rigidbody);
rigidbody.shape = CollisionShapeUtil.createBoxShape(obj);
rigidbody.mass = mass;
if (disableHibernation) rigidbody.activationState = ActivationState.DISABLE_DEACTIVATION;
if (damping) rigidbody.damping = damping;
return rigidbody;
}
}
new Sample_MultipleConstraints().run();