mapbox-gl添加threejs飞线

文章目录

  • 更新
  • 前言
  • 飞线实现
    • 1 初始化地图并加载three图层
    • 2 绘制飞线几何体
      • 将几何体正确定位在mapbox上
      • 正确操作BufferGeometry几何体
    • 3 tween实现动画
  • 全部代码
  • 总结
    • 待改进之处
    • 参考


更新

蝌蚪状飞线效果如下,思路可参照threejs多种方式封装飞线组件


前言

mapbox-gl是一个基于webgl开发的三维地图渲染引擎,但是很多三维特效只用mapbox并不容易实现,比如带高度的飞线,但是使用threejs就有多种实现的方式,本文结合之前对threebox的研究,使用一种使用线图层动画的方式实现带高度的飞线。


飞线实现

1 初始化地图并加载three图层

mapbox中操作threejs建议使用threebox,相比直接操作threejs会简便很多,通过添加一个自定义图层实现。

 mapRef.current = new mapboxgl.Map({
      zoom: 12,
      center: [104.807073, 29.35702],
      pitch: 60,
      style: 'mapbox://styles/mapbox/dark-v11',
      container: mapContainerRef.current,
      antialias: true,
    });
    mapRef.current.on('load', () => {
      mapRef.current.addLayer(
        {
          id: 'custom_layer',
          type: 'custom',
          onAdd: function (map, mbxContext) {
            this.map = map;
            tbRef.current = new Threebox(
              map,
              mbxContext,
              { defaultLights: true }
            );
            let obj3D = draw2();
            tbRef.current.add(obj3D);
          },
          render: function (gl, matrix) {
            if (this.map) {
              this.map.triggerRepaint();
            }
            tbRef.current.update();
            TWEEN.update();
          }
        }
      )
    })

2 绘制飞线几何体

实现动画飞线的效果基本思路很直接:建立路径后,在线上截取一段作为飞痕,设置好颜色后使其沿着路线不断运动即可。在这一步中,关键的有两点:

将几何体正确定位在mapbox上

mapbox是3857坐标系,threejs是右手空间坐标系,在使用three的方法前要将坐标进行转换:

	let startPoint = [104.807073, 29.35702]
    let endPoint = [105.807073, 30.35702]
    // 坐标转换
    const xyz_start = tbRef.current.utils.lnglatsToWorld([[...startPoint, 0]])
    const xyz_end = tbRef.current.utils.lnglatsToWorld([[...endPoint, 0]])
	// 创建3d飞线路径
    const pointInLine = [
      new THREE.Vector3(xyz_start[0].x, xyz_start[0].y, 0),
      new THREE.Vector3((xyz_start[0].x + xyz_end[0].x) / 2, (xyz_start[0].y + xyz_end[0].y) / 2, curveH),
      new THREE.Vector3(xyz_end[0].x, xyz_end[0].y, 0)
    ];

正确操作BufferGeometry几何体

操作几何体参考之前有讲,一些核心方法在本例中常用。

// 为几何体attributes存储顶点信息
flyLineGeom.setFromPoints(pointsList);
// 几何体根据attributes中的position绘制
flyLineGeom.attributes.position.needsUpdate = true;
// 几何体attributes存储颜色信息
flyLineGeom.attributes.color = new THREE.BufferAttribute(new Float32Array(colorArr), 3);
// 几何体根据attributes中的color信息上色
 flyLineGeom.attributes.color.needsUpdate = true;
 // 几何体渲染时颜色采用geometry.attributes.color中对应的颜色
 const material2 = new THREE.LineBasicMaterial({
      vertexColors: THREE.VertexColors, //使用顶点本身颜色
    });

3 tween实现动画

tween可以方便实现动画,每帧跟新飞痕的位置

let tween = new TWEEN.Tween({ index: 1 })
 		.to({ index: 50 }, 2000)
        .onUpdate(function (t) {
            let id = Math.ceil(t.index);
            let pointsList = points.slice(id, id + 10); //从曲线上获取一段
            flyLineGeom && flyLineGeom.setFromPoints(pointsList);
            flyLineGeom.attributes.position.needsUpdate = true;
        }) .repeat(Infinity)
        tween.start();

全部代码

import React, { useRef, useEffect } from 'react';
import mapboxgl from 'mapbox-gl';
import { Threebox, THREE } from 'threebox-plugin';
import TWEEN from '@tweenjs/tween.js'

function App() {
  const mapContainerRef = useRef();
  const tbRef = useRef();
  const mapRef = useRef();

  // 初始化基础图层
  useEffect(() => {
    mapboxgl.accessToken = 'your token'
    mapRef.current = new mapboxgl.Map({
      zoom: 12,
      center: [104.807073, 29.35702],
      pitch: 60,
      style: 'mapbox://styles/mapbox/dark-v11',
      container: mapContainerRef.current,
      antialias: true,
    });
    mapRef.current.on('load', () => {
      mapRef.current.addLayer(
        {
          id: 'custom_layer',
          type: 'custom',
          onAdd: function (map, mbxContext) {
            this.map = map;
            tbRef.current = new Threebox(
              map,
              mbxContext,
              { defaultLights: true }
            );
            let obj3D = draw2();

            console.log(obj3D)
            tbRef.current.add(obj3D);
          },
          render: function (gl, matrix) {
            if (this.map) {
              this.map.triggerRepaint();
            }
            tbRef.current.update();
            TWEEN.update();
          }
        }
      )
    })
  }, []);

  function draw2() {
    const curveH = 800; // 飞线最大高
    const lineGroup = new THREE.Group();
    lineGroup.name = 'lineGroup';
    let startPoint = [104.807073, 29.35702]
    let endPoint = [105.807073, 30.35702]
    const xyz_start = tbRef.current.utils.lnglatsToWorld([[...startPoint, 0]])
    const xyz_end = tbRef.current.utils.lnglatsToWorld([[...endPoint, 0]])

    const pointInLine = [
      new THREE.Vector3(xyz_start[0].x, xyz_start[0].y, 0),
      new THREE.Vector3((xyz_start[0].x + xyz_end[0].x) / 2, (xyz_start[0].y + xyz_end[0].y) / 2, curveH),
      new THREE.Vector3(xyz_end[0].x, xyz_end[0].y, 0)
    ];

    // 创建轨迹线
    const curve = new THREE.CatmullRomCurve3(pointInLine);
    const points = curve.getSpacedPoints(50);
    const lineGeom = new THREE.BufferGeometry().setFromPoints(points);

    const material = new THREE.LineBasicMaterial({
      color:0x006666
    });
    const curveObject = new THREE.Line(lineGeom, material);


    // 创建移动的线
    const index = 20; //取点索引位置
    const num = 10; //从曲线上获取点数量
    const points2 = points.slice(index, index + num); //从曲线上获取一段
    const flyLineGeom = new THREE.BufferGeometry();
    flyLineGeom.setFromPoints(points2);

    // 操作颜色
    const colorArr = [];
    for (let i = 0; i < points2.length; i++) {
      const color1 = new THREE.Color(0x006666); // 线颜色 
      const color2 = new THREE.Color(0xffff00); //飞痕颜色
      // 飞痕渐变色
      let  color = color1.lerp(color2, i / 5)
      colorArr.push(color.r, color.g, color.b);
    }
    // 设置几何体顶点颜色数据
    flyLineGeom.attributes.color = new THREE.BufferAttribute(new Float32Array(colorArr), 3);
    flyLineGeom.attributes.position.needsUpdate = true;

    const material2 = new THREE.LineBasicMaterial({
      vertexColors: THREE.VertexColors, //使用顶点本身颜色
    });
  
    const curveFlyObject = new THREE.Line(flyLineGeom, material2);
    lineGroup.add(curveObject,curveFlyObject)

    // 创建动画
    let tween = new TWEEN.Tween({ index: 1 })
                    .to({ index: 50 }, 2000)
                    .onUpdate(function (t) {
                        let id = Math.ceil(t.index);
                        let pointsList = points.slice(id, id + 10); //从曲线上获取一段
                        flyLineGeom && flyLineGeom.setFromPoints(pointsList);
                        flyLineGeom.attributes.position.needsUpdate = true;
                    })
                    .repeat(Infinity)
                tween.start();

    return lineGroup
  }


  return (
    <div style={{ display: 'flex' }}>
      <div
        id="map-container"
        ref={mapContainerRef}
        style={{ height: '100vh', width: '100vw' }}
      />
      <div style={{ position: 'fixed', top: '0', right: '0' }}>
      </div>
    </div>
  );
}
export default App;

总结

飞线实现

  • 1 初始化地图并加载three图层
  • 2 绘制飞线几何体
  • 3 tween实现动画

待改进之处

本文采用简单的思路实现了在mapbox地图中加入three飞线,但是还有改进方案,比如:

  • 通过更改geometry.attributes.color实现动画效果,不需要额外新建几何体
  • 通过着色器实现更加生动的效果

参考

  • mapboxgl + three.js 开发实践

你可能感兴趣的:(THREE.JS,#,Mapbox,WebGIS,webgl,mapbox,threejs)