使用raycaster在mapbox上对3D对象进行不均匀的点击检测。

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

我在mapbox中使用raycaster与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/[email protected]/build/three.min.js"></script>
<script src="https://unpkg.com/[email protected]/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坐标未定义的错误。然后我试着移动 l 矩阵 onAdd 像下面这样,但intersect对象仍然只在第二个对象上填充,而不是在第一个对象上填充。这里是更新后的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);
       }

    }
}
three.js mapbox mapbox-gl-js raycasting mapbox-gl
1个回答
1
投票

点击调用

你提供的代码很乱,如果你能把它清理一下,将会对你的问题有很大的帮助(对将来阅读你的问题的人)。下次提问时也请记住这一点。

我不得不在代码中添加了一个对 onClick 事件处理程序的结束 showMap(callback):

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

雷卡斯特

解决方法很简单,只要你看了我的 对另一个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 == "");
    }
}

几个对象

你的代码是一个混乱的扩展 例子 你所引用的。在你的原始代码示例中,你准备的方式是为每个3D对象创建一个新的自定义层。这应该是一种工作方式,但是当添加更多的对象时,性能很快就会受到影响。原因是您将为每个单一的e3D对象创建一个单独的三层实例,包括场景、渲染循环等。

我认为正确的解决方案是把所有的3D对象放到一个共同的层中,用一个THREE场景来加载所有的对象。这样的改变需要对你的代码进行较大的修改。我在这里简单总结一下。你可以在这里看到整个工作示例 抚弄. 耐心点,第二个对象加载速度很慢.

  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!
  1. 内的 onAddGLTFLoader 应该在所有模型上循环,加载它们的网格,并将它们相对放置在选定的原点上。
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. 渲染函数需要调整为使用 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();
}
© www.soinside.com 2019 - 2024. All rights reserved.