ThreeJs实现导航动画

 创建场景:

this.scene = new THREE.Scene()
this.camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000)
this.renderer = new THREE.WebGLRenderer()
this.renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
this.camera.position.z = 5

在场景中根据所给的点数据画线 

drawLine() {
 this.lines = new THREE.Object3D()
 let routeLine = new THREE.LineBasicMaterial({
  linewidth: 100,
  color: 0x2342fe,
  depthTest: false
 })
 this.routeList = []
 for (let i = 0; i < route.length - 1; i++) { // route是线上每一个点的数据
  let position = []
  let v1 = [route[i].x, route[i].y, route[i].z]
  let v2 = [route[i + 1].x, route[i + 1].y, route[i + 1].z]
  position.push(...v1)
  position.push(...v2)
  this.routeList.push({
   start: v1,
   end: v2
  })
  let geometry = new THREE.BufferGeometry()
  geometry.addAttribute('position', new THREE.Float32BufferAttribute(position, 3))
  let line = new THREE.Line(geometry, routeLine)
  this.lines.add(line)
 }
 scene.add(this.lines)
 this.drawMark()
},

 在线上添加marker,实现marker在线上移动的效果

drawMark() {
 let imgUrl = this.$config.staticFileUrl + 'img/arrow.png' // staticFileUrl是静态资源的地址
 let img = new THREE.TextureLoader().load(imgUrl)
 let material = new THREE.SpriteMaterial({ // 精灵材质
  map: img,
  up: (0, 0, 1),
  depthTest: false
 })
 this.spriteMarker = new THREE.Sprite(material)
 let scale = 1000 // 因为精灵图片很小,所以要缩放好多倍才能看得到
 this.spriteMarker.scale.set(scale, scale, scale)
 this.spriteMarker.position.set(0, 0, 0)
 this.lines.add(this.spriteMarker)
 this.moveOnLine(this.spriteMarker, this.routeList); // marker在线上移动的动画
}

 使用了Tween.js来做动画效果,具体使用查看api,Tween.js和TweenJs是不一样的,现在使用vue来安装的包是@tweenjs/tween.js

moveOnroute(mark, routes, duration = 300) {
 this.results = []
 for (let i = 0; i < routes.length; i++) {
  const varg = this.addTween(mark, routes[i].start, routes[i].end, duration)
  this.results.push(varg)
 }
 if (this.results.length > 0) {
  for (let i = 0; i < this.results.length; i++) {
   if (this.results[i + 1]) {
    this.results[i].chain(this.results[i + 1])
   }
  }
  this.results[0].start()
 }
 this.results[this.results.length - 1].onComplete(() => {
  this.stopRender = true
 })
}

添加动画,设置Sprite对象中的rotation属性是没有作用的,只有改变SpriteMaterial中的rotation才行,它接受的是一个弧度值

addTween(mark, from, to, duration) {
 this.stopRender = false
 const start = {
  x: from[0],
  y: from[1],
  z: from[2]
 }
 const end = {
  x: to[0],
  y: to[1],
  z: to[2]
 }
 const position = JSON.parse(JSON.stringify(start))
 const render = () => {
  if (this.stopRender) {
   return
  }
  TWEEN.update()
  requestAnimationFrame(render)
 };
 this.tweenObj = new TWEEN.Tween(position).to(end, duration)
 let obj = this.tweenObj.onUpdate(() => {
  mark.position.set(position.x, position.y, position.z)
  let coord1 = this.get2DCoordFrom3DCoord(from) // 将三维坐标转为平面坐标
  let coord2 = this.get2DCoordFrom3DCoord(to)
  let angle = this.getAngle(coord1, coord2)
  // 当处于第一个线段或者下一个角度与上一次保存的角度大于1°时就改变旋转角
  if (!this.latestAngle || Math.abs(this.latestAngle - angle) > (Math.PI / 180)) {
   this.latestAngle = angle
   mark.material.rotation = this.latestAngle
 }
 }).easing(TWEEN.Easing.Linear.None).onStop(() => {
  this.stopRender = true
 })
 render()
 return obj
}

这里参考了经纬度求距离求与正北方向的夹角(方向角)的博客,其实这里是不是经纬度没关系,可以传入屏幕坐标来计算

getAngle(v1, v2) {
 let vector1 = this.initObj(v1[0], v1[1])
 let vector2 = this.initObj(v2[0], v2[1])
 let dx = (vector2.m_RadLo - vector1.m_RadLo) * vector1.Ed
 let dy = (vector2.m_RadLa - vector1.m_RadLa) * vector1.Ec
 let angle = 0.0
 angle = Math.atan(Math.abs(dx / dy)) * 180 / Math.PI
 let dLo = vector2.m_Longitude - vector1.m_Longitude
 let dLa = vector2.m_Latitude - vector1.m_Latitude
 if (dLo > 0 && dLa <= 0) {
  angle = (90 - angle) + 90
 } else if (dLo <= 0 && dLa < 0) {
  angle = angle + 180
 } else if (dLo < 0 && dLa >= 0) {
  angle = (90 - angle) + 270
 }
 // 所有的角度算出后都会加上180并模360,因为一开始的方向是相反的
 angle = (angle + 180) % 360
 return angle * (Math.PI / 180)
}
initObj(longitude, latitude) {
 let obj = {
  Rc: 6378137,
  Rj: 6356725,
  m_LoDeg: 0,
  m_LoMin: 0,
  m_LoSec: 0,
  m_LaDeg: 0,
  m_LaMin: 0,
  m_LaSec: 0,
  m_Longitude: 0,
  m_Latitude: 0,
  m_RadLo: 0,
  m_RadLa: 0,
  Ec: 0,
  Ed: 0
 }
 obj.m_LoDeg = Math.round(longitude)
 obj.m_LoMin = Math.round((longitude - obj.m_LoDeg) * 60)
 obj.m_LoSec = (longitude - obj.m_LoDeg - obj.m_LoMin / 60) * 3600
 obj.m_LaDeg = Math.round(latitude)
 obj.m_LaMin = Math.round((latitude - obj.m_LaDeg) * 60)
 obj.m_LaSec = (latitude - obj.m_LaDeg - obj.m_LaMin / 60) * 3600
 obj.m_Longitude = longitude
 obj.m_Latitude = latitude
 obj.m_RadLo = longitude * Math.PI / 180
 obj.m_RadLa = latitude * Math.PI / 180
 obj.Ec = obj.Rj + (obj.Rc - obj.Rj) * (90 - obj.m_Latitude) / 90
 obj.Ed = obj.Ec * Math.cos(obj.m_RadLa)
 return obj
}

这个是将三维坐标转为平面坐标 

get2DCoordFrom3DCoord(p) {
  let t
  let bornIdList = []
  if (p.isVector3) {
    p.distanceTo(this.camera.position)
    p.project(this.camera)
    bornIdList[0] = (p.x + 1) * this.renderer.domElement.clientWidth / 2
    bornIdList[1] = (1 - p.y) * this.renderer.domElement.clientHeight / 2
    bornIdList[2] = t
    return t = bornIdList
  }
  if ($.isArray(p)) {
    let a = new THREE.Vector3(p[0], p[1], p[2])
    a.distanceTo(this.camera.position)
    a.project(this.camera)
    bornIdList[0] = (a.x + 1) * self.renderer.domElement.clientWidth / 2
    bornIdList[1] = (1 - a.y) * self.renderer.domElement.clientHeight / 2
    bornIdList[2] = t
    a = null
    return t = bornIdList
  }
}
发布了51 篇原创文章 · 获赞 33 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/shelbyandfxj/article/details/99947402