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.
In map.on('load', ...)
you need to load the new layer (replacing the model3
calls. Let's call it addThreeLayer()
.
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!
- 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));
}
- 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();
}