• 用Physijs在场景中添加物理效果


    1.创建可用Physijs的基本Three.js场景

        创建一个可用Physijs的Three.js场景非常简单,只要几个步骤即可。首先我们要包含正确的文件, 需要引入physi.js文件。实际模拟物理场景时非常耗费CPU的,如果我么能在render线程中做的话,场景的帧频会受到严重的影响。为了弥补这一点,Physijs选择在后台线程中执行计算。这里的后台是有Web workers(网页线程)规范定义的额,现在大多数浏览器都实现了该功能。

        对Physijs来说也就意味着我们需要配置一个带有执行任务的JavaScipt文件,并告诉Physijs在哪里可以找到用来模拟场景的ammo.js文件。所以需要添加以下代码:

    Physijs.scripts.worker = "../libs/physijs_worker.js";
            Physijs.scripts.ammo = "../libs/ammo.js";

        Physijs在Three.js的普通场景外又提供了一个包装器,所以我们代码可以想这样创建场景:

    scene = new Physijs.Scene();
                scene.setGravity(new THREE.Vector3(0, -50, 0));

        在模拟物理效果之前,我们需要在场景中添加一些对象。为此,我们可以使用Three.js的普通方法来定义对象,但必须用一个特定的Physijs对象将这些对象包裹起来:

    var stoneGeom = new THREE.BoxGeometry(0.6, 6, 2);
                            var stone = new Physijs.BoxMesh(stoneGeom, Physijs.createMaterial(new THREE.MeshPhongMaterial({
                                color: scale(Math.random()).hex(),
                                transparent: true,
                                opacity: 0.8
                            })));
                           ...
                            scene.add(stone);

        我们第一个Physijs场景中的各个部分都有了。剩下要做的就是告诉Physijs模拟物理效果,并更新场景中各对象的位置和角色。为此,我们可以调用创建的场景的simulate方法。修改基础render循环代码:

    render = function(){
                requestAnimationFrame(render);
                renderer.render(scene, camera);
                render_stats.update();
    
                scene.simulate(undefined, 1);
            }

        假设我们要实现下面图片中放倒多米若骨牌的效果。

    image

        下面是实现功能的一段核心代码,points是所有多米诺骨牌的点集合。遍历每个骨牌的顶点,创建一个类型为BoxMesh对象(多米诺骨牌)。这里需要注意的是通过stone.lookAt()函数设置了对象的旋转角度,在手动更新了Physijs包装的对象的角度(或位置)之后,我们必须告诉Physijs有什么东西改变了。对于角度,我么可以将__dirtRotation设置为true;对于位置,我们可以将__dirtyPosition设置为true。

    this.resetScene = function(){
                        scene.setGravity(new THREE.Vector3(controls.gravityX, controls.gravityY, controls.gravityZ));
                        stones.forEach(function(st){
                            scene.remove(st);
                        });
                        stones = [];
    
                        points.forEach(function(point){
                            var stoneGeom = new THREE.BoxGeometry(0.6, 6, 2);
                            var stone = new Physijs.BoxMesh(stoneGeom, Physijs.createMaterial(new THREE.MeshPhongMaterial({
                                color: scale(Math.random()).hex(),
                                transparent: true,
                                opacity: 0.8
                            })))
                            //console.log(stone.position);
                            stone.position.copy(point);
                            stone.lookAt(scene.position);
                            stone.__dirtyRotation = true;
                            stone.position.y = 3.5;
    
                            scene.add(stone);
                            stones.push(stone);
                        });
    
                        stones[0].rotation.x = 0.2;
                        stones[0].__dirtyRotation = true;
                    }

    2.材质属性

        Physijs中材质对象最重要的两个属性分别是restitution和firction。restitution设置材质弹性,值越大,弹性越强;值越小弹性越弱。而restitution设置摩擦系数,值越小,摩擦就越小,物体越容易移动;值越大,摩擦越大,物体越难移动。

        假如我们要实现下图的效果。地板一直都在左右旋转,球体也会跟着地板一起移动。这里我们主要看下球体的实现代码如何。

    image

        下面的代码是圆球的实现代码。首先生成了一个随机颜色colorSphere,每次我们批量创建五个球体。创建球体对象使用Physijs.SphereMesh类创建。这里主要看下如何创建材质。创建材质和我们普通的方法不同,必须使用Physijs.createMaterial函数创建。第三个参数friction用来设置摩擦系数,范围0到1。第四个参数restitution设置弹性,范围0到1。只要我们修改这两个参数,我们就能看到球体落到地板时以及移动时的效果区别。

    this.addSpheres = function () {
                        var colorSphere = scale(Math.random()).hex();
                        for(var i = 0; i < 5; i++){
                            box = new Physijs.SphereMesh(
                                    new THREE.SphereGeometry(2, 20),
                                    Physijs.createMaterial(
                                            new THREE.MeshPhongMaterial({
                                                color: colorSphere,
                                                opacity: 0.8,
                                                transparent: true
                                            }),
                                            controls.sphereFriction,
                                            controls.sphereRestitution
                                    )
                            );
                            box.position.set(
                                    Math.random() * 50 - 25,
                                    20 + Math.random() * 5,
                                    Math.random() * 50 - 25
                            );
                            meshes.push(box);
                            scene.add(box);
                        }
                    };

    3.基础图形

        Physijs提供了一些可以用来包装几何体的图形类。使用这些几何体唯一要做的就是讲THREE.Mesh的构造函数替换成这些网格对象的构造函数。下表是Physijs中所有网格对象的概览:

        Physijs.PlaneMesh/这个网格可以用来创建一个厚度为0的平面。这样的平面也可以用BoxMesh对象包装一个高度很低的THREE.CubeGeometry来表示

        Physijs.BoxMesh/如果是类似方块的几何体,你可以使用这个网格。例如,它的属性跟THREE.CubeGeometry的属性很相配

        Physijs.SphereMesh/对于球形可以使用这个网格。它跟THREE.SphereGeometry的属性很相配

        Physijs.CylinderMesh/通过设置THREE.Cylinder的属性你可以创建出各种柱状图形。Physijs为各种柱性提供了不同网格。Physijs.CylinderMesh可以用于一般的、上下一致的圆柱形

        Physijs.ConeMesh/如果顶部的半径为0,底部的半径值大于0,那么你可以用THREE.Cylinder创建一个圆锥体。如果你想在这样一个对象上应用物理效果,那么可以使用的、最相匹配的网格类就是ConeMesh

        Physijs.CapsuleMesh(胶囊网格)/跟THREE.Cylinder属性很相似,但其底部和底部是圆的

        Physijs.ConvexMesh(凸包网格)/Physijs.ConvexMesh是一种比较粗略的图形,可用于多数复杂退行。它可以创建一个模拟复杂图形的凸包

        Physijs.ConcaveMesh/ConvexMesh是一个比较粗略的图形,而ConcaveMesh则可以对负责图形进行比较细致的表现。需要注意的是使用ConcaveMesh对效率的影响比较大

        Physijs.HeightfieldMesh(高度场网格)/这是一种非常特别的网格。通过该网格你可以从一个THREE.PlaneGeometry对象创建出一个高度场。

    4.使用约束限制对象移动

        我们已经了解到各种图形如何对重力、摩擦和弹性做出反应。并影响碰撞。Physijs还提供了一些高级对象,让i可以限制对象的移动。在Physijs里,这些对象呗称作约束。下表是Physijs中可用约束概览:

        PointConstraint/通过这个约束,你可以将一个对象与另一个对象之间的位置固定下来。例如一个对象动了,另一个对象也会随着移动,它们之间的距离和方向保持不变

        HingeConstraint/通过活页约束,你可以限制一个对象只能像活页一样移动,例如门

        SliderConstraint/将对象的移动限制在一个轴上。例如移门

        ConeTwistConstraint/通过这个约束,你可以用一个对象限制另一个对象的旋转和移动。这个约束的功能类似于一个球削式关节。例如,胳膊在肩关节中的活动

        DOFConstraint/通过自由度约束,你可以限制对象在任意轴上的活动,你可以设置对象活动的额最小、最大角度。这是最灵活的约束方式

    5.用PointConstraint限制亮点间的移动

        实现代码如下,我们在这段代码里可以看到,我们使用特定的Physijs网格创建对象,然后将它们添加到场景中。我们使用Physijs.PointConstraint构造函数创建约束。

    function createPointToPoint() {
                var obj1 = new THREE.SphereGeometry(2);
                var obj2 = new THREE.SphereGeometry(2);
    
                var objectOne = new Physijs.SphereMesh(obj1, Physijs.createMaterial(
                        new THREE.MeshPhongMaterial({color: 0xff4444, transparent: true, opacity: 0.7}), 0, 0));
                objectOne.position.z = -18;
                objectOne.position.x = -10;
                objectOne.position.y = 2;
                objectOne.castShadow = true;
                scene.add(objectOne);
    
                var objectTwo = new Physijs.SphereMesh(obj2, Physijs.createMaterial(
                        new THREE.MeshPhongMaterial({color: 0xff4444, transparent: true, opacity: 0.7}), 0, 0));
                objectTwo.position.z = -5;
                objectTwo.position.x = -20;
                objectTwo.position.y = 2;
                objectTwo.castShadow = true;
                scene.add(objectTwo);
    
                // if no position two, its fixed to a position. Else fixed to objectTwo and both will move
                var constraint = new Physijs.PointConstraint(objectOne, objectTwo, objectTwo.position);
                scene.addConstraint(constraint);
            }

        构造函数有三个参数。前两个参数指定要连接的两个对象。第三个参数指定约束绑定的位置。一般来说,如果你指向将两个对象连在一起,那么你最好将这个位置设置在第二个对象的位置上。如果你不想将一个对象绑定到另一个对象,而绑定到场景中某个固定的点,那么你可以忽略第二个参数。这样第一个对象就会跟着你指定的位置保持固定距离。

    6.用HingeConstraint创建类似们的约束

        顾名思义,通过HingeConstraint你可以创建一个行为类似活页的对象。它可以绕固定的轴旋转,并可限制在一定角度内。假如我们现在要实现下图中框选部分的活页门效果,白色长条随着右边的小方块旋转。

    image

        实现代码如下,HingeConstraint构造函数包含四个参数,定义为new Physijs.HingeConstraint(mesh_a, mesh_b, position, axis)。mesh_a第一个对象是将要被约束的对象;mesh_b指定mesh_a受哪个对象约束。这里flipperLeft受flipperLetPivot小方块影响;position约束应用的点。在本例中这个点就是Mesh_a绕着旋转的点;axis活页绕着旋转的轴。在本例中我们将活页设置在水平方向(0, 1, 0)。最后我们还需要设置约束对象的属性,为此我们调用setLimits函数。该函数包含四个参数,分别是low(指定旋转的最下弧度)、high(指定旋转的最大弧度)、bias_factor(该属性指定处于错误位置时,约束进行纠正的速度)、relaxation_factor(改属性指定约束以什么样的比例改变速度)。如果该属性的值越高,哪儿对象在达到最小或最大角度时会被弹回来。

    function createLeftFlipper() {
                var flipperLeft = new Physijs.BoxMesh(
                        new THREE.BoxGeometry(12, 2, 2), Physijs.createMaterial(new THREE.MeshPhongMaterial(
                                {opacity: 0.6, transparent: true}
                        )), 0.3
                );
                flipperLeft.position.x = -6;
                flipperLeft.position.y = 2;
                flipperLeft.position.z = 0;
                flipperLeft.castShadow = true;
                scene.add(flipperLeft);
                var flipperLeftPivot = new Physijs.SphereMesh(
                        new THREE.BoxGeometry(1, 1, 1), ground_material, 0);
    
                flipperLeftPivot.position.y = 1;
                flipperLeftPivot.position.x = -15;
                flipperLeftPivot.position.z = 0;
                flipperLeftPivot.rotation.y = 1.4;
                flipperLeftPivot.castShadow = true;
    
                scene.add(flipperLeftPivot);
    
                // when looking at the axis, the axis of object two are used.
                // so as long as that one is the same as the scene, no problems
                // rotation and axis are relative to object2. If position == cube2.position it works as expected
                var constraint = new Physijs.HingeConstraint(flipperLeft, flipperLeftPivot, flipperLeftPivot.position, new THREE.Vector3(0, 1, 0));
                scene.addConstraint(constraint);
    
                constraint.setLimits(
                        -2.2, // minimum angle of motion, in radians, from the point object 1 starts (going back)
                        -0.6, // maximum angle of motion, in radians, from the point object 1 starts (going forward)
                        0.1, // applied as a factor to constraint error, how big the kantelpunt is moved when a constraint is hit
                        0 // controls bounce at limit (0.0 == no bounce)
                );
    
                return constraint;
            }

    7.用SliderConstraint将移动限制到一个轴

        通过SliderConstraint约束,你可以将某个对象的移动限制到某个轴上。用代码 创建这些约束非常简单:

    var constraint = new Physijs.SliderConstraint(sliderMesh, new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, 1, 0));
    
                scene.addConstraint(constraint);
                constraint.setLimits(-10, 10, 0, 0);
                constraint.setRestitution(0.1, 0.1);

        该约束对象接收三个参数(或者四个,如果想将一个对象约束到另外一个对象)。构造函数定义为new Physijs.SliderConstraint(mesh_a, mesh_b, position, axis)。这些参数和HingeConstraint的参数相似。我么还需要通过constraint.setLimits函数限定滑块能滑多远:constriant.setLimits(-10, 10, 0, 0)。参数依次为linear_lower指定对象的线性下限;linear_upper该属性指定对象的线性上限;anguar_lower该属性指定对象的角度下限;angular_higher该属性指定对象的角度上限。

    8.用ConeTwistConstraint创建类似球削的约束

        通过ConeTwistConstraint可以创建出一个移动受一系列角度限制的约束。我们可以指定一个对象绕着另一个对象转动时在x、y、z轴上的最小角度和最大角度。理解ConeTwistConstraint最好的方法就是看看创建约束的代码:

    function createConeTwist() {
                var baseMesh = new THREE.SphereGeometry(1);
                var armMesh = new THREE.BoxGeometry(2, 12, 3);
    
                var objectOne = new Physijs.BoxMesh(baseMesh, Physijs.createMaterial(
                        new THREE.MeshPhongMaterial({color: 0x4444ff, transparent: true, opacity: 0.7}), 0, 0), 0);
                objectOne.position.z = 0;
                objectOne.position.x = 20;
                objectOne.position.y = 15.5;
                objectOne.castShadow = true;
                scene.add(objectOne);
    
    
                var objectTwo = new Physijs.SphereMesh(armMesh, Physijs.createMaterial(
                        new THREE.MeshPhongMaterial({color: 0x4444ff, transparent: true, opacity: 0.7}), 0, 0), 10);
                objectTwo.position.z = 0;
                objectTwo.position.x = 20;
                objectTwo.position.y = 7.5;
                scene.add(objectTwo);
    
                objectTwo.castShadow = true;
    
                //position is the position of the axis, relative to the ref, based on the current position
                var constraint = new Physijs.ConeTwistConstraint(objectOne, objectTwo, objectOne.position);
    
                scene.addConstraint(constraint);
                // set limit to quarter circle for each axis
                constraint.setLimit(0.5 * Math.PI, 0.5 * Math.PI, 0.5 * Math.PI);
                constraint.setMaxMotorImpulse(1);
                constraint.setMotorTarget(new THREE.Vector3(0, 0, 0)); // desired rotation
    
                return constraint;
            }

        我们先是创建出几个用约束连接起来的对象:ojectOne(球)和objectTwo(盒子)。ConeTwistConstraint的第一个参数是要约束的对象,第二个参数是第一个参数要约束到的对象,最后一个参数是约束应用的位置(在本例中,这个位置就是objectOne绕着旋转的位置)。将约束添加到场景中之后,我们就可以通过setLimts函数设置它的限制。setLimit函数接收三个弧度值,表示对象绕每个轴旋转的最大角度。

  • 相关阅读:
    React Native的生命周期解析
    React Native中组件的props和state
    centos7修改主机名
    nodejs搭建web项目
    centos7默认防火墙firewalld
    初窥 MongoDB
    阿里云Ubuntu安装图形界面与中文语言包
    安装nginx
    PHP静态化技术
    PHP工厂模式
  • 原文地址:https://www.cnblogs.com/w-wanglei/p/6790942.html
Copyright © 2020-2023  润新知