Constraint
Physical constraints are used to limit the relative motion between two physical objects (usually rigidbodies). They allow more complex behaviors to be created in physics simulations, such as hinges, sliders, or fixed connections. By properly configuring constraints, you can implement various mechanical structures and connection methods found in the real world.
Constraint Component Overview
Constraints are added and used in the form of components. The constraint base class implements common constraint functionality, while each specific constraint component inherits from this base class and encapsulates the underlying Ammo.js constraint types, providing APIs similar to the native constraints. This allows developers to skip the process of manually creating constraints and to quickly and conveniently integrate various physical constraints to achieve complex physical behaviors.
import { Object3D } from '@orillusion/core';
import { HingeConstraint, Rigidbody } from '@orillusion/physics';
let object = new Object3D();
let targetObject = new Object3D();
let rigidbody = object.addComponent(Rigidbody);
let targetRigidbody = targetObject.addComponent(Rigidbody);
// Configure the two rigidbodies respectively, such as setting mass, shape, etc.
...
// Add a hinge constraint to object and specify the target rigidbody; the constraint will connect these two rigidbodies
let hingeConstraint = object.addComponent(HingeConstraint);
hingeConstraint.targetRigidbody = targetRigidbody;
// For specific constraint configuration, refer to the API described below
...Please note that an object must have a Rigidbody component added before adding a constraint component.
Basic Usage
The following are the common APIs for constraints. Each constraint type also provides its own unique configuration options.
| Property | Type | Description |
|---|---|---|
| constraint | Ammo.btTypedConstraint | Gets the native Ammo.js constraint |
| breakingThreshold | number | Breaking threshold; the larger the value, the harder it is for the constraint to break |
| disableCollisionsBetweenLinkedBodies | boolean | Disables collisions between linked rigidbodies; default value is true |
| targetRigidbody | Rigidbody | Target rigidbody; the constraint will limit the relative motion between the current rigidbody and the target rigidbody |
| pivotSelf | Vector3 | The pivot point of the rigidbody itself, which determines the rotation center of the constraint |
| pivotTarget | Vector3 | The pivot point of the target rigidbody |
| rotationSelf | Quaternion | The rotation setting of the rigidbody itself |
| rotationTarget | Quaternion | The rotation setting of the target rigidbody |
| Method | Description |
|---|---|
| wait() | Asynchronously gets the native constraint instance once initialization is complete |
| resetConstraint() | Resets the constraint, destroying the current constraint instance, then recreating and returning a new constraint instance |
Overload Support
In native Ammo.js, except for FixedConstraint, all other constraints provide multiple constructor overloads. To ensure the completeness of these features, the constraint components also provide corresponding overload support. Generally, if the targetRigidbody property is not set, the constraint will be created with a single rigidbody by default. Developers can freely choose the appropriate constraint construction method based on their specific needs.
Constraint Types
The current system has integrated the 7 main constraint types from Ammo.js, each suitable for specific application scenarios.
1. Hinge Constraint HingeConstraint
The hinge constraint allows an object to rotate around a certain axis, suitable for scenarios requiring single-axis rotation such as doors and robotic arms.
| Property | Type | Description |
|---|---|---|
| axisSelf | Vector3 | The hinge axis direction on the rigidbody itself; default value is Vector3.UP |
| axisTarget | Vector3 | The hinge axis direction on the target rigidbody; default value is Vector3.UP |
| useReferenceFrameA | boolean | Whether to use the reference frame of the rigidbody itself; default value is true |
| useTwoBodiesTransformOverload | boolean | Whether to use the two-rigidbody transform overload; default value is false |
| Method | Description |
|---|---|
| setLimit() | Sets the rotation limit |
| enableAngularMotor() | Enables or disables the angular motor |
let hingeConstraint = object.addComponent(HingeConstraint);
hingeConstraint.setLimit(-Math.PI / 2, Math.PI / 2, 0.9, 0.3);
hingeConstraint.enableAngularMotor(true, 1.0, 10.0);2. Slider Constraint SliderConstraint
The slider constraint allows an object to translate along an axis and rotate around that axis, suitable for application scenarios such as slide rails or elevators.
| Property | Type | Description |
|---|---|---|
| lowerLinLimit | number | Lower limit of linear motion |
| upperLinLimit | number | Upper limit of linear motion |
| lowerAngLimit | number | Lower limit of angular motion |
| upperAngLimit | number | Upper limit of angular motion |
| poweredLinMotor | boolean | Whether to enable the linear motor |
| maxLinMotorForce | number | The maximum force of the linear motor |
| targetLinMotorVelocity | number | The target velocity of the linear motor |
let sliderConstraint = object.addComponent(SliderConstraint);
sliderConstraint.lowerLinLimit = -10;
sliderConstraint.upperLinLimit = 10;
sliderConstraint.poweredLinMotor = true;
sliderConstraint.maxLinMotorForce = 100;
sliderConstraint.targetLinMotorVelocity = 5;3. Fixed Constraint FixedConstraint
The fixed constraint completely fixes two objects together, limiting their relative position and rotation, thereby achieving a rigid connection effect.
let fixedConstraint = object.addComponent(FixedConstraint);
fixedConstraint.targetRigidbody = targetRigidbody; // The fixed constraint type must specify a target rigidbody4. Point-to-Point Constraint PointToPointConstraint
This constraint limits the relative motion between two points but allows them to rotate freely in space. It is commonly used to simulate the connection of ropes or chains.
let p2pConstraint = object.addComponent(PointToPointConstraint);
p2pConstraint.targetRigidbody = targetRigidbody;
p2pConstraint.pivotSelf.set(0, 0, 0);
p2pConstraint.pivotTarget.set(0, 5, 0);5. Cone Twist Constraint ConeTwistConstraint
The cone twist constraint is used to create motion similar to a ball-and-socket joint, allowing an object to rotate freely within a cone-shaped range and limiting its twist angle around a certain axis.
| Property | Type | Description |
|---|---|---|
| twistSpan | number | Twist angle limit, the twist range around the X axis |
| swingSpan1 | number | Swing angle limit 1, the swing range around the Y axis |
| swingSpan2 | number | Swing angle limit 2, the swing range around the Z axis |
let coneTwistConstraint = object.addComponent(ConeTwistConstraint);
coneTwistConstraint.twistSpan = Math.PI / 4; // Limit the twist angle to 45 degrees6. Generic 6-DOF Constraint Generic6DofConstraint
This constraint allows motion limits to be freely set along three linear axes and three angular axes, providing maximum flexibility to meet various complex connection requirements.
| Property | Type | Description |
|---|---|---|
| linearLowerLimit | Vector3 | Lower limit of linear motion |
| linearUpperLimit | Vector3 | Upper limit of linear motion |
| angularLowerLimit | Vector3 | Lower limit of angular motion |
| angularUpperLimit | Vector3 | Upper limit of angular motion |
| useLinearFrameReferenceFrame | boolean | Whether to use the linear reference coordinate frame |
let sixDofConstraint = object.addComponent(Generic6DofConstraint);
sixDofConstraint.linearLowerLimit = new Vector3(-1, -1, -1); // Set the linear lower limit
sixDofConstraint.linearUpperLimit = new Vector3(1, 1, 1); // Set the linear upper limit7. Generic 6-DOF Spring Constraint Generic6DofSpringConstraint
This constraint adds spring characteristics on top of the generic 6-DOF constraint, allowing it to simulate spring effects such as stretching and vibration.
| Method | Description |
|---|---|
| enableSpring() | Enables or disables the spring functionality |
| setStiffness() | Sets the stiffness of the spring |
| setDamping() | Sets the damping of the spring |
| setEquilibriumPoint() | Sets the equilibrium point of the spring |
let springConstraint = object.addComponent(Generic6DofSpringConstraint);
// Enable and configure the spring: indices 0, 1, 2 correspond to the linear axes (x, y, z), and 3, 4, 5 correspond to the angular axes (x, y, z)
for (let j = 3; j < 6; j++) {
dofSpringConstraint.enableSpring(j, true);
dofSpringConstraint.setStiffness(j, 10.0);
dofSpringConstraint.setDamping(j, 0.5);
dofSpringConstraint.setEquilibriumPoint(j);
}import { Engine3D, Object3D, Scene3D, View3D, Object3DUtil, Vector3, AtmosphericComponent, DirectLight, CameraUtil, HoverCameraController, Quaternion } from "@orillusion/core";
import { Stats } from "@orillusion/stats";
import { ActivationState, CollisionShapeUtil, DebugDrawMode, Generic6DofSpringConstraint, Physics, Rigidbody } from "@orillusion/physics";
import dat from "dat.gui";
import { Graphic3D } from "@orillusion/graphic";
class Sample_dofSpringConstraint {
scene: Scene3D;
gui: dat.GUI;
engine: Engine3D;
async run() {
// Initialize physics and engine
await Physics.init({ useDrag: true });
let engine = this.engine = await Engine3D.init({ renderLoop: () => Physics.update() });
let scene = this.scene = new Scene3D();
scene.addComponent(Stats);
// Initialize the physics debug feature after the engine starts; a graphic3D object must be passed to the drawer
const graphic3D = new Graphic3D();
scene.addChild(graphic3D);
Physics.initDebugDrawer(graphic3D, {
enable: false,
debugDrawMode: DebugDrawMode.DrawConstraintLimits
})
this.gui = new dat.GUI();
let f = this.gui.addFolder('PhysicsDebug');
f.add(Physics.debugDrawer, 'enable');
f.add(Physics.debugDrawer, 'debugMode', Physics.debugDrawer.debugModeList);
f.open();
let camera = CameraUtil.createCamera3DObject(scene);
camera.perspective(60, engine.aspect, 0.1, 800.0);
camera.object3D.addComponent(HoverCameraController).setCamera(140, -25, 20, new Vector3(8, 4, 0));
// Create directional light
let lightObj3D = new Object3D();
lightObj3D.localRotation = new Vector3(36, -130, 60);
let light = lightObj3D.addComponent(DirectLight);
light.castShadow = true;
light.enableCSM = true;
scene.addChild(lightObj3D);
// Initialize sky
scene.addComponent(AtmosphericComponent).sunY = 0.6;
let view = new View3D();
view.camera = camera;
view.scene = scene;
engine.startRenderView(view);
// Create ground, bridge, and ball
this.createGround();
this.createBridge();
this.createBall();
}
//Create the ground plane.
private async createGround() {
let ground = Object3DUtil.GetPlane(this.engine.context3D, this.engine.res.whiteTexture);
ground.scaleX = 50;
ground.scaleZ = 50;
this.scene.addChild(ground);
let rigidbody = ground.addComponent(Rigidbody);
rigidbody.shape = CollisionShapeUtil.createStaticPlaneShape();
rigidbody.mass = 0;
}
// Create a ball with a rigid body.
private createBall() {
let ball = Object3DUtil.GetSingleSphere(1, 1, 1, 1);
ball.localPosition = new Vector3(2, 10, 0);
this.scene.addChild(ball);
let ballRb = ball.addComponent(Rigidbody);
ballRb.shape = CollisionShapeUtil.createSphereShape(ball);
ballRb.mass = 50;
ballRb.restitution = 1.2;
let f = this.gui.addFolder('ball');
f.add({
ResetPosition: () => {
let pos = new Vector3(Math.random() * 15, 10, 0);
ballRb.updateTransform(pos, Quaternion._zero, true);
}
}, 'ResetPosition');
f.open();
}
// Create a bridge using multiple segments and constraints.
private createBridge() {
const numSegments = 15;
const segmentWidth = 1;
const segmentHeight = 0.2;
const segmentDepth = 5;
const distance = 0.1; // Distance between bridge segments
const pierHeight = 5; // Height of the piers
let bridgeSegments: Rigidbody[] = [];
for (let i = 0; i < numSegments; i++) {
const isStatic = i === 0 || i === numSegments - 1;
const mass = isStatic ? 0 : 2;
const staticHeight = isStatic ? pierHeight : 0;
let bridgeObj = Object3DUtil.GetSingleCube(segmentWidth, segmentHeight + staticHeight, segmentDepth, Math.random(), Math.random(), Math.random());
const posX = i * segmentWidth + i * distance || distance;
const posY = isStatic ? pierHeight / 2 + segmentHeight / 2 : pierHeight;
bridgeObj.localPosition = new Vector3(posX, posY, 0);
this.scene.addChild(bridgeObj);
let segment = this.addBoxShapeRigidBody(bridgeObj, mass, !isStatic);
bridgeSegments.push(segment);
}
let constraintList: Generic6DofSpringConstraint[] = [];
for (let i = 0; i < numSegments - 1; i++) {
let segmentA = bridgeSegments[i];
let segmentB = bridgeSegments[i + 1];
let dofSpringConstraint = segmentA.object3D.addComponent(Generic6DofSpringConstraint);
dofSpringConstraint.targetRigidbody = segmentB;
let selfHeight = i === 0 ? pierHeight / 2 : 0; // Start
let targetHeight = i === numSegments - 2 ? pierHeight / 2 : 0; // End
dofSpringConstraint.pivotSelf.set(segmentWidth / 2, selfHeight, 0);
dofSpringConstraint.pivotTarget.set(-segmentWidth / 2, targetHeight, 0);
dofSpringConstraint.linearLowerLimit.set(-distance, 0, 0);
dofSpringConstraint.linearUpperLimit.set(distance, 0, 0);
dofSpringConstraint.angularLowerLimit.set(0, -0.03, -Math.PI / 2);
dofSpringConstraint.angularUpperLimit.set(0, 0.03, Math.PI / 2);
// Enable angular spring and configure parameters
for (let j = 3; j < 6; j++) {
dofSpringConstraint.enableSpring(j, true);
dofSpringConstraint.setStiffness(j, 10.0);
dofSpringConstraint.setDamping(j, 0.5);
dofSpringConstraint.setEquilibriumPoint(j);
}
constraintList.push(dofSpringConstraint);
}
this.debug(constraintList, distance);
}
// Add a rigid body with a box shape to an object.
private addBoxShapeRigidBody(obj: Object3D, mass: number, disableHibernation?: boolean) {
let rigidbody = obj.addComponent(Rigidbody);
rigidbody.shape = CollisionShapeUtil.createBoxShape(obj);
rigidbody.mass = mass;
if (disableHibernation) rigidbody.activationState = ActivationState.DISABLE_DEACTIVATION;
return rigidbody;
}
// Debug constraints using the dat.GUI interface.
private debug(constraintList: Generic6DofSpringConstraint[], distance: number) {
let f = this.gui.addFolder('Constraint');
let refer = constraintList[0];
const spring = {
stiffness: 10.0,
damping: 0.5
};
f.add(spring, 'stiffness', 0, 100, 0.1).onChange(() => updateSpring()).listen();
f.add(spring, 'damping', 0, 100, 0.1).onChange(() => updateSpring()).listen();
const updateSpring = () => {
constraintList.forEach(constraint => {
for (let j = 0; j < 6; j++) {
constraint.enableSpring(j, true);
constraint.setStiffness(j, spring.stiffness);
constraint.setDamping(j, spring.damping);
}
constraint.setEquilibriumPoint();
});
};
f.add({ angularLower: "angularLowerLimit" }, "angularLower");
f.add(refer.angularLowerLimit, 'x', -Math.PI, 0, 0.01).onChange(() => updateLimit('angularLowerLimit')).listen();
f.add(refer.angularLowerLimit, 'y', -Math.PI, 0, 0.01).onChange(() => updateLimit('angularLowerLimit')).listen();
f.add(refer.angularLowerLimit, 'z', -Math.PI, 0, 0.01).onChange(() => updateLimit('angularLowerLimit')).listen();
f.add({ angularUpper: "angularUpperLimit" }, "angularUpper");
f.add(refer.angularUpperLimit, 'x', 0, Math.PI, 0.01).onChange(() => updateLimit('angularUpperLimit')).listen();
f.add(refer.angularUpperLimit, 'y', 0, Math.PI, 0.01).onChange(() => updateLimit('angularUpperLimit')).listen();
f.add(refer.angularUpperLimit, 'z', 0, Math.PI, 0.01).onChange(() => updateLimit('angularUpperLimit')).listen();
f.add({ linearLower: "linearLowerLimit" }, "linearLower");
f.add(refer.linearLowerLimit, 'x', -10, 0, 0.01).onChange(() => updateLimit('linearLowerLimit')).listen();
f.add(refer.linearLowerLimit, 'y', -10, 0, 0.01).onChange(() => updateLimit('linearLowerLimit')).listen();
f.add(refer.linearLowerLimit, 'z', -10, 0, 0.01).onChange(() => updateLimit('linearLowerLimit')).listen();
f.add({ linearUpper: "linearUpperLimit" }, "linearUpper");
f.add(refer.linearUpperLimit, 'x', 0, 10, 0.01).onChange(() => updateLimit('linearUpperLimit')).listen();
f.add(refer.linearUpperLimit, 'y', 0, 10, 0.01).onChange(() => updateLimit('linearUpperLimit')).listen();
f.add(refer.linearUpperLimit, 'z', 0, 10, 0.01).onChange(() => updateLimit('linearUpperLimit')).listen();
f.add({
Reset: () => {
constraintList.forEach(constraint => {
constraint.linearLowerLimit = new Vector3(-distance, 0, 0);
constraint.linearUpperLimit = new Vector3(distance, 0, 0);
constraint.angularLowerLimit = new Vector3(0, -0.03, -Math.PI / 2);
constraint.angularUpperLimit = new Vector3(0, 0.03, Math.PI / 2);
});
spring['stiffness'] = 10.0;
spring['damping'] = 0.5;
updateSpring();
}
}, 'Reset');
const updateLimit = (key: string) => {
constraintList.forEach(constraint => constraint[key] = refer[key]);
};
}
}
new Sample_dofSpringConstraint().run();Notes
When two rigidbodies are connected through a constraint, the connection point of the target rigidbody is by default located at the center of the rigidbody itself. You can modify their relative positions by adjusting the pivotSelf or pivotTarget properties when creating the constraint. However, if the two rigidbodies are overlapping before the constraint is added, this may cause instability in the constraint simulation. It is recommended to ensure that the two rigidbodies do not overlap before adding the constraint.
Example
Properly configuring physical constraints can significantly enhance the expressiveness and realism of physics simulations. The following example demonstrates the interaction among rigidbodies, various constraints, and softbodies, fully reflecting their collaborative effects.
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;
engine: Engine3D;
async run() {
// init physics and engine
await Physics.init({ useSoftBody: true, useDrag: true });
let engine = this.engine = await Engine3D.init({ renderLoop: () => Physics.update() });
this.gui = new dat.GUI();
this.scene = new Scene3D();
this.scene.addComponent(Stats);
// Initialize the physics debug feature after the engine starts; a graphic3D object must be passed to the debugger
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, engine.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;
dl.enableCSM = true;
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();
engine.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(this.engine.context3D);
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(this.engine.context3D);
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(this.engine.context3D);
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(this.engine.context3D);
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(this.engine.context3D);
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(this.engine.context3D);
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 = this.engine.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();
