<!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>