约束
物理约束用于限制两个物理对象(通常是刚体)之间的相对运动。它们允许在物理模拟中创建更复杂的行为,如铰链、滑块或固定的连接。通过合理配置约束,可以实现现实世界中的各种机械结构和连接方式。
约束组件概述
约束以组件的形式添加和使用。约束父类实现了通用的约束功能,各个具体的约束组件通过继承该父类,并封装了底层的 Ammo.js
约束类型,提供了与原生约束类似的 API
。这使得开发者能够省去手动创建约束的流程,可以快速、便捷地集成各种物理约束,实现复杂的物理行为。
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);
// 分别为两个刚体进行相关配置,如设置 mass、shape 等
...
// 为 object 添加铰链约束,并指定目标刚体,约束会将这两个刚体进行连接
let hingeConstraint = object.addComponent(HingeConstraint);
hingeConstraint.targetRigidbody = targetRigidbody;
// 具体的约束配置请参考下述介绍的API
...
请注意,对象必须在添加约束组件之前添加 刚体 组件。
基本用法
以下是约束的通用 API
,各个约束类型还提供了独特的设置选项。
属性 | 类型 | 描述 |
---|---|---|
constraint | Ammo.btTypedConstraint | 获取 Ammo.js 的原生约束 |
breakingThreshold | number | 断裂阈值,值越大,约束越不易断裂 |
disableCollisionsBetweenLinkedBodies | boolean | 禁用关联刚体的碰撞,默认值为 true |
targetRigidbody | Rigidbody | 目标刚体,约束将限制当前刚体与目标刚体之间的相对运动 |
pivotSelf | Vector3 | 自身刚体的枢轴点,决定了约束的旋转中心 |
pivotTarget | Vector3 | 目标刚体的枢轴点 |
rotationSelf | Quaternion | 自身刚体的旋转设置 |
rotationTarget | Quaternion | 目标刚体的旋转设置 |
方法 | 描述 |
---|---|
wait() | 异步获取完成初始化的原生约束实例 |
resetConstraint() | 重置约束,销毁当前约束实例后重新创建并返回新的约束实例 |
重载支持
在原生 Ammo.js
中,除了 FixedConstraint
之外,其余约束都提供了多种构造方法的重载。为了确保这些功能的完整性,约束组件中也提供了相应的重载支持。通常情况下,如果 targetRigidbody
属性未设置,约束将默认以单个刚体的形式创建。开发者可以根据具体需求,自由选择适合的约束构造方式。
约束类型
当前系统已集成了 Ammo.js
中的 7 种主要约束类型,每种约束均适用于特定的应用场景。
1. 铰链约束 HingeConstraint
铰链约束允许物体绕某个轴进行旋转,适用于门、机械臂等需要单轴旋转的场景。
属性 | 类型 | 描述 |
---|---|---|
axisSelf | Vector3 | 自身刚体上的铰链轴方向,默认值为 Vector3.UP |
axisTarget | Vector3 | 目标刚体上的铰链轴方向,默认值为 Vector3.UP |
useReferenceFrameA | boolean | 是否使用自身刚体的参考框架,默认值为 true |
useTwoBodiesTransformOverload | boolean | 是否使用两个刚体的变换重载方式,默认值为 false |
方法 | 描述 |
---|---|
setLimit() | 设置旋转限制 |
enableAngularMotor() | 启用或禁用角度马达 |
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. 滑动关节约束 SliderConstraint
滑动关节约束允许物体沿着一个轴进行平移,并绕该轴旋转,适用于滑轨或电梯等应用场景。
属性 | 类型 | 描述 |
---|---|---|
lowerLinLimit | number | 线性运动的下限限制 |
upperLinLimit | number | 线性运动的上限限制 |
lowerAngLimit | number | 角度运动的下限限制 |
upperAngLimit | number | 角度运动的上限限制 |
poweredLinMotor | boolean | 是否启用线性马达 |
maxLinMotorForce | number | 线性马达的最大推力 |
targetLinMotorVelocity | number | 线性马达的目标速度 |
let sliderConstraint = object.addComponent(SliderConstraint);
sliderConstraint.lowerLinLimit = -10;
sliderConstraint.upperLinLimit = 10;
sliderConstraint.poweredLinMotor = true;
sliderConstraint.maxLinMotorForce = 100;
sliderConstraint.targetLinMotorVelocity = 5;
3. 固定约束 FixedConstraint
固定约束将两个物体完全固定在一起,限制其相对位置和旋转,从而实现刚性的连接效果。
let fixedConstraint = object.addComponent(FixedConstraint);
fixedConstraint.targetRigidbody = targetRigidbody; // 固定约束类型必须指定目标刚体
4. 点到点约束 PointToPointConstraint
该约束限制了两个点之间的相对运动,但允许它们在空间中自由旋转。通常用于模拟绳索或链条的连接。
let p2pConstraint = object.addComponent(PointToPointConstraint);
p2pConstraint.targetRigidbody = targetRigidbody;
p2pConstraint.pivotSelf.set(0, 0, 0);
p2pConstraint.pivotTarget.set(0, 5, 0);
5. 锥形扭转约束 ConeTwistConstraint
锥形扭转约束用于创建类似球窝关节的运动,允许物体在一个锥形范围内自由旋转,并限制其绕某轴的扭转角度。
属性 | 类型 | 描述 |
---|---|---|
twistSpan | number | 扭转角度限制,绕 X 轴的扭转范围 |
swingSpan1 | number | 摆动角度限制1,绕 Y 轴的摆动范围 |
swingSpan2 | number | 摆动角度限制2,绕 Z 轴的摆动范围 |
let coneTwistConstraint = object.addComponent(ConeTwistConstraint);
coneTwistConstraint.twistSpan = Math.PI / 4; // 限制扭转角度为 45 度
6. 通用六自由度约束 Generic6DofConstraint
该约束允许沿三个线性轴和三个角度轴自由设置运动限制,提供了最大的灵活性以满足各种复杂的连接需求。
属性 | 类型 | 描述 |
---|---|---|
linearLowerLimit | Vector3 | 线性运动的下限限制 |
linearUpperLimit | Vector3 | 线性运动的上限限制 |
angularLowerLimit | Vector3 | 角度运动的下限限制 |
angularUpperLimit | Vector3 | 角度运动的上限限制 |
useLinearFrameReferenceFrame | boolean | 是否使用线性参考坐标系 |
let sixDofConstraint = object.addComponent(Generic6DofConstraint);
sixDofConstraint.linearLowerLimit = new Vector3(-1, -1, -1); // 设置线性下限
sixDofConstraint.linearUpperLimit = new Vector3(1, 1, 1); // 设置线性上限
7. 弹簧特性六自由度约束 Generic6DofSpringConstraint
此约束是在通用六自由度约束的基础上增加了弹簧特性,可以模拟弹簧的效果,如伸缩和振动。
方法 | 描述 |
---|---|
enableSpring() | 启用或禁用弹簧功能 |
setStiffness() | 设置弹簧的刚度 |
setDamping() | 设置弹簧的阻尼 |
setEquilibriumPoint() | 设置弹簧的平衡点 |
let springConstraint = object.addComponent(Generic6DofSpringConstraint);
// 启用并配置弹簧:索引0、1、2对应线性轴(x, y, z),3、4、5对应角度轴(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;
async run() {
// Initialize physics and engine
await Physics.init({ useDrag: true });
await Engine3D.init({ renderLoop: () => Physics.update() });
let scene = this.scene = new Scene3D();
scene.addComponent(Stats);
// 在引擎启动后初始化物理调试功能,需要为绘制器传入 graphic3D 对象
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, Engine3D.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);
lightObj3D.addComponent(DirectLight).castShadow = true;
scene.addChild(lightObj3D);
// Initialize sky
scene.addComponent(AtmosphericComponent).sunY = 0.6;
let view = new View3D();
view.camera = camera;
view.scene = scene;
Engine3D.startRenderView(view);
// Create ground, bridge, and ball
this.createGround();
this.createBridge();
this.createBall();
}
//Create the ground plane.
private async createGround() {
let ground = Object3DUtil.GetPlane(Engine3D.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();
注意事项
当两个刚体通过约束连接时,目标刚体的连接点默认会位于自身刚体的中心位置。可以在创建约束时通过调整 pivotSelf
或 pivotTarget
属性来修改它们的相对位置。然而,如果两个刚体在添加约束前处于重叠状态,这可能会导致约束模拟的不稳定。建议在添加约束前确保两个刚体没有重叠。
示例
合理配置物理约束可以显著提升物理模拟的表现力和真实性。以下示例展示了刚体、多种约束和软体之间的相互联动,充分体现了它们的协同作用。
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();