diff --git a/ShadowEditor.Web/src/editor/menubar/TerrainMenu.js b/ShadowEditor.Web/src/editor/menubar/TerrainMenu.js index 255a10b3..0fd0be3d 100644 --- a/ShadowEditor.Web/src/editor/menubar/TerrainMenu.js +++ b/ShadowEditor.Web/src/editor/menubar/TerrainMenu.js @@ -91,7 +91,16 @@ TerrainMenu.prototype.createShaderTerrain = function () { // ----------------------------- 创建物理地形 --------------------------------- TerrainMenu.prototype.createPhysicsTerrain = function () { - this.app.editor.execute(new AddObjectCommand(new PhysicsTerrain())); + var terrain = new PhysicsTerrain(); + + this.app.editor.execute(new AddObjectCommand(terrain)); + + this.app.physics.world.addRigidBody(terrain.userData.physicsBody); + this.app.physics.rigidBodies.push(terrain); + + // this.app.on(`animate.${this.id}`, (clock, deltaTime) => { + // terrain.update(deltaTime, this.app.physics.world); + // }); }; // ---------------------------- 升高地形 ----------------------------------- diff --git a/ShadowEditor.Web/src/object/terrain/PhysicsTerrain.js b/ShadowEditor.Web/src/object/terrain/PhysicsTerrain.js index 2340bab0..dbc622f0 100644 --- a/ShadowEditor.Web/src/object/terrain/PhysicsTerrain.js +++ b/ShadowEditor.Web/src/object/terrain/PhysicsTerrain.js @@ -20,7 +20,7 @@ function PhysicsTerrain() { var heightData = this.generateHeight(terrainWidth, terrainDepth, terrainMinHeight, terrainMaxHeight); - for (var i = 0, j = 0, l = vertices.length; i < l; i++, j += 3) { + 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]; } @@ -35,6 +35,7 @@ function PhysicsTerrain() { // 创建网格 THREE.Mesh.call(this, geometry, material); + this.name = '地形'; this.castShadow = true; this.receiveShadow = true; @@ -47,6 +48,21 @@ function PhysicsTerrain() { material.map = texture; material.needsUpdate = true; }); + + // 创建地形刚体 + var groundShape = this.createTerrainShape(terrainWidth, terrainDepth, terrainWidthExtents, terrainDepthExtents, heightData, terrainMinHeight, terrainMaxHeight); + var groundTransform = new Ammo.btTransform(); + groundTransform.setIdentity(); + // 由于子弹将其重新定位在其边界框上,因此会改变地形。 + groundTransform.setOrigin(new Ammo.btVector3(0, (terrainMinHeight + terrainMaxHeight) / 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)); + + this.userData.physicsBody = groundBody; } PhysicsTerrain.prototype = Object.create(THREE.Mesh.prototype); @@ -78,222 +94,74 @@ PhysicsTerrain.prototype.generateHeight = function (width, depth, minHeight, max return data; }; -export default PhysicsTerrain; +/** + * 创建物理地形形状 + * @param {*} terrainWidth + * @param {*} terrainDepth + * @param {*} terrainWidthExtents + * @param {*} terrainDepthExtents + * @param {*} heightData + * @param {*} terrainMinHeight + * @param {*} terrainMaxHeight + */ +PhysicsTerrain.prototype.createTerrainShape = function (terrainWidth, terrainDepth, terrainWidthExtents, terrainDepthExtents, heightData, terrainMinHeight, terrainMaxHeight) { + // 此参数并未真正使用,因为我们使用的是PHY_FLOAT高度数据类型,因此会被忽略。 + var heightScale = 1; + // 向上轴 0表示X,1表示Y,2表示Z。通常使用1=Y。 + var upAxis = 1; + // hdt,高度数据类型。 使用“PHY_FLOAT”。 可能的值为“PHY_FLOAT”,“PHY_UCHAR”,“PHY_SHORT”。 + var hdt = "PHY_FLOAT"; + // 根据您的需要设置(反转三角形)。 + var flipQuadEdges = false; + // 在Ammo堆中创建高度数据缓冲区。 + var ammoHeightData = Ammo._malloc(4 * terrainWidth * terrainDepth); + // 将javascript高度数据数组复制到Ammo。 + var p = 0; + var p2 = 0; + for (var j = 0; j < terrainDepth; j++) { + for (var i = 0; i < terrainWidth; i++) { + // 将32位浮点数写入内存。 + Ammo.HEAPF32[ammoHeightData + p2 >> 2] = heightData[p]; + p++; + // 4个字节/浮点数 + p2 += 4; + } + } + // 创建高度场物理形状 + var heightFieldShape = new Ammo.btHeightfieldTerrainShape( + terrainWidth, + terrainDepth, + ammoHeightData, + heightScale, + terrainMinHeight, + terrainMaxHeight, + upAxis, + hdt, + flipQuadEdges + ); + // 设置水平缩放 + var scaleX = terrainWidthExtents / (terrainWidth - 1); + var scaleZ = terrainDepthExtents / (terrainDepth - 1); + heightFieldShape.setLocalScaling(new Ammo.btVector3(scaleX, 1, scaleZ)); + heightFieldShape.setMargin(0.05); + return heightFieldShape; +}; -// function init() { -// heightData = generateHeight(terrainWidth, terrainDepth, terrainMinHeight, terrainMaxHeight); -// initGraphics(); -// initPhysics(); -// } +PhysicsTerrain.prototype.update = function (deltaTime, physicsWorld) { + physicsWorld.stepSimulation(deltaTime, 10); -// function initGraphics() { -// var geometry = new THREE.PlaneBufferGeometry(terrainWidthExtents, terrainDepthExtents, 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 textureLoader = new THREE.TextureLoader(); -// textureLoader.load("textures/grid.png", function (texture) { -// texture.wrapS = THREE.RepeatWrapping; -// texture.wrapT = THREE.RepeatWrapping; -// texture.repeat.set(terrainWidth - 1, terrainDepth - 1); -// groundMaterial.map = texture; -// groundMaterial.needsUpdate = true; -// }); -// } + var body = this.userData.physics.body; + var state = body.getMotionState(); -// function initPhysics() { -// // Physics configuration -// 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, -6, 0)); -// // Create the terrain body -// var groundShape = createTerrainShape(); -// var groundTransform = new Ammo.btTransform(); -// groundTransform.setIdentity(); -// // Shifts the terrain, since bullet re-centers it on its bounding box. -// 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); -// } + if (state) { + var transformAux1 = new Ammo.btTransform(); -// function generateHeight(width, depth, minHeight, maxHeight) { -// // Generates the height data (a sinus wave) -// var size = width * depth; -// var data = new Float32Array(size); -// var hRange = maxHeight - minHeight; -// var w2 = width / 2; -// var d2 = depth / 2; -// var phaseMult = 12; -// var p = 0; -// for (var j = 0; j < depth; j++) { -// for (var i = 0; i < width; i++) { -// var radius = Math.sqrt( -// Math.pow((i - w2) / w2, 2.0) + -// Math.pow((j - d2) / d2, 2.0)); -// var height = (Math.sin(radius * phaseMult) + 1) * 0.5 * hRange + minHeight; -// data[p] = height; -// p++; -// } -// } -// return data; -// } + state.getWorldTransform(transformAux1); + var p = transformAux1.getOrigin(); + var q = transformAux1.getRotation(); + this.position.set(p.x(), p.y(), p.z()); + this.quaternion.set(q.x(), q.y(), q.z(), q.w()); + } +}; -// function createTerrainShape() { -// // 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; -// } - -// function generateObject() { -// var numTypes = 4; -// var objectType = Math.ceil(Math.random() * numTypes); -// var threeObject = null; -// var shape = null; -// var objectSize = 3; -// var margin = 0.05; -// switch (objectType) { -// case 1: -// // Sphere -// var radius = 1 + Math.random() * objectSize; -// threeObject = new THREE.Mesh(new THREE.SphereBufferGeometry(radius, 20, 20), createObjectMaterial()); -// shape = new Ammo.btSphereShape(radius); -// shape.setMargin(margin); -// break; -// case 2: -// // Box -// var sx = 1 + Math.random() * objectSize; -// var sy = 1 + Math.random() * objectSize; -// var sz = 1 + Math.random() * objectSize; -// threeObject = new THREE.Mesh(new THREE.BoxBufferGeometry(sx, sy, sz, 1, 1, 1), createObjectMaterial()); -// shape = new Ammo.btBoxShape(new Ammo.btVector3(sx * 0.5, sy * 0.5, sz * 0.5)); -// shape.setMargin(margin); -// break; -// case 3: -// // Cylinder -// var radius = 1 + Math.random() * objectSize; -// var height = 1 + Math.random() * objectSize; -// threeObject = new THREE.Mesh(new THREE.CylinderBufferGeometry(radius, radius, height, 20, 1), createObjectMaterial()); -// shape = new Ammo.btCylinderShape(new Ammo.btVector3(radius, height * 0.5, radius)); -// shape.setMargin(margin); -// break; -// default: -// // Cone -// var radius = 1 + Math.random() * objectSize; -// var height = 2 + Math.random() * objectSize; -// threeObject = new THREE.Mesh(new THREE.ConeBufferGeometry(radius, height, 20, 2), createObjectMaterial()); -// shape = new Ammo.btConeShape(radius, height); -// break; -// } -// threeObject.position.set((Math.random() - 0.5) * terrainWidth * 0.6, terrainMaxHeight + objectSize + 2, (Math.random() - 0.5) * terrainDepth * 0.6); -// var mass = objectSize * 5; -// var localInertia = new Ammo.btVector3(0, 0, 0); -// shape.calculateLocalInertia(mass, localInertia); -// var transform = new Ammo.btTransform(); -// transform.setIdentity(); -// var pos = threeObject.position; -// transform.setOrigin(new Ammo.btVector3(pos.x, pos.y, pos.z)); -// var motionState = new Ammo.btDefaultMotionState(transform); -// var rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, shape, localInertia); -// var body = new Ammo.btRigidBody(rbInfo); -// threeObject.userData.physicsBody = body; -// threeObject.receiveShadow = true; -// threeObject.castShadow = true; -// scene.add(threeObject); -// dynamicObjects.push(threeObject); -// physicsWorld.addRigidBody(body); -// } - -// function createObjectMaterial() { -// var c = Math.floor(Math.random() * (1 << 24)); -// return new THREE.MeshPhongMaterial({ -// color: c -// }); -// } - -// function animate() { -// requestAnimationFrame(animate); -// render(); -// stats.update(); -// } - -// function render() { -// var deltaTime = clock.getDelta(); -// if (dynamicObjects.length < maxNumObjects && time > timeNextSpawn) { -// generateObject(); -// timeNextSpawn = time + objectTimePeriod; -// } -// updatePhysics(deltaTime); -// renderer.render(scene, camera); -// time += deltaTime; -// } - -// function updatePhysics(deltaTime) { -// physicsWorld.stepSimulation(deltaTime, 10); -// // Update objects -// for (var i = 0, il = dynamicObjects.length; i < il; i++) { -// var objThree = dynamicObjects[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()); -// } -// } -// } \ No newline at end of file +export default PhysicsTerrain; \ No newline at end of file