Получение неравномерного обнаружения кликов на 3d-объектах на mapbox с помощью raycaster - PullRequest
0 голосов
/ 07 мая 2020

Я использую raycaster в mapbox с 3d-объектами. Мне нужно обнаружить щелчок по трехмерному объекту. Но когда я щелкаю по объекту, я иногда получаю пустой raycaster.intersectObject (массив) и иногда заполненный массив, скорее, он всегда должен иметь трехмерный объект, который я щелкнул.

У меня есть простая карта, как описано здесь . Я добавил к нему только часть Raycasting, чтобы сделать его максимально простым.

Вот мой код.

var longitude, latitude, diff, map, destLongitude, destLatitude, distFrmlast = 0;
var interval = 5000; // initial condition

var assetArr = [];



var modelOrigin, modelOrigin2;
var modelAltitude, modelAltitude2;
var modelRotate, modelRotate2;
var modelAsMercatorCoordinate, modelAsMercatorCoordinate2;
var modelTransform, modelTransform2;
var THREE;
var customLayer, customLayer2;
var previousDistance = 0, currentDistance = 0;
var clock = new THREE.Clock();
var mixer;
var renderer = null;






$(document).ready(function(){

 
  longitude = 77.123643;
  latitude =  28.707272;
  assetArr.push({id:"3d0", cord:{lng:longitude,lat:latitude}, url:'https://docs.mapbox.com/mapbox-gl-js/assets/34M_17/34M_17.gltf', scaleFactor:3, rad: 0.02, origCoord:{lng:longitude,lat:latitude}});
  showmap();


});





function showmap(callback){


    mapboxgl.accessToken = 'pk.eyJ1IjoibWFzaDEyIiwiYSI6ImNrNzBhMHc2aTFhMnkza3J2OG51azV6aW0ifQ.1FeBAzRjqkSQex-u-GiPyw';
    map = new mapboxgl.Map({

        style: 'mapbox://styles/mapbox/streets-v11',
        center: [longitude,latitude],
        zoom: 17.6,
        pitch: 95,
        bearing: -17.6,
        container: 'map',
        antialias: false,
       
       

    });


    var geojson = {
            'type': 'FeatureCollection',
            'features': [
              {
                'type': 'Feature',
                'geometry': {
                  'type': 'Point',
                  'coordinates': [longitude,latitude]
                },
                'properties': {
                  'title': 'Mapbox',
                  'description': 'Park Centra'
                }
              }
            ]
          };




    // The 'building' layer in the mapbox-streets vector source contains building-height
    // data from OpenStreetMap.
    map.on('zoom',function(){

       map.jumpTo({ center: [longitude,latitude] });

    });





    map.on('rotate',function(){
        document.getElementById('info').innerHTML = JSON.stringify(longitude +" : : "+latitude+" : : "+diff);
        map.jumpTo({center: [longitude,latitude]});
    });





    map.on('load', function() {
        // Insert the layer beneath any symbol layer.
        console.log("map loaded");
        var layers = map.getStyle().layers;
        console.log(layers);

        var labelLayerId;
        for (var i = 0; i < layers.length; i++) {
            if (layers[i].type === 'symbol' && layers[i].layout['text-field']) {
                labelLayerId = layers[i].id;
                break;
            }
        }




//        map.addLayer(customLayer, 'road-label');

        map.addLayer(
            {
                'id': '3d-buildings',
                'source': 'composite',
                'source-layer': 'building',
                'filter': ['==', 'extrude', 'true'],
                'type': 'fill-extrusion',
                'minzoom': 15,
                'paint': {
                    'fill-extrusion-color': '#aaa',

                    // use an 'interpolate' expression to add a smooth transition effect to the
                    // buildings as the user zooms in
                    'fill-extrusion-height': [
                        'interpolate',
                        ['linear'],
                        ['zoom'],
                        15,
                        0,
                        15.05,
                        ['get', 'height']
                    ],
                    'fill-extrusion-base': [
                        'interpolate',
                        ['linear'],
                        ['zoom'],
                        15,
                        0,
                        15.05,
                        ['get', 'min_height']
                    ],
                    'fill-extrusion-opacity': 0.6
                }
            },
            labelLayerId

        );
        
        
          model3(assetArr[0].id, assetArr[0].cord, assetArr[0].url , assetArr[0].scaleFactor);
          map.addLayer(customLayer3, 'waterway-label');
        



    });


}











var renderFlag = true,scene, camera;
function model3(Id, coordinates, gltfUrl , scaleFactor){


// parameters to ensure the model is georeferenced correctly on the map
//var modelOrigin = [77.052024, 28.459822];
 console.log("Values ",coordinates.lng, coordinates.lat);
 modelOrigin3 = [coordinates.lng, coordinates.lat];
 modelAltitude3 = 0;
 modelRotate3 = [Math.PI / 2, 0, 0];

 modelAsMercatorCoordinate3 = mapboxgl.MercatorCoordinate.fromLngLat(
    modelOrigin3,
    modelAltitude3
);



// transformation parameters to position, rotate and scale the 3D model onto the map
 modelTransform3 = {
    translateX: modelAsMercatorCoordinate3.x,
    translateY: modelAsMercatorCoordinate3.y,
    translateZ: modelAsMercatorCoordinate3.z,
    rotateX: modelRotate3[0],
    rotateY: modelRotate3[1],
    rotateZ: modelRotate3[2],
    /* Since our 3D model is in real world meters, a scale transform needs to be
    * applied since the CustomLayerInterface expects units in MercatorCoordinates.
    */
    scale: modelAsMercatorCoordinate3.meterInMercatorCoordinateUnits(),
    langlat: modelOrigin3

};

 THREE = window.THREE;

// configuration of the custom layer for a 3D model per the CustomLayerInterface
 customLayer3 = {
    id: Id,
    type: 'custom',
    renderingMode: '3d',
    onAdd: function (map, gl) {

        this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
        this.scene = new THREE.Scene();
        camera = this.camera;
        scene = this.scene;

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

        var light2 = new THREE.AmbientLight( 0xffffff );
        light2.position.set(0, 70, 100).normalize();
        this.scene.add(light2);

        // use the three.js GLTF loader to add the 3D model to the three.js scene
        var loader = new THREE.GLTFLoader();

        loader.load(
            gltfUrl,
            function (gltf) {
                this.scene.add(gltf.scene);
            }.bind(this)
        );









        this.map = map;

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

        this.renderer.autoClear = false;
        if(renderFlag == true){
         renderFlag = false;
         this.renderer.domElement.addEventListener( 'touchend', onClick, false );
        }
    },





    render: function (gl, matrix) {



        var rotationX = new THREE.Matrix4().makeRotationAxis(
            new THREE.Vector3(1, 0, 0),
            modelTransform3.rotateX

        );
        var rotationY = new THREE.Matrix4().makeRotationAxis(
            new THREE.Vector3(0, 1, 0),
            modelTransform3.rotateY

        );
        var rotationZ = new THREE.Matrix4().makeRotationAxis(
            new THREE.Vector3(0, 0, 1),
            modelTransform3.rotateZ

        );

        var m = new THREE.Matrix4().fromArray(matrix);
        var l = new THREE.Matrix4()
            .makeTranslation(
                modelTransform3.translateX,
                modelTransform3.translateY,
                modelTransform3.translateZ


            )
            .scale(
                new THREE.Vector3(
                    modelTransform3.scale*scaleFactor,
                    -modelTransform3.scale*scaleFactor,
                    modelTransform3.scale*scaleFactor


                )
            )
            .multiply(rotationX)
            .multiply(rotationY)
            .multiply(rotationZ);

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

     }
    }

  

};


var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2( Infinity, Infinity );



function onClick( event ) {

      	event.preventDefault();

     	mouse.x = ( event.changedTouches[0].clientX / window.innerWidth ) * 2 - 1;
      	mouse.y = - ( event.changedTouches[0].clientY / window.innerHeight ) * 2 + 1;


        

      	raycaster.setFromCamera( mouse, camera );

      	var intersects = raycaster.intersectObjects( scene.children, true );
        console.log("Here",intersects);
      	if ( intersects.length > 0 ) {
            alert("hi");
      		console.log( 'Intersection:', intersects[ 0 ].object.name == "");

      		}

      	}
<!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" />
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
    <script src="https://api.mapbox.com/mapbox-gl-js/v1.9.1/mapbox-gl.js"></script>
    <link href="https://api.mapbox.com/mapbox-gl-js/v1.9.1/mapbox-gl.css" rel="stylesheet" />
    <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.106.2/build/three.min.js"></script>
<script src="https://unpkg.com/three@0.106.2/examples/js/loaders/GLTFLoader.js"></script>
<div id="map"></div>

<script src = "js/test.js"> </script>
</body>
</html>

Журнал иногда при нажатии сообщает мне это

Here [] (js line 347).

, а иногда это похоже на

    Here 
[{…}]
0:
distance: 45.707979283756266
face: Tb {a: 6, b: 89, c: 23, normal: n, vertexNormals: Array(0), …}
faceIndex: 98
object: ra {uuid: "C0D03F0E-CE94-4B4B-A9B2-C8117268E863", name: "Briks_302", type: "Mesh", parent: G, children: Array(0), …}
point: n {x: 12.70179089111657, y: 2.0773969954241096, z: -43.858503167413765}
uv: C {x: 0.940550558052383, y: 0.42664070999192283}
__proto__: Object
length: 1
__proto__: Array(0)

Почему Я получаю такое неравномерное поведение при нажатии на 3D-объект? Я застрял на этом этапе. Кто-нибудь может мне здесь помочь?

Изменить

Извините за беспорядочный код. На этот раз я попытался удалить нежелательный код. Я попытался переместить матрицу l в функцию onAdd, но, согласно вашему ответу здесь , когда я пытался использовать MercatorCoordinate.fromLngLat, я получил неопределенную ошибку MercatorCoordinate cordinate. Затем я попытался переместить матрицу l в onAdd, как показано ниже, но все же объект пересечения заполняется только на втором объекте, а не на первом. вот обновленный js

var longitude, latitude, diff, map;

var assetArr = [];



var modelOrigin3;
var modelAltitude3;
var modelRotate3;
var modelAsMercatorCoordinate3;
var modelTransform3;
var THREE;
var renderer = null;


var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2( Infinity, Infinity );
var lastsArr = [],ar = [],renderFlag = true, scene, camera;



$(document).ready(function(){


 longitude = 77.122279;
 latitude = 28.706246;
 assetArr.push({ id:"3d1", cord:{lng:77.122279,lat:28.706246}, url:'https://docs.mapbox.com/mapbox-gl-js/assets/34M_17/34M_17.gltf', scaleFactor: 0.5 , rad: 0.015, origCoord:{lng:77.122279,lat:28.706246}});
 assetArr.push({ id:"3d2", cord:{lng:77.125959,lat:28.707724}, url:'https://www.zamit.one/location/gun/scene.gltf', scaleFactor: 9 , rad: 0.015, origCoord:{lng:77.125959,lat:28.707724}});

 showmap();

});


function showmap(callback){


    mapboxgl.accessToken = 'pk.eyJ1IjoibWFzaDEyIiwiYSI6ImNrNzBhMHc2aTFhMnkza3J2OG51azV6aW0ifQ.1FeBAzRjqkSQex-u-GiPyw';
    map = new mapboxgl.Map({

        style: 'mapbox://styles/mapbox/streets-v11',
        center: [longitude,latitude],
        zoom: 17.6,
        pitch: 95,
        bearing: -17.6,
        container: 'map',
        antialias: false,
        dragPan: true,
        dragRotate: false,
        maxZoom: 18.7,
        minZoom: 16

    });


    var geojson = {
            'type': 'FeatureCollection',
            'features': [
              {
                'type': 'Feature',
                'geometry': {
                  'type': 'Point',
                  'coordinates': [longitude,latitude]
                },
                'properties': {
                  'title': 'Mapbox',
                  'description': 'Park Centra'
                }
              }
            ]
          };




    // The 'building' layer in the mapbox-streets vector source contains building-height
    // data from OpenStreetMap.
    map.on('zoom',function(){

       map.jumpTo({ center: [longitude,latitude] });

    });





    map.on('rotate',function(){
        document.getElementById('info').innerHTML = JSON.stringify(longitude +" : : "+latitude+" : : "+diff);
        map.jumpTo({center: [longitude,latitude]});
    });





    map.on('load', function() {
        // Insert the layer beneath any symbol layer.
        console.log("map loaded");
        var layers = map.getStyle().layers;
        console.log(layers);

        var labelLayerId;
        for (var i = 0; i < layers.length; i++) {
            if (layers[i].type === 'symbol' && layers[i].layout['text-field']) {
                labelLayerId = layers[i].id;
                break;
            }
        }

     map.on('click', e => {
         onClick(e);
     });



     map.style.stylesheet.layers.forEach(function(layer) {
       if (layer.type === 'symbol' && layer.id != 'waterway-label') {
          map.removeLayer(layer.id);
       }
     });


    for(var i = 0; i< assetArr.length; i++){
      model3(assetArr[i].id, assetArr[i].cord, assetArr[i].url , assetArr[i].scaleFactor,i);

    }

    for(var i = 0;i<lastsArr.length; i++){
      map.addLayer(lastsArr[i], 'waterway-label');
      console.log("i ",i,lastsArr[i]);
    }



    });


}












function model3(Id, coordinates, gltfUrl , scaleFactor,i){

  // parameters to ensure the model is georeferenced correctly on the map
            //var modelOrigin = [77.052024, 28.459822];
             console.log("Values ",coordinates.lng, coordinates.lat);
             modelOrigin3 = [coordinates.lng, coordinates.lat];
             modelAltitude3 = 0;
             modelRotate3 = [Math.PI / 2, 0, 0];

             modelAsMercatorCoordinate3 = mapboxgl.MercatorCoordinate.fromLngLat(
                modelOrigin3,
                modelAltitude3
            );



            // transformation parameters to position, rotate and scale the 3D model onto the map
             modelTransform3 = {
                translateX: modelAsMercatorCoordinate3.x,
                translateY: modelAsMercatorCoordinate3.y,
                translateZ: modelAsMercatorCoordinate3.z,
                rotateX: modelRotate3[0],
                rotateY: modelRotate3[1],
                rotateZ: modelRotate3[2],
                /* Since our 3D model is in real world meters, a scale transform needs to be
                * applied since the CustomLayerInterface expects units in MercatorCoordinates.
                */
                scale: modelAsMercatorCoordinate3.meterInMercatorCoordinateUnits(),
                langlat: modelOrigin3

            };
            ar.push(modelTransform3);
var l;


 THREE = window.THREE;

// configuration of the custom layer for a 3D model per the CustomLayerInterface
 customLayer3 = {
    id: Id,
    type: 'custom',
    renderingMode: '3d',
    onAdd: function (map, gl) {
//        this.camera = new THREE.Camera();
        this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
        this.scene = new THREE.Scene();
        camera = this.camera;
        scene = this.scene;

        var rotationX = new THREE.Matrix4().makeRotationAxis(
                            new THREE.Vector3(1, 0, 0),
                            ar[i].rotateX

                        );
                        var rotationY = new THREE.Matrix4().makeRotationAxis(
                            new THREE.Vector3(0, 1, 0),
                            ar[i].rotateY

                        );
                        var rotationZ = new THREE.Matrix4().makeRotationAxis(
                            new THREE.Vector3(0, 0, 1),
                            ar[i].rotateZ

                        );

                    l = new THREE.Matrix4()
                    .makeTranslation(
                        ar[i].translateX,
                        ar[i].translateY,
                        ar[i].translateZ


                    )
                    .scale(
                        new THREE.Vector3(
                            ar[i].scale*scaleFactor,
                            -ar[i].scale*scaleFactor,
                            ar[i].scale*scaleFactor


                        )
                    )
                    .multiply(rotationX)
                    .multiply(rotationY)
                    .multiply(rotationZ);



        var light1 = new THREE.AmbientLight( 0xffffff );
        light1.position.set(0, -70, 100).normalize();
        this.scene.add(light1);

        var light2 = new THREE.AmbientLight( 0xffffff );
        light2.position.set(0, 70, 100).normalize();
        this.scene.add(light2);

        // use the three.js GLTF loader to add the 3D model to the three.js scene
        var loader = new THREE.GLTFLoader();

        loader.load(
            gltfUrl,
            function (gltf) {
                this.scene.add(gltf.scene);
            }.bind(this)
        );

        this.map = map;

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

        this.renderer.autoClear = false;
        if(renderFlag == true){
         renderFlag = false;

        }


    },





    render: function (gl, matrix) {
        var m = new THREE.Matrix4().fromArray(matrix);


        this.camera.projectionMatrix = m.multiply(l);

        this.renderer.state.reset();
        this.renderer.render(this.scene, this.camera);
        this.map.triggerRepaint();

     }
    }

    lastsArr.push(customLayer3)

};






function onClick( event ) {
    event.preventDefault();
    //I had to change the changedTouches to point to adapt to the incoming event object
    mouse.x = ( event.point.x / window.innerWidth ) * 2 - 1;
    mouse.y = - ( event.point.y / window.innerHeight ) * 2 + 1;

    const camInverseProjection =
        new THREE.Matrix4().getInverse(this.camera.projectionMatrix);
    const cameraPosition =
        new THREE.Vector3().applyMatrix4(camInverseProjection);
    const mousePosition =
        new THREE.Vector3(mouse.x, mouse.y, 1)
        .applyMatrix4(camInverseProjection);
    const viewDirection = mousePosition.clone()
        .sub(cameraPosition).normalize();

    this.raycaster.set(cameraPosition, viewDirection);

    //no change from here on
    var intersects = raycaster.intersectObjects( scene.children, true );
    console.log("Here",intersects);
    if ( intersects.length > 0 ) {
//        alert("hi");
       for(var i = 0;i<intersects.length;i++){
         console.log( 'Intersection:', intersects[ 0 ].object.name);
       }

    }
}

1 Ответ

1 голос
/ 07 мая 2020

Click call

Код, который вы предоставили, довольно беспорядочный, было бы очень полезно (для любого, кто прочитает ваш вопрос в будущем), если бы вы могли его очистить. Пожалуйста, помните об этом, задавая следующие вопросы.

Мне пришлось добавить вызов обработчика событий onClick в конец showMap(callback):

    map.on('click', e => {
        onClick(e);
    });

Raycaster

Решение простое, если вы прочитаете мой ответ на другой вопрос MapBox + Three . Просто замените свой onClick следующим кодом:

function onClick( event ) {
    event.preventDefault();
    //I had to change the changedTouches to point to adapt
    //  to the incoming event object as for me there was no such property
    mouse.x = ( event.point.x / window.innerWidth ) * 2 - 1;
    mouse.y = - ( event.point.y / window.innerHeight ) * 2 + 1;

    const camInverseProjection = 
        new THREE.Matrix4().getInverse(this.camera.projectionMatrix);
    const cameraPosition =
        new THREE.Vector3().applyMatrix4(camInverseProjection);
    const mousePosition =
        new THREE.Vector3(mouse.x, mouse.y, 1)
        .applyMatrix4(camInverseProjection);
    const viewDirection = mousePosition.clone()
        .sub(cameraPosition).normalize();

    this.raycaster.set(cameraPosition, viewDirection);

    //no change from here on
    var intersects = raycaster.intersectObjects( scene.children, true );
    console.log("Here",intersects);
    if ( intersects.length > 0 ) {
        alert("hi");
        console.log( 'Intersection:', intersects[ 0 ].object.name == "");
    }
}

Несколько объектов

Ваш код - это расширение chaoti c для example , на которое вы ссылались. В исходном образце кода вы подготовили его таким образом, чтобы создать новый пользовательский слой для каждого 3D-объекта. Это должно работать, но производительность скоро пострадает при добавлении большего количества объектов. Причина этого в том, что у вас будет отдельный ТРИ экземпляра, включая сцену, render-l oop et c. для каждого отдельного объекта e3D.

Я думаю, что правильное решение - поместить все трехмерные объекты в один общий слой с одной ТРЕТЬЕЙ сценой, которая загружает все объекты. Такое изменение требует больших изменений в вашем коде. Я кратко их резюмирую. Вы можете увидеть весь рабочий пример в этой скрипке . Будьте терпеливы, второй объект загружается очень медленно .

  1. В map.on('load', ...) вам нужно загрузить новый слой (заменяя вызовы model3. Назовем его addThreeLayer().

  2. addThreeLayer() должен сначала сохранить исходную точку сцены в глобальной переменной. Начало координат можно выбрать, я взял позицию первого объекта. Следующий код также подготавливает матрицу преобразования для преобразования левой системы в правую и масштабирования до единиц измерения. Это соответствует вашей матрице l.

const originAsset = assetArr[0].cord;
const mc = mapboxgl.MercatorCoordinate.fromLngLat([originAsset.lng, originAsset.lat], 0);
const meterScale = mc.meterInMercatorCoordinateUnits();

sceneTransform = {};
sceneTransform.matrix = new THREE.Matrix4()
   .makeTranslation(mc.x, mc.y, mc.z)
   .scale(new THREE.Vector3(meterScale, -meterScale, meterScale));
sceneTransform.origin = new THREE.Vector3(mc.x, mc.y, mc.z); //not in meters!
Внутри onAdd GLTFLoader должен l oop над всеми моделями, загружать их сетки и размещать их относительно выбранной исходной точки.
var loader = new THREE.GLTFLoader();
// use the three.js GLTF loader to add the 3D model
// to the three.js scene            
for (var i = 0; i < assetArr.length; i++) {                
    const modelOrigin3 = [assetArr[i].cord.lng, assetArr[i].cord.lat];
    const modelAltitude3 = 0;
    const modelRotate3 = new THREE.Euler(Math.PI / 2, 0, 0, 'XYZ');                
    const modelScale = assetArr[i].scaleFactor;

    const mc = mapboxgl.MercatorCoordinate.fromLngLat(modelOrigin3, modelAltitude3);
    loader.load(assetArr[i].url, function (gltf) {
        const scene = gltf.scene;
        const origin = sceneTransform.origin;
        // division necessary, since the scene is in meters
        // but mc and origin are not
        scene.position.set(
           (mc.x - origin.x) / meterScale,
          -(mc.y - origin.y) / meterScale,
           (mc.z - origin.z) / meterScale);
        scene.quaternion.setFromEuler(modelRotate3);
        scene.scale.set(modelScale, modelScale, modelScale)
        this.scene.add(gltf.scene);
    }.bind(this));
}
Для использования функции рендеринга необходимо адаптировать sceneTransform
render: function (gl, matrix) {
    this.camera.projectionMatrix =  new THREE.Matrix4().fromArray(matrix).multiply(sceneTransform.matrix);
    this.renderer.state.reset();
    this.renderer.render(this.scene, this.camera);
    this.map.triggerRepaint();
}
...