224 lines
7.3 KiB
JavaScript
224 lines
7.3 KiB
JavaScript
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,
|
||
}
|
||
}
|