threebox "projectToWorld" returns 值超过 canvas,我该如何解决这个问题? (附示例代码)

threebox "projectToWorld" returns values exceeding canvas, how do I fix this? (with sample code)

我最近发现三框有一个非常方便的方法可以在地图上放置three.js个对象,就是 "projectToworld".

在尝试使用该方法放置我的 three.js 对象时, 我意识到 Vector3 方法 returns 真的很大而且不在地图上。

根据threebox的文档,它说

projectToWorld

tb.projectToWorld(lnglat) : THREE.Vector3

Calculate the corresponding THREE.Vector3 for a given lnglat. It's inverse method is tb.unprojectFromWorld.

所以我决定用这个方法在三个js中定位我的动画对象canvas。 但是什么方法returns真的很庞大

正如我所预料的那样,这些值并未将这三个对象放置在地图上并且所有对象都消失了,因为它们可能被放置在非常远的位置。

我该如何解决这个问题?

我做了一个最小的代码来演示这个问题,如下所示。

  1. instantiating map
var viewOrigin = [-73.8, 40.7];

var map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/jotnajoa/ckpj6g4ho3nvf18pg0r98pjes/draft',
    zoom: 12,
    center: viewOrigin,
    pitch: 60,
    antialias: true // create the gl context with MSAA antialiasing, so custom layers are antialiased
});

var modelAltitude = 0;
  1. Set mapboxgl
function translateCoords(lon, lat) {

    let destination = [lon, lat];
    let finalCoord = mapboxgl.MercatorCoordinate.fromLngLat(destination, 0);

    return finalCoord
}
var modelAsMercatorCoordinate = translateCoords(viewOrigin[0], viewOrigin[1]);

var modelTransform = {
    translateX: modelAsMercatorCoordinate.x,
    translateY: modelAsMercatorCoordinate.y,
    translateZ: modelAsMercatorCoordinate.z,
    scale: modelAsMercatorCoordinate.meterInMercatorCoordinateUnits()
};
  1. set variables for three.js render outside of mapboxgl because I need to access it outside of mapboxgl lifecycle hook.
const camera = new THREE.Camera();
const scene = new THREE.Scene();

// Setting map, canvas, renderer variable from the outside to access that from outside later

var map;
var canvas;
var renderer;
let particleGroup;
var frameCount = 0;  
 
  1. Instantiate mapboxgl customlayer
function generateMap() {

    var customLayer = {
        id: '3d-model',
        type: 'custom',
        renderingMode: '3d',
        onAdd: function(map, gl) {
            map = map;
            canvas = map.getCanvas()
            renderer = new THREE.WebGLRenderer({
                canvas: map.getCanvas(),
                context: gl,
                antialias: true,
                preserveDrawingBuffer: true
            });
            renderer.autoClear = false;
        },
        render: function(gl, matrix) {
            frameCount += 0.1;
            rotate(frameCount)
            var m = new THREE.Matrix4().fromArray(matrix);

            var l = new THREE.Matrix4()
                .makeTranslation(
                    modelTransform.translateX,
                    modelTransform.translateY,
                    modelTransform.translateZ
                )
                .scale(
                    new THREE.Vector3(
                        modelTransform.scale, -modelTransform.scale,
                        modelTransform.scale
                    )
                )

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

        }
    };

    map.on('style.load', function() {
        map.addLayer(customLayer, 'waterway-label');
    });

}
  1. Draw constantly revolving sphere
function fillingSpheres(group) {
    const meshGroup = new THREE.Group();

    const { count } = group.geometry.attributes.position
    const { array } = group.geometry.attributes.position

    for (let i = 0; i < count; i++) {

        let i3 = i * 3;
        const x = array[i3]
        const y = array[i3 + 1]
        const z = array[i3 + 2];
        const posVec = new THREE.Vector3(x, y, z);

        const circleGeo = new THREE.SphereGeometry(10, 10, 10);
        const circleMtl = new THREE.MeshBasicMaterial({ color: 'green' });
        const circleMesh = new THREE.Mesh(circleGeo, circleMtl);
        circleMesh.userData.destination = posExample[i % 10];
        circleMesh.position.set(posVec.x, posVec.y, posVec.z)
        meshGroup.add(circleMesh)
    };
    console.log(meshGroup)
    return meshGroup;

}
  1. Move the spheres to the corresponding location on the map (This is where the issue occurs)

首先,我根据当前地图设置实例化了tb对象

function setThreeBox() {

    window.tb = new Threebox(
        map,
        map.getCanvas().getContext('webgl'), {
            realSunlight: true,
            enableSelectingObjects: true, //change this to false to disable 3D objects selection
            enableTooltips: true, // change this to false to disable default tooltips on fill-extrusion and 3D models
        }
    );
    tb.altitudeStep = 1;
}

其次,通过projectToWorld获取坐标并定位

function moveSphere() {

    // instantiate threebox object based on map

    setThreeBox()

    const { children } = particleGroup
    children.forEach((d) => {
        const { destination } = d.userData;
        const coordVec = tb.projectToWorld([destination.lng, destination.lat])
        console.log(coordVec)
        d.position.set(coordVec)
    })
}

然后,所有的坐标都像x:200000,不再显示场景中的三个物体

我做错了什么?

之所以基于three.js而不是单单基于三盒,是因为无法在三盒中说明three.js坐标space上的旋转圆。

运行代码在下面link.

https://codepen.io/jotnajoa/pen/poeKoOW (由于某些原因,示例在刷新代码后是运行,打开后不会立即启动)

如果有懂三盒的朋友能帮我解决一下,将不胜感激。 我认为如果我可以在只有三框的环境中直接访问 scene() 对象,那么解决这个问题会更容易。如果可以访问,我可以轻松地在三框中制作基于帧的动画。 (也许我错了)

点击按钮时触发放置。

奇怪的是没有人能回答这个问题。 所以我终于想出了自己做的方法。

解决方法如下link.

主要原因是 mapbox 绘制的东西不是基于其矢量配置。它通过其矩阵呈现事物如下。

var m = new THREE.Matrix4().fromArray(矩阵); var l = new THREE.Matrix4().makeTranslation(modelTransform.translateX, modelTransform.translateY, modelTransform.translateZ) .scale(new THREE.Vector3(modelTransform.scale, -modelTransform.scale, modelTransform.scale))

            sphere.position.y = 0.2;

            camera.projectionMatrix.elements = matrix;
            camera.projectionMatrix = m.multiply(l);
            renderer.state.reset();
            renderer.render(scene, camera);

https://codepen.io/jotnajoa/pen/BaWGBoL?editors=1010