UNPKG

@polygonjs/plugin-mapbox

Version:

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

207 lines (187 loc) 6.92 kB
import {CoreMapboxTransform} from '../../../../core/mapbox/Transform'; import {MapboxCameraObjNode} from '../../../nodes/obj/MapboxCamera'; import {MapboxViewer} from '../../Mapbox'; import {Mesh, Camera, Matrix4, Scene, Vector3, WebGLRenderer, Vector2, PlaneGeometry} from 'three'; import mapboxgl from 'mapbox-gl'; import {PolyScene} from '@polygonjs/polygonjs/dist/src/engine/scene/PolyScene'; import {TIME_CONTROLLER_UPDATE_TIME_OPTIONS_DEFAULT} from '@polygonjs/polygonjs/dist/src/engine/scene/utils/TimeController'; import { CoreCameraCSSRendererController, CSSRendererConfig, } from '@polygonjs/polygonjs/dist/src/core/camera/CoreCameraCSSRendererController'; const ID = 'threejs_layer'; type RenderFunc = () => void; export class ThreejsLayer { public readonly id: string = ID; public readonly type: 'custom' = 'custom'; public readonly renderingMode: '3d' = '3d'; // 2d or 3d, the threejs will be either as an overlay or intersecting with buildings private _camera: Camera; private _scene: PolyScene; private _renderer: WebGLRenderer | undefined; private _map: mapboxgl.Map | undefined; private _gl: WebGLRenderingContext | undefined; private _renderCSSFunc: RenderFunc | undefined; private _cssRendererConfig: CSSRendererConfig | undefined; constructor(private _cameraNode: MapboxCameraObjNode, private _displayScene: Scene, private _viewer: MapboxViewer) { this._camera = this._cameraNode.object; this._scene = this._cameraNode.scene(); } onAdd(map: mapboxgl.Map, gl: WebGLRenderingContext) { this._map = map; this._gl = gl; this.createRenderer(); } onRemove() { this._renderer?.dispose(); } private createRenderer() { if (this._renderer != null) { this._renderer.dispose(); } if (!this._map) { console.error('no map given'); return; } if (!this._gl) { console.error('no gl context given'); return; } this._renderer = new WebGLRenderer({ // alpha: true // antialias: true, canvas: this._map.getCanvas(), context: this._gl, }); this._renderer.autoClear = false; this._renderer.shadowMap.enabled = true; this._cssRendererConfig = CoreCameraCSSRendererController.cssRendererConfig({ scene: this._scene, camera: this._camera, canvas: this._viewer.canvas(), }); const cssRendererNode = this._cssRendererConfig?.cssRendererNode; if (cssRendererNode) { cssRendererNode.mountRenderer(this._viewer.canvas()); } const cssRenderer = this._cssRendererConfig?.cssRenderer; if (cssRenderer) { cssRenderer.domElement.style.zIndex = '99999'; } this._renderCSSFunc = cssRenderer ? () => cssRenderer.render(this._displayScene, this._camera) : undefined; this._hack(); } resize(size: Vector2) { // TODO: resize is currently broken. // re-creating a renderer is the only way I found to reliably resize. // It seems that if I only resize, // the render will appear to be the right size (as in no antialiasing issue) // but 3d object end up slipping on the map (as opposed to staying anchored where they are expected to) this.createRenderer(); // this._renderer?.setSize(size.x, size.y); this._cssRendererConfig?.cssRenderer.setSize(size.x, size.y); } async render(gl: WebGLRenderingContext, matrix: number[]) { if (!this._renderer || !this._map) { return; } if (this._displayScene.background) { console.warn('scene background is not null, this will cover the map and prevent it from being seen'); } this._scene.timeController.updateClockDelta(); this._scene.timeController.incrementTimeIfPlaying(TIME_CONTROLLER_UPDATE_TIME_OPTIONS_DEFAULT); this._updateCameraMatrix(matrix); this._renderer.state.reset(); this._renderer.render(this._displayScene, this._camera); this._map.triggerRepaint(); if (this._renderCSSFunc) { this._renderCSSFunc(); } } // from https://docs.mapbox.com/mapbox-gl-js/example/add-3d-model/ // this now rotates objects correctly private _vX = new Vector3(1, 0, 0); private _vY = new Vector3(0, 1, 0); private _vZ = new Vector3(0, 0, 1); private mRX = new Matrix4(); private mRY = new Matrix4(); private mRZ = new Matrix4(); private s = new Vector3(); private m = new Matrix4(); private l = new Matrix4(); _updateCameraMatrix(matrix: number[]) { const lng_lat = this._viewer.cameraLngLat(); if (!lng_lat) { return; } const mercator = mapboxgl.MercatorCoordinate.fromLngLat([lng_lat.lng, lng_lat.lat], 0); const transform = { position: mercator, rotation: {x: Math.PI / 2, y: 0, z: 0}, scale: CoreMapboxTransform.WORLD_SCALE, }; this.mRX.identity(); this.mRY.identity(); this.mRZ.identity(); const rotationX = this.mRX.makeRotationAxis(this._vX, transform.rotation.x); const rotationY = this.mRY.makeRotationAxis(this._vY, transform.rotation.y); const rotationZ = this.mRZ.makeRotationAxis(this._vZ, transform.rotation.z); this.s.x = transform.scale; this.s.y = -transform.scale; this.s.z = transform.scale; this.m.fromArray(matrix); this.l.identity(); this.l .makeTranslation(1 * transform.position.x, 1 * transform.position.y, 1 * (transform.position.z || 0)) .scale(this.s) .multiply(rotationX) .multiply(rotationY) .multiply(rotationZ); this._camera.projectionMatrix.elements = matrix; this._camera.projectionMatrix = this.m.multiply(this.l); } // This is a very dirty hack that seems to allow objects to render properly. // If this was not called, // all objects created would render for a couple frames and then disappear. // There sometimes would be an WebGL warning along the lines of "buffer not large enough" // but it is completely unclear what could have caused it. // // What I tried to debug this: // // - upgrade from mapbox 1 to 2 // this made no difference // // - using Babylon Spector // but I was unable to isolate which call was problematic // // - fiddle with renderes options // that solved nothing // // - integrate the mapbox example as a layer instead of this one // When using the example layer and its included THREE.Scene, // it renders just fine. // But as soon as I replace the included scene with the one created by Polygonjs, // Then the problem reappears. // That's even if the scene is as simple as a Hemisphere Light and a Plane. // So that did not allow me to find a solution. // // - use src/debug.js (I forgot now where I copied it from) // to help find bad webgl calls. // but that didn't help. // // - setting Polygonjs scene's objects to // matrixAutoUpdate = true // or // frustumCulled = false // But that solved nothing. // // In short.... WFT?!?! // But for now, with this hack, it seems to work fine. private _hack() { const hackObject = new Mesh(new PlaneGeometry()); hackObject.frustumCulled = false; hackObject.position.z = -1000; hackObject.scale.set(0.01, 0.01, 0.01); const scene = this._scene.threejsScene(); scene.add(hackObject); } }