Three.js - 在地图上绘制飞线(二十一)

6,614 阅读3分钟

「这是我参与2022首次更文挑战的第21天,活动详情查看:2022首次更文挑战

简介

在可视化开发中飞线图应该是很常见的一种。为了灵活的使用three.js,本节我们在3D地图上绘制一个简单的飞线图。

绘制

  • 上一节已经绘制好了一个中国地图,我们直接在此基础上开发。

起始点添加动画

  1. 在飞线图的起始点都会有醒目的标识。这里实现一个扩散光圈效果。
  2. 起始点会有很多,我们要创建一个公用方法。
  3. 先在起始点坐标,绘制一个平面圆和一个平面圆环。在渲染函数中对平面圆环放大和透明。
  // 圆环网格对象组
  const circleYs = []
  /**
   * 目标位置
   * */
  function spotCircle(spot) {
    // 圆
    const geometry1 = new THREE.CircleGeometry(0.5, 200)
    const material1 = new THREE.MeshBasicMaterial({ color: 0xff0000, side: THREE.DoubleSide })
    const circle = new THREE.Mesh(geometry1, material1)
    // 绘制地图时 y轴取反 这里同步
    circle.position.set(spot[0], -spot[1], spot[2] + 0.1)
    scene.add(circle)

    // 圆环
    const geometry2 = new THREE.RingGeometry(0.5, 0.7, 50)
    // transparent 设置 true 开启透明
    const material2 = new THREE.MeshBasicMaterial({ color: 0xff0000, side: THREE.DoubleSide, transparent: true })
    const circleY = new THREE.Mesh(geometry2, material2)\
    // 绘制地图时 y轴取反 这里同步
    circleY.position.set(spot[0], -spot[1], spot[2] + 0.1)
    scene.add(circleY)

    circleYs.push(circleY)
  }
  1. 渲染函数中放大圆环同时修改其透明度。
// 渲染函数
function render() {
    circleYs.forEach(function (mesh) {
      // 目标 圆环放大 并 透明
      mesh._s += 0.01
      mesh.scale.set(1 * mesh._s, 1 * mesh._s, 1 * mesh._s)
      if (mesh._s <= 2) {
        mesh.material.opacity = 2 - mesh._s
      } else {
        mesh._s = 1
      }
    })
}

起始点连线

  1. 使用.QuadraticBezierCurve3()创建三维二次贝塞尔曲线。
  2. 使用自定义方法spotCircle()传入三维坐标,绘制目标点
  3. 为了线更好看使用.BufferGeometry()对线的每一个顶点设置颜色,让线有渐变色。
  /**
   * 两点链接飞线
   * */
  function lineConnect(posStart, posEnd) {
    // 根据目标坐标设置3D坐标  z轴位置在地图表面
    const [x0, y0, z0] = [...posStart, 10.01]
    const [x1, y1, z1] = [...posEnd, 10.01]

    // 使用QuadraticBezierCurve3() 创建 三维二次贝塞尔曲线
    const curve = new THREE.QuadraticBezierCurve3(
      new THREE.Vector3(x0, -y0, z0),
      new THREE.Vector3((x0 + x1) / 2, -(y0 + y1) / 2, 20),
      new THREE.Vector3(x1, -y1, z1)
    )

    // 绘制 目标位置
    spotCircle([x0, y0, z0])
    spotCircle([x1, y1, z1])

    const lineGeometry = new THREE.BufferGeometry()
    // 获取曲线 上的50个点
    var points = curve.getPoints(50)
    var positions = []
    var colors = []
    var color = new THREE.Color()

    // 给每个顶点设置演示 实现渐变
    for (var j = 0; j < points.length; j++) {
      color.setHSL(0.81666 + j, 0.88, 0.715 + j * 0.0025) // 粉色
      colors.push(color.r, color.g, color.b)
      positions.push(points[j].x, points[j].y, points[j].z)
    }
    // 放入顶点 和 设置顶点颜色
    lineGeometry.addAttribute('position', new THREE.BufferAttribute(new Float32Array(positions), 3, true))
    lineGeometry.addAttribute('color', new THREE.BufferAttribute(new Float32Array(colors), 3, true))

    const material = new THREE.LineBasicMaterial({ vertexColors: THREE.VertexColors, side: THREE.DoubleSide })
    const line = new THREE.Line(lineGeometry, material)

    return line
  }
  1. 通过上节的projection()方法,转换经纬度为3D坐标。
const line = lineConnect(projection([106.557691, 29.559296]), projection([121.495721, 31.236797]))
scene.add(line)

1.gif

绘制移动物体

  1. 飞线都有一个从起点到终点的移动物体,这里绘制一个简单的几何体。为了好看你也可以加载一个飞机模型在这使用。
  2. 移动物体是在飞线上的,这里需要传入三维二次贝塞尔曲线的实例,在渲染函数中使用。
  // 移动物体网格对象组
  const moveSpots = []
  /**
   * 线上移动物体
   * */
  function moveSpot(curve) {
    // 线上的移动物体
    const aGeo = new THREE.SphereGeometry(0.4, 0.4, 0.4)
    const aMater = new THREE.MeshPhongMaterial({ color: 0xff0000, side: THREE.DoubleSide })
    const aMesh = new THREE.Mesh(aGeo, aMater)
    // 保存曲线实例
    aMesh.curve = curve
    aMesh._s = 0
    scene.add(aMesh)

    moveSpots.push(aMesh)
  }
  1. 在渲染函数中修改移动物体的位置。
function render() {
    ...
    moveSpots.forEach(function (mesh) {
      mesh._s += 0.006
      let tankPosition = new THREE.Vector3()
      tankPosition = mesh.curve.getPointAt(mesh._s % 1)
      mesh.position.set(tankPosition.x, tankPosition.y, tankPosition.z)
    })
    ...
}
  • getPointAt() 根据弧长在曲线上的位置。必须在范围[0,1]内。
  1. 这里使用曲线实例的.getPointAt()方法,获取[0~1]曲线上点的位置。设置给移动物体让其在线上移动。
  2. lineConnect()函数中调用。
function lineConnect(posStart, posEnd) {
    ...
    
    // 绘制 目标位置
    spotCircle([x0, y0, z0])
    spotCircle([x1, y1, z1])
    moveSpot(curve)
    
    ...
}

1.gif

创建多个飞线

  • 公用方法全部创建完毕,多加几条飞线让内容丰富一下。
const line = lineConnect(projection([106.557691, 29.559296]), projection([121.495721, 31.236797]))
scene.add(line)
const line2 = lineConnect(projection([106.557691, 29.559296]), projection([104.006215, 30.650055]))
scene.add(line2)
const line3 = lineConnect(projection([106.557691, 29.559296]), projection([116.396795, 39.93242]))
scene.add(line3)

1.gif