UNPKG

mapillary-js

Version:

A WebGL interactive street imagery library

267 lines (225 loc) 9.71 kB
import * as THREE from "three"; import { empty as observableEmpty, Observable, } from "rxjs"; import { map, filter, withLatestFrom, switchMap, publishReplay, refCount, } from "rxjs/operators"; import { Transform } from "../../geo/Transform"; import { Spatial } from "../../geo/Spatial"; import { EulerRotation } from "../../state/interfaces/EulerRotation"; import { State } from "../../state/State"; import { ViewportCoords } from "../../geo/ViewportCoords"; import { RenderCamera } from "../../render/RenderCamera"; import { Container } from "../../viewer/Container"; import { Navigator } from "../../viewer/Navigator"; import { Component } from "../Component"; import { PointerConfiguration } from "../interfaces/PointerConfiguration"; import { HandlerBase } from "../util/HandlerBase"; import { MouseOperator } from "../util/MouseOperator"; import { SubscriptionHolder } from "../../util/SubscriptionHolder"; export class EarthControlHandler extends HandlerBase<PointerConfiguration> { private _viewportCoords: ViewportCoords; private _spatial: Spatial; private _subscriptions: SubscriptionHolder; /** @ignore */ constructor( component: Component<PointerConfiguration>, container: Container, navigator: Navigator, viewportCoords: ViewportCoords, spatial: Spatial) { super(component, container, navigator); this._spatial = spatial; this._viewportCoords = viewportCoords; this._subscriptions = new SubscriptionHolder(); } protected _enable(): void { const earth$ = this._navigator.stateService.state$.pipe( map( (state: State): boolean => { return state === State.Earth; }), publishReplay(1), refCount()); const subs = this._subscriptions; subs.push(earth$.pipe( switchMap( (earth: boolean): Observable<MouseEvent> => { return earth ? this._container.mouseService.mouseWheel$ : observableEmpty(); })) .subscribe( (event: WheelEvent): void => { event.preventDefault(); })); subs.push(earth$.pipe( switchMap( (earth: boolean): Observable<[MouseEvent, MouseEvent]> => { if (!earth) { return observableEmpty(); } return MouseOperator.filteredPairwiseMouseDrag$(this._component.name, this._container.mouseService).pipe( filter( ([e1, e2]: [MouseEvent, MouseEvent]): boolean => { return !(e1.ctrlKey && e2.ctrlKey); })); }), withLatestFrom( this._container.renderService.renderCamera$, this._navigator.stateService.currentTransform$), map( ([[previous, current], render, transform]: [[MouseEvent, MouseEvent], RenderCamera, Transform]): number[] => { const planeNormal = [0, 0, 1]; const planePoint = [0, 0, -2]; const currentIntersection = this._planeIntersection( current, planeNormal, planePoint, render.perspective, this._container.container); const previousIntersection = this._planeIntersection( previous, planeNormal, planePoint, render.perspective, this._container.container); if (!currentIntersection || !previousIntersection) { return null; } const direction = new THREE.Vector3() .subVectors(currentIntersection, previousIntersection) .multiplyScalar(-1) .toArray(); return direction; }), filter( (direction: number[]): boolean => { return !!direction; })) .subscribe( (direction: number[]): void => { this._navigator.stateService.truck(direction); })); subs.push(earth$.pipe( switchMap( (earth: boolean): Observable<[MouseEvent, MouseEvent]> => { if (!earth) { return observableEmpty(); } return MouseOperator.filteredPairwiseMouseDrag$(this._component.name, this._container.mouseService).pipe( filter( ([e1, e2]: [MouseEvent, MouseEvent]): boolean => { return e1.ctrlKey && e2.ctrlKey; })); }), map( ([previous, current]: [MouseEvent, MouseEvent]): EulerRotation => { return this._mousePairToRotation(previous, current); })) .subscribe( (rotation: EulerRotation): void => { this._navigator.stateService.orbit(rotation); })); subs.push(earth$.pipe( switchMap( (earth: boolean): Observable<[MouseEvent, MouseEvent]> => { if (!earth) { return observableEmpty(); } return MouseOperator.filteredPairwiseMouseRightDrag$(this._component.name, this._container.mouseService).pipe( filter( ([e1, e2]: [MouseEvent, MouseEvent]): boolean => { return !e1.ctrlKey && !e2.ctrlKey; })); }), map( ([previous, current]: [MouseEvent, MouseEvent]): EulerRotation => { return this._mousePairToRotation(previous, current); })) .subscribe( (rotation: EulerRotation): void => { this._navigator.stateService.orbit(rotation); })); subs.push(earth$.pipe( switchMap( (earth: boolean): Observable<WheelEvent> => { if (!earth) { return observableEmpty(); } return this._container.mouseService .filteredWheel$(this._component.name, this._container.mouseService.mouseWheel$); }), map( (event: WheelEvent): number => { let delta = event.deltaY; if (event.deltaMode === 1) { delta = 40 * delta; } else if (event.deltaMode === 2) { delta = 800 * delta; } const canvasSize = this._viewportCoords.containerToCanvas(this._container.container); return -delta / canvasSize[1]; })) .subscribe( (delta: number): void => { this._navigator.stateService.dolly(delta); })); } protected _disable(): void { this._subscriptions.unsubscribe(); } protected _getConfiguration(): PointerConfiguration { return {}; } private _eventToViewport(event: MouseEvent, element: HTMLElement): number[] { const previousCanvas = this._viewportCoords.canvasPosition(event, element); return this._viewportCoords.canvasToViewport(previousCanvas[0], previousCanvas[1], element); } private _mousePairToRotation( previous: MouseEvent, current: MouseEvent): EulerRotation { const [currentX, currentY] = this._eventToViewport(current, this._container.container); const [previousX, previousY]: number[] = this._eventToViewport(previous, this._container.container); const phi = (previousX - currentX) * Math.PI; const theta = (currentY - previousY) * Math.PI / 2; return { phi: phi, theta: theta }; } private _planeIntersection( event: MouseEvent, planeNormal: number[], planePoint: number[], camera: THREE.Camera, element: HTMLElement): THREE.Vector3 { const [canvasX, canvasY] = this._viewportCoords.canvasPosition(event, element); const direction: THREE.Vector3 = this._viewportCoords .unprojectFromCanvas( canvasX, canvasY, element, camera) .sub(camera.position) .normalize(); if (Math.abs(this._spatial.angleToPlane(direction.toArray(), planeNormal)) < Math.PI / 90) { return null; } const l0 = camera.position.clone(); const n = new THREE.Vector3().fromArray(planeNormal); const p0 = new THREE.Vector3().fromArray(planePoint); const d = new THREE.Vector3().subVectors(p0, l0).dot(n) / direction.clone().dot(n); const intersection = new THREE.Vector3().addVectors(l0, direction.multiplyScalar(d)); if (this._viewportCoords.worldToCamera(intersection.toArray(), camera)[2] > 0) { return null; } return intersection; } }