src/component/slider/SliderGLRenderer.ts (354 lines of code) (raw):

import * as THREE from "three"; import { Subscription } from "rxjs"; import { Spatial } from "../../geo/Spatial"; import { Transform } from "../../geo/Transform"; import { Image } from "../../graph/Image"; import { IAnimationState } from "../../state/interfaces/IAnimationState"; import { AnimationFrame } from "../../state/interfaces/AnimationFrame"; import { TextureProvider } from "../../tile/TextureProvider"; import { BBoxProjectorShaderMaterial } from "../image/interfaces/BBoxProjectorShaderMaterial"; import { ProjectorShaderMaterial } from "../image/interfaces/ProjectorShaderMaterial"; import { SliderConfigurationMode } from "../interfaces/SliderConfiguration"; import { MeshFactory } from "../util/MeshFactory"; import { MeshScene } from "../util/MeshScene"; import { isSpherical } from "../../geo/Geo"; import { CameraType } from "../../geo/interfaces/CameraType"; export class SliderGLRenderer { private _factory: MeshFactory; private _scene: MeshScene; private _spatial: Spatial; private _currentKey: string; private _previousKey: string; private _disabled: boolean; private _curtain: number; private _frameId: number; private _needsRender: boolean; private _mode: SliderConfigurationMode; private _currentProviderDisposers: { [key: string]: () => void }; private _previousProviderDisposers: { [key: string]: () => void }; constructor() { this._factory = new MeshFactory(); this._scene = new MeshScene(); this._spatial = new Spatial(); this._currentKey = null; this._previousKey = null; this._disabled = false; this._curtain = 1; this._frameId = 0; this._needsRender = false; this._mode = null; this._currentProviderDisposers = {}; this._previousProviderDisposers = {}; } public get disabled(): boolean { return this._disabled; } public get frameId(): number { return this._frameId; } public get needsRender(): boolean { return this._needsRender; } public setTextureProvider(key: string, provider: TextureProvider): void { this._setTextureProvider( key, this._currentKey, provider, this._currentProviderDisposers, this._updateTexture.bind(this)); } public setTextureProviderPrev(key: string, provider: TextureProvider): void { this._setTextureProvider( key, this._previousKey, provider, this._previousProviderDisposers, this._updateTexturePrev.bind(this)); } public update(frame: AnimationFrame, mode: SliderConfigurationMode): void { this._updateFrameId(frame.id); this._updateImagePlanes(frame.state, mode); } public updateCurtain(curtain: number): void { if (this._curtain === curtain) { return; } this._curtain = curtain; this._updateCurtain(); this._needsRender = true; } public updateTexture( imageElement: HTMLImageElement, image: Image) : void { const planes: { [key: string]: THREE.Mesh } = image.id === this._currentKey ? this._scene.planes : image.id === this._previousKey ? this._scene.planesOld : {}; if (Object.keys(planes).length === 0) { return; } this._needsRender = true; for (const key in planes) { if (!planes.hasOwnProperty(key)) { continue; } const plane: THREE.Mesh = planes[key]; let material: ProjectorShaderMaterial = <ProjectorShaderMaterial>plane.material; let texture: THREE.Texture = <THREE.Texture>material.uniforms.projectorTex.value; texture.image = imageElement; texture.needsUpdate = true; } } public updateTextureImage( imageElement: HTMLImageElement, image?: Image) : void { if (this._currentKey !== image.id) { return; } 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: ProjectorShaderMaterial = <ProjectorShaderMaterial>plane.material; let texture: THREE.Texture = <THREE.Texture>material.uniforms.projectorTex.value; texture.image = imageElement; texture.needsUpdate = true; } } public render( perspectiveCamera: THREE.PerspectiveCamera, renderer: THREE.WebGLRenderer): void { if (!this.disabled) { renderer.render(this._scene.sceneOld, perspectiveCamera); } renderer.render(this._scene.scene, perspectiveCamera); this._needsRender = false; } public dispose(): void { this._scene.clear(); for (const key in this._currentProviderDisposers) { if (!this._currentProviderDisposers.hasOwnProperty(key)) { continue; } this._currentProviderDisposers[key](); } for (const key in this._previousProviderDisposers) { if (!this._previousProviderDisposers.hasOwnProperty(key)) { continue; } this._previousProviderDisposers[key](); } this._currentProviderDisposers = {}; this._previousProviderDisposers = {}; } private _getBasicCorners(currentAspect: number, previousAspect: number): number[][] { let offsetX: number; let offsetY: number; if (currentAspect > previousAspect) { offsetX = 0.5; offsetY = 0.5 * currentAspect / previousAspect; } else { offsetX = 0.5 * previousAspect / currentAspect; offsetY = 0.5; } return [[0.5 - offsetX, 0.5 - offsetY], [0.5 + offsetX, 0.5 + offsetY]]; } private _setDisabled(state: IAnimationState): void { this._disabled = state.currentImage == null || state.previousImage == null || (isSpherical(state.currentImage.cameraType) && !isSpherical(state.previousImage.cameraType)); } private _setTextureProvider( key: string, originalKey: string, provider: TextureProvider, providerDisposers: { [key: string]: () => void }, updateTexture: (texture: THREE.Texture) => void): void { if (key !== originalKey) { return; } let createdSubscription: Subscription = provider.textureCreated$ .subscribe(updateTexture); 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 providerDisposers) { let disposeProvider: () => void = providerDisposers[key]; disposeProvider(); delete providerDisposers[key]; } providerDisposers[key] = dispose; } private _updateCurtain(): void { 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 shaderMaterial = <BBoxProjectorShaderMaterial>plane.material; if (!!shaderMaterial.uniforms.curtain) { shaderMaterial.uniforms.curtain.value = this._curtain; } } } private _updateFrameId(frameId: number): void { this._frameId = frameId; } private _updateImagePlanes(state: IAnimationState, mode: SliderConfigurationMode): void { const currentChanged: boolean = state.currentImage != null && this._currentKey !== state.currentImage.id; const previousChanged: boolean = state.previousImage != null && this._previousKey !== state.previousImage.id; const modeChanged: boolean = this._mode !== mode; if (!(currentChanged || previousChanged || modeChanged)) { return; } this._setDisabled(state); this._needsRender = true; this._mode = mode; const motionless = state.motionless || mode === SliderConfigurationMode.Stationary || isSpherical(state.currentImage.cameraType); if (this.disabled || previousChanged) { if (this._previousKey in this._previousProviderDisposers) { this._previousProviderDisposers[this._previousKey](); delete this._previousProviderDisposers[this._previousKey]; } } if (this.disabled) { this._scene.setImagePlanesOld({}); } else { if (previousChanged || modeChanged) { const previousNode: Image = state.previousImage; this._previousKey = previousNode.id; const elements: number[] = state.currentTransform.rt.elements; let translation: number[] = [elements[12], elements[13], elements[14]]; const currentAspect: number = state.currentTransform.basicAspect; const previousAspect: number = state.previousTransform.basicAspect; const textureScale: number[] = currentAspect > previousAspect ? [1, previousAspect / currentAspect] : [currentAspect / previousAspect, 1]; let rotation: number[] = state.currentImage.rotation; let width: number = state.currentImage.width; let height: number = state.currentImage.height; if (isSpherical(previousNode.cameraType)) { rotation = state.previousImage.rotation; translation = this._spatial .rotate( this._spatial .opticalCenter( state.currentImage.rotation, translation) .toArray(), rotation) .multiplyScalar(-1) .toArray(); width = state.previousImage.width; height = state.previousImage.height; } const transform: Transform = new Transform( state.currentImage.exifOrientation, width, height, state.currentImage.scale, rotation, translation, previousNode.image, textureScale, state.currentImage.cameraParameters, <CameraType>state.currentImage.cameraType); let mesh: THREE.Mesh = undefined; if (isSpherical(previousNode.cameraType)) { mesh = this._factory.createMesh( previousNode, motionless || isSpherical(state.currentImage.cameraType) ? transform : state.previousTransform); } else { if (motionless) { const [[basicX0, basicY0], [basicX1, basicY1]]: number[][] = this._getBasicCorners(currentAspect, previousAspect); mesh = this._factory.createFlatMesh( state.previousImage, transform, basicX0, basicX1, basicY0, basicY1); } else { mesh = this._factory.createMesh(state.previousImage, state.previousTransform); } } const previousPlanes: { [key: string]: THREE.Mesh } = {}; previousPlanes[previousNode.id] = mesh; this._scene.setImagePlanesOld(previousPlanes); } } if (currentChanged || modeChanged) { if (this._currentKey in this._currentProviderDisposers) { this._currentProviderDisposers[this._currentKey](); delete this._currentProviderDisposers[this._currentKey]; } this._currentKey = state.currentImage.id; const planes: { [key: string]: THREE.Mesh } = {}; if (isSpherical(state.currentImage.cameraType)) { planes[state.currentImage.id] = this._factory.createCurtainMesh( state.currentImage, state.currentTransform); } else { if (motionless) { planes[state.currentImage.id] = this._factory.createDistortedCurtainMesh(state.currentImage, state.currentTransform); } else { planes[state.currentImage.id] = this._factory.createCurtainMesh(state.currentImage, state.currentTransform); } } this._scene.setImagePlanes(planes); this._updateCurtain(); } } 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: ProjectorShaderMaterial = <ProjectorShaderMaterial>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 _updateTexturePrev(texture: THREE.Texture): void { this._needsRender = true; const planes: { [key: string]: THREE.Mesh } = this._scene.planesOld; for (const key in planes) { if (!planes.hasOwnProperty(key)) { continue; } const plane: THREE.Mesh = planes[key]; let material: ProjectorShaderMaterial = <ProjectorShaderMaterial>plane.material; let oldTexture: THREE.Texture = <THREE.Texture>material.uniforms.projectorTex.value; material.uniforms.projectorTex.value = null; oldTexture.dispose(); material.uniforms.projectorTex.value = texture; } } }