物理引擎anno.js使用场景及api介绍

前言

最近在写前端的一些项目时,涉及到物理引擎,比如将一个物体抛向空中,然后自由落到地面上,然后就了解到了anno.js,本文对anno.js进一个简单地说明,介绍其适用场景,在摸索的过程中没有找到比较完整的官方文档,但是最后还是一点点的给把常用类和方法进行啃了一下。

anno概述

anno是一个轻量级的JavaScript物理引擎,并且他也是开源的,其内部是通过Verlet积分来模拟物理系统的运动和碰撞,通过计算所有物体在每一帧所处的位置、速度以及加速度,然后根据轴对齐碰撞箱(AABB)的碰撞检测算法来模拟真实的物理世界效果,他比较适用下面这几种场景:

  1. 模拟使用悬挂的铁球撞击物体
  1. 模拟柔软的布料
  1. 模拟物体之间碰撞的弹跳效果
  1. 制作一个地形,进行碰撞检测
  1. 制作一个赛车游戏

anno github地址:github.com/kripken/amm...

如何使用anno

anno的使用主要分为三个大的步骤,首先是创建物理世界,包括创建配置、分发器、创建物理世界、配置重力参数等,第二部分则是创建刚体,包括创建刚体的形状、质量、运动状态,惯性等参数,并且将创建的刚体添加到物理世界当中,最后一步就是渲染部分,通过计算每一个物体的运动情况,和物体之间的相互影响,来渲染每一个物体在每一帧的状态。

细分步骤:

  1. 初始化物理世界,包括定义碰撞配置、碰撞调度器对象、解算器、阶段算法
  2. 设置物理世界的重力参数
  3. 创建刚体,包括设置和质量和位置等参数,配置刚体的运动方向
  4. 将刚体添加到物理世界
  5. 在渲染过程中更新物理世界
  6. 根据刚体的运动状态渲染页面效果

重要属性列表

参数名称 说明
btDefaultCollisionConfiguration 碰撞检测的默认配置
btCollisionDispatcher 碰撞调度器对象
btDbvtBroadphase 使用动态AABB树(DBVT)的碰撞检测,目的是剔除没有相互作用的对象
btSequentialImpulseConstraintSolver 创建解算器,处理约束和碰撞响应
btDiscreteDynamicsWorld 创建了一个动态的物理世界
setGravity 设置物理世界的重力加速度向量
btGImpactCollisionAlgorithm 碰撞检测算法
registerAlgorithm 注册自定义碰撞检测算法
btVector3 表示三维向量
btQuaternion 四元数的类,表示三维空间中的旋转
btDefaultMotionState 刚体的运动状态,用于同步刚体在物理引擎和图形渲染之间的位置/旋转信息
btRigidBody 表示为一个刚体,包括质量、旋转四元数、角速度、线速度等参数
btTransform 表示刚体的信息修改,包括位置和旋转等
btVehicleTuning 用于设置车辆的调节参数,可以设置悬挂刚度等属性
btDefaultVehicleRaycaster 默认的车辆射线检测器,可以用于车辆的轮胎碰撞检测
btRaycastVehicle 车辆类,可以创建一个车辆
setCoordinateSystem 定义车轮的坐标系统和运动方向, 参数含义: rightIndex: 表示车轮索引,左右轮应设置不同索引 wheelDirectionCS:表示车轮运动方向的坐标系统 wheelAxleCS:表示车轮轴的坐标系统
applyEngineForce 对车辆施加引擎驱动力
setBrake 对车轮施加制动力
setSteeringValue 设置方向盘转角,控制方向
getChassisWorldTransform 获取车辆底盘在世界坐标系中的位置和旋转
stepSimulation 更新物理世界

示例讲解

这里我对官网的一个小车的代码示例进行讲解,主要是来介绍在threeJs中如何引入anno和实际使用的流程及细节说明。

1.初始化场景

创建相机灯光等基础组件。

ini 复制代码
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.2, 2000 );
camera.position.x = -4.84;
camera.position.y = 4.39;
camera.position.z = -35.11;
camera.lookAt( new THREE.Vector3( 0.33, -0.40, 0.85 ) );
controls = new THREE.OrbitControls( camera );
​
renderer = new THREE.WebGLRenderer({antialias:true});
renderer.setClearColor( 0xbfd1e5 );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
​
var ambientLight = new THREE.AmbientLight( 0x404040 );
scene.add( ambientLight );
.....省略
2.初始化物理世界

这一部分是用于初始化物理世界

ini 复制代码
//创建碰撞检测的默认配置
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, -9.82, 0 ) );
3.创建立方体

创建立方体,下面这是创建立方体的公共方法,根据传入的参数不同,可以创建墙体、地面以及斜坡。

ini 复制代码
//根据质量不同,创建不同颜色的材质
var material = mass > 0 ? materialDynamic : materialStatic;
var shape = new THREE.BoxGeometry(w, l, h, 1, 1, 1);
//创建立方体形状的物体
var geometry = new Ammo.btBoxShape(new Ammo.btVector3(w * 0.5, l * 0.5, h * 0.5));
​
//如果质量、摩擦力为空,则初始化
if(!mass) mass = 0;
if(!friction) friction = 1;
​
//添加到场景中
var mesh = new THREE.Mesh(shape, material);
mesh.position.copy(pos);
mesh.quaternion.copy(quat);
scene.add( mesh );
​
//初始化刚体的位置和旋转
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);
geometry.calculateLocalInertia(mass, localInertia);
​
//初始化一个刚体
var rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, geometry, localInertia);
var body = new Ammo.btRigidBody(rbInfo);
​
//设置刚体的摩擦力
body.setFriction(friction);
//body.setRestitution(.9);
//body.setDamping(0.2, 0.2);
​
//添加刚体到物理世界
physicsWorld.addRigidBody( body );
​
//如果质量大于零,放入集合中,用于渲染物体后续的变化情况
if (mass > 0) {
    body.setActivationState(DISABLE_DEACTIVATION);
    // Sync physics and graphics
    function sync(dt) {
        var ms = body.getMotionState();
        if (ms) {
            ms.getWorldTransform(TRANSFORM_AUX);
            var p = TRANSFORM_AUX.getOrigin();
            var q = TRANSFORM_AUX.getRotation();
            mesh.position.set(p.x(), p.y(), p.z());
            mesh.quaternion.set(q.x(), q.y(), q.z(), q.w());
        }
    }
    syncList.push(sync);
}
4.创建一个小车

anno还提供了创建和操作车辆的属性,这里只对部分关键代码进行说明,因为这块代码还是有点长的,主要就是包括创建底盘和轮子。

ini 复制代码
// 创建车辆底盘形状,并且初始化位置和旋转及计算惯性等属性
var geometry = new Ammo.btBoxShape(new Ammo.btVector3(chassisWidth * .5, chassisHeight * .5, chassisLength * .5));
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);
geometry.calculateLocalInertia(massVehicle, localInertia);
//创建底盘,添加到物理世界当中
var body = new Ammo.btRigidBody(new Ammo.btRigidBodyConstructionInfo(massVehicle, motionState, geometry, localInertia));
body.setActivationState(DISABLE_DEACTIVATION);
physicsWorld.addRigidBody(body);
var chassisMesh = createChassisMesh(chassisWidth, chassisHeight, chassisLength);
​
// 车辆配置信息
var engineForce = 0;
var vehicleSteering = 0;
var breakingForce = 0;
//调节参数设置
var tuning = new Ammo.btVehicleTuning();
//定义车辆的轮胎碰撞检测
var rayCaster = new Ammo.btDefaultVehicleRaycaster(physicsWorld);
//创建车辆
var vehicle = new Ammo.btRaycastVehicle(tuning, body, rayCaster);
//设置车轮的坐标系统
vehicle.setCoordinateSystem(0, 1, 2);
physicsWorld.addAction(vehicle);
​
//新增轮子
function addWheel(isFront, pos, radius, width, index) {
    //初始化轮子,参数说明:pos位置,wheelDirectionCS0 车轮方向坐标系,wheelAxleCS 车轮轴坐标系
    //suspensionRestLength 悬挂静止长度 radius
    var wheelInfo = vehicle.addWheel(
        pos,
        wheelDirectionCS0,
        wheelAxleCS,
        suspensionRestLength,
        radius,
        tuning,
        isFront);
    //设置悬挂刚度
    wheelInfo.set_m_suspensionStiffness(suspensionStiffness);
    //设置减震伸长阻尼
    wheelInfo.set_m_wheelsDampingRelaxation(suspensionDamping);
    //设置减震压缩阻尼
    wheelInfo.set_m_wheelsDampingCompression(suspensionCompression);
    //设置摩擦力
    wheelInfo.set_m_frictionSlip(friction);
    //设置轮胎侧倾角影响因子
    wheelInfo.set_m_rollInfluence(rollInfluence);
​
    wheelMeshes[index] = createWheelMesh(radius, width);
}
//新增四个轮子
addWheel(true, new Ammo.btVector3(wheelHalfTrackFront, wheelAxisHeightFront, wheelAxisFrontPosition), wheelRadiusFront, wheelWidthFront, FRONT_LEFT);
addWheel(true, new Ammo.btVector3(-wheelHalfTrackFront, wheelAxisHeightFront, wheelAxisFrontPosition), wheelRadiusFront, wheelWidthFront, FRONT_RIGHT);
addWheel(false, new Ammo.btVector3(-wheelHalfTrackBack, wheelAxisHeightBack, wheelAxisPositionBack), wheelRadiusBack, wheelWidthBack, BACK_LEFT);
addWheel(false, new Ammo.btVector3(wheelHalfTrackBack, wheelAxisHeightBack, wheelAxisPositionBack), wheelRadiusBack, wheelWidthBack, BACK_RIGHT);
​
// 设置键盘控制
function sync(dt) {
​
    var speed = vehicle.getCurrentSpeedKmHour();
​
    speedometer.innerHTML = (speed < 0 ? '(R) ' : '') + Math.abs(speed).toFixed(1) + ' km/h';
​
    breakingForce = 0;
    engineForce = 0;
​
    if (actions.acceleration) {
        if (speed < -1)
            breakingForce = maxBreakingForce;
        else engineForce = maxEngineForce;
    }
    if (actions.braking) {
        if (speed > 1)
            breakingForce = maxBreakingForce;
        else engineForce = -maxEngineForce / 2;
    }
    if (actions.left) {
        if (vehicleSteering < steeringClamp)
            vehicleSteering += steeringIncrement;
    }
    else {
        if (actions.right) {
            if (vehicleSteering > -steeringClamp)
                vehicleSteering -= steeringIncrement;
        }
        else {
            if (vehicleSteering < -steeringIncrement)
                vehicleSteering += steeringIncrement;
            else {
                if (vehicleSteering > steeringIncrement)
                    vehicleSteering -= steeringIncrement;
                else {
                    vehicleSteering = 0;
                }
            }
        }
    }
​
    //对车辆施加驱动力
    vehicle.applyEngineForce(engineForce, BACK_LEFT);
    vehicle.applyEngineForce(engineForce, BACK_RIGHT);
    //对车轮施加制动力
    vehicle.setBrake(breakingForce / 2, FRONT_LEFT);
    vehicle.setBrake(breakingForce / 2, FRONT_RIGHT);
    vehicle.setBrake(breakingForce, BACK_LEFT);
    vehicle.setBrake(breakingForce, BACK_RIGHT);
    //设置方向盘转角
    vehicle.setSteeringValue(vehicleSteering, FRONT_LEFT);
    vehicle.setSteeringValue(vehicleSteering, FRONT_RIGHT);
​
    //更新轮胎位置和旋转
    var tm, p, q, i;
    var n = vehicle.getNumWheels();
    for (i = 0; i < n; i++) {
        vehicle.updateWheelTransform(i, true);
        tm = vehicle.getWheelTransformWS(i);
        p = tm.getOrigin();
        q = tm.getRotation();
        wheelMeshes[i].position.set(p.x(), p.y(), p.z());
        wheelMeshes[i].quaternion.set(q.x(), q.y(), q.z(), q.w());
    }
    //车辆底盘在世界坐标系中的位置和旋转
    tm = vehicle.getChassisWorldTransform();
    p = tm.getOrigin();
    q = tm.getRotation();
    chassisMesh.position.set(p.x(), p.y(), p.z());
    chassisMesh.quaternion.set(q.x(), q.y(), q.z(), q.w());
}
5.实时渲染

threeJs可以通过requestAnimationFrame不停的调用tick方法,进行渲染,但是渲染的时机是不确定的,调用函数执行的时间在16.7ms~33.3ms之间,一般情况渲染时间间隔是不需要我们来关注的。

ini 复制代码
function tick() {
    requestAnimationFrame( tick );
    var dt = clock.getDelta();
    //执行syncList中存储的方法,更新各个物体
    for (var i = 0; i < syncList.length; i++)
        syncList[i](dt);
    //更新物理世界
    physicsWorld.stepSimulation( dt, 10 );
    controls.update( dt );
    renderer.render( scene, camera );
    time += dt;
    stats.update();
}

总结

本章对anno.js中一些重要的方法进行简单的介绍,对使用流程也做了整体的说明,除此之外,官方还提供了一些其他的示例,大家可以按照自己的需求来进行参考,我觉得这个物理引擎的话,可能难点是在于存在很多的api需要了解其含义是使用方法,其他得难点倒是没有很多。

相关推荐
正小安1 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch3 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光3 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   3 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   3 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web3 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常3 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇4 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr4 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
Tiffany_Ho5 小时前
【TypeScript】知识点梳理(三)
前端·typescript