mapillary-js
Version:
A WebGL interactive street imagery library
219 lines (177 loc) • 6.63 kB
text/typescript
import {
filter,
map,
publishReplay,
refCount,
scan,
skip,
startWith,
tap,
withLatestFrom,
} from "rxjs/operators";
import {
BehaviorSubject,
Observable,
Subject,
} from "rxjs";
import { RenderCamera } from "./RenderCamera";
import { RenderMode } from "./RenderMode";
import { ViewportSize } from "./interfaces/ViewportSize";
import { Spatial } from "../geo/Spatial";
import { AnimationFrame } from "../state/interfaces/AnimationFrame";
import { SubscriptionHolder } from "../util/SubscriptionHolder";
interface RenderCameraOperation {
(rc: RenderCamera): RenderCamera;
}
export class RenderService {
private _bearing$: Observable<number>;
private _element: HTMLElement;
private _currentFrame$: Observable<AnimationFrame>;
private _projectionMatrix$: Subject<number[]>;
private _renderCameraOperation$: Subject<RenderCameraOperation>;
private _renderCameraHolder$: Observable<RenderCamera>;
private _renderCameraFrame$: Observable<RenderCamera>;
private _renderCamera$: Observable<RenderCamera>;
private _resize$: Subject<void>;
private _size$: BehaviorSubject<ViewportSize>;
private _spatial: Spatial;
private _renderMode$: BehaviorSubject<RenderMode>;
private _subscriptions: SubscriptionHolder = new SubscriptionHolder();
constructor(
element: HTMLElement,
currentFrame$: Observable<AnimationFrame>,
renderMode: RenderMode,
renderCamera?: RenderCamera) {
this._element = element;
this._currentFrame$ = currentFrame$;
this._spatial = new Spatial();
renderMode = renderMode != null ? renderMode : RenderMode.Fill;
this._resize$ = new Subject<void>();
this._projectionMatrix$ = new Subject<number[]>();
this._renderCameraOperation$ =
new Subject<RenderCameraOperation>();
this._size$ =
new BehaviorSubject<ViewportSize>({
height: this._element.offsetHeight,
width: this._element.offsetWidth,
});
const subs = this._subscriptions;
subs.push(this._resize$.pipe(
map(
(): ViewportSize => {
return {
height: this._element.offsetHeight,
width: this._element.offsetWidth,
};
}))
.subscribe(this._size$));
this._renderMode$ = new BehaviorSubject<RenderMode>(renderMode);
this._renderCameraHolder$ = this._renderCameraOperation$.pipe(
startWith(
(rc: RenderCamera): RenderCamera => {
return rc;
}),
scan(
(rc: RenderCamera, operation: RenderCameraOperation): RenderCamera => {
return operation(rc);
},
renderCamera ??
new RenderCamera(
this._element.offsetWidth,
this._element.offsetHeight,
renderMode)),
publishReplay(1),
refCount());
this._renderCameraFrame$ = this._currentFrame$.pipe(
withLatestFrom(this._renderCameraHolder$),
tap(
([frame, rc]: [AnimationFrame, RenderCamera]): void => {
rc.setFrame(frame);
}),
map(
(args: [AnimationFrame, RenderCamera]): RenderCamera => {
return args[1];
}),
publishReplay(1),
refCount());
this._renderCamera$ = this._renderCameraFrame$.pipe(
filter(
(rc: RenderCamera): boolean => {
return rc.changed;
}),
publishReplay(1),
refCount());
this._bearing$ = this._renderCamera$.pipe(
map(
(rc: RenderCamera): number => {
let bearing: number =
this._spatial.radToDeg(
this._spatial.azimuthalToBearing(rc.rotation.phi));
return this._spatial.wrap(bearing, 0, 360);
}),
publishReplay(1),
refCount());
subs.push(this._size$.pipe(
skip(1),
map(
(size: ViewportSize) => {
return (rc: RenderCamera): RenderCamera => {
rc.setSize(size);
return rc;
};
}))
.subscribe(this._renderCameraOperation$));
subs.push(this._renderMode$.pipe(
skip(1),
map(
(rm: RenderMode) => {
return (rc: RenderCamera): RenderCamera => {
rc.setRenderMode(rm);
return rc;
};
}))
.subscribe(this._renderCameraOperation$));
subs.push(this._projectionMatrix$.pipe(
map(
(projectionMatrix: number[]) => {
return (rc: RenderCamera): RenderCamera => {
rc.setProjectionMatrix(projectionMatrix);
return rc;
};
}))
.subscribe(this._renderCameraOperation$));
subs.push(this._bearing$.subscribe(() => { /*noop*/ }));
subs.push(this._renderCameraHolder$.subscribe(() => { /*noop*/ }));
subs.push(this._size$.subscribe(() => { /*noop*/ }));
subs.push(this._renderMode$.subscribe(() => { /*noop*/ }));
subs.push(this._renderCamera$.subscribe(() => { /*noop*/ }));
subs.push(this._renderCameraFrame$.subscribe(() => { /*noop*/ }));
}
public get bearing$(): Observable<number> {
return this._bearing$;
}
public get element(): HTMLElement {
return this._element;
}
public get projectionMatrix$(): Subject<number[]> {
return this._projectionMatrix$;
}
public get renderCamera$(): Observable<RenderCamera> {
return this._renderCamera$;
}
public get renderCameraFrame$(): Observable<RenderCamera> {
return this._renderCameraFrame$;
}
public get renderMode$(): Subject<RenderMode> {
return this._renderMode$;
}
public get resize$(): Subject<void> {
return this._resize$;
}
public get size$(): Observable<ViewportSize> {
return this._size$;
}
public dispose(): void {
this._subscriptions.unsubscribe();
}
}