我在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);
}
}
}
你提供的代码很乱,如果你能把它清理一下,将会对你的问题有很大的帮助(对将来阅读你的问题的人)。下次提问时也请记住这一点。
我不得不在代码中添加了一个对 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场景来加载所有的对象。这样的改变需要对你的代码进行较大的修改。我在这里简单总结一下。你可以在这里看到整个工作示例 抚弄. 耐心点,第二个对象加载速度很慢.
在 map.on('load', ...)
你需要加载新的图层(替换成 model3
呼叫。我们就叫它 addThreeLayer()
.
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
应该在所有模型上循环,加载它们的网格,并将它们相对放置在选定的原点上。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();
}