I'm rendering some custom layers using Three.js in a Mapbox GL JS page following this example. I'd like to add raycasting to determine which object a user has clicked on.
The issue is that I only get a projection matrix from Mapbox, which I use to render the scene:
class CustomLayer {
type = 'custom';
renderingMode = '3d';
onAdd(map, gl) {
this.map = map;
this.camera = new THREE.Camera();
this.renderer = new THREE.WebGLRenderer({
canvas: map.getCanvas(),
context: gl,
antialias: true,
});
this.scene = new THREE.Scene();
// ...
}
render(gl, matrix) {
this.camera.projectionMatrix = new THREE.Matrix4()
.fromArray(matrix)
.multiply(this.cameraTransform);
this.renderer.state.reset();
this.renderer.render(this.scene, this.camera);
}
}
This renders just great, and tracks changes in view when I pan/rotate/zoom the map.
Unfortunately, when I try to add raycasting I get an error:
raycast(point) {
var mouse = new THREE.Vector2();
mouse.x = ( point.x / this.map.transform.width ) * 2 - 1;
mouse.y = 1 - ( point.y / this.map.transform.height ) * 2;
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, this.camera);
console.log(raycaster.intersectObjects(this.scene.children, true));
}
This gives me an exception:
THREE.Raycaster: Unsupported camera type.
I can change from a generic THREE.Camera
to a THREE.PerspectiveCamera
without affecting the rendering of the scene:
this.camera = new THREE.PerspectiveCamera(28, window.innerWidth / window.innerHeight, 0.1, 1e6);
This fixes the exception but also doesn't result in any objects being logged. Digging a bit reveals that the camera's projectionMatrixInverse
is all NaN
s, which we can fix by calculating it:
raycast(point) {
var mouse = new THREE.Vector2();
mouse.x = ( point.x / this.map.transform.width ) * 2 - 1;
mouse.y = 1 - ( point.y / this.map.transform.height ) * 2;
this.camera.projectionMatrixInverse.getInverse(this.camera.projectionMatrix); // <--
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, this.camera);
console.log(raycaster.intersectObjects(this.scene.children, true));
}
Now I get two intersections wherever I click, with two faces of the cube. Their distances are 0:
[
{ distance: 0, faceIndex: 10, point: Vector3 { x: 0, y: 0, z: 0 }, uv: Vector2 {x: 0.5, y: 0.5}, ... },
{ distance: 0, faceIndex: 11, point: Vector3 { x: 0, y: 0, z: 0 }, uv: Vector2 {x: 0.5, y: 0.5}, ... },
]
So clearly something isn't working here. Looking at the code for setCamera
, it involves both projectionMatrix
and matrixWorld
. Is there a way I can set matrixWorld
, or construct the raycaster's ray directly using only the projection matrix? It seems that I only need the projection matrix to render the scene, so I'd hope that it would also be all I need to cast a ray.
Full example in this codepen.
matrixWorld
" Have you triedupdateMatrixWorld()
? All Cameras are also Object3Ds... – Relatedthis.camera.updateMatrixWorld(true);
both instead of and in addition to thethis.camera.projectionMatrixInverse
line in the codepen but to no avail. Same behavior. – Mamelukethis.camera.matrixWorld
is the identity matrix both before & after callingupdateMatrixWorld
. – Mameluke