Ammo.btSoftRigidDynamicsWorld 是 Bullet Physics 引擎中用于实现软体和硬体动力学模拟的一个重要类。它扩展了 btDiscreteDynamicsWorld

本文详细介绍了如何在BulletPhysics引擎中利用Ammo.btSoftRigidDynamicsWorld类实现软体和硬体物体的混合动力学模拟,包括初始化世界、添加刚体和软体物体,以及与Three.js的同步等内容。
摘要由CSDN通过智能技术生成

demo案例
在这里插入图片描述

Ammo.btSoftRigidDynamicsWorld 是 Bullet Physics 引擎中用于实现软体和硬体动力学模拟的一个重要类。它扩展了 btDiscreteDynamicsWorld 类,增加了对软体物体(soft bodies)的支持,这样就可以在一个世界中同时模拟刚体(rigid bodies)和软体物体的行为。

使用 Ammo.btSoftRigidDynamicsWorld 的基本步骤通常包括以下几个部分:

  1. 初始化世界

    • 导入 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)); // 设置重力
    
  2. 添加刚体

    • 创建刚体,并加入到这个世界中。
    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);
    
  3. 添加软体

    • 创建软体物体,并将其添加到世界中。
    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);
    
  4. 模拟与更新

    • 每一帧调用 stepSimulation 方法来模拟物理世界。
    var timeStep = 1.0 / 60.0; // 常见的时间步长
    softWorld.stepSimulation(timeStep, maxSubSteps, fixedTimeStep);
    
  5. 同步 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());
        }
    }
}

本内容来源于小豆包,想要更多内容请跳转小豆包 》

  • 27
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值