ConvexObjectBreaker
是 Three.js 中的一个工具类,用于将凸多边形对象(ConvexObject)分解为较小的凸多边形对象,以提高物理引擎的性能和稳定性。以下是关于 ConvexObjectBreaker
的入参、出参、属性和方法的简要说明:
入参:
threshold
: 分解凸多边形对象的阈值。较低的阈值会导致更多的分解,但可能会增加性能开销。maxEdgeLength
: 分解后的凸多边形对象的最大边长。超过此长度的边将被切割成更小的部分。
方法:
computeSubdivision
: 对给定的凸多边形对象进行分解,并返回分解后的凸多边形对象数组。
属性:
- 无特定属性,
ConvexObjectBreaker
主要通过方法来执行分解操作。
使用方法:
- 导入
ConvexObjectBreaker
类。 - 创建一个
ConvexObjectBreaker
实例。 - 使用
computeSubdivision
方法对需要分解的凸多边形对象进行分解。 - 处理分解后的凸多边形对象数组,将其用于物理引擎等相关操作。
以下是一个简单的示例代码:
import { ConvexObjectBreaker } from 'three/examples/jsm/misc/ConvexObjectBreaker.js';
// 创建 ConvexObjectBreaker 实例
const convexObjectBreaker = new ConvexObjectBreaker();
// 假设 convexObject 是需要分解的凸多边形对象
const convexObject = ...;
// 使用 computeSubdivision 方法进行分解
const subdivisions = convexObjectBreaker.computeSubdivision(convexObject);
// 处理分解后的凸多边形对象数组
subdivisions.forEach(subdivision => {
// 在这里进行相关操作,比如添加到场景中或者应用于物理引擎
});
ConvexObjectBreaker
主要应用于处理物理引擎中的凸多边形碰撞体。具体应用场景包括但不限于:
-
物理引擎优化:在使用物理引擎进行碰撞检测和响应时,较大的凸多边形对象可能会导致性能下降。通过将大的凸多边形对象分解为更小的部分,可以提高物理引擎的性能和稳定性。
-
碰撞体生成:游戏开发中常常需要为游戏场景中的物体创建碰撞体以进行碰撞检测。
ConvexObjectBreaker
可以帮助将复杂的几何体分解为多个凸多边形碰撞体,以更准确地模拟物体的形状和边界。 -
实时模拟:在实时模拟中,凸多边形对象的分解可以提高模拟的精确度和稳定性。例如,车辆模拟器中的碰撞检测和响应,以及物体之间的交互等场景都可以受益于凸多边形对象的分解。
-
虚拟现实(VR)和增强现实(AR):在虚拟现实和增强现实应用中,物体之间的碰撞和交互是非常重要的。通过使用
ConvexObjectBreaker
进行碰撞体的分解,可以提高虚拟环境的真实感和交互性。
ConvexObjectBreaker
主要用于处理凸多边形对象的分解,以提高物理引擎性能、改善碰撞检测的精确度,并增强虚拟环境的交互性。
demo 源码
<!DOCTYPE html>
<html lang="en">
<head>
<title>Convex object breaking example</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
<style>
body {
color: #333;
}
</style>
</head>
<body>
<div id="info">Physics threejs demo with convex objects breaking in real time<br />Press mouse to throw balls and move the camera.</div>
<div id="container"></div>
<script src="jsm/libs/ammo.wasm.js"></script>
<script type="importmap">
{
"imports": {
"three": "../build/three.module.js", // 导入 Three.js 库
"three/addons/": "./jsm/" // 导入 Three.js 插件目录
}
}
</script>
<script type="module">
import * as THREE from 'three'; // 导入 Three.js 库
import Stats from 'three/addons/libs/stats.module.js'; // 导入性能统计模块
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; // 导入轨道控制器
import { ConvexObjectBreaker } from 'three/addons/misc/ConvexObjectBreaker.js'; // 导入凸物体分解器
import { ConvexGeometry } from 'three/addons/geometries/ConvexGeometry.js'; // 导入凸物体几何体
// - Global variables -
// Graphics variables
let container, stats; // 容器、性能统计
let camera, controls, scene, renderer; // 相机、控制器、场景、渲染器
let textureLoader; // 纹理加载器
const clock = new THREE.Clock(); // 时钟
const mouseCoords = new THREE.Vector2(); // 鼠标坐标
const raycaster = new THREE.Raycaster(); // 射线投射器
const ballMaterial = new THREE.MeshPhongMaterial( { color: 0x202020 } ); // 球体材质
// Physics variables
const gravityConstant = 7.8; // 重力常数
let collisionConfiguration; // 碰撞配置
let dispatcher; // 碰撞调度器
let broadphase; // 宽相位
let solver; // 求解器
let physicsWorld; // 物理世界
const margin = 0.05; // 间隔
const convexBreaker = new ConvexObjectBreaker(); // 凸物体分解器
// Rigid bodies include all movable objects
const rigidBodies = []; // 刚体数组
const pos = new THREE.Vector3(); // 位置向量
const quat = new THREE.Quaternion(); // 四元数
let transformAux1; // 辅助变换
let tempBtVec3_1; // 临时向量
const objectsToRemove = []; // 待移除对象数组
for ( let i = 0; i < 500; i ++ ) {
objectsToRemove[ i ] = null;
}
let numObjectsToRemove = 0;
const impactPoint = new THREE.Vector3(); // 碰撞点
const impactNormal = new THREE.Vector3(); // 碰撞法线
// - Main code -
Ammo().then( function ( AmmoLib ) { // 加载 Ammo.js
Ammo = AmmoLib;
init(); // 初始化
animate(); // 动画循环
} );
// - Functions -
function init() {
initGraphics(); // 初始化图形
initPhysics(); // 初始化物理
createObjects(); // 创建物体
initInput(); // 初始化输入
}
function initGraphics() {
container = document.getElementById( 'container' ); // 获取容器元素
camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.2, 2000 ); // 创建透视相机
scene = new THREE.Scene(); // 创建场景
scene.background = new THREE.Color( 0xbfd1e5 ); // 设置背景颜色
camera.position.set( - 14, 8, 16 ); // 设置相机位置
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 ); // 创建平行光
light.position.set( - 10, 18, 5 ); // 设置光源位置
light.castShadow = true; // 开启阴影投射
const d = 14;
light.shadow.camera.left = - d; // 设置阴影相机左边界
light.shadow.camera.right = d; // 设置阴影相机右边界
light.shadow.camera.top = d; // 设置阴影相机顶边界
light.shadow.camera.bottom = - d; // 设置阴影相机底边界
light.shadow.camera.near = 2; // 设置阴影相机近裁剪面
light.shadow.camera.far =
50; // 设置阴影相机远裁剪面
light.shadow.mapSize.x = 1024; // 设置阴影贴图宽度
light.shadow.mapSize.y = 1024; // 设置阴影贴图高度
scene.add( light ); // 将光源添加到场景中
stats = new Stats(); // 创建性能统计器
stats.domElement.style.position = 'absolute'; // 设置性能统计器样式
stats.domElement.style.top = '0px'; // 设置性能统计器样式
container.appendChild( stats.domElement ); // 将性能统计器添加到容器中
window.addEventListener( 'resize', onWindowResize ); // 监听窗口大小变化事件
}
function initPhysics() {
// Physics configuration
collisionConfiguration = new Ammo.btDefaultCollisionConfiguration(); // 创建碰撞配置
dispatcher = new Ammo.btCollisionDispatcher( collisionConfiguration ); // 创建碰撞调度器
broadphase = new Ammo.btDbvtBroadphase(); // 创建宽相位
solver = new Ammo.btSequentialImpulseConstraintSolver(); // 创建顺序冲量约束求解器
physicsWorld = new Ammo.btDiscreteDynamicsWorld( dispatcher, broadphase, solver, collisionConfiguration ); // 创建离散动力学世界
physicsWorld.setGravity( new Ammo.btVector3( 0, - gravityConstant, 0 ) ); // 设置重力
transformAux1 = new Ammo.btTransform(); // 创建辅助变换
tempBtVec3_1 = new Ammo.btVector3( 0, 0, 0 ); // 创建临时向量
}
function createObject( mass, halfExtents, pos, quat, material ) {
const object = new THREE.Mesh( new THREE.BoxGeometry( halfExtents.x * 2, halfExtents.y * 2, halfExtents.z * 2 ), material ); // 创建网格对象
object.position.copy( pos ); // 复制位置
object.quaternion.copy( quat ); // 复制四元数
convexBreaker.prepareBreakableObject( object, mass, new THREE.Vector3(), new THREE.Vector3(), true ); // 准备可破坏物体
createDebrisFromBreakableObject( object ); // 从可破坏物体创建碎片
}
function createObjects() {
// Ground
pos.set( 0, - 0.5, 0 ); // 设置位置
quat.set( 0, 0, 0, 1 ); // 设置四元数
const ground = createParalellepipedWithPhysics( 40, 1, 40, 0, pos, quat, new THREE.MeshPhongMaterial( { color: 0xFFFFFF } ) ); // 创建平行六面体
ground.receiveShadow = true; // 接收阴影
textureLoader.load( 'textures/grid.png', function ( texture ) { // 加载纹理
texture.wrapS = THREE.RepeatWrapping; // 设置纹理水平重复方式
texture.wrapT = THREE.RepeatWrapping; // 设置纹理垂直重复方式
texture.repeat.set( 40, 40 ); // 设置重复次数
ground.material.map = texture; // 设置材质纹理
ground.material.needsUpdate = true; // 更新材质
} );
// Tower 1
const towerMass = 1000; // 塔的质量
const towerHalfExtents = new THREE.Vector3( 2, 5, 2 ); // 塔的半尺寸
pos.set( - 8, 5, 0 ); // 设置位置
quat.set( 0, 0, 0, 1 ); // 设置四元数
createObject( towerMass, towerHalfExtents, pos, quat, createMaterial( 0xB03014 ) ); // 创建物体
// Tower 2
pos.set( 8, 5, 0 ); // 设置位置
quat.set( 0, 0, 0, 1 ); // 设置四元数
createObject( towerMass, towerHalfExtents, pos, quat, createMaterial( 0xB03214 ) ); // 创建物体
//Bridge
const bridgeMass = 100; // 桥的质量
const bridgeHalfExtents = new THREE.Vector3( 7, 0.2, 1.5 ); // 桥的半尺寸
pos.set( 0, 10.2, 0 ); // 设置位置
quat.set( 0, 0, 0, 1 ); // 设置四元数
createObject( bridgeMass, bridgeHalfExtents, pos, quat, createMaterial( 0xB3B865 ) ); // 创建物体
// Stones
const stoneMass = 120; // 石头的质量
const stoneHalfExtents = new THREE.Vector3( 1, 2, 0.15 ); // 石头的半尺寸
const numStones = 8; // 石头数量
quat.set( 0, 0, 0, 1 ); // 设置四元数
for ( let i = 0; i < numStones; i ++ ) {
pos.set( 0, 2, 15 * ( 0.5 - i / ( numStones + 1 ) ) ); // 设置位置
createObject( stoneMass, stoneHalfExtents, pos, quat, createMaterial( 0xB0B0B0 ) ); // 创建物体
}
// Mountain
const mountainMass = 860; // 山的质量
const mountainHalfExtents = new THREE.Vector3( 4, 5, 4
); // 山的半尺寸
pos.set( 5, mountainHalfExtents.y * 0.5, - 7 ); // 设置位置
quat.set( 0, 0, 0, 1 ); // 设置四元数
const mountainPoints = []; // 山的顶点
mountainPoints.push( new THREE.Vector3( mountainHalfExtents.x, - mountainHalfExtents.y, mountainHalfExtents.z ) ); // 添加顶点
mountainPoints.push( new THREE.Vector3( - mountainHalfExtents.x, - mountainHalfExtents.y, mountainHalfExtents.z ) ); // 添加顶点
mountainPoints.push( new THREE.Vector3( mountainHalfExtents.x, - mountainHalfExtents.y, - mountainHalfExtents.z ) ); // 添加顶点
mountainPoints.push( new THREE.Vector3( - mountainHalfExtents.x, - mountainHalfExtents.y, - mountainHalfExtents.z ) ); // 添加顶点
mountainPoints.push( new THREE.Vector3( 0, mountainHalfExtents.y, 0 ) ); // 添加顶点
const mountain = new THREE.Mesh( new ConvexGeometry( mountainPoints ), createMaterial( 0xB03814 ) ); // 创建山体
mountain.position.copy( pos ); // 复制位置
mountain.quaternion.copy( quat ); // 复制四元数
convexBreaker.prepareBreakableObject( mountain, mountainMass, new THREE.Vector3(), new THREE.Vector3(), true ); // 准备可破坏物体
createDebrisFromBreakableObject( mountain ); // 从可破坏物体创建碎片
}
function createParalellepipedWithPhysics( sx, sy, sz, mass, pos, quat, material ) {
const object = new THREE.Mesh( new THREE.BoxGeometry( sx, sy, sz, 1, 1, 1 ), material ); // 创建网格对象
const shape = new Ammo.btBoxShape( new Ammo.btVector3( sx * 0.5, sy * 0.5, sz * 0.5 ) ); // 创建盒子形状
shape.setMargin( margin ); // 设置边距
createRigidBody( object, shape, mass, pos, quat ); // 创建刚体
return object;
}
function createDebrisFromBreakableObject( object ) {
object.castShadow = true; // 投射阴影
object.receiveShadow = true; // 接收阴影
const shape = createConvexHullPhysicsShape( object.geometry.attributes.position.array ); // 创建凸包物理形状
shape.setMargin( margin ); // 设置边距
const body = createRigidBody( object, shape, object.userData.mass, null, null, object.userData.velocity, object.userData.angularVelocity ); // 创建刚体
// Set pointer back to the three object only in the debris objects
const btVecUserData = new Ammo.btVector3( 0, 0, 0 ); // 创建用户数据向量
btVecUserData.threeObject = object; // 设置网格对象
body.setUserPointer( btVecUserData ); // 设置用户指针
}
function removeDebris( object ) {
scene.remove( object ); // 从场景中移除物体
physicsWorld.removeRigidBody( object.userData.physicsBody ); // 移除刚体
}
function createConvexHullPhysicsShape( coords ) {
const shape = new Ammo.btConvexHullShape(); // 创建凸包形状
for ( let i = 0, il = coords.length; i < il; i += 3 ) {
tempBtVec3_1.setValue( coords[ i ], coords[ i + 1 ], coords[ i + 2 ] ); // 设置值
const lastOne = ( i >= ( il - 3 ) ); // 是否为最后一个点
shape.addPoint( tempBtVec3_1, lastOne ); // 添加点
}
return shape;
}
function createRigidBody( object, physicsShape, mass, pos, quat, vel, angVel ) {
if ( pos ) {
object.position.copy( pos ); // 复制位置
} else {
pos = object.position; // 设置位置
}
if ( quat ) {
object.quaternion.copy( quat ); // 复制四元数
} else {
quat = object.quaternion; // 设置四元数
}
const transform = new Ammo.btTransform(); // 创建变换
transform.setIdentity(); // 设置单位变换
transform.setOrigin( new Ammo.btVector3( pos.x, pos.y, pos.z ) ); // 设置原点
transform.setRotation( new Ammo.btQuaternion( quat.x, quat.y, quat.z, quat.w ) ); // 设置旋转
const motionState = new Ammo.btDefaultMotionState( transform ); // 创建默认动力状态
const localInertia = new Ammo.btVector3( 0, 0, 0 ); // 创建局部惯性向量
physicsShape.calculateLocalInertia( mass, localInertia ); // 计算局部惯性
const rbInfo = new Ammo.btRigidBodyConstructionInfo( mass, motionState, physicsShape, localInertia ); // 创建刚体信息
const body = new Ammo.btRigidBody( rbInfo ); // 创建刚体
body.setFriction( 0.5 ); // 设置摩擦力
if ( vel ) {
body.setLinearVelocity( new Ammo.btVector3( vel.x, vel.y, vel.z ) ); // 设置线性速度
}
if ( angVel ) {
body.setAngularVelocity( new Ammo.btVector3( angVel.x, angVel.y, angVel.z ) ); // 设置角速度
}
object.userData.physicsBody = body; // 设置物体的刚体
object.userData.collided = false; // 设置物体的碰撞状态
scene.add( object ); // 将物体添加到场景中
if ( mass > 0 ) {
rigidBodies.push( object ); // 将物体添加到刚体列表中
// Disable deactivation
body.setActivationState( 4 ); // 禁用去激活
}
physicsWorld.addRigidBody( body ); // 将刚体添加到物理
世界中
return body;
}
function createMaterial( color ) {
return new THREE.MeshPhongMaterial( { color: color } ); // 创建材质
}
function initInput() {
window.addEventListener( 'mousedown', function ( event ) { // 鼠标按下事件
event.preventDefault();
mouseCoords.set( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1 ); // 设置鼠标坐标
raycaster.setFromCamera( mouseCoords, camera ); // 从相机创建射线
// Creates a ball
const ballMass = 35; // 球的质量
const ballRadius = 0.4; // 球的半径
const ball = new THREE.Mesh( new THREE.SphereGeometry( ballRadius, 18, 18 ), ballMaterial ); // 创建球体
ball.castShadow = true; // 投射阴影
const ballShape = new Ammo.btSphereShape( ballRadius ); // 创建球形状
ballShape.setMargin( margin ); // 设置边距
pos.copy( raycaster.ray.direction ); // 复制射线方向
pos.add( raycaster.ray.origin ); // 将原点添加到射线方向
quat.set( 0, 0, 0, 1 ); // 设置四元数
const ballBody = createRigidBody( ball, ballShape, ballMass, pos, quat ); // 创建刚体
pos.copy( raycaster.ray.direction ); // 复制射线方向
pos.multiplyScalar( 24 ); // 缩放
ballBody.setLinearVelocity( new Ammo.btVector3( pos.x, pos.y, pos.z ) ); // 设置线性速度
}, false );
}
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() {
const deltaTime = clock.getDelta(); // 获取帧间隔时间
updatePhysics( deltaTime ); // 更新物理
renderer.render( scene, camera ); // 渲染
}
function updatePhysics( deltaTime ) {
// Step world
physicsWorld.stepSimulation( deltaTime, 10 ); // 步进物理世界
// Update rigid bodies
for ( let i = 0, il = rigidBodies.length; i < il; i ++ ) {
const objThree = rigidBodies[ i ]; // 获取 Three.js 对象
const objPhys = objThree.userData.physicsBody; // 获取物理刚体
const ms = objPhys.getMotionState(); // 获取动力状态
if ( ms ) {
ms.getWorldTransform( transformAux1 ); // 获取世界变换
const p = transformAux1.getOrigin(); // 获取原点
const q = transformAux1.getRotation(); // 获取旋转
objThree.position.set( p.x(), p.y(), p.z() ); // 设置位置
objThree.quaternion.set( q.x(), q.y(), q.z(), q.w() ); // 设置四元数
objThree.userData.collided = false; // 重置碰撞状态
}
}
// Remove random debris
if ( numObjectsToRemove > 0 ) {
for ( let i = 0; i < numObjectsToRemove; i ++ ) {
const obj = objectsToRemove[ i ]; // 获取物体
removeDebris( obj ); // 移除碎片
objectsToRemove[ i ] = null; // 清空
}
numObjectsToRemove = 0; // 重置
}
// Remove constraints from objects that are sleeping
for ( let i = 0, il = rigidBodies.length; i < il; i ++ ) {
const objThree = rigidBodies[ i ]; // 获取 Three.js 对象
const objPhys = objThree.userData.physicsBody; // 获取物理刚体
if ( objPhys ) {
const ms = objPhys.getMotionState(); // 获取动力状态
if ( ms ) {
ms.getWorldTransform( transformAux1 ); // 获取世界变换
const p = transformAux1.getOrigin(); // 获取原点
const q = transformAux1.getRotation(); // 获取旋转
if ( p.y() < - 10 ) { // 如果物体高度小于-10
scene.remove( objThree ); // 从场景中移除
physicsWorld.removeRigidBody( objPhys ); // 从物理世界中移除
objThree.userData.physicsBody = null; // 清空刚体
rigidBodies.splice( i, 1 ); // 移除物体
i --; // 更新索引
il --; // 更新长度
continue; // 跳过当前循环
}
// Remove all constraints from sleeping objects
if ( ! objPhys.isActive() ) {
const cons = objPhys.getConstraintList(); // 获取约束列表
while ( cons ) {
const next = cons.getNext(); // 获取下一个约束
physicsWorld.removeConstraint( cons ); // 移除约束
cons = next; // 更新约束
}
}
}
}
}
}
</script>
</body>
</html>