将经纬度坐标投影到自定义 Mapbox 图层中的 Threejs 世界

问题描述 投票:0回答:1

我正在为 Mapbox 开发一个自定义图层,以使用 Three.js 渲染 3D 对象。我的目标是将 3D 对象加载到地图上并在特定的纬度和经度坐标处渲染它。虽然我已成功将对象加载到地图上,但我正在努力将对象放置在不同的经纬度位置。挑战是将纬度、经度坐标投影到 Threejs 世界。

这是我到目前为止编写的代码:

import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { MercatorCoordinate, type Map } from 'mapbox-gl';

export type ModelOptions = {
  location: [number, number];
  rotation: [number, number, number];
  scale: number;
};

export default class Map3DLayer {
  private map: Map;
  private camera: THREE.Camera;
  private scene: THREE.Scene;
  private renderer: THREE.WebGLRenderer;
  private cameraTransform: THREE.Matrix4;
  private scale: number;

  constructor(map: Map) {
    this.map = map;

    // Build the scene
    this.camera = new THREE.Camera();
    this.scene = new THREE.Scene();

    this.renderer = new THREE.WebGLRenderer({
      canvas: map.getCanvas(),
      context: map.getCanvas().getContext('webgl') ?? undefined,
      antialias: true,
    });
    this.renderer.autoClear = false;

    const light = new THREE.AmbientLight(0xffffff, 5);
    this.scene.add(light);

    // Synchronize THREE with Mapbox
    // const center = MercatorCoordinate.fromLngLat(map.getCenter(), 0);
    const center = MercatorCoordinate.fromLngLat([0, 0], 0);
    const { x, y, z = 0 } = center;
    this.scale = center.meterInMercatorCoordinateUnits();

    const scale = new THREE.Matrix4().makeScale(this.scale, this.scale, -this.scale);
    const rotation = new THREE.Matrix4().multiplyMatrices(
      new THREE.Matrix4().makeRotationX(-0.5 * Math.PI),
      new THREE.Matrix4().makeRotationY(Math.PI),
    );

    this.cameraTransform = new THREE.Matrix4()
      .multiplyMatrices(scale, rotation)
      .setPosition(x, y, z);
  }

  loadModel(name: string, path: string, options: ModelOptions) {
    const loader = new GLTFLoader();
    return new Promise<void>((resolve, reject) => {
      loader.load(path, ({ scene }: any) => {
        scene.name = name;
        this.scene.add(scene);
        this.update(name, options);
        resolve();
      }, undefined, reject);
    });
  }

  update(name: string, options: Partial<ModelOptions>) {
    const item = this.scene.getObjectByName(name);
    if (!item) return;
    const { location, rotation, scale } = options;
    if (rotation) item.rotation.set(...rotation);
    if (scale) item.scale.set(scale, scale, scale);
    if (location) {
      const { x, y, z = 0 } = MercatorCoordinate.fromLngLat(location, 0);
      item.position.set(x, y, z);
    }
    this.renderer.state.reset();
    this.renderer.render(this.scene, this.camera);
    this.map.triggerRepaint();
  }

  render(ctx: WebGLRenderingContext, matrix: number[]) {
    this.camera.projectionMatrix = new THREE.Matrix4()
      .fromArray(matrix)
      .multiply(this.cameraTransform);
    this.renderer.resetState();
    this.renderer.render(this.scene, this.camera);
  }
}

我尝试使用 Mapbox 中的 MercatorCooperative.fromLngLat 函数将 Three.js 的 lat、lon 转换为 x、y、z 坐标,但对象没有出现在预期位置。我知道这会将坐标转换为 0-1 范围,并尝试使用计算出的比例将对象移动到正确的位置,但运气不佳。

预期行为: 3D 对象应在指定的经纬度坐标处渲染。

实际行为: 对象已渲染,但未处于所需的纬度、经度坐标。

任何有关如何将经纬度坐标正确投影到 Threejs 世界的见解或建议将不胜感激!

three.js 3d coordinates mapbox mapbox-gl-js
1个回答
0
投票

这是一段稍作修改的工作代码:

import { CustomLayerInterface, Map, MercatorCoordinate } from 'mapbox-gl';
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';

export class ExternalModelDefinition {
    public url: string;
    public origin: [number, number];
    public rotation: Array<any>;
    public altitude: number;
    public scaleFactor: number;
}

function add3DModelToMap(externalModelDefinition: ExternalModelDefinition, map: Map) {
    const modelAsMercatorCoordinate: MercatorCoordinate = MercatorCoordinate.fromLngLat(externalModelDefinition.origin, externalModelDefinition.altitude);
    const modelScale = modelAsMercatorCoordinate.meterInMercatorCoordinateUnits() * externalModelDefinition.scaleFactor;

    // transformation parameters to position, rotate and scale the 3D model onto the map
    const modelTransform = {
        translateX: modelAsMercatorCoordinate.x,
        translateY: modelAsMercatorCoordinate.y,
        translateZ: modelAsMercatorCoordinate.z,
        rotateX: externalModelDefinition.rotation[0],
        rotateY: externalModelDefinition.rotation[1],
        rotateZ: externalModelDefinition.rotation[2],
        /* Since the 3D model is in real world meters, a scale transform needs to be
         * applied since the CustomLayerInterface expects units in MercatorCoordinates.
         */
        scale: modelScale,
    };

    //const THREE = window['THREE'];

    const camera = new THREE.Camera();
    const scene = new THREE.Scene();
    let renderer = new THREE.WebGLRenderer();

    // create two three.js lights to illuminate the model
    const directionalLight = new THREE.DirectionalLight(0xffffff);
    directionalLight.position.set(0, -70, 100).normalize();
    scene.add(directionalLight);

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

    const customLayer: CustomLayerInterface = {
        id: 'your-custom-3D-object-layer-id',
        type: 'custom',
        renderingMode: '3d',
        onAdd: (map, gl) => {
            // use the three.js GLTF loader to add the 3D model to the three.js scene
            const loader = new GLTFLoader();
            loader.load(
                externalModelDefinition.url,
                (gltf) => {
                    scene.add(gltf.scene);
                },
                (progressEvent: ProgressEvent) => {},
                (error) => {
                    console.log(error);
                }
            );

            // use the Mapbox GL JS map canvas for three.js
            renderer = new THREE.WebGLRenderer({
                canvas: map.getCanvas(),
                context: gl,
                antialias: true,
            });

            renderer.autoClear = false;
        },
        render: (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 as number)
                .scale(new THREE.Vector3(modelTransform.scale, -modelTransform.scale, modelTransform.scale))
                .multiply(rotationX)
                .multiply(rotationY)
                .multiply(rotationZ);

            renderer.autoClear = false;
            camera.projectionMatrix = m.multiply(l);

            renderer.resetState();
            renderer.render(scene, camera);

            this.map.triggerRepaint();
        },
    };

    map.addLayer(customLayer);
    map.setLayoutProperty(customLayer.id, 'visibility', 'visible');
}

这必须提供足够的指导来实现它🙌🏼祝你好运!

© www.soinside.com 2019 - 2024. All rights reserved.