import * as THREE from 'three' import TWEEN from '@tweenjs/tween.js' import useCoord from '@/hooks/useCoord' import { deepMerge, random } from '@/utils' /** * * @param {object} { * pointTextureUrl:标记点的图片url * lightHaloTextureUrl:光圈的URL * lightPillarUrl:光柱的URL * scaleFactor:1 缩放系数,用来调整标记点和光圈的缩放大小 * } * * @returns */ export default function useMarkedLightPillar(options) { const { geoSphereCoord } = useCoord() // 默认参数 let defaultOptions = { pointTextureUrl: './assets/texture/标注.png', lightHaloTextureUrl: './assets/texture/标注光圈.png', lightPillarUrl: './assets/texture/光柱.png', scaleFactor: 1, // 缩放系数 } defaultOptions = deepMerge(defaultOptions, options) // 纹理加载器 const textureLoader = new THREE.TextureLoader() // 射线拾取对象 const raycaster = new THREE.Raycaster() let containerWidth = window.width let containerHeight = window.height // 对象属性 let getBoundingClientRect = null /** * 创建标记点 * @param {*} R 地球半径,根据R来进行缩放 * @returns */ const createPointMesh = () => { // 标记点:几何体,材质, const geometry = new THREE.PlaneBufferGeometry(1, 1) const material = new THREE.MeshBasicMaterial({ map: textureLoader.load(defaultOptions.pointTextureUrl), color: 0x00ffff, side: THREE.DoubleSide, transparent: true, depthWrite: false, //禁止写入深度缓冲区数据 }) let mesh = new THREE.Mesh(geometry, material) mesh.name = 'createPointMesh' // 缩放 const scale = 0.15 * defaultOptions.scaleFactor mesh.scale.set(scale, scale, scale) return mesh } /** * 创建光圈 * @param {*} R 地球半径,根据R来进行缩放 * @returns */ const createLightHalo = () => { // 标记点:几何体,材质, const geometry = new THREE.PlaneBufferGeometry(1, 1) const material = new THREE.MeshBasicMaterial({ map: textureLoader.load(defaultOptions.lightHaloTextureUrl), color: 0x00ffff, side: THREE.DoubleSide, opacity: 0, transparent: true, depthWrite: false, //禁止写入深度缓冲区数据 }) let mesh = new THREE.Mesh(geometry, material) mesh.name = 'createLightHalo' // 缩放 const scale = 0.3 * defaultOptions.scaleFactor mesh.scale.set(scale, scale, scale) // 动画延迟时间 const delay = random(0, 2000) // 动画:透明度缩放动画 mesh.tween1 = new TWEEN.Tween({ scale: scale, opacity: 0 }) .to({ scale: scale * 1.5, opacity: 1 }, 1000) .delay(delay) .onUpdate(params => { let { scale, opacity } = params mesh.scale.set(scale, scale, scale) mesh.material.opacity = opacity }) mesh.tween2 = new TWEEN.Tween({ scale: scale * 1.5, opacity: 1 }) .to({ scale: scale * 2, opacity: 0 }, 1000) .onUpdate(params => { let { scale, opacity } = params mesh.scale.set(scale, scale, scale) mesh.material.opacity = opacity }) mesh.tween1.chain(mesh.tween2) mesh.tween2.chain(mesh.tween1) mesh.tween1.start() return mesh } /** * 创建光柱 * @param {*} lon * @param {*} lat * @param {*} heightScaleFactor 光柱高度的缩放系数 * @returns */ const createLightPillar = (lon, lat, heightScaleFactor = 1) => { let group = new THREE.Group() // 柱体高度 const height = heightScaleFactor // 柱体的geo,6.19=柱体图片高度/宽度的倍数 const geometry = new THREE.PlaneBufferGeometry(height / 6.219, height) // 柱体旋转90度,垂直于Y轴 geometry.rotateX(Math.PI / 2) // 柱体的z轴移动高度一半对齐中心点 geometry.translate(0, 0, height / 2) // 柱子材质 const material = new THREE.MeshBasicMaterial({ map: textureLoader.load(defaultOptions.lightPillarUrl), color: 0x00ffff, transparent: true, depthWrite: false, side: THREE.DoubleSide, }) // 光柱01 let light01 = new THREE.Mesh(geometry, material) light01.name = 'createLightPillar01' // 光柱02:复制光柱01 let light02 = light01.clone() light02.name = 'createLightPillar02' // 光柱02,旋转90°,跟 光柱01交叉 light02.rotateZ(Math.PI / 2) // 创建底部标点 const bottomMesh = createPointMesh() // 创建光圈 const lightHalo = createLightHalo() // 将光柱和标点添加到组里 group.add(bottomMesh, lightHalo, light01, light02) // 设置组对象的姿态 // group = setMeshQuaternion(group, R, lon, lat) group.position.set(lon, lat, 0) return group } /** * 设置光柱颜色 * @param {*} group * @param {*} color */ const setLightPillarColor = (group, color) => { group.children.forEach(item => { item.material.color = new THREE.Color(color) }) } /** * 设置网格的位置及姿态 * @param {*} mesh * @param {*} R * @param {*} lon * @param {*} lat * @returns */ const setMeshQuaternion = (mesh, R, lon, lat) => { const { x, y, z } = geoSphereCoord(R, lon, lat) mesh.position.set(x, y, z) // 姿态设置 // mesh在球面上的法线方向(球心和球面坐标构成的方向向量) let meshVector = new THREE.Vector3(x, y, z).normalize() // mesh默认在XOY平面上,法线方向沿着z轴new THREE.Vector3(0, 0, 1) let normal = new THREE.Vector3(0, 0, 1) // 四元数属性.quaternion表示mesh的角度状态 //.setFromUnitVectors();计算两个向量之间构成的四元数值 mesh.quaternion.setFromUnitVectors(normal, meshVector) return mesh } /** * 射线拾取,返回选中的mesh * @param {*} event * @param {*} container * @param {*} camera * @param {*} mesh // 光柱group * @returns */ const getRaycasterObj = (event, container, camera, mesh) => { //屏幕坐标转WebGL标准设备坐标 if (!getBoundingClientRect) { getBoundingClientRect = container.getBoundingClientRect() containerWidth = container.offsetWidth containerHeight = container.offsetHeight } var x = ((event.clientX - getBoundingClientRect.left) / containerWidth) * 2 - 1 var y = -((event.clientY - getBoundingClientRect.top) / containerHeight) * 2 + 1 //通过鼠标单击位置标准设备坐标和相机参数计算射线投射器`Raycaster`的射线属性.ray raycaster.setFromCamera(new THREE.Vector2(x, y), camera) //返回.intersectObjects()参数中射线选中的网格模型对象 // 未选中对象返回空数组[],选中一个数组1个元素,选中两个数组两个元素 var intersects = raycaster.intersectObjects(mesh) return intersects } /** * 选择光柱,返回选择的对象 * @param {*} event * @param {*} container * @param {*} camera * @param {*} mesh * @returns 返回选择的对象 */ const chooseLightPillar = (event, container, camera, mesh) => { event.preventDefault() // 获取拾取的对象数组 let intersects = getRaycasterObj(event, container, camera, mesh) // 大于0说明,说明选中了mesh,返回对象 if (intersects.length > 0) { return intersects[0] } return null } return { createLightPillar, setLightPillarColor, chooseLightPillar, } }