UNPKG

mapillary-js

Version:

A WebGL interactive street imagery library

148 lines (121 loc) 5.94 kB
import * as THREE from "three"; import { empty as observableEmpty, combineLatest as observableCombineLatest, Observable, Subscription, } from "rxjs"; import { first, map, distinctUntilChanged, switchMap, withLatestFrom, } from "rxjs/operators"; import { Transform } from "../../geo/Transform"; import { Image } from "../../graph/Image"; import { ViewportCoords } from "../../geo/ViewportCoords"; import { RenderCamera } from "../../render/RenderCamera"; import { AnimationFrame } from "../../state/interfaces/AnimationFrame"; 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 { Spatial } from "../../geo/Spatial"; import * as ImageBoundary from "./ImageBoundary"; /** * The `BounceHandler` ensures that the viewer bounces back to the image * when drag panning outside of the image edge. */ export class BounceHandler extends HandlerBase<PointerConfiguration> { private _spatial: Spatial; private _viewportCoords: ViewportCoords; private _bounceSubscription: Subscription; constructor( component: Component<PointerConfiguration>, container: Container, navigator: Navigator, viewportCoords: ViewportCoords, spatial: Spatial) { super(component, container, navigator); this._spatial = spatial; this._viewportCoords = viewportCoords; } protected _enable(): void { const inTransition$: Observable<boolean> = this._navigator.stateService.currentState$.pipe( map( (frame: AnimationFrame): boolean => { return frame.state.alpha < 1; }), distinctUntilChanged()); this._bounceSubscription = observableCombineLatest( inTransition$, this._navigator.stateService.inTranslation$, this._container.mouseService.active$, this._container.touchService.active$).pipe( map( (noForce: boolean[]): boolean => { return noForce[0] || noForce[1] || noForce[2] || noForce[3]; }), distinctUntilChanged(), switchMap( (noForce: boolean): Observable<[RenderCamera, Transform]> => { return noForce ? observableEmpty() : observableCombineLatest( this._container.renderService.renderCamera$, this._navigator.stateService.currentTransform$.pipe(first())); }), withLatestFrom(this._navigator.panService.panImages$)) .subscribe( ([[render, transform], nts]: [[RenderCamera, Transform], [Image, Transform, number][]]): void => { if (!transform.hasValidScale && render.camera.focal < 0.1) { return; } if (render.perspective.aspect === 0 || render.perspective.aspect === Number.POSITIVE_INFINITY) { return; } const distances: number[] = ImageBoundary.viewportDistances(transform, render.perspective, this._viewportCoords); const basic: number[] = this._viewportCoords.viewportToBasic(0, 0, transform, render.perspective); if ((basic[0] < 0 || basic[0] > 1) && nts.length > 0) { distances[0] = distances[2] = 0; } for (const [, t] of nts) { const d: number[] = ImageBoundary.viewportDistances(t, render.perspective, this._viewportCoords); for (let i: number = 1; i < distances.length; i += 2) { if (d[i] < distances[i]) { distances[i] = d[i]; } } } if (Math.max(...distances) < 0.01) { return; } const horizontalDistance: number = distances[1] - distances[3]; const verticalDistance: number = distances[0] - distances[2]; const currentDirection: THREE.Vector3 = this._viewportCoords .unprojectFromViewport(0, 0, render.perspective) .sub(render.perspective.position); const directionPhi: THREE.Vector3 = this._viewportCoords .unprojectFromViewport(horizontalDistance, 0, render.perspective) .sub(render.perspective.position); const directionTheta: THREE.Vector3 = this._viewportCoords .unprojectFromViewport(0, verticalDistance, render.perspective) .sub(render.perspective.position); let phi: number = (horizontalDistance > 0 ? 1 : -1) * directionPhi.angleTo(currentDirection); let theta: number = (verticalDistance > 0 ? 1 : -1) * directionTheta.angleTo(currentDirection); const threshold: number = Math.PI / 60; const coeff: number = 1e-1; phi = this._spatial.clamp(coeff * phi, -threshold, threshold); theta = this._spatial.clamp(coeff * theta, -threshold, threshold); this._navigator.stateService.rotateUnbounded({ phi: phi, theta: theta }); }); } protected _disable(): void { this._bounceSubscription.unsubscribe(); } protected _getConfiguration(): PointerConfiguration { return {}; } }