UNPKG

@polygonjs/plugin-mapbox

Version:

Mapbox plugin for the 3D engine https://polygonjs.com

526 lines (486 loc) 15.7 kB
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; } }