Getting uneven click-detection on 3d-objects on mapbox with raycaster

瘦欲@ 提交于 2020-05-17 06:05:41

问题


I am using raycaster in mapbox with 3d-objects. I need to detect the click on the 3d-object.But When I click on the object I get a empty raycaster.intersectObject (array) sometimes and filled array sometimes, rather it should always have the 3d object that I have clicked.

I have a simple map as explained here. I have only added the raycasting part to it to keep it as simple as possible.

Here is my code.

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>

The log sometimes on clicking tells me this

Here [] (js line 347).

while sometimes it is like

    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)

Why am I getting this uneven behaivior on clicking the 3d object? I am stuck at this point. Can anybody help me out here please?

Edit

Sorry for the messy code. I have tried to remove the unwanted code this time. I tried to move the l matrix to onAdd function, but as per your answer here when i tried to use MercatorCoordinate.fromLngLat I got a MercatorCoordinate cordinate undefined error. Then i tried moving the l matrix to onAdd like this below, but still the intersect object is only filled on second object, an not on the first object. here is the updated 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:


Click call

The code you provided is quite messy, it would be very helpful (for any one who will read your question in the future) if you could clean it up. Please remember this also when asking next questions.

I had to add a call to the onClick event handler ad the end of showMap(callback):

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

Raycaster

The solution is simple provided you read my answer to another MapBox+Three question. Just replace your onClick with the following code:

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 == "");
    }
}

Several objects

Your code is a chaotic extension of the example you referenced. In your original code sample you prepared it the way that you would create a new custom layer for each 3D object. This should kind-of work, but the performance would suffer soon with more objects added. The reason for that is that you would have a separate THREE instance including a scene, render-loop etc. for each singl e3D object.

I think the proper solution is to put all 3D objects into a single common layer with a single THREE scene that loads all the objects. Such change requires larger changes in your code. I will briefly summarize them here. You can see the whole working example in this fiddle. Be patient, the second object loads really slow.

  1. In map.on('load', ...) you need to load the new layer (replacing the model3 calls. Let's call it addThreeLayer().

  2. addThreeLayer() should first store the scene origin in a global variable. The origin is free to chose, I took the position of the first object. The following code also prepares the transformation matrix to flip the left-handed to a right-handed system and scale to meter units. This corresponds to your l matrix.

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!
  1. Inside of onAdd the GLTFLoader should loop over all models, load their meshes and place them relatively to the selected origin.
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));
}
  1. The render function needs to be adapted to use 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();
}


来源:https://stackoverflow.com/questions/61656054/getting-uneven-click-detection-on-3d-objects-on-mapbox-with-raycaster

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!