3D模型移动

75 阅读4分钟

如何将轨迹回放中的小车图层改成3D模型呢?

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title>Add a 3D model</title>
  <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
  <link href="https://api.mapbox.com/mapbox-gl-js/v3.7.0/mapbox-gl.css" rel="stylesheet">
  <script src="https://api.mapbox.com/mapbox-gl-js/v3.7.0/mapbox-gl.js"></script>
  <style>
    body {
      margin: 0;
      padding: 0;
    }

    #map {
      position: absolute;
      top: 0;
      bottom: 0;
      width: 100%;
    }
  </style>
</head>

<body>
  <script src="https://unpkg.com/three@0.126.0/build/three.min.js"></script>
  <script src="https://unpkg.com/three@0.126.0/examples/js/loaders/GLTFLoader.js"></script>
  <div id="map"></div>
  <script>
    mapboxgl.accessToken = 'pk.eyJ1Ijoiaml5ZXdlbiIsImEiOiJjbG5qeW9nZTIxbmtlMm1ybHVzMW04djh5In0._6aML1rN-1KVGurR9aIVCg'; // 替换为你的 Mapbox 访问令牌
    const map = new mapboxgl.Map({
      container: 'map',
      style: 'mapbox://styles/mapbox/light-v11',
      zoom: 18,
      center: [148.9819, -35.3981],
      pitch: 60,
      antialias: true
    });

    const modelOrigin = [148.9819, -35.39847];
    const modelAltitude = 0;
    const modelRotate = [Math.PI / 2, 0, 0];

    const modelAsMercatorCoordinate = mapboxgl.MercatorCoordinate.fromLngLat(
      modelOrigin,
      modelAltitude
    );

    const modelTransform = {
      translateX: modelAsMercatorCoordinate.x,
      translateY: modelAsMercatorCoordinate.y,
      translateZ: modelAsMercatorCoordinate.z,
      rotateX: modelRotate[0],
      rotateY: modelRotate[1],
      rotateZ: modelRotate[2],
      scale: modelAsMercatorCoordinate.meterInMercatorCoordinateUnits()
    };

    const THREE = window.THREE;

    const customLayer = {
      id: '3d-model',
      type: 'custom',
      renderingMode: '3d',
      onAdd: function (map, gl) {
        this.camera = new THREE.Camera();
        this.scene = new THREE.Scene();

        const directionalLight = new THREE.DirectionalLight(0xffffff);
        directionalLight.position.set(0, -70, 100).normalize();
        this.scene.add(directionalLight);

        const directionalLight2 = new THREE.DirectionalLight(0xffffff);
        directionalLight2.position.set(0, 70, 100).normalize();
        this.scene.add(directionalLight2);

        const loader = new THREE.GLTFLoader();
        loader.load(
          'https://docs.mapbox.com/mapbox-gl-js/assets/34M_17/34M_17.gltf',
          (gltf) => {
            this.model = gltf.scene; // 将模型存储在 this.model 中
            this.scene.add(this.model);
          }
        );

        this.map = map;

        this.renderer = new THREE.WebGLRenderer({
          canvas: map.getCanvas(),
          context: gl,
          antialias: true
        });

        this.renderer.autoClear = false;

        // 添加地图点击事件=》animate下一个点
        map.on('click', (e) => {
          const clickedLngLat = [e.lngLat.lng, e.lngLat.lat];
          const clickedMercatorCoordinate = mapboxgl.MercatorCoordinate.fromLngLat(
            clickedLngLat,
            modelAltitude
          );

          // 计算模型的目标位置
          const targetPosition = {
            x: clickedMercatorCoordinate.x,
            y: clickedMercatorCoordinate.y,
            z: clickedMercatorCoordinate.z
          };

          // 平滑移动模型到目标位置
          this.moveModelTo(targetPosition);
        });
      },
      moveModelTo: function (targetPosition) {
        const duration = 1000; // 动画持续时间
        const steps = 60; // 动画步骤
        const stepTime = duration / steps;
        const originalPosition = {
          x: modelTransform.translateX,
          y: modelTransform.translateY,
          z: modelTransform.translateZ
        };

        let stepCount = 0;
        const animate = () => {
          if (stepCount < steps) {
            const progress = stepCount / steps;

            // 使用线性插值计算新位置
            modelTransform.translateX = originalPosition.x + (targetPosition.x - originalPosition.x) * progress;
            modelTransform.translateY = originalPosition.y + (targetPosition.y - originalPosition.y) * progress;
            modelTransform.translateZ = originalPosition.z + (targetPosition.z - originalPosition.z) * progress;

            stepCount++;
            requestAnimationFrame(animate);
          }
        };
        animate();
      },
      render: function (gl, matrix) {
        const rotationX = new THREE.Matrix4().makeRotationAxis(
          new THREE.Vector3(1, 0, 0),
          modelTransform.rotateX
        );
        const rotationY = new THREE.Matrix4().makeRotationAxis(
          new THREE.Vector3(0, 1, 0),
          modelTransform.rotateY
        );
        const rotationZ = new THREE.Matrix4().makeRotationAxis(
          new THREE.Vector3(0, 0, 1),
          modelTransform.rotateZ
        );

        const m = new THREE.Matrix4().fromArray(matrix);
        const l = new THREE.Matrix4()
          .makeTranslation(
            modelTransform.translateX,
            modelTransform.translateY,
            modelTransform.translateZ
          )
          .scale(
            new THREE.Vector3(
              modelTransform.scale,
              -modelTransform.scale,
              modelTransform.scale
            )
          )
          .multiply(rotationX)
          .multiply(rotationY)
          .multiply(rotationZ);

        this.camera.projectionMatrix = m.multiply(l);
        this.renderer.resetState();
        this.renderer.render(this.scene, this.camera);
        this.map.triggerRepaint();
      }
    };

    map.on('style.load', () => {
      map.addLayer(customLayer, 'waterway-label');
    });
  </script>
</body>

</html>
  • 我们再让它生成一个我们需要的场景
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title>Add a 3D model</title>
  <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
  <link href="https://api.mapbox.com/mapbox-gl-js/v3.7.0/mapbox-gl.css" rel="stylesheet">
  <script src="https://api.mapbox.com/mapbox-gl-js/v3.7.0/mapbox-gl.js"></script>
  <style>
    body {
      margin: 0;
      padding: 0;
    }

    #map {
      position: absolute;
      top: 0;
      bottom: 0;
      width: 100%;
    }

    #controls {
      position: fixed;
      z-index: 2;
      top: 10px;
      left: 10px;
      background: white;
      padding: 10px;
      border-radius: 5px;
    }
  </style>
</head>

<body>
  <div id="controls">
    <button id="play">播放</button>
    <button id="pause">暂停</button>
    <button id="reset">重置</button> <!-- 添加重置按钮 -->
  </div>
  <div id="map"></div>
  <script src="https://unpkg.com/three@0.126.0/build/three.min.js"></script>
  <script src="https://unpkg.com/three@0.126.0/examples/js/loaders/GLTFLoader.js"></script>
  <script>
    mapboxgl.accessToken = 'pk.eyJ1Ijoiaml5ZXdlbiIsImEiOiJjbG5qeW9nZTIxbmtlMm1ybHVzMW04djh5In0._6aML1rN-1KVGurR9aIVCg'; // 替换为你的 Mapbox 访问令牌
    const map = new mapboxgl.Map({
      container: 'map',
      style: 'mapbox://styles/mapbox/light-v11',
      zoom: 13,
      center: [114.254338, 30.521912],
    });

    const modelOrigin = [114.254338, 30.521912];
    const modelAltitude = 0;
    const modelRotate = [Math.PI / 2, 0, 0];

    const modelAsMercatorCoordinate = mapboxgl.MercatorCoordinate.fromLngLat(
      modelOrigin,
      modelAltitude
    );

    const modelTransform = {
      translateX: modelAsMercatorCoordinate.x,
      translateY: modelAsMercatorCoordinate.y,
      translateZ: modelAsMercatorCoordinate.z,
      rotateX: modelRotate[0],
      rotateY: modelRotate[1],
      rotateZ: modelRotate[2],
      scale: modelAsMercatorCoordinate.meterInMercatorCoordinateUnits()
    };

    let trajectory = [
      [114.246563, 30.512626],
      [114.246588, 30.512679],
      [114.246609, 30.512735]
    ];
    let currentStep = 0;
    let animationFrameId;
    let isPlaying = false;

    const THREE = window.THREE;

    const customLayer = {
      id: '3d-model',
      type: 'custom',
      renderingMode: '3d',
      onAdd: function (map, gl) {
        this.camera = new THREE.Camera();
        this.scene = new THREE.Scene();

        const directionalLight = new THREE.DirectionalLight(0xffffff);
        directionalLight.position.set(0, -70, 100).normalize();
        this.scene.add(directionalLight);

        const directionalLight2 = new THREE.DirectionalLight(0xffffff);
        directionalLight2.position.set(0, 70, 100).normalize();
        this.scene.add(directionalLight2);

        const loader = new THREE.GLTFLoader();
        loader.load(
          'https://docs.mapbox.com/mapbox-gl-js/assets/34M_17/34M_17.gltf',
          (gltf) => {
            this.model = gltf.scene; // 将模型存储在 this.model 中
            this.scene.add(this.model);
          }
        );

        this.map = map;

        this.renderer = new THREE.WebGLRenderer({
          canvas: map.getCanvas(),
          context: gl,
          antialias: true
        });

        this.renderer.autoClear = false;
      },
      moveModelTo: function (targetPosition) {
        const duration = 1000; // 动画持续时间
        const steps = 60; // 动画步骤
        const stepTime = duration / steps;
        const originalPosition = {
          x: modelTransform.translateX,
          y: modelTransform.translateY,
          z: modelTransform.translateZ
        };

        let stepCount = 0;
        const animate = () => {
          if (stepCount < steps && isPlaying) {
            const progress = stepCount / steps;

            // 使用线性插值计算新位置
            modelTransform.translateX = originalPosition.x + (targetPosition.x - originalPosition.x) * progress;
            modelTransform.translateY = originalPosition.y + (targetPosition.y - originalPosition.y) * progress;
            modelTransform.translateZ = originalPosition.z + (targetPosition.z - originalPosition.z) * progress;

            stepCount++;
            requestAnimationFrame(animate);
          }
        };
        animate();
      },
      render: function (gl, matrix) {
        const rotationX = new THREE.Matrix4().makeRotationAxis(
          new THREE.Vector3(1, 0, 0),
          modelTransform.rotateX
        );
        const rotationY = new THREE.Matrix4().makeRotationAxis(
          new THREE.Vector3(0, 1, 0),
          modelTransform.rotateY
        );
        const rotationZ = new THREE.Matrix4().makeRotationAxis(
          new THREE.Vector3(0, 0, 1),
          modelTransform.rotateZ
        );

        const m = new THREE.Matrix4().fromArray(matrix);
        const l = new THREE.Matrix4()
          .makeTranslation(
            modelTransform.translateX,
            modelTransform.translateY,
            modelTransform.translateZ
          )
          .scale(
            new THREE.Vector3(
              modelTransform.scale,
              -modelTransform.scale,
              modelTransform.scale
            )
          )
          .multiply(rotationX)
          .multiply(rotationY)
          .multiply(rotationZ);

        this.camera.projectionMatrix = m.multiply(l);
        this.renderer.resetState();
        this.renderer.render(this.scene, this.camera);
        this.map.triggerRepaint();
      }
    };

    function updateModelPosition() {
      if (isPlaying && currentStep < trajectory.length) {
        const lngLat = trajectory[currentStep];
        const targetMercatorCoordinate = mapboxgl.MercatorCoordinate.fromLngLat(lngLat, modelAltitude);
        modelTransform.translateX = targetMercatorCoordinate.x;
        modelTransform.translateY = targetMercatorCoordinate.y;
        modelTransform.translateZ = targetMercatorCoordinate.z;

        currentStep++;
      } else {
        // 停止动画
        isPlaying = false;
        cancelAnimationFrame(animationFrameId);
      }

      animationFrameId = requestAnimationFrame(updateModelPosition);
    }

    document.getElementById('play').onclick = function () {
      if (!isPlaying) {
        isPlaying = true;
        updateModelPosition(); // 开始更新模型位置
        currentStep = currentStep < trajectory.length ? currentStep : 0; // 从当前步骤开始播放
      }
    };

    document.getElementById('pause').onclick = function () {
      isPlaying = false; // 暂停播放
      cancelAnimationFrame(animationFrameId); // 取消动画帧
    };

    document.getElementById('reset').onclick = function () {
      isPlaying = false; // 停止播放
      cancelAnimationFrame(animationFrameId); // 取消动画帧
      currentStep = 0; // 重置当前步骤
      const startLngLat = trajectory[0];
      const targetMercatorCoordinate = mapboxgl.MercatorCoordinate.fromLngLat(startLngLat, modelAltitude);
      modelTransform.translateX = targetMercatorCoordinate.x;
      modelTransform.translateY = targetMercatorCoordinate.y;
      modelTransform.translateZ = targetMercatorCoordinate.z; // 回到起始位置
    };

    map.on('style.load', () => {
      map.addLayer(customLayer, 'waterway-label');
    });
  </script>
</body>

</html>
  • 转成tsx
import React, { useEffect, useRef, useState } from 'react';
import mapboxgl from 'mapbox-gl';
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { getDistance2point } from '@/utils/common'

mapboxgl.accessToken = 'pk.eyJ1Ijoiaml5ZXdlbiIsImEiOiJjbG5qeW9nZTIxbmtlMm1ybHVzMW04djh5In0._6aML1rN-1KVGurR9aIVCg';

const App: React.FC = () => {
  const mapRef = useRef<mapboxgl.Map | null>(null);
  const isPlaying = useRef(false)
  const currentStep = useRef(0)
  const animationFrameId = useRef<number | null>(null)
  const trajectory: [number, number][] = [
    [
      110.17113,
      20.051918
    ],
    [
      110.171129,
      20.051918
    ],
    [
      110.171128,
      20.051919
    ],
    [
      110.171128,
      20.051918
    ],
    [
      110.171128,
      20.051919
    ],
    [
      110.171128,
      20.051918
    ],
    [
      110.171128,
      20.051919
    ],
    [
      110.171126,
      20.05192
    ],
    [
      110.171124,
      20.05192
    ],
    [
      110.171122,
      20.051919
    ],
    [
      110.17112,
      20.05192
    ],
    [
      110.171118,
      20.051919
    ],
    [
      110.171117,
      20.05192
    ],
    [
      110.171115,
      20.051923
    ],
    [
      110.171107,
      20.051931
    ],
    [
      110.1711,
      20.051934
    ],
    [
      110.171086,
      20.051936
    ],
    [
      110.17107,
      20.051931
    ],
    [
      110.171059,
      20.051924
    ],
    [
      110.171056,
      20.051904
    ],
    [
      110.171041,
      20.051891
    ],
    [
      110.171013,
      20.051862
    ],
    [
      110.170985,
      20.051836
    ],
    [
      110.170951,
      20.051807
    ],
    [
      110.170915,
      20.051773
    ],
    [
      110.170884,
      20.051745
    ],
    [
      110.170838,
      20.0517
    ],
    [
      110.170799,
      20.051662
    ],
    [
      110.170761,
      20.051623
    ],
    [
      110.170723,
      20.051586
    ],
    [
      110.170691,
      20.05156
    ],
    [
      110.170651,
      20.051535
    ],
    [
      110.170601,
      20.051521
    ],
    [
      110.170561,
      20.051521
    ],
    [
      110.170541,
      20.051526
    ],
    [
      110.170521,
      20.051518
    ],
    [
      110.170494,
      20.051493
    ],
    [
      110.170444,
      20.051471
    ],
    [
      110.170448,
      20.051425
    ],
    [
      110.170492,
      20.051374
    ],
    [
      110.170477,
      20.051342
    ],
    [
      110.170468,
      20.051282
    ],
    [
      110.170452,
      20.051249
    ],
    [
      110.170446,
      20.05123
    ],
    [
      110.17044,
      20.051223
    ],
    [
      110.170437,
      20.051222
    ],
    [
      110.170437,
      20.05122
    ],
    [
      110.170436,
      20.051219
    ],
    [
      110.170435,
      20.051214
    ],
    [
      110.170433,
      20.051195
    ],
    [
      110.170434,
      20.051172
    ],
    [
      110.170439,
      20.051149
    ],
    [
      110.170453,
      20.051119
    ],
    [
      110.170475,
      20.051091
    ],
    [
      110.170506,
      20.051066
    ],
    [
      110.170548,
      20.051048
    ],
    [
      110.170599,
      20.051037
    ],
    [
      110.170673,
      20.051036
    ],
    [
      110.170729,
      20.051037
    ],
    [
      110.170806,
      20.051037
    ],
    [
      110.170906,
      20.051036
    ],
    [
      110.170993,
      20.051032
    ],
    [
      110.171063,
      20.05103
    ],
    [
      110.171151,
      20.051028
    ],
    [
      110.171241,
      20.051028
    ],
    [
      110.171333,
      20.05103
    ],
    [
      110.17144,
      20.051032
    ],
    [
      110.17151,
      20.051032
    ],
    [
      110.171596,
      20.051034
    ],
    [
      110.171682,
      20.051036
    ],
    [
      110.171783,
      20.051041
    ],
    [
      110.171865,
      20.051044
    ],
    [
      110.171931,
      20.051047
    ],
    [
      110.172017,
      20.051048
    ],
    [
      110.17212,
      20.051047
    ],
    [
      110.172207,
      20.051045
    ],
    [
      110.172273,
      20.051044
    ],
    [
      110.172355,
      20.051043
    ],
    [
      110.172438,
      20.051039
    ],
    [
      110.172538,
      20.051037
    ],
    [
      110.172587,
      20.051036
    ],
    [
      110.172693,
      20.051037
    ],
    [
      110.17275,
      20.051038
    ],
    [
      110.172822,
      20.051039
    ],
    [
      110.172906,
      20.051038
    ],
    [
      110.172973,
      20.051039
    ],
    [
      110.173036,
      20.05104
    ],
    [
      110.173086,
      20.051042
    ],
    [
      110.173146,
      20.051043
    ],
    [
      110.17322,
      20.051042
    ],
    [
      110.173284,
      20.051041
    ],
    [
      110.173349,
      20.051041
    ],
    [
      110.173412,
      20.051041
    ],
    [
      110.173462,
      20.051041
    ],
    [
      110.173538,
      20.051042
    ],
    [
      110.1736,
      20.051043
    ],
    [
      110.17366,
      20.051043
    ],
    [
      110.173702,
      20.051044
    ],
    [
      110.173749,
      20.051044
    ],
    [
      110.173802,
      20.051045
    ],
    [
      110.173841,
      20.051045
    ],
    [
      110.173875,
      20.051047
    ],
    [
      110.173906,
      20.051046
    ],
    [
      110.173952,
      20.051047
    ],
    [
      110.174004,
      20.051049
    ],
    [
      110.174038,
      20.051051
    ],
    [
      110.174141,
      20.051048
    ],
    [
      110.1742,
      20.051047
    ],
    [
      110.17428,
      20.051045
    ],
    [
      110.174383,
      20.051043
    ],
    [
      110.174471,
      20.051043
    ],
    [
      110.17456,
      20.051043
    ],
    [
      110.174647,
      20.051044
    ],
    [
      110.174728,
      20.051046
    ],
    [
      110.174788,
      20.051048
    ],
    [
      110.174868,
      20.051052
    ],
    [
      110.174925,
      20.051055
    ],
    [
      110.174973,
      20.051057
    ],
    [
      110.175007,
      20.051059
    ],
    [
      110.175056,
      20.051059
    ],
    [
      110.175091,
      20.05106
    ],
    [
      110.175145,
      20.051064
    ],
    [
      110.175192,
      20.051076
    ],
    [
      110.175239,
      20.051097
    ],
    [
      110.175273,
      20.051122
    ],
    [
      110.175308,
      20.051165
    ],
    [
      110.175334,
      20.051234
    ],
    [
      110.175338,
      20.051305
    ],
    [
      110.175334,
      20.051381
    ],
    [
      110.175329,
      20.051461
    ],
    [
      110.175327,
      20.051538
    ],
    [
      110.175325,
      20.05161
    ],
    [
      110.175324,
      20.051677
    ],
    [
      110.175323,
      20.05174
    ],
    [
      110.175322,
      20.051791
    ],
    [
      110.175322,
      20.051873
    ],
    [
      110.175322,
      20.051948
    ],
    [
      110.175322,
      20.052014
    ],
    [
      110.175324,
      20.052101
    ],
    [
      110.175326,
      20.052209
    ],
    [
      110.175326,
      20.0523
    ],
    [
      110.175327,
      20.05239
    ],
    [
      110.175326,
      20.052462
    ],
    [
      110.175326,
      20.052552
    ],
    [
      110.175326,
      20.052659
    ],
    [
      110.175326,
      20.052745
    ],
    [
      110.175327,
      20.052825
    ],
    [
      110.175328,
      20.052897
    ],
    [
      110.175329,
      20.052952
    ],
    [
      110.175329,
      20.053031
    ],
    [
      110.175328,
      20.053096
    ],
    [
      110.175329,
      20.05315
    ],
    [
      110.17533,
      20.053236
    ],
    [
      110.175331,
      20.053311
    ],
    [
      110.175332,
      20.053376
    ],
    [
      110.175334,
      20.053482
    ],
    [
      110.175334,
      20.05354
    ],
    [
      110.175336,
      20.053686
    ],
    [
      110.175342,
      20.053794
    ],
    [
      110.175346,
      20.053884
    ],
    [
      110.175347,
      20.054022
    ],
    [
      110.175341,
      20.054142
    ],
    [
      110.175335,
      20.054241
    ],
    [
      110.175328,
      20.054367
    ],
    [
      110.175321,
      20.054522
    ],
    [
      110.175318,
      20.054653
    ],
    [
      110.175316,
      20.054761
    ],
    [
      110.175315,
      20.054893
    ],
    [
      110.175314,
      20.055049
    ],
    [
      110.175314,
      20.055126
    ],
    [
      110.175313,
      20.0553
    ],
    [
      110.175313,
      20.055397
    ],
    [
      110.175313,
      20.055543
    ],
    [
      110.175315,
      20.055661
    ],
    [
      110.175317,
      20.055776
    ],
    [
      110.175321,
      20.055888
    ],
    [
      110.175327,
      20.055997
    ],
    [
      110.175328,
      20.056081
    ],
    [
      110.175324,
      20.056185
    ],
    [
      110.175319,
      20.056286
    ],
    [
      110.175315,
      20.056402
    ],
    [
      110.175313,
      20.056486
    ],
    [
      110.175312,
      20.056537
    ],
    [
      110.175312,
      20.056578
    ],
    [
      110.175312,
      20.056598
    ],
    [
      110.175312,
      20.0566
    ],
    [
      110.175312,
      20.056598
    ],
    [
      110.175312,
      20.056601
    ],
    [
      110.175312,
      20.056618
    ],
    [
      110.175315,
      20.056651
    ],
    [
      110.175325,
      20.056685
    ],
    [
      110.175354,
      20.05673
    ],
    [
      110.175403,
      20.056769
    ],
    [
      110.175488,
      20.0568
    ],
    [
      110.175575,
      20.056813
    ],
    [
      110.175675,
      20.056816
    ],
    [
      110.175762,
      20.056818
    ],
    [
      110.175878,
      20.056818
    ],
    [
      110.176027,
      20.056815
    ],
    [
      110.176156,
      20.056814
    ],
    [
      110.176262,
      20.056815
    ],
    [
      110.176399,
      20.056817
    ],
    [
      110.17654,
      20.056818
    ],
    [
      110.176714,
      20.056818
    ],
    [
      110.176862,
      20.056817
    ],
    [
      110.177011,
      20.056817
    ],
    [
      110.17716,
      20.056818
    ],
    [
      110.17731,
      20.056819
    ],
    [
      110.177429,
      20.056819
    ],
    [
      110.177579,
      20.056819
    ],
    [
      110.17773,
      20.056819
    ],
    [
      110.177882,
      20.056819
    ],
    [
      110.178063,
      20.056819
    ],
    [
      110.178215,
      20.05682
    ],
    [
      110.178337,
      20.056821
    ],
    [
      110.178491,
      20.056823
    ],
    [
      110.178648,
      20.056824
    ],
    [
      110.178839,
      20.056826
    ],
    [
      110.179,
      20.056826
    ],
    [
      110.179131,
      20.056827
    ],
    [
      110.179299,
      20.056827
    ],
    [
      110.179469,
      20.056827
    ],
    [
      110.179607,
      20.056828
    ],
    [
      110.179854,
      20.056829
    ],
    [
      110.180032,
      20.056828
    ],
    [
      110.180175,
      20.056828
    ],
    [
      110.180391,
      20.056829
    ],
    [
      110.180567,
      20.05683
    ],
    [
      110.18074,
      20.05683
    ],
    [
      110.180909,
      20.05683
    ],
    [
      110.181041,
      20.056831
    ],
    [
      110.181234,
      20.056833
    ],
    [
      110.181389,
      20.056835
    ],
    [
      110.181536,
      20.056836
    ],
    [
      110.181649,
      20.056836
    ],
    [
      110.181783,
      20.056835
    ],
    [
      110.181931,
      20.056834
    ],
    [
      110.182044,
      20.056834
    ],
    [
      110.182145,
      20.056839
    ],
    [
      110.182215,
      20.056843
    ],
    [
      110.182345,
      20.056848
    ],
    [
      110.182361,
      20.056848
    ],
    [
      110.182366,
      20.056848
    ],
    [
      110.182366,
      20.056849
    ],
    [
      110.182368,
      20.056849
    ],
    [
      110.182369,
      20.056848
    ],
    [
      110.18237,
      20.056848
    ],
    [
      110.182372,
      20.056847
    ],
    [
      110.182374,
      20.056847
    ],
    [
      110.182383,
      20.056847
    ],
    [
      110.182399,
      20.056846
    ],
    [
      110.182435,
      20.056846
    ],
    [
      110.182499,
      20.056845
    ]
  ]
  const modelOrigin = [110.17113,
    20.051918];
  const modelScale = 2; // 放大因子,调整此值以放大模型
  const modelAltitude = 0;
  const modelRotate = [Math.PI / 2, 0, 0];
  const modelAsMercatorCoordinate = mapboxgl.MercatorCoordinate.fromLngLat(
    modelOrigin,
    modelAltitude
  );
  const modelTransform = {
    translateX: modelAsMercatorCoordinate.x,
    translateY: modelAsMercatorCoordinate.y,
    translateZ: modelAsMercatorCoordinate.z,
    rotateX: modelRotate[0],
    rotateY: modelRotate[1],
    rotateZ: modelRotate[2],
    scale: modelAsMercatorCoordinate.meterInMercatorCoordinateUnits() * modelScale // 放大模型
  };
  const centerOffset = new THREE.Vector3(); // 用于存储模型重心偏移

  useEffect(() => {
    mapboxgl.accessToken = 'pk.eyJ1Ijoiaml5ZXdlbiIsImEiOiJjbG5qeW9nZTIxbmtlMm1ybHVzMW04djh5In0._6aML1rN-1KVGurR9aIVCg'; // 替换为你的 Mapbox 访问令牌
    const map = new mapboxgl.Map({
      container: 'map',
      style: 'mapbox://styles/mapbox/light-v11',
      zoom: 15,
      center: modelOrigin,
    });
    const customLayer = {
      id: '3d-model',
      type: 'custom',
      renderingMode: '3d',
      onAdd: function (map, gl) {
        this.camera = new THREE.Camera();
        this.scene = new THREE.Scene();
        const frontLight = new THREE.DirectionalLight(0xffffff)
        frontLight.position.set(1, 0, 0) // 前
        this.scene.add(frontLight)
        const backLight = new THREE.DirectionalLight(0xffffff)
        backLight.position.set(-1, 0, 0) // 后
        this.scene.add(backLight)
        const leftLight = new THREE.DirectionalLight(0xffffff)
        leftLight.position.set(0, 1, 0) // 左
        this.scene.add(leftLight)
        const rightLight = new THREE.DirectionalLight(0xffffff)
        rightLight.position.set(0, -1, 0) // 右
        this.scene.add(rightLight)

        const loader = new GLTFLoader();
        loader.load(
          'Alpha5000.glb',
          (gltf) => {
            this.model = gltf.scene; // 将模型存储在 this.model 中
            this.model.scale.set(modelScale, modelScale, modelScale); // 设置模型的放大比例
            this.scene.add(this.model);
            // 计算模型重心
            const box = new THREE.Box3().setFromObject(this.model); // 获取模型的包围盒
            const center = box.getCenter(new THREE.Vector3()); // 计算重心
            centerOffset.copy(center).multiplyScalar(-1); // 计算重心偏移
            this.model.position.add(centerOffset); // 将模型位置移动到重心
          }
        );

        this.map = map;

        this.renderer = new THREE.WebGLRenderer({
          canvas: map.getCanvas(),
          context: gl,
          antialias: true
        });

        this.renderer.autoClear = false;
      },
      render: function (gl: WebGLRenderingContext, matrix: number[]) {
        const rotationX = new THREE.Matrix4().makeRotationAxis(
          new THREE.Vector3(1, 0, 0),
          modelTransform.rotateX
        );
        const rotationY = new THREE.Matrix4().makeRotationAxis(
          new THREE.Vector3(0, 1, 0),
          modelTransform.rotateY
        );
        const rotationZ = new THREE.Matrix4().makeRotationAxis(
          new THREE.Vector3(0, 0, 1),
          modelTransform.rotateZ
        );

        const m = new THREE.Matrix4().fromArray(matrix);
        const l = new THREE.Matrix4()
          .makeTranslation(
            modelTransform.translateX,
            modelTransform.translateY,
            modelTransform.translateZ
          )
          .scale(
            new THREE.Vector3(
              modelTransform.scale,
              -modelTransform.scale,
              modelTransform.scale
            )
          )
          .multiply(rotationX)
          .multiply(rotationY)
          .multiply(rotationZ);

        this.camera.projectionMatrix = m.multiply(l);
        this.renderer.resetState();
        this.renderer.render(this.scene, this.camera);
        this.map.triggerRepaint();
      }
    };
    map.on('style.load', () => {
      map.addLayer(customLayer, 'waterway-label');
    });
    mapRef.current = map
    return () => {
      map.remove(); // 清理地图实例
    };
  }, [])
  function needRotate(coord, coord1) {
    return getDistance2point(coord, coord1) > 0.0002
  }
  function updateModelPosition() {
    if (isPlaying.current && currentStep.current < trajectory.length) {
      const lngLat = trajectory[currentStep.current];
      const targetMercatorCoordinate = mapboxgl.MercatorCoordinate.fromLngLat(lngLat, modelAltitude);
      // 更新模型的平移
      modelTransform.translateX = targetMercatorCoordinate.x;
      modelTransform.translateY = targetMercatorCoordinate.y;
      modelTransform.translateZ = targetMercatorCoordinate.z;
      // 计算当前步骤和下一个步骤之间的方向角
      const nextLngLat = trajectory[currentStep.current + 1];
      if (nextLngLat && needRotate(lngLat, nextLngLat) && currentStep.current < trajectory.length - 1) {
        const deltaLng = nextLngLat[0] - lngLat[0];
        const deltaLat = nextLngLat[1] - lngLat[1];

        // 计算方向角(弧度),注意纬度转换为米
        const angle = Math.atan2(deltaLat, deltaLng);
        modelTransform.rotateY = angle + 2 * Math.PI / 4; // 更新模型的Z轴旋转

      }
      currentStep.current++;
    } else {
      // 停止动画
      isPlaying.current = false;
      cancelAnimationFrame(animationFrameId.current);
    }

    animationFrameId.current = requestAnimationFrame(updateModelPosition);
  }
  const handlePlay = () => {
    if (!isPlaying.current) {
      isPlaying.current = true;
      updateModelPosition(); // 开始更新模型位置
      currentStep.current = currentStep.current < trajectory.length ? currentStep.current : 0; // 从当前步骤开始播放
    }
  };

  const handlePause = () => {
    isPlaying.current = false; // 暂停播放
    cancelAnimationFrame(animationFrameId.current); // 取消动画帧
  };

  const handleReset = () => {
    isPlaying.current = false; // 停止播放
    cancelAnimationFrame(animationFrameId.current); // 取消动画帧
    currentStep.current = 0; // 重置当前步骤
    const startLngLat = trajectory[0];
    const targetMercatorCoordinate = mapboxgl.MercatorCoordinate.fromLngLat(startLngLat, modelAltitude);
    modelTransform.translateX = targetMercatorCoordinate.x;
    modelTransform.translateY = targetMercatorCoordinate.y;
    modelTransform.translateZ = targetMercatorCoordinate.z; // 回到起始位置
  };

  return (
    <div>
      <div id="controls" style={{ position: 'fixed', zIndex: 2, top: '10px', left: '10px', background: 'white', padding: '10px', borderRadius: '5px' }}>
        <button onClick={handlePlay}>播放</button>
        <button onClick={handlePause}>暂停</button>
        <button onClick={handleReset}>重置</button>
      </div>
      <div id='map' style={{ position: 'absolute', top: 0, bottom: 0, width: '100%' }} />
    </div>
  );
};

export default App;