@polygonjs/plugin-mapbox
Version:
Mapbox plugin for the 3D engine https://polygonjs.com
207 lines (187 loc) • 6.92 kB
text/typescript
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);
}
}