@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
256 lines (233 loc) • 8.36 kB
text/typescript
import {CoreMapboxTransform} from '../Transform';
import {
// Mesh,
Camera,
Matrix4,
Scene,
Vector3,
WebGLRenderer,
Matrix4Tuple,
// Vector2,
// PlaneGeometry,
} from 'three';
import mapboxgl from 'mapbox-gl';
import {PolyScene} from '../../../../engine/scene/PolyScene';
import {MapboxViewer} from '../../../../engine/viewers/Mapbox';
// import {TIME_CONTROLLER_UPDATE_TIME_OPTIONS_DEFAULT} from '../../../../engine/scene/utils/TimeController';
// import {CoreCameraCSSRendererController, CSSRendererConfig} from '../../../camera/CoreCameraCSSRendererController';
import {LngLat} from '../Common';
const ID = 'threejs_layer';
export type ThreejsLayerRenderFunc = (renderer: WebGLRenderer) => void;
export interface ThreejsLayerOptions {
map: mapboxgl.Map;
scene: PolyScene;
camera: Camera;
canvas: HTMLCanvasElement;
lngLat: LngLat;
viewer: MapboxViewer;
renderFunc: ThreejsLayerRenderFunc;
}
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 _renderer: WebGLRenderer | undefined;
private _displayScene: Scene;
private _map: mapboxgl.Map;
private _viewer: MapboxViewer;
// private _gl: WebGLRenderingContext | undefined;
private _renderFunc: ThreejsLayerRenderFunc;
// private _renderCSSFunc: RenderFunc | undefined;
// private _cssRendererConfig: CSSRendererConfig | undefined;
private _camera: Camera;
// private _scene: PolyScene;
// private _canvas: HTMLCanvasElement;
private _lngLat: LngLat;
constructor(options: ThreejsLayerOptions) {
this._map = options.map;
this._camera = options.camera;
// this._scene = options.scene;
// this._canvas = options.canvas;
this._lngLat = options.lngLat;
this._displayScene = options.scene.threejsScene();
this._renderFunc = options.renderFunc;
this._viewer = options.viewer;
}
onAdd(map: mapboxgl.Map, gl: WebGLRenderingContext) {
// this._map = map;
// this._gl = gl;
// console.log(this._gl);
this.createRenderer(gl);
}
// onRemove() {
// this._renderer?.dispose();
// }
private createRenderer(gl: WebGLRenderingContext) {
// 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: gl,
});
this._viewer.setRenderer(this._renderer);
this._renderer.autoClear = false;
this._renderer.shadowMap.enabled = true;
// this._cssRendererConfig = CoreCameraCSSRendererController.cssRendererConfig({
// scene: this._scene,
// camera: this._camera,
// canvas: this._canvas,
// });
// const cssRendererNode = this._cssRendererConfig?.cssRendererNode;
// if (cssRendererNode) {
// cssRendererNode.mountRenderer(this._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) {
// console.warn('no renderer');
// return;
// }
if (!this._renderer) {
console.warn('no renderer');
return;
}
let previousBackground = this._displayScene.background;
if (this._displayScene.background) {
// console.warn('scene background is not null, this will cover the map and prevent it from being seen');
this._displayScene.background = null;
}
// this._scene.timeController.updateClockDelta();
// this._scene.timeController.incrementTimeIfPlaying(TIME_CONTROLLER_UPDATE_TIME_OPTIONS_DEFAULT);
// const delta = this._scene.timeController.updateClockDelta();
this._updateCameraMatrix(matrix);
// this._renderer.state.reset();
this._renderer.resetState();
this._renderFunc(this._renderer);
// const delta = this._scene.timeController.updateClockDelta();
// this._runOnBeforeTickCallbacks(delta);
// this.scene().update(delta);
// this._runOnAfterTickCallbacks(delta);
// // this._markerTrackingConfig?.renderFunction();
// this.render(delta);
// this._renderer.render(this._displayScene, this._camera);
this._map.triggerRepaint();
this._displayScene.background = previousBackground;
// 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([this._lngLat.lng, this._lngLat.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 as Matrix4Tuple;
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);
// }
}