【three.js练习程序】创建简单物理地形

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>ceshi</title>
    <style>
        body
        {
            margin: 0;
            overflow: hidden;
        }
    </style>
    <script src="./build/three.js"></script>
    <script src="./examples/js/libs/ammo.js"></script>
    <script src="./examples/js/controls/OrbitControls.js"></script>
    <script src="./examples/js/ImprovedNoise.js"></script>
</head>
<body>
    <div id="ThreeJs">
    </div>
    <script>
        var camera, controls, scene, renderer;
        var clock = new THREE.Clock();

        // 物理引擎相关变量
        var gravityConstant = -9.8;
        var collisionConfiguration;
        var dispatcher;
        var broadphase;
        var solver;
        var physicsWorld;
        var rigidBodies = [];
        var margin = 0.05;
        var transformAux1 = new Ammo.btTransform();
        var time = 0;

        // 高度场相关
        var terrainWidthExtents = 50;
        var terrainDepthExtents = 50;
        var terrainWidth = 50;
        var terrainDepth = 50;
        var terrainHalfWidth = terrainWidth / 2;
        var terrainHalfDepth = terrainDepth / 2;
        var terrainMaxHeight = 50;
        var terrainMinHeight = -20;
        var heightData = null;
        var ammoHeightData = null;

        init();
        animate();

        function init() {

            heightData = generateHeight(terrainWidth, terrainDepth);

            initGraphics();
            initPhysics();
            createObjects();
        }

        function initGraphics() {
            // three.js基本场景配置
            camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 2000);
            camera.position.x = 50;
            camera.position.y = 50;
            camera.position.z = 50;

            controls = new THREE.OrbitControls(camera);
            controls.target.y = 2;

            renderer = new THREE.WebGLRenderer();
            renderer.setClearColor(new THREE.Color("#bfd1e5"));
            renderer.shadowMapEnabled = true;
            renderer.setSize(window.innerWidth, window.innerHeight);
            // 场景
            scene = new THREE.Scene();
            // 环境光
            var ambientLight = new THREE.AmbientLight(0x404040);
            scene.add(ambientLight);
            // 线性光
            var light = new THREE.DirectionalLight(0xffffff, 1);
            light.position.set(-20, 20, 10);
            light.castShadow = true;
            var d = 50;
            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);

            var axes = new THREE.AxisHelper(50);               //创建三轴表示
            scene.add(axes);
            // 添加窗口大小变化监听
            window.addEventListener('resize', onWindowResize, false);
        }

        function onWindowResize() {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        }

        function initPhysics() {
            // bullet基本场景配置
            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));
        }

        function createObjects() {
            var pos = new THREE.Vector3();
            var quat = new THREE.Quaternion();

            //创建物理地形
            var geometry = new THREE.PlaneBufferGeometry(50, 50, terrainWidth - 1, terrainDepth - 1);
            geometry.rotateX(-Math.PI / 2);
            var vertices = geometry.attributes.position.array;
            for (var i = 0, j = 0, l = vertices.length; i < l; i++, j += 3) {
                // j + 1 because it is the y component that we modify
                vertices[j + 1] = heightData[i];
            }
            geometry.computeVertexNormals();
            var groundMaterial = new THREE.MeshPhongMaterial({ color: 0xC7C7C7 });
            terrainMesh = new THREE.Mesh(geometry, groundMaterial);
            terrainMesh.receiveShadow = true;
            terrainMesh.castShadow = true;
            scene.add(terrainMesh);

            var groundShape = createTerrainShape(heightData);
            var groundTransform = new Ammo.btTransform();
            groundTransform.setIdentity();
            // 设置bullet计算时物体中心
            groundTransform.setOrigin(new Ammo.btVector3(0, (terrainMaxHeight + terrainMinHeight) / 2, 0));
            var groundMass = 0;
            var groundLocalInertia = new Ammo.btVector3(0, 0, 0);
            var groundMotionState = new Ammo.btDefaultMotionState(groundTransform);
            var groundBody = new Ammo.btRigidBody(new Ammo.btRigidBodyConstructionInfo(groundMass, groundMotionState, groundShape, groundLocalInertia));
            physicsWorld.addRigidBody(groundBody);

            //创建50个小球
            for (var i = 0; i < 50; i++) {
                var ballMass = 1.2;
                var ballRadius = 0.5;

                var ball = new THREE.Mesh(new THREE.SphereGeometry(ballRadius, 20, 20), createRendomColorObjectMeatrial());
                ball.castShadow = true;
                ball.receiveShadow = true;
                var ballShape = new Ammo.btSphereShape(ballRadius);
                ballShape.setMargin(margin);
                pos.set(Math.random() + 10, 3 * (i + 1) + 20, Math.random() - 10);
                quat.set(0, 0, 0, 1);
                createRigidBody(ball, ballShape, ballMass, pos, quat);
                ball.userData.physicsBody.setFriction(1.5);
            }

            //创建50个方块
            for (var i = 0; i < 50; i++) {
                pos.set(Math.random() - 10, 3 * (i + 1) + 20, Math.random() + 10);
                quat.set(0, 0, 0, 1);
                createParallellepiped(1, 1, 1, 1, pos, quat, createRendomColorObjectMeatrial());
            }
        }

        function createRendomColorObjectMeatrial() {
            var color = Math.floor(Math.random() * (1 << 24));
            return new THREE.MeshPhongMaterial({ color: color });
        }

        function createParallellepiped(sx, sy, sz, mass, pos, quat, material) {
            var threeObject = new THREE.Mesh(new THREE.BoxGeometry(sx, sy, sz, 1, 1, 1), material);
            threeObject.castShadow = true;
            threeObject.receiveShadow = true;
            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);
            return body;
        }

        function animate() {
            requestAnimationFrame(animate);
            var deltaTime = clock.getDelta();
            updatePhysics(deltaTime);
            controls.update(deltaTime);
            renderer.render(scene, camera);
            time += deltaTime;
        }

        function updatePhysics(deltaTime) {
            physicsWorld.stepSimulation(deltaTime);
            // 更新物体位置
            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());
                }
            }
        }

        function generateHeight(width, height) {
            var size = width * height, data = new Float32Array(size),
                perlin = new ImprovedNoise(), quality = 1, z = Math.random() * 10;
            for (var j = 0; j < 4; j++) {
                for (var i = 0; i < size; i++) {
                    var x = i % width, y = ~ ~(i / width);
                    data[i] += Math.abs(perlin.noise(x / quality, y / quality, z) * quality);
                }
                quality *= 3;
            }
            return data;
        }

        // 生成物理引擎用高度场
        function createTerrainShape(heightData) {
            // This parameter is not really used, since we are using PHY_FLOAT height data type and hence it is ignored
            var heightScale = 1;
            // Up axis = 0 for X, 1 for Y, 2 for Z. Normally 1 = Y is used.
            var upAxis = 1;
            // hdt, height data type. "PHY_FLOAT" is used. Possible values are "PHY_FLOAT", "PHY_UCHAR", "PHY_SHORT"
            var hdt = "PHY_FLOAT";
            // Set this to your needs (inverts the triangles)
            var flipQuadEdges = false;
            // Creates height data buffer in Ammo heap
            ammoHeightData = Ammo._malloc(4 * terrainWidth * terrainDepth);
            // Copy the javascript height data array to the Ammo one.
            var p = 0;
            var p2 = 0;
            for (var j = 0; j < terrainDepth; j++) {
                for (var i = 0; i < terrainWidth; i++) {
                    // write 32-bit float data to memory
                    Ammo.HEAPF32[ammoHeightData + p2 >> 2] = heightData[p];
                    p++;
                    // 4 bytes/float
                    p2 += 4;
                }
            }
            // Creates the heightfield physics shape
            var heightFieldShape = new Ammo.btHeightfieldTerrainShape(
                terrainWidth,
                terrainDepth,
                ammoHeightData,
                heightScale,
                terrainMinHeight,
                terrainMaxHeight,
                upAxis,
                hdt,
                flipQuadEdges
        );
            // Set horizontal scale
            var scaleX = terrainWidthExtents / (terrainWidth - 1);
            var scaleZ = terrainDepthExtents / (terrainDepth - 1);
            heightFieldShape.setLocalScaling(new Ammo.btVector3(scaleX, 1, scaleZ));
            heightFieldShape.setMargin(0.05);
            return heightFieldShape;
        }

        document.getElementById("ThreeJs").appendChild(renderer.domElement);
    </script>
</html>

 

转载于:https://www.cnblogs.com/tiandsp/p/8455228.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值