@polygonjs/plugin-mapbox
Version:
Mapbox plugin for the 3D engine https://polygonjs.com
526 lines (486 loc) • 15.7 kB
text/typescript
import type {BaseViewerOptions} from '@polygonjs/polygonjs/dist/src/engine/nodes/obj/_BaseCamera';
import {
TypedCameraObjNode,
CameraMainCameraParamConfig,
} from '@polygonjs/polygonjs/dist/src/engine/nodes/obj/_BaseCamera';
import mapboxgl from 'mapbox-gl';
import {CoreMapboxClient} from '../../../core/mapbox/Client';
import {ParamConfig, NodeParamsConfig} from '@polygonjs/polygonjs/dist/src/engine/nodes/utils/params/ParamsConfig';
import type {BaseNodeType} from '@polygonjs/polygonjs/dist/src/engine/nodes/_Base';
import type {BaseParamType} from '@polygonjs/polygonjs/dist/src/engine/params/_Base';
import type {Number2} from '@polygonjs/polygonjs/dist/src/types/GlobalTypes';
import {isBooleanTrue} from '@polygonjs/polygonjs/dist/src/core/Type';
import {Poly} from '@polygonjs/polygonjs/dist/src/engine/Poly';
import {MapboxPerspectiveCamera} from '../../../core/mapbox/MapboxPerspectiveCamera';
import type {MapboxCameraObjNodeType} from '../../../core/mapbox/registerMapboxCamera';
import {MAPBOX_CAMERA_OBJ_NODE_TYPE, registerMapboxCamera} from '../../../core/mapbox/registerMapboxCamera';
import {MapboxViewer} from '../../viewers/Mapbox';
import {NodeContext} from '@polygonjs/polygonjs/dist/src/engine/poly/NodeContext';
import {RopType} from '@polygonjs/polygonjs/dist/src/engine/poly/registers/nodes/types/Rop';
import {CameraCSSRendererSopOperation} from '@polygonjs/polygonjs/dist/src/engine/operations/sop/CameraCSSRenderer';
const PRESETS = {
LONDON: {
style: 'mapbox://styles/mapbox/dark-v10',
lngLat: [-0.07956, 51.5146] as Number2,
},
SAN_FRANCISCO: {
style: 'mapbox://styles/mapbox/dark-v10',
lngLat: [-122.4726194, 37.7577627] as Number2,
},
MOUNTAIN: {
style: 'mapbox://styles/mapbox-map-design/ckhqrf2tz0dt119ny6azh975y',
lngLat: [-114.34411, 32.6141] as Number2,
},
};
const PRESET = PRESETS.LONDON;
class MapboxCameraObjParamConfig extends CameraMainCameraParamConfig(NodeParamsConfig) {
style = ParamConfig.STRING(PRESET.style, {
callback: (node: BaseNodeType) => {
MapboxCameraObjNode.PARAM_CALLBACK_update_style(node as MapboxCameraObjNode);
},
});
lngLat = ParamConfig.VECTOR2(PRESET.lngLat, {
callback: (node: BaseNodeType) => {
MapboxCameraObjNode.PARAM_CALLBACK_update_nav(node as MapboxCameraObjNode);
},
});
zoom = ParamConfig.FLOAT(15.55, {
range: [0, 24],
rangeLocked: [true, true],
callback: (node: BaseNodeType) => {
MapboxCameraObjNode.PARAM_CALLBACK_update_nav(node as MapboxCameraObjNode);
},
});
zoomRange = ParamConfig.VECTOR2([0, 24], {
// range: [0, 24],
// rangeLocked: [true, true]
callback: (node: BaseNodeType) => {
MapboxCameraObjNode.PARAM_CALLBACK_update_nav(node as MapboxCameraObjNode);
},
});
pitch = ParamConfig.FLOAT(60, {
range: [0, 85],
rangeLocked: [true, true],
callback: (node: BaseNodeType) => {
MapboxCameraObjNode.PARAM_CALLBACK_update_nav(node as MapboxCameraObjNode);
},
});
bearing = ParamConfig.FLOAT(60.373613, {
range: [0, 360],
callback: (node: BaseNodeType) => {
MapboxCameraObjNode.PARAM_CALLBACK_update_nav(node as MapboxCameraObjNode);
},
});
updateParamsFromMap = ParamConfig.BUTTON(null, {
label: 'Set Navigation Params as Default',
callback: (node: BaseNodeType, param: BaseParamType) => {
MapboxCameraObjNode.PARAM_CALLBACK_update_params_from_map(node as MapboxCameraObjNode);
},
});
allowDragRotate = ParamConfig.BOOLEAN(1, {
callback: (node: BaseNodeType) => {
MapboxCameraObjNode.PARAM_CALLBACK_update_nav(node as MapboxCameraObjNode);
},
});
addZoomControl = ParamConfig.BOOLEAN(1, {
callback: (node: BaseNodeType) => {
MapboxCameraObjNode.PARAM_CALLBACK_update_nav(node as MapboxCameraObjNode);
},
});
// this.create_player_camera_params();
tlayerBuildings = ParamConfig.BOOLEAN(0);
tlayer3D = ParamConfig.BOOLEAN(0);
tlayerSky = ParamConfig.BOOLEAN(0);
/** @param toggle on to add a CSSRenderer to have html elements on top of the 3D objects */
setCSSRenderer = ParamConfig.BOOLEAN(0, {
callback: (node: BaseNodeType) => {
MapboxCameraObjNode.PARAM_CALLBACK_updateCameraAttributes(node as MapboxCameraObjNode);
},
});
/** @param add a css renderer */
CSSRenderer = ParamConfig.NODE_PATH('', {
visibleIf: {setCSSRenderer: 1},
nodeSelection: {
context: NodeContext.ROP,
types: [RopType.CSS2D, RopType.CSS3D],
},
dependentOnFoundNode: true,
callback: (node: BaseNodeType) => {
MapboxCameraObjNode.PARAM_CALLBACK_updateCameraAttributes(node as MapboxCameraObjNode);
},
});
}
const ParamsConfig = new MapboxCameraObjParamConfig();
export class MapboxCameraObjNode extends TypedCameraObjNode<MapboxPerspectiveCamera, MapboxCameraObjParamConfig> {
override paramsConfig = ParamsConfig;
static override type(): Readonly<MapboxCameraObjNodeType> {
return MAPBOX_CAMERA_OBJ_NODE_TYPE;
}
static override onRegister = registerMapboxCamera;
public integration_data() {
return CoreMapboxClient.integration_data();
}
private _maps_by_container_id: Map<string, mapboxgl.Map> = new Map();
private _map_containers_by_container_id: Map<string, HTMLElement> = new Map();
private _canvases_by_container_id: Map<string, HTMLCanvasElement> = new Map();
private _controls_by_container_id: Map<string, mapboxgl.NavigationControl> = new Map();
private _moving_maps = false;
override createObject() {
return new MapboxPerspectiveCamera(); // I use a PerspectiveCamera to have the picker working
}
override async cook() {
this.updateMaps();
this._updateCameraAttributes();
this.cookController.endCook();
}
static PARAM_CALLBACK_updateCameraAttributes(node: MapboxCameraObjNode) {
node._updateCameraAttributes();
}
private _updateCameraAttributes() {
const objects = [this._object];
const node = this;
CameraCSSRendererSopOperation.updateObject({
objects,
params: {node: this.pv.CSSRenderer},
node,
active: this.pv.setCSSRenderer,
});
}
// private _inverse_proj_mat = new Matrix4();
// private _cam_pos = new Vector3();
// private _mouse_pos = new Vector3();
// private _view_dir = new Vector3();
// override prepareRaycaster(mouse: Vector2, raycaster: Raycaster) {
// // adapted from https://github.com/mapbox/mapbox-gl-js/issues/7395
// // const camInverseProjection = this._inverse_proj_mat.getInverse(this._object.projectionMatrix);
// // this._cam_pos.set(0, 0, 0);
// // this._cam_pos.applyMatrix4(camInverseProjection);
// // this._mouse_pos.set(mouse.x, mouse.y, 1);
// // this._mouse_pos.applyMatrix4(camInverseProjection);
// // this._view_dir.copy(this._mouse_pos).sub(this._cam_pos).normalize();
// // raycaster.set(this._cam_pos, this._view_dir);
// this._inverse_proj_mat.copy(this._object.projectionMatrix);
// this._inverse_proj_mat.invert();
// this._cam_pos.set(0, 0, 0);
// this._cam_pos.applyMatrix4(this._inverse_proj_mat);
// this._mouse_pos.set(mouse.x, mouse.y, 1);
// this._mouse_pos.applyMatrix4(this._inverse_proj_mat);
// this._view_dir.copy(this._mouse_pos).sub(this._cam_pos).normalize();
// raycaster.set(this._cam_pos, this._view_dir);
// }
createMap(container: HTMLElement) {
const map = new mapboxgl.Map({
style: this.pv.style,
container,
center: this.pv.lngLat.toArray() as Number2,
zoom: this.pv.zoom,
minZoom: this.pv.zoomRange.x,
maxZoom: this.pv.zoomRange.y,
pitch: this.pv.pitch,
bearing: this.pv.bearing,
// preserveDrawingBuffer: true,
dragRotate: this.pv.allowDragRotate,
pitchWithRotate: this.pv.allowDragRotate,
antialias: true,
});
this._updateCameraAttributes();
this._addRemoveControls(map, container.id);
this._maps_by_container_id.set(container.id, map);
this._map_containers_by_container_id.set(container.id, container);
this._canvases_by_container_id.set(container.id, container.querySelector('canvas')!);
return map;
}
// private _fetch_token(){
// const token = POLY.mapbox_token()
// if(token){
// return token
// } else {
// const scene = this.scene();
// const scene_uuid = scene.uuid();
// let url;
// if(scene_uuid){
// url = `/api/scenes/${scene_uuid}/mapbox`;
// } else {
// // in case the scene has not been saved yet
// url = `/api/account/mapbox_token`;
// }
// return new Promise((resolve, reject)=> {
// axios.get(url).then((response)=>{
// const token = response.data.token
// POLY.register_mapbox_token(token)
// resolve(token)
// }).catch(()=>{
// resolve()
// })
// })
// }
// }
updateMaps() {
this._maps_by_container_id.forEach((map, container_id) => {
this.updateMapFromContainerId(container_id);
});
this._updateCameraAttributes();
}
//this.object().dispatchEvent('change')
updateMapFromContainerId(container_id: string) {
const map = this._maps_by_container_id.get(container_id);
if (!map) {
return;
}
this.updateMapNav(map);
// controls
this._addRemoveControls(map, container_id);
// style
map.setStyle(this.pv.style);
}
updateMapNav(map: mapboxgl.Map) {
// position/zoom/pitch/bearing
map.jumpTo(this.cameraOptionsFromParams());
map.setMinZoom(this.pv.zoomRange.x);
map.setMaxZoom(this.pv.zoomRange.y);
const drag_rotate_handler = map.dragRotate;
if (isBooleanTrue(this.pv.allowDragRotate)) {
drag_rotate_handler.enable();
} else {
drag_rotate_handler.disable();
}
}
firstMap() {
let first_map: mapboxgl.Map | undefined;
this._maps_by_container_id.forEach((map, id) => {
if (!first_map) {
first_map = map;
}
});
return first_map;
}
firstId() {
let first_id: string | undefined;
this._maps_by_container_id.forEach((map, id) => {
if (!first_id) {
first_id = id;
}
});
return first_id;
}
firstMapElement() {
const id = this.firstId();
if (id) {
return this._map_containers_by_container_id.get(id);
}
}
bounds() {
const map = this.firstMap();
if (map) {
return map.getBounds();
}
}
zoom() {
const map = this.firstMap();
if (map) {
return map.getZoom();
}
}
center() {
const map = this.firstMap();
if (map) {
return map.getCenter();
}
}
horizontal_lng_lat_points() {
const id = this.firstId();
if (id) {
// const x = Math.floor(map._container.clientWidth*0.5*1.01)
// const y = map._container.clientHeight / 2;
// return [
// map.unproject([-x, y]),
// map.unproject([+x, y])
// ]
const map = this._maps_by_container_id.get(id);
const element = this._canvases_by_container_id.get(id);
if (map && element) {
const y = element.clientHeight / 2;
return [map.unproject([0, y]), map.unproject([100, y])];
}
}
}
// vertical_near_lng_lat_point(){
// const map = this.first_map()
// if(map){
// const x = 0
// const y = map._container.clientHeight
// return map.unproject([+x, y])
// }
// }
centerLngLatPoint() {
const id = this.firstId();
if (id) {
const map = this._maps_by_container_id.get(id);
const element = this._canvases_by_container_id.get(id);
if (map && element) {
const x = element.clientWidth * 0.5;
const y = element.clientHeight * 0.5;
return map.unproject([x, y]);
}
}
}
verticalFarLngLatPoints() {
const id = this.firstId();
if (id) {
const map = this._maps_by_container_id.get(id);
const element = this._canvases_by_container_id.get(id);
if (map && element) {
const x = element.clientWidth;
const y = 0;
return [map.unproject([0, y]), map.unproject([x, y])];
}
}
}
verticalNearLngLatPoints() {
const id = this.firstId();
if (id) {
const map = this._maps_by_container_id.get(id);
const element = this._canvases_by_container_id.get(id);
if (map && element) {
const x = element.clientWidth;
const y = element.clientHeight;
return [map.unproject([0, y]), map.unproject([x, y])];
}
}
}
// lng_lat_corners(){
// const map = this.first_map()
// if(map){
// const x = map._container.clientWidth
// const y = map._container.clientHeight
// return [
// map.unproject([0, 0]),
// map.unproject([0, y]),
// map.unproject([x, 0]),
// map.unproject([x, y])
// ]
// }
// }
removeMap(container: HTMLElement) {
if (container) {
const map = this._maps_by_container_id.get(container.id);
if (map) {
map.remove();
this._maps_by_container_id.delete(container.id);
this._map_containers_by_container_id.delete(container.id);
this._canvases_by_container_id.delete(container.id);
this._controls_by_container_id.delete(container.id);
}
}
}
// allows all mapbox viewers depending on the same camera to sync up
// once one has completed a move
onMoveEnd(container: HTMLElement) {
if (this._moving_maps === true) {
return;
}
this._moving_maps = true; // to avoid infinite loop, as the moved maps will trigger the same event
if (container != null) {
const triggering_map = this._maps_by_container_id.get(container.id);
if (triggering_map != null) {
const camera_options = this.cameraOptionsFromMap(triggering_map);
this._maps_by_container_id.forEach((map, container_id) => {
if (container_id !== container.id) {
const map = this._maps_by_container_id.get(container_id);
map?.jumpTo(camera_options);
}
});
}
}
this.object.dispatchEvent({type: 'moveend'});
this._moving_maps = false;
}
lngLat() {
const val = this.pv.lngLat;
return {
lng: val.x,
lat: val.y,
};
}
cameraOptionsFromParams() {
return {
center: this.lngLat(),
pitch: this.pv.pitch,
bearing: this.pv.bearing,
zoom: this.pv.zoom,
};
}
cameraOptionsFromMap(map: mapboxgl.Map) {
// let data;
// this.pv.lng_lat.toArray();
return {
center: map.getCenter(),
pitch: map.getPitch(),
bearing: map.getBearing(),
zoom: map.getZoom(),
};
}
_addRemoveControls(map: mapboxgl.Map, container_id: string) {
let nav_control = this._controls_by_container_id.get(container_id);
if (nav_control) {
if (!isBooleanTrue(this.pv.addZoomControl)) {
map.removeControl(nav_control);
this._controls_by_container_id.delete(container_id);
}
} else {
if (isBooleanTrue(this.pv.addZoomControl)) {
nav_control = new mapboxgl.NavigationControl();
map.addControl(nav_control, 'bottom-right');
this._controls_by_container_id.set(container_id, nav_control);
}
}
}
updateParamsFromMap() {
const map = this.firstMap();
if (map) {
const center = map.getCenter();
const zoom = map.getZoom();
const pitch = map.getPitch();
const bearing = map.getBearing();
this.p.lngLat.set([center.lng, center.lat]);
this.p.zoom.set(zoom);
this.p.pitch.set(pitch);
this.p.bearing.set(bearing);
}
}
static PARAM_CALLBACK_update_params_from_map(node: MapboxCameraObjNode) {
node.updateParamsFromMap();
}
static PARAM_CALLBACK_update_style(node: MapboxCameraObjNode) {
node.updateStyle();
}
static PARAM_CALLBACK_update_nav(node: MapboxCameraObjNode) {
node.updateNav();
}
updateStyle() {
this._maps_by_container_id.forEach((map, container_id) => {
map.setStyle(this.pv.style);
});
}
updateNav() {
this._maps_by_container_id.forEach((map) => {
this.updateMapNav(map);
});
}
async createViewer(options?: BaseViewerOptions | HTMLElement): Promise<MapboxViewer | undefined> {
const viewer = Poly.camerasRegister.createViewer<MapboxPerspectiveCamera>({
camera: this.object,
scene: this.scene() as any,
}) as MapboxViewer | undefined;
let element: HTMLElement | undefined;
if (options && options instanceof HTMLElement) {
element = options;
} else {
element = options?.element;
}
if (viewer && element) {
viewer.mount(element);
}
return viewer;
}
}