使用 raycaster 对 mapbox 上的 3d 对象进行不均匀的点击检测
Getting uneven click-detection on 3d-objects on mapbox with raycaster
我在带有 3d 对象的 mapbox 中使用 raycaster。我需要检测 3d-object.But 上的点击 当我点击对象时,我有时会得到一个空的 raycaster.intersectObject (数组),有时会得到填充数组,而它应该始终具有我拥有的 3d 对象点击了。
我有一张简单的地图,如[此处][1] 所述。我只添加了光线投射部分以使其尽可能简单。
有人可以帮我吗?
点击通话
你提供的代码很乱,如果你能把它清理干净,那将是非常有帮助的(对于任何将来会阅读你的问题的人)。问下一个问题时也请记住这一点。
我必须在 showMap(callback)
:
末尾添加对 onClick
事件处理程序的调用
map.on('click', e => {
onClick(e);
});
光线投射器
如果您阅读我的 answer to another MapBox+Three question,解决方案很简单。只需将您的 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 == "");
}
}
几个对象
您的代码是您引用的 example 的混乱扩展。在您的原始代码示例中,您准备它的方式是为每个 3D 对象创建一个新的自定义层。这应该可以工作,但是随着添加更多对象,性能很快就会受到影响。这样做的原因是,对于每个单独的 e3D 对象,您将拥有一个单独的三个实例,包括场景、渲染循环等。
我认为正确的解决方案是将所有 3D 对象放入一个公共层中,并使用一个加载所有对象的三个场景。此类更改需要对代码进行更大的更改。我将在这里简要总结一下。您可以在 fiddle 中看到整个工作示例。 请耐心等待,第二个对象加载真的很慢。
在 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));
}
- render函数需要适配才能使用
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();
}
我在带有 3d 对象的 mapbox 中使用 raycaster。我需要检测 3d-object.But 上的点击 当我点击对象时,我有时会得到一个空的 raycaster.intersectObject (数组),有时会得到填充数组,而它应该始终具有我拥有的 3d 对象点击了。
我有一张简单的地图,如[此处][1] 所述。我只添加了光线投射部分以使其尽可能简单。
有人可以帮我吗?
点击通话
你提供的代码很乱,如果你能把它清理干净,那将是非常有帮助的(对于任何将来会阅读你的问题的人)。问下一个问题时也请记住这一点。
我必须在 showMap(callback)
:
onClick
事件处理程序的调用
map.on('click', e => {
onClick(e);
});
光线投射器
如果您阅读我的 answer to another MapBox+Three question,解决方案很简单。只需将您的 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 == "");
}
}
几个对象
您的代码是您引用的 example 的混乱扩展。在您的原始代码示例中,您准备它的方式是为每个 3D 对象创建一个新的自定义层。这应该可以工作,但是随着添加更多对象,性能很快就会受到影响。这样做的原因是,对于每个单独的 e3D 对象,您将拥有一个单独的三个实例,包括场景、渲染循环等。
我认为正确的解决方案是将所有 3D 对象放入一个公共层中,并使用一个加载所有对象的三个场景。此类更改需要对代码进行更大的更改。我将在这里简要总结一下。您可以在 fiddle 中看到整个工作示例。 请耐心等待,第二个对象加载真的很慢。
在
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));
}
- render函数需要适配才能使用
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();
}