mapillary-js
Version:
WebGL JavaScript library for displaying street level imagery from mapillary.com
307 lines (231 loc) • 9.21 kB
text/typescript
import * as THREE from "three";
import {Subscription} from "rxjs";
import {
MeshFactory,
MeshScene,
IShaderMaterial,
} from "../../Component";
import {Node} from "../../Graph";
import {
ICurrentState,
IFrame,
} from "../../State";
import {TextureProvider} from "../../Tiles";
import { Transform } from "../../geo/Transform";
export class ImagePlaneGLRenderer {
private _factory: MeshFactory;
private _scene: MeshScene;
private _alpha: number;
private _alphaOld: number;
private _fadeOutSpeed: number;
private _currentKey: string;
private _previousKey: string;
private _providerDisposers: { [key: string]: () => void };
private _frameId: number;
private _needsRender: boolean;
constructor() {
this._factory = new MeshFactory();
this._scene = new MeshScene();
this._alpha = 0;
this._alphaOld = 0;
this._fadeOutSpeed = 0.05;
this._currentKey = null;
this._previousKey = null;
this._providerDisposers = {};
this._frameId = 0;
this._needsRender = false;
}
public get frameId(): number {
return this._frameId;
}
public get needsRender(): boolean {
return this._needsRender;
}
public indicateNeedsRender(): void {
this._needsRender = true;
}
public addPeripheryPlane(node: Node, transform: Transform): void {
const mesh: THREE.Mesh = this._factory.createMesh(node, transform);
const planes: { [key: string]: THREE.Mesh } = {};
planes[node.key] = mesh;
this._scene.addPeripheryPlanes(planes);
this._needsRender = true;
}
public clearPeripheryPlanes(): void {
this._scene.setPeripheryPlanes({});
this._needsRender = true;
}
public updateFrame(frame: IFrame): void {
this._updateFrameId(frame.id);
this._needsRender = this._updateAlpha(frame.state.alpha) || this._needsRender;
this._needsRender = this._updateAlphaOld(frame.state.alpha) || this._needsRender;
this._needsRender = this._updateImagePlanes(frame.state) || this._needsRender;
}
public setTextureProvider(key: string, provider: TextureProvider): void {
if (key !== this._currentKey) {
return;
}
let createdSubscription: Subscription = provider.textureCreated$
.subscribe(
(texture: THREE.Texture): void => {
this._updateTexture(texture);
});
let updatedSubscription: Subscription = provider.textureUpdated$
.subscribe(
(updated: boolean): void => {
this._needsRender = true;
});
let dispose: () => void = (): void => {
createdSubscription.unsubscribe();
updatedSubscription.unsubscribe();
provider.dispose();
};
if (key in this._providerDisposers) {
let disposeProvider: () => void = this._providerDisposers[key];
disposeProvider();
delete this._providerDisposers[key];
}
this._providerDisposers[key] = dispose;
}
public updateTextureImage(image: HTMLImageElement, node: Node): void {
this._needsRender = true;
const planes: { [key: string]: THREE.Mesh } =
this._extend({}, this._scene.planes, this._scene.planesOld, this._scene.planesPeriphery);
for (const key in planes) {
if (!planes.hasOwnProperty(key)) {
continue;
}
if (key !== node.key) {
continue;
}
const plane: THREE.Mesh = planes[key];
let material: IShaderMaterial = <IShaderMaterial>plane.material;
let texture: THREE.Texture = <THREE.Texture>material.uniforms.projectorTex.value;
texture.image = image;
texture.needsUpdate = true;
}
}
public render(
perspectiveCamera: THREE.PerspectiveCamera,
renderer: THREE.WebGLRenderer): void {
const planes: { [key: string]: THREE.Mesh } = this._scene.planes;
const planesOld: { [key: string]: THREE.Mesh } = this._scene.planesOld;
const planesPeriphery: { [key: string]: THREE.Mesh } = this._scene.planesPeriphery;
const planeAlpha: number = Object.keys(planesOld).length ? 1 : this._alpha;
const peripheryAlpha: number = Object.keys(planesOld).length ? 1 : Math.floor(this._alpha);
for (const key in planes) {
if (!planes.hasOwnProperty(key)) {
continue;
}
const plane: THREE.Mesh = planes[key];
(<IShaderMaterial>plane.material).uniforms.opacity.value = planeAlpha;
}
for (const key in planesOld) {
if (!planesOld.hasOwnProperty(key)) {
continue;
}
const plane: THREE.Mesh = planesOld[key];
(<IShaderMaterial>plane.material).uniforms.opacity.value = this._alphaOld;
}
for (const key in planesPeriphery) {
if (!planesPeriphery.hasOwnProperty(key)) {
continue;
}
const plane: THREE.Mesh = planesPeriphery[key];
(<IShaderMaterial>plane.material).uniforms.opacity.value = peripheryAlpha;
}
renderer.render(this._scene.scenePeriphery, perspectiveCamera);
renderer.render(this._scene.scene, perspectiveCamera);
renderer.render(this._scene.sceneOld, perspectiveCamera);
for (const key in planes) {
if (!planes.hasOwnProperty(key)) {
continue;
}
const plane: THREE.Mesh = planes[key];
(<IShaderMaterial>plane.material).uniforms.opacity.value = this._alpha;
}
renderer.render(this._scene.scene, perspectiveCamera);
}
public clearNeedsRender(): void {
this._needsRender = false;
}
public dispose(): void {
this._scene.clear();
}
private _updateFrameId(frameId: number): void {
this._frameId = frameId;
}
private _updateAlpha(alpha: number): boolean {
if (alpha === this._alpha) {
return false;
}
this._alpha = alpha;
return true;
}
private _updateAlphaOld(alpha: number): boolean {
if (alpha < 1 || this._alphaOld === 0) {
return false;
}
this._alphaOld = Math.max(0, this._alphaOld - this._fadeOutSpeed);
return true;
}
private _updateImagePlanes(state: ICurrentState): boolean {
if (state.currentNode == null || state.currentNode.key === this._currentKey) {
return false;
}
let previousKey: string = state.previousNode != null ? state.previousNode.key : null;
let currentKey: string = state.currentNode.key;
if (this._previousKey !== previousKey &&
this._previousKey !== currentKey &&
this._previousKey in this._providerDisposers) {
let disposeProvider: () => void = this._providerDisposers[this._previousKey];
disposeProvider();
delete this._providerDisposers[this._previousKey];
}
if (previousKey != null) {
if (previousKey !== this._currentKey && previousKey !== this._previousKey) {
let previousMesh: THREE.Mesh =
this._factory.createMesh(state.previousNode, state.previousTransform);
const previousPlanes: { [key: string]: THREE.Mesh } = {};
previousPlanes[previousKey] = previousMesh;
this._scene.updateImagePlanes(previousPlanes);
}
this._previousKey = previousKey;
}
this._currentKey = currentKey;
let currentMesh: THREE.Mesh =
this._factory.createMesh(state.currentNode, state.currentTransform);
const planes: { [key: string]: THREE.Mesh } = {};
planes[currentKey] = currentMesh;
this._scene.updateImagePlanes(planes);
this._alphaOld = 1;
return true;
}
private _updateTexture(texture: THREE.Texture): void {
this._needsRender = true;
const planes: { [key: string]: THREE.Mesh } = this._scene.planes;
for (const key in planes) {
if (!planes.hasOwnProperty(key)) {
continue;
}
const plane: THREE.Mesh = planes[key];
let material: IShaderMaterial = <IShaderMaterial>plane.material;
let oldTexture: THREE.Texture = <THREE.Texture>material.uniforms.projectorTex.value;
material.uniforms.projectorTex.value = null;
oldTexture.dispose();
material.uniforms.projectorTex.value = texture;
}
}
private _extend<T>(dest: T, ...sources: T[]): T {
for (const src of sources) {
for (const k in src) {
if (!src.hasOwnProperty(k)) {
continue;
}
dest[k] = src[k];
}
}
return dest;
}
}
export default ImagePlaneGLRenderer;