Skip to content

Instantly share code, notes, and snippets.

@danvk
Created February 25, 2020 23:27
Show Gist options
  • Save danvk/1c76f51822873b5a38b1bef2e355b6cd to your computer and use it in GitHub Desktop.
Save danvk/1c76f51822873b5a38b1bef2e355b6cd to your computer and use it in GitHub Desktop.
Flailing around with raycasting
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<title>Raycasting with Mapbox</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.4.0/mapbox-gl.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v1.4.0/mapbox-gl.css' rel='stylesheet' />
<style>
body { margin:0; padding:0; }
#map { position:absolute; top:0; bottom:0; width:100%; }
</style>
</head>
<body>
<div id='map'></div>
<script src="raycast.js" type="module"></script>
</body>
</html>
import * as THREE from 'https://unpkg.com/[email protected]/build/three.module.js';
import * as mat4 from 'https://unpkg.com/[email protected]/esm/mat4.js';
const {MercatorCoordinate} = mapboxgl;
mapboxgl.accessToken = 'pk.eyJ1IjoiZGFudmsiLCJhIjoiY2lrZzJvNDR0MDBhNXR4a2xqNnlsbWx3ciJ9.myJhweYd_hrXClbKk8XLgQ';
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/light-v9',
center: [-74.0445, 40.6892],
zoom: 16,
pitch: 60,
bearing: 120,
});
class BoxCustomLayer {
type = 'custom';
renderingMode = '3d';
constructor(id) {
this.id = id;
THREE.Object3D.DefaultUp.set(0, 0, 1);
}
async onAdd(map, gl) {
this.camera = new THREE.PerspectiveCamera(28, window.innerWidth / window.innerHeight, 0.1, 1e6);
// this.camera = new THREE.Camera();
const centerLngLat = map.getCenter();
this.center = MercatorCoordinate.fromLngLat(centerLngLat, 0);
const {x, y, z} = this.center;
// const s = 1 / this.center.meterInMercatorCoordinateUnits();
this.cameraTransform = new THREE.Matrix4()
.makeTranslation(x, y, z)
.scale(new THREE.Vector3(1, -1, 1))
// .scale(new THREE.Vector3(s, s, s));
this.map = map;
this.scene = this.makeScene();
// 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;
this.raycaster = new THREE.Raycaster();
}
makeScene() {
const scene = new THREE.Scene();
const skyColor = 0xb1e1ff; // light blue
const groundColor = 0xb97a20; // brownish orange
scene.add(new THREE.AmbientLight(0xffffff, 0.25));
scene.add(new THREE.HemisphereLight(skyColor, groundColor, 0.25));
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5);
directionalLight.position.set(-70, -70, 100).normalize();
// Directional lights implicitly point at (0, 0, 0).
scene.add(directionalLight);
const group = new THREE.Group();
group.name = '$group';
// The models are all in meter coordinates. This shifts them to Mapbox world coordinates.
// group.matrix.multiply(this.cameraTransform);
group.scale.setScalar(this.center.meterInMercatorCoordinateUnits());
group.updateMatrix();
// const inv = new THREE.Matrix4();
// inv.getInverse(this.cameraTransform);
// group.matrix.premultiply(inv);
// group.matrixAutoUpdate = false;
group.applyMatrix(this.cameraTransform);
const geometry = new THREE.BoxGeometry( 100, 100, 100 );
geometry.translate(0, 0, 50);
const material = new THREE.MeshPhongMaterial({
color: 0xff0000,
});
const cube = new THREE.Mesh( geometry, material );
group.add(cube);
scene.add(group);
console.log(scene);
return scene;
}
render(gl, viewProjectionMatrix) {
const transform = this.map.transform;
const camera = this.camera;
const projectionMatrix = new Float64Array(16),
projectionMatrixI = new Float64Array(16),
viewMatrix = new Float64Array(16),
viewMatrixI = new Float64Array(16);
// from https://github.com/mapbox/mapbox-gl-js/blob/master/src/geo/transform.js#L556-L568
const halfFov = transform._fov / 2;
const groundAngle = Math.PI / 2 + transform._pitch;
const topHalfSurfaceDistance = Math.sin(halfFov) * transform.cameraToCenterDistance / Math.sin(Math.PI - groundAngle - halfFov);
const furthestDistance = Math.cos(Math.PI / 2 - transform._pitch) * topHalfSurfaceDistance + transform.cameraToCenterDistance;
const farZ = furthestDistance * 1.01;
mat4.perspective(projectionMatrix, transform._fov, transform.width / transform.height, 1, farZ);
mat4.invert(projectionMatrixI, projectionMatrix);
mat4.multiply(viewMatrix, projectionMatrixI, viewProjectionMatrix);
mat4.invert(viewMatrixI, viewMatrix);
camera.projectionMatrix = new THREE.Matrix4().fromArray(projectionMatrix);
camera.matrix = new THREE.Matrix4().fromArray(viewMatrixI);
camera.matrix.decompose(camera.position, camera.quaternion, camera.scale);
const prod = camera.matrix.clone();
prod.multiply(camera.projectionMatrix);
console.log(prod.elements);
console.log(viewProjectionMatrix);
// console.log(camera.position)
// camera.projectionMatrix = new THREE.Matrix4().fromArray(viewProjectionMatrix);
// camera.matrix = new THREE.Matrix4();
// camera.matrix.decompose(camera.position, camera.quaternion, camera.scale);
this.renderer.state.reset();
this.renderer.render(this.scene, camera);
}
raycast(point) {
var mouse = new THREE.Vector2();
// scale mouse pixel position to a percentage of the screen's width and height
mouse.x = ( point.x / this.map.transform.width ) * 2 - 1;
mouse.y = 1 - ( point.y / this.map.transform.height ) * 2;
this.raycaster.setFromCamera(mouse, this.camera);
// calculate objects intersecting the picking ray
var intersects = this.raycaster.intersectObjects(this.scene.children, true);
if (intersects.length) {
console.log(intersects);
}
}
}
let boxLayer = new BoxCustomLayer('box')
map.on('load', () => {
map.addLayer(boxLayer);
});
map.on('click', e => {
boxLayer.raycast(e.point);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment