import * as mars3d from "mars3d" export let map // mars3d.Map三维地图对象 // 需要覆盖config.json中地图属性参数(当前示例框架中自动处理合并) export const mapOptions = { scene: { center: { lat: 31.834648, lng: 117.219733, alt: 83, heading: 64, pitch: -34 }, fxaa: true } } /** * 初始化地图业务,生命周期钩子函数(必须) * 框架在地图初始化完成后自动调用该函数 * @param {mars3d.Map} mapInstance 地图对象 * @returns {void} 无 */ export function onMounted(mapInstance) { map = mapInstance // 记录map globalNotify("操作提示", `鼠标左键单击进行发射`) addDemo() } /** * 释放当前地图业务的生命周期函数 * @returns {void} 无 */ export function onUnmounted() { map = null } function addDemo() { const GeometryUtils = Cesium.GeometryUtils const MeshPhongMaterial = Cesium.MeshPhongMaterial const BasicMeshMaterial = Cesium.BasicMeshMaterial const center = Cesium.Cartesian3.fromDegrees(117.220206, 31.834866, 50) const modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(center) const meshVisualizer = new Cesium.MeshVisualizer({ modelMatrix: modelMatrix, up: { y: 1 }, referenceAxisParameter: { length: 100, width: 0.05, headLength: 2, headWidth: 0.1 } }) map.scene.primitives.add(meshVisualizer) meshVisualizer.showReference = true // 显示坐标轴 function createRandomColor() { return Cesium.Color.fromRandom({ alpha: 1 }) // fromRgba(Math.floor(Math.random() * (1 << 24))); } function createMaterial() { return new MeshPhongMaterial({ defaultColor: createRandomColor(), side: Cesium.MeshMaterial.Sides.DOUBLE, translucent: false }) } Cesium.Cartesian3.prototype.set = function (x, y, z) { this.x = x this.y = y this.z = z } Cesium.Cartesian2.prototype.set = function (x, y) { this.x = x this.y = y } Cesium.Quaternion.prototype.set = function (x, y, z, w) { this.x = x this.y = y this.z = z this.w = w } Ammo().then(function () { // - Global variables - // Graphics variables let clickRequest = false const mouseCoords = new Cesium.Cartesian2() const ballMaterial = createMaterial() const pos = new Cesium.Cartesian3() const quat = new Cesium.Quaternion() // Physics variables const gravityConstant = -9.8 let collisionConfiguration let dispatcher let broadphase let solver let physicsWorld const rigidBodies = [] const softBodies = [] const margin = 0.05 // let hinge const transformAux1 = new Ammo.btTransform() const softBodyHelpers = new Ammo.btSoftBodyHelpers() // const armMovement = 0 const ray = new Cesium.Ray() let softBodySolver function initPhysics() { // Physics configuration collisionConfiguration = new Ammo.btSoftBodyRigidBodyCollisionConfiguration() dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration) broadphase = new Ammo.btDbvtBroadphase() solver = new Ammo.btSequentialImpulseConstraintSolver() softBodySolver = new Ammo.btDefaultSoftBodySolver() physicsWorld = new Ammo.btSoftRigidDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration, softBodySolver) physicsWorld.setGravity(new Ammo.btVector3(0, gravityConstant, 0)) physicsWorld.getWorldInfo().set_m_gravity(new Ammo.btVector3(0, gravityConstant, 0)) } function createObjects() { // Ground pos.set(0, -0.5, 0) quat.set(0, 0, 0, 1) const ground = createParalellepiped( 40, 1, 40, 0, pos, quat, new MeshPhongMaterial({ defaultColor: "rgb(200,200,200)", side: Cesium.MeshMaterial.Sides.DOUBLE, translucent: false }) ) GeometryUtils.computeVertexNormals(ground.geometry) // Create soft volumes const volumeMass = 15 const sphereGeometry = new THREE.SphereBufferGeometry(1.5, 40, 25) sphereGeometry.translate(5, 5, 0) createSoftVolume(sphereGeometry, volumeMass, 250) const boxGeometry = new THREE.BufferGeometry().fromGeometry(new THREE.BoxGeometry(1, 1, 5, 4, 4, 20)) boxGeometry.translate(-2, 5, 0) createSoftVolume(boxGeometry, volumeMass, 120) // Ramp pos.set(3, 1, 0) Cesium.Quaternion.fromAxisAngle(new Cesium.Cartesian3(0, 0, 1), (30 * Math.PI) / 180, quat) const obstacle = createParalellepiped( 10, 1, 4, 0, pos, quat, new MeshPhongMaterial({ defaultColor: "rgb(28,28,28)", side: Cesium.MeshMaterial.Sides.DOUBLE, translucent: false }) ) GeometryUtils.computeVertexNormals(obstacle.geometry) } function processGeometry(bufGeometry) { // Obtain a Geometry const geometry = new THREE.Geometry().fromBufferGeometry(bufGeometry) // Merge the vertices so the triangle soup is converted to indexed triangles // const vertsDiff = geometry.mergeVertices() // Convert again to BufferGeometry, indexed const indexedBufferGeom = createIndexedBufferGeometryFromGeometry(geometry) // Create index arrays mapping the indexed vertices to bufGeometry vertices mapIndices(bufGeometry, indexedBufferGeom) } function createIndexedBufferGeometryFromGeometry(geometry) { const numVertices = geometry.vertices.length const numFaces = geometry.faces.length const bufferGeom = new THREE.BufferGeometry() const vertices = new Float32Array(numVertices * 3) const indices = new (numFaces * 3 > 65535 ? Uint32Array : Uint16Array)(numFaces * 3) for (let i = 0; i < numVertices; i++) { const p = geometry.vertices[i] const i3 = i * 3 vertices[i3] = p.x vertices[i3 + 1] = p.y vertices[i3 + 2] = p.z } for (let i = 0; i < numFaces; i++) { const f = geometry.faces[i] const i3 = i * 3 indices[i3] = f.a indices[i3 + 1] = f.b indices[i3 + 2] = f.c } bufferGeom.setIndex(new THREE.BufferAttribute(indices, 1)) bufferGeom.addAttribute("position", new THREE.BufferAttribute(vertices, 3)) return bufferGeom } function isEqual(x1, y1, z1, x2, y2, z2) { const delta = 0.000001 return Math.abs(x2 - x1) < delta && Math.abs(y2 - y1) < delta && Math.abs(z2 - z1) < delta } function mapIndices(bufGeometry, indexedBufferGeom) { // Creates ammoVertices, ammoIndices and ammoIndexAssociation in bufGeometry const vertices = bufGeometry.attributes.position.array const idxVertices = indexedBufferGeom.attributes.position.array const indices = indexedBufferGeom.index.array const numIdxVertices = idxVertices.length / 3 const numVertices = vertices.length / 3 bufGeometry.ammoVertices = idxVertices bufGeometry.ammoIndices = indices bufGeometry.ammoIndexAssociation = [] for (let i = 0; i < numIdxVertices; i++) { const association = [] bufGeometry.ammoIndexAssociation.push(association) const i3 = i * 3 for (let j = 0; j < numVertices; j++) { const j3 = j * 3 if (isEqual(idxVertices[i3], idxVertices[i3 + 1], idxVertices[i3 + 2], vertices[j3], vertices[j3 + 1], vertices[j3 + 2])) { association.push(j3) } } } } function createSoftVolume(bufferGeom, mass, pressure) { processGeometry(bufferGeom) const volume = new Cesium.Mesh( bufferGeom, new BasicMeshMaterial({ uniforms: { diffuseColorMap: "img/textures/colors.png" }, translucent: false }) ) volume.geometry.ammoIndexAssociation = bufferGeom.ammoIndexAssociation meshVisualizer.add(volume) // Volume physic object const volumeSoftBody = softBodyHelpers.CreateFromTriMesh( physicsWorld.getWorldInfo(), bufferGeom.ammoVertices, bufferGeom.ammoIndices, bufferGeom.ammoIndices.length / 3, true ) const sbConfig = volumeSoftBody.get_m_cfg() sbConfig.set_viterations(40) sbConfig.set_piterations(40) // Soft-soft and soft-rigid collisions sbConfig.set_collisions(0x11) // Friction sbConfig.set_kDF(0.1) // Damping sbConfig.set_kDP(0.01) // Pressure sbConfig.set_kPR(pressure) // Stiffness volumeSoftBody.get_m_materials().at(0).set_m_kLST(0.9) volumeSoftBody.get_m_materials().at(0).set_m_kAST(0.9) volumeSoftBody.setTotalMass(mass, false) Ammo.castObject(volumeSoftBody, Ammo.btCollisionObject).getCollisionShape().setMargin(margin) physicsWorld.addSoftBody(volumeSoftBody, 1, -1) volume.physicsBody = volumeSoftBody // Disable deactivation volumeSoftBody.setActivationState(4) softBodies.push(volume) } function createParalellepiped(sx, sy, sz, mass, pos, quat, material) { const box = Cesium.BoxGeometry.fromDimensions({ dimensions: new Cesium.Cartesian3(sx, sy, sz), vertexFormat: new Cesium.VertexFormat({ position: true, normal: true }) }) const threeObject = new Cesium.Mesh(box, material) const 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) { Cesium.Cartesian3.clone(pos, threeObject.position) if (!threeObject.quaternion) { threeObject.quaternion = new Cesium.Quaternion() } Cesium.Quaternion.clone(quat, threeObject.quaternion) const 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)) const motionState = new Ammo.btDefaultMotionState(transform) const localInertia = new Ammo.btVector3(0, 0, 0) physicsShape.calculateLocalInertia(mass, localInertia) const rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, physicsShape, localInertia) const body = new Ammo.btRigidBody(rbInfo) threeObject.physicsBody = body meshVisualizer.add(threeObject) if (mass > 0) { rigidBodies.push(threeObject) // Disable deactivation body.setActivationState(4) } physicsWorld.addRigidBody(body) return body } function updatePhysics(deltaTime) { try { // Step world physicsWorld.stepSimulation(deltaTime, 10) // Update soft volumes for (let i = 0, il = softBodies.length; i < il; i++) { const volume = softBodies[i] const geometry = volume.geometry const softBody = volume.physicsBody const volumePositions = geometry.attributes.position.values const volumeNormals = geometry.attributes.normal.values const association = geometry.ammoIndexAssociation const numVerts = association.length const nodes = softBody.get_m_nodes() for (let j = 0; j < numVerts; j++) { const node = nodes.at(j) const nodePos = node.get_m_x() const x = nodePos.x() const y = nodePos.y() const z = nodePos.z() const nodeNormal = node.get_m_n() const nx = nodeNormal.x() const ny = nodeNormal.y() const nz = nodeNormal.z() const assocVertex = association[j] for (let k = 0, kl = assocVertex.length; k < kl; k++) { let indexVertex = assocVertex[k] volumePositions[indexVertex] = x volumeNormals[indexVertex] = nx indexVertex++ volumePositions[indexVertex] = y volumeNormals[indexVertex] = ny indexVertex++ volumePositions[indexVertex] = z volumeNormals[indexVertex] = nz } } geometry.attributes.position.needsUpdate = true geometry.attributes.normal.needsUpdate = true volume.modelMatrixNeedsUpdate = true } // Update rigid bodies for (let i = 0, il = rigidBodies.length; i < il; i++) { const objThree = rigidBodies[i] const objPhys = objThree.physicsBody const ms = objPhys.getMotionState() if (ms) { ms.getWorldTransform(transformAux1) const p = transformAux1.getOrigin() const q = transformAux1.getRotation() objThree.position.set(p.x(), p.y(), p.z()) objThree.quaternion.set(q.x(), q.y(), q.z(), q.w()) objThree.modelMatrixNeedsUpdate = true } } } catch (e) { meshVisualizer.beforeUpdate.removeEventListener(update) console.log("崩溃了", e) } } let start = false let init = false let startTime = new Date() const rayDir = new Cesium.Cartesian3() // const maxDistance = 100 // 发射点与射线和局部场景的交点的距离不能太远,过远会撕碎软体进而碎片过多时导致ammo物理引擎崩溃 function initInput() { const scene = map.scene const handler = new Cesium.ScreenSpaceEventHandler(scene.canvas) // const lastMesh = null handler.setInputAction(function (movement) { if (!clickRequest) { Cesium.Cartesian2.clone(movement.position, mouseCoords) clickRequest = true } }, Cesium.ScreenSpaceEventType.LEFT_DOWN) } function processClick() { if (clickRequest) { meshVisualizer.getPickRay(mouseCoords, ray) if (!ray) { clickRequest = false return } // Creates a ball const ballMass = 3 const ballRadius = 0.4 const ball = new Cesium.Mesh(new THREE.SphereGeometry(ballRadius, 18, 16), ballMaterial) const ballShape = new Ammo.btSphereShape(ballRadius) ballShape.setMargin(margin) Cesium.Cartesian3.clone(ray.direction, rayDir) Cesium.Cartesian3.subtract(ray.origin, ray.direction, pos) quat.set(0, 0, 0, 1) const ballBody = createRigidBody(ball, ballShape, ballMass, pos, quat) ballBody.setFriction(0.5) Cesium.Cartesian3.normalize(rayDir, rayDir) Cesium.Cartesian3.multiplyByScalar(rayDir, 30, rayDir) ballBody.setLinearVelocity(new Ammo.btVector3(rayDir.x, rayDir.y, rayDir.z)) clickRequest = false } } function update(frameState) { const deltaTime = (new Date() - startTime) / 1000.0 updatePhysics(deltaTime) processClick() startTime = new Date() } setTimeout(function () { if (!init) { // - Init - // initGraphics(); initPhysics() createObjects() initInput() init = true } if (!start) { meshVisualizer.beforeUpdate.addEventListener(update) start = true } else { meshVisualizer.beforeUpdate.removeEventListener(update) start = false } }, 1000 * 3) }) }