Closed Questionboy closed 5 years ago
You need to listen to a click on the map (map.on('click', function(e) { ...
) and then do your own calculations of whether there was any feature in the layer at that point
Adding on to @ansis' answer, you can use queryRenderedFeatures
inside the click function to determine what layers are visible at that point on the map.
thank you very much. @ansis @ryanhamley
Hi, as you have said before, I listen to a click on the map and use queryRenderedFeatures inside the click function. But, I cann't get the feature from customlayer. I use the sample of Add a 3D model.
map.on('click', function (e) {
var bbox = [[e.point.x - 5, e.point.y - 5], [e.point.x + 5, e.point.y + 5]];
var features = map.queryRenderedFeatures(bbox, { layers: ['3d-model'] });
console.log(features);
});
Unfortunately map.queryRenderedFeatures(...)
can't provide feature querying for custom layers because we have no knowledge of what is being rendered or how. We just provide the canvas and let the layer do whatever it wants.
If you are using threejs you will need to use a threejs api to query the mesh. I think this may be a good starting point: https://threejs.org/docs/#api/en/core/Raycaster
You can listen to a click event with map.on('click', ...)
and then use the threejs raycaster to check if there is anything at that point.
Thank you very much for your patient reply.
When I get the model through Api using Three.js getObjectByName, how do I know the LngLat value for this model
@fengfenghao first you need to find the objects MercatorCoordinate by unprojecting the object's position using an inverted version of your matrix. Then you can use mercatorCoordinate.toLngLat()
to convert to a lnglat: https://docs.mapbox.com/mapbox-gl-js/api/#mercatorcoordinate#tolnglat
<!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://api.tiles.mapbox.com/mapbox-gl-js/v1.6.0/mapbox-gl.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v1.6.0/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/three@0.106.2/build/three.min.js'></script>
<script src="https://unpkg.com/three@0.106.2/examples/js/loaders/GLTFLoader.js"></script>
<div id='map'></div>
<input type="button" name="flyTo" style="position: absolute;left: 10px;bottom: 30px" value="flyTo"
onclick="focusObject()">
<script>
var modelString = "{\"metadata\":{\"version\":\"2.0.0\",\"type\":\"Object\",\"generator\":\"zhouzf\"},\"materials\":[{\"color\":\"0xff0000\",\"shininess\":0,\"reflectivity\":0,\"emissive\":\"0x000000\",\"emissiveIntensity\":0,\"uuid\":\"8EA3BB96-0F53-42A7-A524-65BF99F0741A\",\"name\":\"Mat_Red\",\"type\":\"MeshLambertMaterial\",\"side\":0,\"opacity\":1,\"transparent\":false,\"alphaTest\":0},{\"color\":\"0xff0000\",\"shininess\":0,\"reflectivity\":0,\"emissive\":\"0x000000\",\"emissiveIntensity\":0,\"uuid\":\"FE8579A9-919F-467A-8C02-A78B62D968F9\",\"name\":\"Mat_Red\",\"type\":\"MeshLambertMaterial\",\"side\":0,\"opacity\":1,\"transparent\":false,\"alphaTest\":0},{\"color\":\"0xff0000\",\"shininess\":0,\"reflectivity\":0,\"emissive\":\"0x000000\",\"emissiveIntensity\":0,\"uuid\":\"1B5F12BC-D067-400C-A1E4-B1B604392E08\",\"name\":\"Mat_Red\",\"type\":\"MeshLambertMaterial\",\"side\":0,\"opacity\":1,\"transparent\":false,\"alphaTest\":0},{\"color\":\"0xff0000\",\"shininess\":0,\"reflectivity\":0,\"emissive\":\"0x000000\",\"emissiveIntensity\":0,\"uuid\":\"DECA5374-77C2-4693-BE7E-30A907B14F42\",\"name\":\"Mat_Red\",\"type\":\"MeshLambertMaterial\",\"side\":0,\"opacity\":1,\"transparent\":false,\"alphaTest\":0},{\"color\":\"0xff0000\",\"shininess\":0,\"reflectivity\":0,\"emissive\":\"0x000000\",\"emissiveIntensity\":0,\"uuid\":\"6C6D922D-2927-4D18-B041-DF33FA8A6380\",\"name\":\"Mat_Red\",\"type\":\"MeshLambertMaterial\",\"side\":0,\"opacity\":1,\"transparent\":false,\"alphaTest\":0}],\"geometries\":[{\"data\":{\"index\":{\"itemSize\":1,\"type\":\"Uint16Array\",\"array\":[0,3,2,0,1,3,8,5,4,8,9,5,10,7,6,10,11,7,12,14,13,12,15,14,16,18,17,16,19,18,20,22,21,20,23,22]},\"attributes\":{\"position\":{\"itemSize\":3,\"type\":\"Float32Array\",\"array\":[0.5,-0.5,-0.5,-0.5,-0.5,-0.5,0.5,0.5,-0.5,-0.5,0.5,-0.5,0.5,0.5,0.5,-0.5,0.5,0.5,0.5,-0.5,0.5,-0.5,-0.5,0.5,0.5,0.5,-0.5,-0.5,0.5,-0.5,0.5,0.5,0.5,-0.5,0.5,0.5,0.5,-0.5,0.5,0.5,-0.5,-0.5,-0.5,-0.5,-0.5,-0.5,-0.5,0.5,-0.5,-0.5,-0.5,-0.5,0.5,-0.5,-0.5,0.5,0.5,-0.5,-0.5,0.5,0.5,-0.5,0.5,0.5,0.5,0.5,0.5,0.5,-0.5,0.5,-0.5,-0.5]},\"normal\":{\"itemSize\":3,\"type\":\"Float32Array\",\"array\":[0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,1,0,0,1,0,0,0,1,0,0,1,0,1,0,0,1,0,0,0,1,0,0,1,0,-1,0,0,-1,0,0,-1,0,0,-1,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,1,0,0,1,0,0,1,0,0,1,0,0]},\"color\":{\"itemSize\":3,\"type\":\"Float32Array\",\"array\":[]},\"uv\":{\"itemSize\":2,\"type\":\"Float32Array\",\"array\":[0,0,1,0,0,1,1,1,0,1,1,1,0,1,1,1,0,0,1,0,0,0,1,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0,0,0,1,1,1,1,0]},\"uv2\":{\"itemSize\":2,\"type\":\"Float32Array\",\"array\":[0.691,0.3472,0.9967,0.3472,0.691,0.6528,0.9967,0.6528,0.3097,0.3472,0.0041,0.3472,0.3472,0.6528,0.6528,0.6528,0.3097,0.6528,0.0041,0.6528,0.3472,0.3472,0.6528,0.3472,0.3472,0.0041,0.3472,0.3097,0.6528,0.3097,0.6528,0.0041,0.6903,0.3097,0.9959,0.3097,0.9959,0.0041,0.6903,0.0041,0.3097,0.0041,0.0041,0.0041,0.0041,0.3097,0.3097,0.3097]}}},\"uuid\":\"02365fa8-2a4d-4039-abb5-99653eaa86cc\",\"type\":\"BufferGeometry\"}],\"object\":{\"visible\":true,\"castShadow\":false,\"receiveShadow\":false,\"children\":[{\"visible\":true,\"geometry\":\"02365fa8-2a4d-4039-abb5-99653eaa86cc\",\"material\":\"8EA3BB96-0F53-42A7-A524-65BF99F0741A\",\"castShadow\":false,\"receiveShadow\":false,\"uuid\":\"0280e927-cab1-4c75-a895-e2312bef42c5\",\"name\":\"Cube_0\",\"type\":\"Mesh\",\"matrix\":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]},{\"visible\":true,\"geometry\":\"02365fa8-2a4d-4039-abb5-99653eaa86cc\",\"material\":\"FE8579A9-919F-467A-8C02-A78B62D968F9\",\"castShadow\":false,\"receiveShadow\":false,\"uuid\":\"ab0b58e8-ea28-45d1-813d-856082ebc59c\",\"name\":\"Cube_1\",\"type\":\"Mesh\",\"matrix\":[1,0,0,0,0,1,0,0,0,0,1,0,-200,0,-200,1]},{\"visible\":true,\"geometry\":\"02365fa8-2a4d-4039-abb5-99653eaa86cc\",\"material\":\"1B5F12BC-D067-400C-A1E4-B1B604392E08\",\"castShadow\":false,\"receiveShadow\":false,\"uuid\":\"91d183a6-a938-4039-9210-3574d5dd8bf7\",\"name\":\"Cube_2\",\"type\":\"Mesh\",\"matrix\":[1,0,0,0,0,1,0,0,0,0,1,0,200,0,-200,1]},{\"visible\":true,\"geometry\":\"02365fa8-2a4d-4039-abb5-99653eaa86cc\",\"material\":\"DECA5374-77C2-4693-BE7E-30A907B14F42\",\"castShadow\":false,\"receiveShadow\":false,\"uuid\":\"4d8e622a-42eb-4404-8366-a3628750789a\",\"name\":\"Cube_3\",\"type\":\"Mesh\",\"matrix\":[1,0,0,0,0,1,0,0,0,0,1,0,200,0,200,1]},{\"visible\":true,\"geometry\":\"02365fa8-2a4d-4039-abb5-99653eaa86cc\",\"material\":\"6C6D922D-2927-4D18-B041-DF33FA8A6380\",\"castShadow\":false,\"receiveShadow\":false,\"uuid\":\"b1c69cc7-c075-4688-b5a8-45d02a2b3cce\",\"name\":\"Cube_4\",\"type\":\"Mesh\",\"matrix\":[1,0,0,0,0,1,0,0,0,0,1,0,-200,0,200,1]}],\"uuid\":\"bd9f1b4b-1887-44df-8f6c-723af23507e1\",\"name\":\"Root\",\"type\":\"Object3D\",\"matrix\":[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]},\"images\":[],\"textures\":[]}"
mapboxgl.accessToken = 'pk.eyJ1IjoiaGFtYnVnZXJkZXZlbG9wIiwiYSI6ImNqNXJtZjF4ZzB3em4yd21pZmVqbHlleDAifQ.I9eqVjtotz7jaU7XcJm9pQ';
var map = window.map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/light-v10',
zoom: 18,
center: [148.9819, -35.3981],
pitch: 60,
antialias: true // create the gl context with MSAA antialiasing, so custom layers are antialiased
});
// parameters to ensure the model is georeferenced correctly on the map
var modelOrigin = [148.98190, -35.39847];
var modelAltitude = 0;
var modelRotate = [Math.PI / 2, 0, 0];
var modelAsMercatorCoordinate = mapboxgl.MercatorCoordinate.fromLngLat(modelOrigin, modelAltitude);
// transformation parameters to position, rotate and scale the 3D model onto the map
var modelTransform = {
translateX: modelAsMercatorCoordinate.x,
translateY: modelAsMercatorCoordinate.y,
translateZ: modelAsMercatorCoordinate.z,
rotateX: modelRotate[0],
rotateY: modelRotate[1],
rotateZ: modelRotate[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: modelAsMercatorCoordinate.meterInMercatorCoordinateUnits()
};
var THREE = window.THREE;
var scene, camera;
// configuration of the custom layer for a 3D model per the CustomLayerInterface
var customLayer = {
id: '3d-model',
type: 'custom',
renderingMode: '3d',
onAdd: function (map, gl) {
camera = new THREE.Camera();
scene = new THREE.Scene();
// create two three.js lights to illuminate the model
var directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(0, -70, 100).normalize();
scene.add(directionalLight);
var directionalLight2 = new THREE.DirectionalLight(0xffffff);
directionalLight2.position.set(0, 70, 100).normalize();
scene.add(directionalLight2);
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;
//use three objcetLoader loading models
var loader = new THREE.ObjectLoader();
loader.parse(JSON.parse(modelString), function (obj) {
scene.add(obj);
for (var i = 0; i < obj.children.length; i++) {
console.log(obj.children[i].name);
}
});
},
render: function (gl, matrix) {
var rotationX = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(1, 0, 0), modelTransform.rotateX);
var rotationY = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0, 1, 0), modelTransform.rotateY);
var rotationZ = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0, 0, 1), modelTransform.rotateZ);
var m = new THREE.Matrix4().fromArray(matrix);
var l = new THREE.Matrix4().makeTranslation(modelTransform.translateX, modelTransform.translateY, modelTransform.translateZ)
.scale(new THREE.Vector3(modelTransform.scale, -modelTransform.scale, modelTransform.scale))
.multiply(rotationX)
.multiply(rotationY)
.multiply(rotationZ);
camera.projectionMatrix = m.multiply(l);
this.renderer.state.reset();
this.renderer.render(scene, camera);
this.map.triggerRepaint();
}
};
map.on('style.load', function () {
map.addLayer(customLayer, 'waterway-label');
});
map.on('mouseup', function (e) {
console.log(JSON.stringify(e.point) + " : " + JSON.stringify(e.lngLat));
});
function focusObject() {
//Cube_1 lngLat:{"lng":148.97969602816397,"lat":-35.400267269123674}
var findObj = scene.getObjectByName("Cube_1");
console.log(findObj);
//How do I get the LngLat value of findObj, and I don't know how to get the MercatorCoordinate value, can you write this code for me
map.flyTo({center: [148.97969602816397, -35.400267269123674], zoom: 19});
}
</script>
</body>
</html>
Hi. I decided to ask my question here since I'm having similar problems to @Questionboy. I'm by no means a WebGL expert, so I apologize in advance if anything of this might sound stupid. I have been researching the subject thoroughly for a couple of days now, though, and I hope it makes at least some sense.
I'm trying to implement a custom layer that renders a bunch of stuff (polygons or 2D textures, not that far yet) on different latLng locations. These models must, of course, be clickable. So I'm trying to implement some sort of click detection, and from what I've read raycasting seems a proper solution. Oh, and I'm not using any graphics engine, just vanilla WebGL. I'm having problems with understanding how to ray cast on a click. I can obviously convert a click (or model's vertices) location to MercatorCoordinate, but don't fully understand how to use that for detecting a collision.
The easiest solution seems to be to just check if click location is close enough to the locations (in screen coordinates), but this seems hacky and imprecise.
I know this is not a place for seeking help with such questions, and I'll be grateful if anybody can even only push me in the right direction.
@Len-art, @Questionboy I know this is a quite old question, but if you still interested in this kind of functionality, I think what you need can be found in this Threebox fork.
@jscastro76 we ended up with actual raycasting and it worked great. Unfortunately, other Mapbox limitations (not having access to the transformation matrix or however it's called, it's been a while) made the whole thing too hacky in the end. Thanks for the link, I might revisit the problem using the fork.
Accessing and calling the pick() method in the layer's scene object may solve the issue:
map.on('click', (e) => { const canvasCoords = [e.point.x, e.point.y]; const picked = map.getLayer("customlayer").implementation.scene.pick(canvasCoords[0], canvasCoords[1]); console.log(picked.hit) });
In which the hit property returns a Boolean value indicating if the cursor hit the layer.
Hi, I want to listen to the customlayer's click event. Just like this:
map.on('load', function () { map.on('click', 'customlayer', function(e) { console.log('customlayer click'); }); })
however, this doesn't work. Is there any way to get the click event in customlayer?