本文是入门文章,为了方便理解,部分机制可能描述得不那么准确
1.工作机制
threejs由5个部分组成,分别是
- Scene,场景,存放Mesh
- Camera,相机,从某个角度拍摄Scene
- Mesh,3D对象
- Renderer,渲染器,用相机拍摄场景
- Canvas,画布,html里显示渲染结果的节点
具体流程可以认为是首先将渲染的3D对象添加到场景里,然后渲染器调用相机拍摄场景并将结果呈现在画布里
ammo在此基础上增加了一个物理世界physicsWorld
将3D对象添加到场景的同时再添加到物理世界里,在物理世界里进行运动并获取运动后的3D对象的状态,将状态同步到场景里,重新用相机拍摄渲染到画布里即为每一帧的更新
2.使用方式
步骤如下
Ammo().then(function(Ammo) {
//初始化
init();
//刷新每帧的运行
animate();
function init() {
//创建threejs的相机,场景,渲染器并绑定画布
initGraphics();
//创建ammo的物理世界
initPhysics();
//创建3D对象并添加到场景和物理世界中
createObjects();
//绑定外部设备比如键盘的事件
initInput();
}
function animate() {
//设置每帧刷新时的回调
requestAnimationFrame( animate );
//更新物理世界并同步到场景,之后重新渲染最新结果
render();
}
}
)
3.初始化threejs,initGraphics
创建摄像头,渲染器,场景并添加环境光和光源,设置画布
function initGraphics() {
//创建相机
camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.2, 2000 );
camera.position.x = -7;
camera.position.y = 5;
camera.position.z = 8;
//设置用鼠标移动相机
controls = new THREE.OrbitControls( camera );
//移动时旋转中心点的坐标
controls.target.y = 2;
//设置渲染器为webgl
renderer = new THREE.WebGLRenderer();
renderer.setClearColor( 0xbfd1e5 );
//设置像素密度和设备相同
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
//启用阴影映射
renderer.shadowMap.enabled = true;
//纹理加载器
textureLoader = new THREE.TextureLoader();
//创建场景
scene = new THREE.Scene();
//环境光
var ambientLight = new THREE.AmbientLight( 0x404040 );
scene.add( ambientLight );
//定向光源
var light = new THREE.DirectionalLight( 0xffffff, 1 );
light.position.set( -10, 10, 5 );
//光源阴影
light.castShadow = true;
var d = 10;
//光照范围,近裁剪面,远裁剪面
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 );
//在画布上绑定渲染结果
container = document.getElementById( 'container' );
container.innerHTML = "";
container.appendChild( renderer.domElement );
//自适应大小
window.addEventListener( 'resize', onWindowResize, false );
}
4.初始化ammo,initPhysics
function initPhysics() {
//创建软体和刚体碰撞的配置
collisionConfiguration = new Ammo.btSoftBodyRigidBodyCollisionConfiguration();
//创建一个碰撞分发器,它将根据碰撞配置处理碰撞事件
dispatcher = new Ammo.btCollisionDispatcher( collisionConfiguration );
//创建一个广相位(broadphase)算法,用于检测潜在的碰撞对
broadphase = new Ammo.btDbvtBroadphase();
//创建一个约束求解器,用于解决物体之间的约束和碰撞响应
solver = new Ammo.btSequentialImpulseConstraintSolver();
//创建一个软体(soft body)求解器,用于模拟柔软的物体
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 ) );
}
5.创建3D对象,createObjects
这一步根据实际需要的场景创建对应的对象
下面代码创建了一个地板设置质量为0,这样子地板就不会因为重力掉落了
在地板上创建了方块堆叠成的墙和一个可以旋转的机械臂,机械臂和球体通过绳子链接
function createObjects() {
//位置信息
var pos = new THREE.Vector3();
//旋转信息
var quat = new THREE.Quaternion();
// 地板
pos.set( 0, - 0.5, 0 );
quat.set( 0, 0, 0, 1 );
//质量为0,所以不会掉下去
var ground = createParalellepiped( 40, 1, 40, 0, pos, quat, new THREE.MeshPhongMaterial( { color: 0xFFFFFF } ) );
//产生阴影
ground.castShadow = true;
//显示其他物理产生的阴影
ground.receiveShadow = true;
textureLoader.load( "../textures/grid.png", function( texture ) {
//设置为重复平铺
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
//每行每列重复40次
texture.repeat.set( 40, 40 );
//设置材质纹理
ground.material.map = texture;
//修改后通知刷新材质
ground.material.needsUpdate = true;
} );
// 球
var ballMass = 0.2;
var ballRadius = 0.6;
var ball = new THREE.Mesh( new THREE.SphereGeometry( ballRadius, 20, 20 ), new THREE.MeshPhongMaterial( { color: 0x202020 } ) );
ball.castShadow = true;
ball.receiveShadow = true;
var ballShape = new Ammo.btSphereShape( ballRadius );
ballShape.setMargin( margin );
pos.set( -3, 2, 0 );
quat.set( 0, 0, 0, 1 );
createRigidBody( ball, ballShape, ballMass, pos, quat );
//设置摩擦系数
ball.userData.physicsBody.setFriction( 0.5 );
// 墙
var brickMass = 0.5;
var brickLength = 1.2;
var brickDepth = 0.6;
var brickHeight = brickLength * 0.5;
var numBricksLength = 6;
var numBricksHeight = 8;
var z0 = - numBricksLength * brickLength * 0.5;
pos.set( 0, brickHeight * 0.5, z0 );
quat.set( 0, 0, 0, 1 );
//创建长方体组成的墙壁
for ( var j = 0; j < numBricksHeight; j ++ ) {
var oddRow = ( j % 2 ) == 1;
pos.z = z0;
if ( oddRow ) {
pos.z -= 0.25 * brickLength;
}
var nRow = oddRow? numBricksLength + 1 : numBricksLength;
for ( var i = 0; i < nRow; i ++ ) {
var brickLengthCurrent = brickLength;
var brickMassCurrent = brickMass;
if ( oddRow && ( i == 0 || i == nRow - 1 ) ) {
brickLengthCurrent *= 0.5;
brickMassCurrent *= 0.5;
}
var brick = createParalellepiped( brickDepth, brickHeight, brickLengthCurrent, brickMassCurrent, pos, quat, createMaterial() );
brick.castShadow = true;
brick.receiveShadow = true;
if ( oddRow && ( i == 0 || i == nRow - 2 ) ) {
pos.z += 0.75 * brickLength;
}
else {
pos.z += brickLength;
}
}
pos.y += brickHeight;
}
// 绳
var ropeNumSegments = 10;
var ropeLength = 4;
var ropeMass = 3;
var ropePos = ball.position.clone();
ropePos.y += ballRadius;
var segmentLength = ropeLength / ropeNumSegments;
var ropeGeometry = new THREE.BufferGeometry();
var ropeMaterial = new THREE.LineBasicMaterial( { color: 0x000000 } );
var ropePositions = [];
var ropeIndices = [];
for ( var i = 0; i < ropeNumSegments + 1; i++ ) {
ropePositions.push( ropePos.x, ropePos.y + i * segmentLength, ropePos.z );
}
for ( var i = 0; i < ropeNumSegments; i++ ) {
ropeIndices.push( i, i + 1 );
}
ropeGeometry.setIndex( new THREE.BufferAttribute( new Uint16Array( ropeIndices ), 1 ) );
ropeGeometry.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( ropePositions ), 3 ) );
ropeGeometry.computeBoundingSphere();
rope = new THREE.LineSegments( ropeGeometry, ropeMaterial );
rope.castShadow = true;
rope.receiveShadow = true;
scene.add( rope );
// 绳
var softBodyHelpers = new Ammo.btSoftBodyHelpers();
var ropeStart = new Ammo.btVector3( ropePos.x, ropePos.y, ropePos.z );
var ropeEnd = new Ammo.btVector3( ropePos.x, ropePos.y + ropeLength, ropePos.z );
var ropeSoftBody = softBodyHelpers.CreateRope( physicsWorld.getWorldInfo(), ropeStart, ropeEnd, ropeNumSegments - 1, 0 );
var sbConfig = ropeSoftBody.get_m_cfg();
sbConfig.set_viterations( 10 );
sbConfig.set_piterations( 10 );
ropeSoftBody.setTotalMass( ropeMass, false )
Ammo.castObject( ropeSoftBody, Ammo.btCollisionObject ).getCollisionShape().setMargin( margin * 3 );
//1是碰撞组,同一组的不碰撞,-1是其与其他组都碰撞
physicsWorld.addSoftBody( ropeSoftBody, 1, -1 );
rope.userData.physicsBody = ropeSoftBody;
ropeSoftBody.setActivationState( 4 );
// 基座和旋转臂
var armMass = 2;
var armLength = 3;
var pylonHeight = ropePos.y + ropeLength;
var baseMaterial = new THREE.MeshPhongMaterial( { color: 0x606060 } );
pos.set( ropePos.x, 0.1, ropePos.z - armLength );
quat.set( 0, 0, 0, 1 );
var base = createParalellepiped( 1, 0.2, 1, 0, pos, quat, baseMaterial );
base.castShadow = true;
base.receiveShadow = true;
pos.set( ropePos.x, 0.5 * pylonHeight, ropePos.z - armLength );
var pylon = createParalellepiped( 0.4, pylonHeight, 0.4, 0, pos, quat, baseMaterial );
pylon.castShadow = true;
pylon.receiveShadow = true;
pos.set( ropePos.x, pylonHeight + 0.2, ropePos.z - 0.5 * armLength );
var arm = createParalellepiped( 0.4, 0.4, armLength + 0.4, armMass, pos, quat, baseMaterial );
arm.castShadow = true;
arm.receiveShadow = true;
// 绳子两端绑定
var influence = 1;
ropeSoftBody.appendAnchor( 0, ball.userData.physicsBody, true, influence );
ropeSoftBody.appendAnchor( ropeNumSegments, arm.userData.physicsBody, true, influence );
//创建铰链约束可以旋转
var pivotA = new Ammo.btVector3( 0, pylonHeight * 0.5, 0 );
var pivotB = new Ammo.btVector3( 0, -0.2, - armLength * 0.5 );
var axis = new Ammo.btVector3( 0, 1, 0 );
hinge = new Ammo.btHingeConstraint( pylon.userData.physicsBody, arm.userData.physicsBody, pivotA, pivotB, axis, axis, true );
physicsWorld.addConstraint( hinge, true );
}
//创建长方体 入参为长宽高,质量,位置,旋转,材质
function createParalellepiped( sx, sy, sz, mass, pos, quat, material ) {
var threeObject = new THREE.Mesh( new THREE.BoxGeometry( sx, sy, sz, 1, 1, 1 ), material );
var shape = new Ammo.btBoxShape( new Ammo.btVector3( sx * 0.5, sy * 0.5, sz * 0.5 ) );
//设置碰撞检测误差范围,在距离多少时算碰到
shape.setMargin( margin );
createRigidBody( threeObject, shape, mass, pos, quat );
return threeObject;
}
function createRigidBody( threeObject, physicsShape, mass, pos, quat ) {
threeObject.position.copy( pos );
threeObject.quaternion.copy( quat );
//创建运动对象
var 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 ) );
var motionState = new Ammo.btDefaultMotionState( transform );
//通过质量计算局部惯性
var localInertia = new Ammo.btVector3(0, 0, 0 );
physicsShape.calculateLocalInertia( mass, localInertia );
//创建刚体
var rbInfo = new Ammo.btRigidBodyConstructionInfo( mass, motionState, physicsShape, localInertia );
var body = new Ammo.btRigidBody( rbInfo );
threeObject.userData.physicsBody = body;
scene.add( threeObject );
if ( mass > 0 ) {
rigidBodies.push( threeObject );
//有质量就响应外部的力或碰撞
body.setActivationState( 4 );
}
physicsWorld.addRigidBody( body );
}
function createRandomColor() {
return Math.floor( Math.random() * ( 1 << 24 ) );
}
function createMaterial() {
return new THREE.MeshPhongMaterial( { color: createRandomColor() } );
}
6.绑定外部时间,initInput
绑定键盘的qa键设置机械臂的角速度方向
function initInput() {
window.addEventListener( 'keydown', function( event ) {
switch ( event.keyCode ) {
// Q
case 81:
armMovement = 1;
break;
// A
case 65:
armMovement = - 1;
break;
}
}, false );
window.addEventListener( 'keyup', function( event ) {
armMovement = 0;
}, false );
}
7.渲染结果,render
按帧渲染
function render() {
//获取2次更新的时间差
var deltaTime = clock.getDelta();
//更新物理世界和场景
updatePhysics( deltaTime );
//更新相机
controls.update( deltaTime );
//渲染到画布
renderer.render( scene, camera );
time += deltaTime;
}
function updatePhysics( deltaTime ) {
//根据输入设置铰链的角加速度和最大速度 控制器会自动更新,无需自己更新物理世界的位置
hinge.enableAngularMotor( true, 1.50 * armMovement, 50 );
//仿真步进,步进时间和最多分割为多少个子步进
physicsWorld.stepSimulation( deltaTime, 10 );
//更新绳子的渲染位置
var softBody = rope.userData.physicsBody;
var ropePositions = rope.geometry.attributes.position.array;
//存了xyz,所以要/3
var numVerts = ropePositions.length / 3;
var nodes = softBody.get_m_nodes();
var indexFloat = 0;
for ( var i = 0; i < numVerts; i ++ ) {
var node = nodes.at( i );
var nodePos = node.get_m_x();
ropePositions[ indexFloat++ ] = nodePos.x();
ropePositions[ indexFloat++ ] = nodePos.y();
ropePositions[ indexFloat++ ] = nodePos.z();
}
rope.geometry.attributes.position.needsUpdate = true;
//更新刚体,从物理世界获取最新位置设置到3D对象的属性里
for ( var i = 0, il = rigidBodies.length; i < il; i++ ) {
var objThree = rigidBodies[ i ];
var objPhys = objThree.userData.physicsBody;
var ms = objPhys.getMotionState();
if ( ms ) {
ms.getWorldTransform( transformAux1 );
var p = transformAux1.getOrigin();
var q = transformAux1.getRotation();
objThree.position.set( p.x(), p.y(), p.z() );
objThree.quaternion.set( q.x(), q.y(), q.z(), q.w() );
}
}
}
8.完整代码
官网示例demo,增加了部分注释,threejs和ammo的库代码自行引入。
<html lang="en">
<head>
<title>Ammo.js softbody rope demo</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<style>
body {
color: #61443e;
font-family:Monospace;
font-size:13px;
text-align:center;
background-color: #bfd1e5;
margin: 0px;
overflow: hidden;
}
#info {
position: absolute;
top: 0px; width: 100%;
padding: 5px;
}
a {
color: #a06851;
}
</style>
</head>
<body>
<div id="info">Ammo.js soft body rope demo<br>Press Q or A to move the arm.</div>
<div id="container"><br /><br /><br /><br /><br />Loading...</div>
<script src="builds/ammo.js"></script>
<script src="js/three/three.min.js"></script>
<script src="js/three/OrbitControls.js"></script>
<script src="js/three/Detector.js"></script>
<script src="js/three/stats.min.js"></script>
<script>
Ammo().then(function(Ammo) {
// Detects webgl
if ( ! Detector.webgl ) {
Detector.addGetWebGLMessage();
document.getElementById( 'container' ).innerHTML = "";
}
// - Global variables -
// Graphics variables
var container, stats;
var camera, controls, scene, renderer;
var textureLoader;
var clock = new THREE.Clock();
// Physics variables
var gravityConstant = -9.8;
var collisionConfiguration;
var dispatcher;
var broadphase;
var solver;
var physicsWorld;
var rigidBodies = [];
var margin = 0.05;
var hinge;
var rope;
var transformAux1 = new Ammo.btTransform();
var time = 0;
var armMovement = 0;
// - Main code -
init();
animate();
// - Functions -
function init() {
initGraphics();
initPhysics();
//
createObjects();
//
initInput();
}
function initGraphics() {
//创建相机
camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.2, 2000 );
camera.position.x = -7;
camera.position.y = 5;
camera.position.z = 8;
//设置用鼠标移动相机
controls = new THREE.OrbitControls( camera );
//移动时旋转中心点的坐标
controls.target.y = 2;
//设置渲染器为webgl
renderer = new THREE.WebGLRenderer();
renderer.setClearColor( 0xbfd1e5 );
//设置像素密度和设备相同
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
//启用阴影映射
renderer.shadowMap.enabled = true;
//纹理加载器
textureLoader = new THREE.TextureLoader();
//创建场景
scene = new THREE.Scene();
//环境光
var ambientLight = new THREE.AmbientLight( 0x404040 );
scene.add( ambientLight );
//定向光源
var light = new THREE.DirectionalLight( 0xffffff, 1 );
light.position.set( -10, 10, 5 );
//光源阴影
light.castShadow = true;
var d = 10;
//光照范围,近裁剪面,远裁剪面
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 );
//在画布上绑定渲染结果
container = document.getElementById( 'container' );
container.innerHTML = "";
container.appendChild( renderer.domElement );
//左上角监事面板
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0px';
container.appendChild( stats.domElement );
//自适应大小
window.addEventListener( 'resize', onWindowResize, false );
}
function initPhysics() {
//创建软体和刚体碰撞的配置
collisionConfiguration = new Ammo.btSoftBodyRigidBodyCollisionConfiguration();
//创建一个碰撞分发器,它将根据碰撞配置处理碰撞事件
dispatcher = new Ammo.btCollisionDispatcher( collisionConfiguration );
//创建一个广相位(broadphase)算法,用于检测潜在的碰撞对
broadphase = new Ammo.btDbvtBroadphase();
//创建一个约束求解器,用于解决物体之间的约束和碰撞响应
solver = new Ammo.btSequentialImpulseConstraintSolver();
//创建一个软体(soft body)求解器,用于模拟柔软的物体
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 ) );
}
function createObjects() {
//位置信息
var pos = new THREE.Vector3();
//旋转信息
var quat = new THREE.Quaternion();
// 地板
pos.set( 0, - 0.5, 0 );
quat.set( 0, 0, 0, 1 );
//质量为0,所以不会掉下去
var ground = createParalellepiped( 40, 1, 40, 0, pos, quat, new THREE.MeshPhongMaterial( { color: 0xFFFFFF } ) );
//产生阴影
ground.castShadow = true;
//显示其他物理产生的阴影
ground.receiveShadow = true;
textureLoader.load( "../textures/grid.png", function( texture ) {
//设置为重复平铺
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
//每行每列重复40次
texture.repeat.set( 40, 40 );
//设置材质纹理
ground.material.map = texture;
//修改后通知刷新材质
ground.material.needsUpdate = true;
} );
// 球
var ballMass = 0.2;
var ballRadius = 0.6;
var ball = new THREE.Mesh( new THREE.SphereGeometry( ballRadius, 20, 20 ), new THREE.MeshPhongMaterial( { color: 0x202020 } ) );
ball.castShadow = true;
ball.receiveShadow = true;
var ballShape = new Ammo.btSphereShape( ballRadius );
ballShape.setMargin( margin );
pos.set( -3, 2, 0 );
quat.set( 0, 0, 0, 1 );
createRigidBody( ball, ballShape, ballMass, pos, quat );
//设置摩擦系数
ball.userData.physicsBody.setFriction( 0.5 );
// 墙
var brickMass = 0.5;
var brickLength = 1.2;
var brickDepth = 0.6;
var brickHeight = brickLength * 0.5;
var numBricksLength = 6;
var numBricksHeight = 8;
var z0 = - numBricksLength * brickLength * 0.5;
pos.set( 0, brickHeight * 0.5, z0 );
quat.set( 0, 0, 0, 1 );
//创建长方体组成的墙壁
for ( var j = 0; j < numBricksHeight; j ++ ) {
var oddRow = ( j % 2 ) == 1;
pos.z = z0;
if ( oddRow ) {
pos.z -= 0.25 * brickLength;
}
var nRow = oddRow? numBricksLength + 1 : numBricksLength;
for ( var i = 0; i < nRow; i ++ ) {
var brickLengthCurrent = brickLength;
var brickMassCurrent = brickMass;
if ( oddRow && ( i == 0 || i == nRow - 1 ) ) {
brickLengthCurrent *= 0.5;
brickMassCurrent *= 0.5;
}
var brick = createParalellepiped( brickDepth, brickHeight, brickLengthCurrent, brickMassCurrent, pos, quat, createMaterial() );
brick.castShadow = true;
brick.receiveShadow = true;
if ( oddRow && ( i == 0 || i == nRow - 2 ) ) {
pos.z += 0.75 * brickLength;
}
else {
pos.z += brickLength;
}
}
pos.y += brickHeight;
}
// 绳
var ropeNumSegments = 10;
var ropeLength = 4;
var ropeMass = 3;
var ropePos = ball.position.clone();
ropePos.y += ballRadius;
var segmentLength = ropeLength / ropeNumSegments;
var ropeGeometry = new THREE.BufferGeometry();
var ropeMaterial = new THREE.LineBasicMaterial( { color: 0x000000 } );
var ropePositions = [];
var ropeIndices = [];
for ( var i = 0; i < ropeNumSegments + 1; i++ ) {
ropePositions.push( ropePos.x, ropePos.y + i * segmentLength, ropePos.z );
}
for ( var i = 0; i < ropeNumSegments; i++ ) {
ropeIndices.push( i, i + 1 );
}
ropeGeometry.setIndex( new THREE.BufferAttribute( new Uint16Array( ropeIndices ), 1 ) );
ropeGeometry.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( ropePositions ), 3 ) );
ropeGeometry.computeBoundingSphere();
rope = new THREE.LineSegments( ropeGeometry, ropeMaterial );
rope.castShadow = true;
rope.receiveShadow = true;
scene.add( rope );
// 绳
var softBodyHelpers = new Ammo.btSoftBodyHelpers();
var ropeStart = new Ammo.btVector3( ropePos.x, ropePos.y, ropePos.z );
var ropeEnd = new Ammo.btVector3( ropePos.x, ropePos.y + ropeLength, ropePos.z );
var ropeSoftBody = softBodyHelpers.CreateRope( physicsWorld.getWorldInfo(), ropeStart, ropeEnd, ropeNumSegments - 1, 0 );
var sbConfig = ropeSoftBody.get_m_cfg();
sbConfig.set_viterations( 10 );
sbConfig.set_piterations( 10 );
ropeSoftBody.setTotalMass( ropeMass, false )
Ammo.castObject( ropeSoftBody, Ammo.btCollisionObject ).getCollisionShape().setMargin( margin * 3 );
//1是碰撞组,同一组的不碰撞,-1是其与其他组都碰撞
physicsWorld.addSoftBody( ropeSoftBody, 1, -1 );
rope.userData.physicsBody = ropeSoftBody;
ropeSoftBody.setActivationState( 4 );
// 基座和旋转臂
var armMass = 2;
var armLength = 3;
var pylonHeight = ropePos.y + ropeLength;
var baseMaterial = new THREE.MeshPhongMaterial( { color: 0x606060 } );
pos.set( ropePos.x, 0.1, ropePos.z - armLength );
quat.set( 0, 0, 0, 1 );
var base = createParalellepiped( 1, 0.2, 1, 0, pos, quat, baseMaterial );
base.castShadow = true;
base.receiveShadow = true;
pos.set( ropePos.x, 0.5 * pylonHeight, ropePos.z - armLength );
var pylon = createParalellepiped( 0.4, pylonHeight, 0.4, 0, pos, quat, baseMaterial );
pylon.castShadow = true;
pylon.receiveShadow = true;
pos.set( ropePos.x, pylonHeight + 0.2, ropePos.z - 0.5 * armLength );
var arm = createParalellepiped( 0.4, 0.4, armLength + 0.4, armMass, pos, quat, baseMaterial );
arm.castShadow = true;
arm.receiveShadow = true;
// 绳子两端绑定
var influence = 1;
ropeSoftBody.appendAnchor( 0, ball.userData.physicsBody, true, influence );
ropeSoftBody.appendAnchor( ropeNumSegments, arm.userData.physicsBody, true, influence );
//创建铰链约束可以旋转
var pivotA = new Ammo.btVector3( 0, pylonHeight * 0.5, 0 );
var pivotB = new Ammo.btVector3( 0, -0.2, - armLength * 0.5 );
var axis = new Ammo.btVector3( 0, 1, 0 );
hinge = new Ammo.btHingeConstraint( pylon.userData.physicsBody, arm.userData.physicsBody, pivotA, pivotB, axis, axis, true );
physicsWorld.addConstraint( hinge, true );
}
//创建长方体 入参为长宽高,质量,位置,旋转,材质
function createParalellepiped( sx, sy, sz, mass, pos, quat, material ) {
var threeObject = new THREE.Mesh( new THREE.BoxGeometry( sx, sy, sz, 1, 1, 1 ), material );
var shape = new Ammo.btBoxShape( new Ammo.btVector3( sx * 0.5, sy * 0.5, sz * 0.5 ) );
//设置碰撞检测误差范围,在距离多少时算碰到
shape.setMargin( margin );
createRigidBody( threeObject, shape, mass, pos, quat );
return threeObject;
}
function createRigidBody( threeObject, physicsShape, mass, pos, quat ) {
threeObject.position.copy( pos );
threeObject.quaternion.copy( quat );
//创建运动对象
var 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 ) );
var motionState = new Ammo.btDefaultMotionState( transform );
//通过质量计算局部惯性
var localInertia = new Ammo.btVector3(0, 0, 0 );
physicsShape.calculateLocalInertia( mass, localInertia );
//创建刚体
var rbInfo = new Ammo.btRigidBodyConstructionInfo( mass, motionState, physicsShape, localInertia );
var body = new Ammo.btRigidBody( rbInfo );
threeObject.userData.physicsBody = body;
scene.add( threeObject );
if ( mass > 0 ) {
rigidBodies.push( threeObject );
//有质量就响应外部的力或碰撞
body.setActivationState( 4 );
}
physicsWorld.addRigidBody( body );
}
function createRandomColor() {
return Math.floor( Math.random() * ( 1 << 24 ) );
}
function createMaterial() {
return new THREE.MeshPhongMaterial( { color: createRandomColor() } );
}
function initInput() {
window.addEventListener( 'keydown', function( event ) {
switch ( event.keyCode ) {
// Q
case 81:
armMovement = 1;
break;
// A
case 65:
armMovement = - 1;
break;
}
}, false );
window.addEventListener( 'keyup', function( event ) {
armMovement = 0;
}, 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() {
//获取2次更新的时间差
var deltaTime = clock.getDelta();
//更新物理世界和场景
updatePhysics( deltaTime );
//更新相机
controls.update( deltaTime );
//渲染到画布
renderer.render( scene, camera );
time += deltaTime;
}
function updatePhysics( deltaTime ) {
//根据输入设置铰链的角加速度和最大速度 控制器会自动更新,无需自己更新物理世界的位置
hinge.enableAngularMotor( true, 1.50 * armMovement, 50 );
//仿真步进,步进时间和最多分割为多少个子步进
physicsWorld.stepSimulation( deltaTime, 10 );
//更新绳子的渲染位置
var softBody = rope.userData.physicsBody;
var ropePositions = rope.geometry.attributes.position.array;
//存了xyz,所以要/3
var numVerts = ropePositions.length / 3;
var nodes = softBody.get_m_nodes();
var indexFloat = 0;
for ( var i = 0; i < numVerts; i ++ ) {
var node = nodes.at( i );
var nodePos = node.get_m_x();
ropePositions[ indexFloat++ ] = nodePos.x();
ropePositions[ indexFloat++ ] = nodePos.y();
ropePositions[ indexFloat++ ] = nodePos.z();
}
rope.geometry.attributes.position.needsUpdate = true;
//更新刚体,从物理世界获取最新位置设置到3D对象的属性里
for ( var i = 0, il = rigidBodies.length; i < il; i++ ) {
var objThree = rigidBodies[ i ];
var objPhys = objThree.userData.physicsBody;
var ms = objPhys.getMotionState();
if ( ms ) {
ms.getWorldTransform( transformAux1 );
var p = transformAux1.getOrigin();
var q = transformAux1.getRotation();
objThree.position.set( p.x(), p.y(), p.z() );
objThree.quaternion.set( q.x(), q.y(), q.z(), q.w() );
}
}
}
});
</script>
</body>
</html>