Ammo.btSoftRigidDynamicsWorld
是 Bullet Physics 引擎中用于实现软体和硬体动力学模拟的一个重要类。它扩展了 btDiscreteDynamicsWorld
类,增加了对软体物体(soft bodies)的支持,这样就可以在一个世界中同时模拟刚体(rigid bodies)和软体物体的行为。
使用 Ammo.btSoftRigidDynamicsWorld
的基本步骤通常包括以下几个部分:
-
初始化世界:
- 导入 Ammo.js 库并初始化必要的组件,例如碰撞配置器(Collision Configuration)、重力(Gravity)、碰撞检测器(Broadphase)、约束求解器(Constraint Solver)等。
var ammo = require('ammo.js'); var Ammo = ammo.Ammo; var collisionConfiguration = new Ammo.btDefaultCollisionConfiguration(); var dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration); var broadphase = new Ammo.btDbvtBroadphase(); var solver = new Ammo.btSequentialImpulseConstraintSolver(); var softWorld = new Ammo.btSoftRigidDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration, null); softWorld.setGravity(new Ammo.btVector3(0, -9.8, 0)); // 设置重力
-
添加刚体:
- 创建刚体,并加入到这个世界中。
var groundShape = new Ammo.btBoxShape(new Ammo.btVector3(50, 50, 50)); var groundTransform = new Ammo.btTransform(); groundTransform.setIdentity(); groundTransform.setOrigin(new Ammo.btVector3(0, -50, 0)); var rigidBody = createRigidBody(groundShape, groundTransform, 0); // 假设有一个createRigidBody的辅助函数 softWorld.addRigidBody(rigidBody);
-
添加软体:
- 创建软体物体,并将其添加到世界中。
var softBodyHelpers = new Ammo.btSoftBodyHelpers(); var softBodyWorldInfo = new Ammo.btSoftBodyWorldInfo(); softBodyWorldInfo.m_gravity.setValue(0, -9.8, 0); softBodyWorldInfo.m_sparsesdf.Initialize(); var vertices = []; // 填充顶点数组 var indices = []; // 填充索引数组,定义多边形面片 // ... 构建软体形状的数据... var softBody = softBodyHelpers.CreateFromTriMesh( softBodyWorldInfo, vertices, indices, /*... 其他参数 ...*/ ); softWorld.addSoftBody(softBody, shortContactFilterGroup, shortContactFilterMask);
-
模拟与更新:
- 每一帧调用
stepSimulation
方法来模拟物理世界。
var timeStep = 1.0 / 60.0; // 常见的时间步长 softWorld.stepSimulation(timeStep, maxSubSteps, fixedTimeStep);
- 每一帧调用
-
同步 Three.js 或其他图形库中的对象位置:
- 在每帧模拟后,你需要获取刚体和软体物体的最新位置和旋转,并同步到你的图形库(比如Three.js)中的相应对象上,这通常涉及到读取物理引擎中的转换矩阵并应用到3D模型上。
demo 源码
// 导入 Three.js 库以及其他相关模块
import * as THREE from 'three';
import Stats from 'three/addons/libs/stats.module.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// 定义全局变量,用于存储图形、物理模拟及交互控制相关对象
let container, stats;
let camera, controls, scene, renderer;
let textureLoader;
const clock = new THREE.Clock();
// 物理模拟相关的变量初始化
const gravityConstant = -9.8;
let collisionConfiguration;
let dispatcher;
let broadphase;
let solver;
let softBodySolver;
let physicsWorld;
const rigidBodies = [];
const margin = 0.05;
let hinge;
let rope;
let transformAux1;
let armMovement = 0;
// 使用 Ammo(Bullet Physics)库异步加载并初始化物理引擎
Ammo().then(function (AmmoLib) {
Ammo = AmmoLib;
// 初始化场景与物理世界
init();
// 开始渲染循环
animate();
});
// 初始化图形界面和相关组件
function init() {
initGraphics();
initPhysics();
createObjects();
initInput();
}
// 初始化图形界面包括创建容器、相机、场景、渲染器等
function initGraphics() {
// 获取HTML容器元素
container = document.getElementById('container');
// 创建透视相机
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.2, 2000);
// 创建Three.js场景并设置背景颜色
scene = new THREE.Scene();
scene.background = new THREE.Color(0xbfd1e5);
// 设置相机初始位置
camera.position.set(-7, 5, 8);
// 创建WebGL渲染器,并设置抗锯齿、像素比、阴影映射等属性
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
container.appendChild(renderer.domElement);
// 创建并初始化轨道控制器
controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 2, 0);
controls.update();
// 创建纹理加载器
textureLoader = new THREE.TextureLoader();
// 添加环境光到场景中
const ambientLight = new THREE.AmbientLight(0xbbbbbb);
scene.add(ambientLight);
// 添加方向光至场景,同时开启阴影投射
const light = new THREE.DirectionalLight(0xffffff, 3);
configureDirectionalLight(light);
// 添加性能监控统计信息到页面
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0px';
container.appendChild(stats.domElement);
// 监听窗口大小变化事件以调整渲染尺寸
window.addEventListener('resize', onWindowResize);
// 此处省略了configureDirectionalLight和onWindowResize的实现
}
// 初始化物理引擎配置
function initPhysics() {
// 创建并配置碰撞检测所需的各类物理组件
collisionConfiguration = new Ammo.btSoftBodyRigidBodyCollisionConfiguration();
dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration);
broadphase = new Ammo.btDbvtBroadphase();
solver = new Ammo.btSequentialImpulseConstraintSolver();
softBodySolver = new Ammo.btDefaultSoftBodySolver();
// 创建软体刚体动力学世界(包含物理世界的实例)
physicsWorld = new Ammo.btSoftRigidDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration, softBodySolver);
// 设置重力值
physicsWorld.setGravity(new Ammo.btVector3(0, gravityConstant, 0));
physicsWorld.getWorldInfo().set_m_gravity(new Ammo.btVector3(0, gravityConstant, 0));
// 初始化辅助变换对象
transformAux1 = new Ammo.btTransform();
}
// 创建场景中的物体
function createObjects() {
// 定义通用向量和四元数
const pos = new THREE.Vector3();
const quat = new THREE.Quaternion();
// 地面
// 创建一个平行六面体作为地面,并设置材质、阴影投射和接收
pos.set(0, -0.5, 0);
quat.set(0, 0, 0, 1);
const ground = createParalellepiped(40, 1, 40, 0, pos, quat, new THREE.MeshPhongMaterial({ color: 0xFFFFFF }));
loadAndApplyGridTextureToGround(ground);
// 球体
// 创建一个球体并将其添加到物理世界,设置质量、半径、材质以及摩擦系数
const ballMass = 1.2;
const ballRadius = 0.6;
const ball = createSphere(ballRadius, new THREE.MeshPhongMaterial({ color: 0x202020 }));
addBallToPhysicsWorld(ball, ballMass);
// 墙体
// 创建一系列砖块组成墙体,每个砖块都有特定的质量、尺寸,并加入到物理世界中
const brickMass = 0.5;
const brickLength = 1.2;
const brickDepth = 0.6;
const brickHeight = brickLength * 0.5;
createWall(brickMass, brickLength, brickDepth, brickHeight);
// 绳索
// 创建绳索的图形对象(线段)和物理对象(软体),并将其一端固定在球上,另一端固定在机械臂上
const ropeNumSegments = 10;
const ropeLength = 4;
const ropeMass = 3;
createRope(ropeNumSegments, ropeLength, ropeMass, ball);
// 基座与机械臂
// 创建基座、立柱和机械臂,并将机械臂通过铰链约束与立柱连接,同时也将绳索的末端连接到机械臂上
const armMass = 2;
const armLength = 3;
createBaseAndArm(armMass, armLength, rope);
// 将绳索的两端用锚点固定在球体和机械臂的物理体上
connectRopeExtremes(ball, arm);
// 创建铰链约束以允许机械臂运动
createHingeConstraint(pylon, arm);
}
// 创建平行六面体形状的三维物体
function createParalellepiped(sx, sy, sz, mass, pos, quat, material) {
const threeObject = new THREE.Mesh(new THREE.BoxGeometry(sx, sy, sz, 1, 1, 1), material);
const physicsShape = createBoxShape(sx, sy, sz);
addRigidBody(threeObject, physicsShape, mass, pos, quat);
return threeObject;
}
// 创建具有指定形状和参数的物理刚体,并关联到THREE.js对象
function createRigidBody(threeObject, physicsShape, mass, pos, quat) {
setupObjectPositionAndRotation(threeObject, pos, quat);
createAndAddRigidBody(threeObject, physicsShape, mass);
}
// 加载纹理并应用到地面上
function loadAndApplyGridTextureToGround(ground) {
textureLoader.load('textures/grid.png', function(texture) {
texture.colorSpace = THREE.SRGBColorSpace;
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(40, 40);
ground.material.map = texture;
ground.material.needsUpdate = true;
});
}
// 其他辅助函数:创建球体、创建墙、创建绳索、创建随机材质等...
// 定义一个函数用于生成随机RGB颜色
function createRandomColor() {
// 使用位运算符创建从0到(16777215)之间的一个随机整数,代表24位颜色值
return Math.floor(Math.random() * (1 << 24));
}
// 创建一个带有随机颜色的新MeshPhongMaterial材质对象
function createMaterial() {
return new THREE.MeshPhongMaterial({ color: createRandomColor() });
}
// 初始化用户输入监听
function initInput() {
// 添加键盘按下事件监听器,响应Q(81)和A(65)键来改变armMovement变量
window.addEventListener('keydown', function(event) {
switch (event.keyCode) {
// 当按Q键时,设置armMovement为正向移动速度
case 81:
armMovement = 1;
break;
// 当按A键时,设置armMovement为反向移动速度
case 65:
armMovement = -1;
break;
}
});
// 添加键盘抬起事件监听器,松开按键时将armMovement重置为停止状态(0)
window.addEventListener('keyup', function() {
armMovement = 0;
});
}
// 窗口尺寸变化时的处理函数
function onWindowResize() {
// 更新相机的长宽比
camera.aspect = window.innerWidth / window.innerHeight;
// 应用新的投影矩阵
camera.updateProjectionMatrix();
// 更新渲染器的尺寸以适应窗口大小
renderer.setSize(window.innerWidth, window.innerHeight);
}
// 动画循环函数
function animate() {
// 请求下一帧动画
requestAnimationFrame(animate);
// 渲染当前帧并更新性能统计信息
render();
stats.update();
}
// 渲染函数,负责每一帧的渲染逻辑
function render() {
// 获取当前时间步长(deltaTime),用于物理模拟的时间推进
const deltaTime = clock.getDelta();
// 更新物理模拟
updatePhysics(deltaTime);
// 渲染Three.js场景到屏幕上
renderer.render(scene, camera);
}
// 物理模拟更新函数
function updatePhysics(deltaTime) {
// 控制铰链关节的速度,基于用户输入(armMovement)
hinge.enableAngularMotor(true, 1.5 * armMovement, 50);
// 对物理世界进行一步模拟计算
physicsWorld.stepSimulation(deltaTime, 10);
// 更新绳索物体的位置(假设rope是一个软体物理对象)
const softBody = rope.userData.physicsBody;
const ropePositions = rope.geometry.attributes.position.array;
const numVerts = ropePositions.length / 3;
const nodes = softBody.get_m_nodes();
let indexFloat = 0;
// 遍历绳索的所有顶点,并将其物理模拟位置同步到Three.js几何体
for (let i = 0; i < numVerts; i++) {
const node = nodes.at(i);
const nodePos = node.get_m_x();
ropePositions[indexFloat++] = nodePos.x();
ropePositions[indexFloat++] = nodePos.y();
ropePositions[indexFloat++] = nodePos.z();
}
// 标记绳索几何体的位置属性需要更新
rope.geometry.attributes.position.needsUpdate = true;
// 更新所有刚体物体的位置与旋转,同步物理引擎中的状态到Three.js场景中
for (let i = 0, il = rigidBodies.length; i < il; i++) {
const objThree = rigidBodies[i];
const objPhys = objThree.userData.physicsBody;
const ms = objPhys.getMotionState();
if (ms) {
// 获取刚体在物理世界的变换矩阵
ms.getWorldTransform(transformAux1);
// 提取变换矩阵中的原点坐标和四元数旋转
const p = transformAux1.getOrigin();
const q = transformAux1.getRotation();
// 将这些值应用到对应的Three.js对象上
objThree.position.set(p.x(), p.y(), p.z());
objThree.quaternion.set(q.x(), q.y(), q.z(), q.w());
}
}
}