in src/component/slider/SliderComponent.ts [169:801]
protected _activate(): void {
const subs = this._subscriptions;
subs.push(this._domRenderer.mode$
.subscribe(
(mode: SliderConfigurationMode): void => {
this.configure({ mode });
}));
subs.push(this._glRenderer$.pipe(
map(
(glRenderer: SliderGLRenderer): GLRenderHash => {
let renderHash: GLRenderHash = {
name: this._name,
renderer: {
frameId: glRenderer.frameId,
needsRender: glRenderer.needsRender,
render: glRenderer.render.bind(glRenderer),
pass: RenderPass.Background,
},
};
return renderHash;
}))
.subscribe(this._container.glRenderer.render$));
const position$ = observableConcat(
this.configuration$.pipe(
map(
(configuration: SliderConfiguration): number => {
return configuration.initialPosition != null ?
configuration.initialPosition : 1;
}),
first()),
this._domRenderer.position$);
const mode$ = this.configuration$.pipe(
map(
(configuration: SliderConfiguration): SliderConfigurationMode => {
return configuration.mode;
}),
distinctUntilChanged());
const motionless$ = this._navigator.stateService.currentState$.pipe(
map(
(frame: AnimationFrame): boolean => {
return frame.state.motionless;
}),
distinctUntilChanged());
const spherical$ = this._navigator.stateService.currentState$.pipe(
map(
(frame: AnimationFrame): boolean => {
return isSpherical(frame.state.currentImage.cameraType);
}),
distinctUntilChanged());
const sliderVisible$ = observableCombineLatest(
this._configuration$.pipe(
map(
(configuration: SliderConfiguration): boolean => {
return configuration.sliderVisible;
})),
this._navigator.stateService.currentState$.pipe(
map(
(frame: AnimationFrame): boolean => {
return !(frame.state.currentImage == null ||
frame.state.previousImage == null ||
(isSpherical(
frame.state.currentImage.cameraType) &&
!isSpherical(
frame.state.previousImage.cameraType)));
}),
distinctUntilChanged())).pipe(
map(
([sliderVisible, enabledState]: [boolean, boolean]): boolean => {
return sliderVisible && enabledState;
}),
distinctUntilChanged());
this._waitSubscription = observableCombineLatest(
mode$,
motionless$,
spherical$,
sliderVisible$).pipe(
withLatestFrom(this._navigator.stateService.state$))
.subscribe(
([[mode, motionless, spherical, sliderVisible], state]:
[[SliderConfigurationMode, boolean, boolean, boolean], State]): void => {
const interactive: boolean = sliderVisible &&
(motionless ||
mode === SliderConfigurationMode.Stationary ||
spherical);
if (interactive && state !== State.WaitingInteractively) {
this._navigator.stateService.waitInteractively();
} else if (!interactive && state !== State.Waiting) {
this._navigator.stateService.wait();
}
});
subs.push(observableCombineLatest(
position$,
mode$,
motionless$,
spherical$,
sliderVisible$)
.subscribe(
([position, mode, motionless, spherical]: [number, SliderConfigurationMode, boolean, boolean, boolean]): void => {
if (motionless || mode === SliderConfigurationMode.Stationary || spherical) {
this._navigator.stateService.moveTo(1);
} else {
this._navigator.stateService.moveTo(position);
}
}));
subs.push(observableCombineLatest(
position$,
mode$,
motionless$,
spherical$,
sliderVisible$,
this._container.renderService.size$).pipe(
map(
([position, mode, motionless, spherical, sliderVisible]:
[number, SliderConfigurationMode, boolean, boolean, boolean, ViewportSize]): VirtualNodeHash => {
return {
name: this._name,
vNode: this._domRenderer.render(position, mode, motionless, spherical, sliderVisible),
};
}))
.subscribe(this._container.domRenderer.render$));
this._glRendererCreator$.next(null);
subs.push(observableCombineLatest(
position$,
spherical$,
sliderVisible$,
this._container.renderService.renderCamera$,
this._navigator.stateService.currentTransform$).pipe(
map(
([position, spherical, visible, render, transform]: [number, boolean, boolean, RenderCamera, Transform]): number => {
if (!spherical) {
return visible ? position : 1;
}
const basicMin: number[] = this._viewportCoords.viewportToBasic(-1.15, 0, transform, render.perspective);
const basicMax: number[] = this._viewportCoords.viewportToBasic(1.15, 0, transform, render.perspective);
const shiftedMax: number = basicMax[0] < basicMin[0] ? basicMax[0] + 1 : basicMax[0];
const basicPosition: number = basicMin[0] + position * (shiftedMax - basicMin[0]);
return basicPosition > 1 ? basicPosition - 1 : basicPosition;
}),
map(
(position: number): GLRendererOperation => {
return (glRenderer: SliderGLRenderer): SliderGLRenderer => {
glRenderer.updateCurtain(position);
return glRenderer;
};
}))
.subscribe(this._glRendererOperation$));
subs.push(observableCombineLatest(
this._navigator.stateService.currentState$,
mode$).pipe(
map(
([frame, mode]: [AnimationFrame, SliderConfigurationMode]): GLRendererOperation => {
return (glRenderer: SliderGLRenderer): SliderGLRenderer => {
glRenderer.update(frame, mode);
return glRenderer;
};
}))
.subscribe(this._glRendererOperation$));
subs.push(this._configuration$.pipe(
filter(
(configuration: SliderConfiguration): boolean => {
return configuration.ids != null;
}),
switchMap(
(configuration: SliderConfiguration): Observable<SliderCombination> => {
return observableZip(
observableZip(
this._catchCacheImage$(
configuration.ids.background),
this._catchCacheImage$(
configuration.ids.foreground)).pipe(
map(
(images: [Image, Image])
: SliderImages => {
return { background: images[0], foreground: images[1] };
})),
this._navigator.stateService.currentState$.pipe(first())).pipe(
map(
(nf: [SliderImages, AnimationFrame]): SliderCombination => {
return { images: nf[0], state: nf[1].state };
}));
}))
.subscribe(
(co: SliderCombination): void => {
if (co.state.currentImage != null &&
co.state.previousImage != null &&
co.state.currentImage.id === co.images.foreground.id &&
co.state.previousImage.id === co.images.background.id) {
return;
}
if (co.state.currentImage.id === co.images.background.id) {
this._navigator.stateService.setImages([co.images.foreground]);
return;
}
if (co.state.currentImage.id === co.images.foreground.id &&
co.state.trajectory.length === 1) {
this._navigator.stateService.prependImages([co.images.background]);
return;
}
this._navigator.stateService.setImages([co.images.background]);
this._navigator.stateService.setImages([co.images.foreground]);
},
(e: Error): void => {
console.error(e);
}));
const textureProvider$ =
this._container.configurationService.imageTiling$.pipe(
switchMap(
(active): Observable<AnimationFrame> => {
return active ?
this._navigator.stateService.currentState$ :
new Subject();
}),
distinctUntilChanged(
undefined,
(frame: AnimationFrame): string => {
return frame.state.currentImage.id;
}),
withLatestFrom(
this._container.glRenderer.webGLRenderer$,
this._container.renderService.size$),
map(
([frame, renderer, size]: [AnimationFrame, THREE.WebGLRenderer, ViewportSize]): TextureProvider => {
const state: IAnimationState = frame.state;
const viewportSize: number = Math.max(size.width, size.height);
const currentImage: Image = state.currentImage;
const currentTransform: Transform = state.currentTransform;
const tileSize: number = viewportSize > 2048 ? 2048 : viewportSize > 1024 ? 1024 : 512;
return new TextureProvider(
currentImage.id,
currentTransform.basicWidth,
currentTransform.basicHeight,
currentImage.image,
this._imageTileLoader,
new TileStore(),
renderer);
}),
publishReplay(1),
refCount());
subs.push(textureProvider$.subscribe(() => { /*noop*/ }));
subs.push(textureProvider$.pipe(
map(
(provider: TextureProvider): GLRendererOperation => {
return (renderer: SliderGLRenderer): SliderGLRenderer => {
renderer.setTextureProvider(provider.id, provider);
return renderer;
};
}))
.subscribe(this._glRendererOperation$));
subs.push(textureProvider$.pipe(
pairwise())
.subscribe(
(pair: [TextureProvider, TextureProvider]): void => {
let previous: TextureProvider = pair[0];
previous.abort();
}));
const roiTrigger$ =
this._container.configurationService.imageTiling$.pipe(
switchMap(
(active): Observable<[RenderCamera, ViewportSize]> => {
return active ?
observableCombineLatest(
this._container.renderService.renderCameraFrame$,
this._container.renderService.size$.pipe(debounceTime(250))) :
new Subject();
}),
map(
([camera, size]: [RenderCamera, ViewportSize]): PositionLookat => {
return [
camera.camera.position.clone(),
camera.camera.lookat.clone(),
camera.zoom.valueOf(),
size.height.valueOf(),
size.width.valueOf()];
}),
pairwise(),
skipWhile(
(pls: [PositionLookat, PositionLookat]): boolean => {
return pls[1][2] - pls[0][2] < 0 || pls[1][2] === 0;
}),
map(
(pls: [PositionLookat, PositionLookat]): boolean => {
let samePosition: boolean = pls[0][0].equals(pls[1][0]);
let sameLookat: boolean = pls[0][1].equals(pls[1][1]);
let sameZoom: boolean = pls[0][2] === pls[1][2];
let sameHeight: boolean = pls[0][3] === pls[1][3];
let sameWidth: boolean = pls[0][4] === pls[1][4];
return samePosition && sameLookat && sameZoom && sameHeight && sameWidth;
}),
distinctUntilChanged(),
filter(
(stalled: boolean): boolean => {
return stalled;
}),
switchMap(
(): Observable<RenderCamera> => {
return this._container.renderService.renderCameraFrame$.pipe(
first());
}),
withLatestFrom(
this._container.renderService.size$,
this._navigator.stateService.currentTransform$));
subs.push(textureProvider$.pipe(
switchMap(
(provider: TextureProvider): Observable<[TileRegionOfInterest, TextureProvider]> => {
return roiTrigger$.pipe(
map(
([camera, size, transform]: [RenderCamera, ViewportSize, Transform]):
[TileRegionOfInterest, TextureProvider] => {
return [
this._roiCalculator.computeRegionOfInterest(camera, size, transform),
provider,
];
}));
}),
filter(
(args: [TileRegionOfInterest, TextureProvider]): boolean => {
return !args[1].disposed;
}))
.subscribe(
(args: [TileRegionOfInterest, TextureProvider]): void => {
let roi: TileRegionOfInterest = args[0];
let provider: TextureProvider = args[1];
provider.setRegionOfInterest(roi);
}));
const hasTexture$ = textureProvider$.pipe(
switchMap(
(provider: TextureProvider): Observable<boolean> => {
return provider.hasTexture$;
}),
startWith(false),
publishReplay(1),
refCount());
subs.push(hasTexture$.subscribe(() => { /*noop*/ }));
const textureProviderPrev$ =
this._container.configurationService.imageTiling$.pipe(
switchMap(
(active): Observable<AnimationFrame> => {
return active ?
this._navigator.stateService.currentState$ :
new Subject();
}),
filter(
(frame: AnimationFrame): boolean => {
return !!frame.state.previousImage;
}),
distinctUntilChanged(
undefined,
(frame: AnimationFrame): string => {
return frame.state.previousImage.id;
}),
withLatestFrom(
this._container.glRenderer.webGLRenderer$,
this._container.renderService.size$),
map(
([frame, renderer, size]: [AnimationFrame, THREE.WebGLRenderer, ViewportSize]): TextureProvider => {
const state = frame.state;
const previousImage = state.previousImage;
const previousTransform = state.previousTransform;
return new TextureProvider(
previousImage.id,
previousTransform.basicWidth,
previousTransform.basicHeight,
previousImage.image,
this._imageTileLoader,
new TileStore(),
renderer);
}),
publishReplay(1),
refCount());
subs.push(textureProviderPrev$.subscribe(() => { /*noop*/ }));
subs.push(textureProviderPrev$.pipe(
map(
(provider: TextureProvider): GLRendererOperation => {
return (renderer: SliderGLRenderer): SliderGLRenderer => {
renderer.setTextureProviderPrev(provider.id, provider);
return renderer;
};
}))
.subscribe(this._glRendererOperation$));
subs.push(textureProviderPrev$.pipe(
pairwise())
.subscribe(
(pair: [TextureProvider, TextureProvider]): void => {
let previous: TextureProvider = pair[0];
previous.abort();
}));
const roiTriggerPrev$ =
this._container.configurationService.imageTiling$.pipe(
switchMap(
(active): Observable<[RenderCamera, ViewportSize]> => {
return active ?
observableCombineLatest(
this._container.renderService.renderCameraFrame$,
this._container.renderService.size$.pipe(debounceTime(250))) :
new Subject();
}),
map(
([camera, size]: [RenderCamera, ViewportSize]): PositionLookat => {
return [
camera.camera.position.clone(),
camera.camera.lookat.clone(),
camera.zoom.valueOf(),
size.height.valueOf(),
size.width.valueOf()];
}),
pairwise(),
skipWhile(
(pls: [PositionLookat, PositionLookat]): boolean => {
return pls[1][2] - pls[0][2] < 0 || pls[1][2] === 0;
}),
map(
(pls: [PositionLookat, PositionLookat]): boolean => {
let samePosition: boolean = pls[0][0].equals(pls[1][0]);
let sameLookat: boolean = pls[0][1].equals(pls[1][1]);
let sameZoom: boolean = pls[0][2] === pls[1][2];
let sameHeight: boolean = pls[0][3] === pls[1][3];
let sameWidth: boolean = pls[0][4] === pls[1][4];
return samePosition && sameLookat && sameZoom && sameHeight && sameWidth;
}),
distinctUntilChanged(),
filter(
(stalled: boolean): boolean => {
return stalled;
}),
switchMap(
(): Observable<RenderCamera> => {
return this._container.renderService.renderCameraFrame$.pipe(
first());
}),
withLatestFrom(
this._container.renderService.size$,
this._navigator.stateService.currentTransform$));
subs.push(textureProviderPrev$.pipe(
switchMap(
(provider: TextureProvider): Observable<[TileRegionOfInterest, TextureProvider]> => {
return roiTriggerPrev$.pipe(
map(
([camera, size, transform]: [RenderCamera, ViewportSize, Transform]):
[TileRegionOfInterest, TextureProvider] => {
return [
this._roiCalculator.computeRegionOfInterest(camera, size, transform),
provider,
];
}));
}),
filter(
(args: [TileRegionOfInterest, TextureProvider]): boolean => {
return !args[1].disposed;
}),
withLatestFrom(this._navigator.stateService.currentState$))
.subscribe(
([[roi, provider], frame]: [[TileRegionOfInterest, TextureProvider], AnimationFrame]): void => {
let shiftedRoi: TileRegionOfInterest = null;
if (isSpherical(frame.state.previousImage.cameraType)) {
if (isSpherical(frame.state.currentImage.cameraType)) {
const currentViewingDirection: THREE.Vector3 =
this._spatial.viewingDirection(frame.state.currentImage.rotation);
const previousViewingDirection: THREE.Vector3 =
this._spatial.viewingDirection(frame.state.previousImage.rotation);
const directionDiff: number = this._spatial.angleBetweenVector2(
currentViewingDirection.x,
currentViewingDirection.y,
previousViewingDirection.x,
previousViewingDirection.y);
const shift: number = directionDiff / (2 * Math.PI);
const bbox: TileBoundingBox = {
maxX: this._spatial.wrap(roi.bbox.maxX + shift, 0, 1),
maxY: roi.bbox.maxY,
minX: this._spatial.wrap(roi.bbox.minX + shift, 0, 1),
minY: roi.bbox.minY,
};
shiftedRoi = {
bbox: bbox,
pixelHeight: roi.pixelHeight,
pixelWidth: roi.pixelWidth,
};
} else {
const currentViewingDirection: THREE.Vector3 =
this._spatial.viewingDirection(frame.state.currentImage.rotation);
const previousViewingDirection: THREE.Vector3 =
this._spatial.viewingDirection(frame.state.previousImage.rotation);
const directionDiff: number = this._spatial.angleBetweenVector2(
currentViewingDirection.x,
currentViewingDirection.y,
previousViewingDirection.x,
previousViewingDirection.y);
const shiftX: number = directionDiff / (2 * Math.PI);
const a1: number = this._spatial.angleToPlane(currentViewingDirection.toArray(), [0, 0, 1]);
const a2: number = this._spatial.angleToPlane(previousViewingDirection.toArray(), [0, 0, 1]);
const shiftY: number = (a2 - a1) / (2 * Math.PI);
const currentTransform: Transform = frame.state.currentTransform;
const size: number = Math.max(currentTransform.basicWidth, currentTransform.basicHeight);
const hFov: number = size > 0 ?
2 * Math.atan(0.5 * currentTransform.basicWidth / (size * currentTransform.focal)) :
Math.PI / 3;
const vFov: number = size > 0 ?
2 * Math.atan(0.5 * currentTransform.basicHeight / (size * currentTransform.focal)) :
Math.PI / 3;
const spanningWidth: number = hFov / (2 * Math.PI);
const spanningHeight: number = vFov / Math.PI;
const basicWidth: number = (roi.bbox.maxX - roi.bbox.minX) * spanningWidth;
const basicHeight: number = (roi.bbox.maxY - roi.bbox.minY) * spanningHeight;
const pixelWidth: number = roi.pixelWidth * spanningWidth;
const pixelHeight: number = roi.pixelHeight * spanningHeight;
const zoomShiftX: number = (roi.bbox.minX + roi.bbox.maxX) / 2 - 0.5;
const zoomShiftY: number = (roi.bbox.minY + roi.bbox.maxY) / 2 - 0.5;
const minX: number = 0.5 + shiftX + spanningWidth * zoomShiftX - basicWidth / 2;
const maxX: number = 0.5 + shiftX + spanningWidth * zoomShiftX + basicWidth / 2;
const minY: number = 0.5 + shiftY + spanningHeight * zoomShiftY - basicHeight / 2;
const maxY: number = 0.5 + shiftY + spanningHeight * zoomShiftY + basicHeight / 2;
const bbox: TileBoundingBox = {
maxX: this._spatial.wrap(maxX, 0, 1),
maxY: maxY,
minX: this._spatial.wrap(minX, 0, 1),
minY: minY,
};
shiftedRoi = {
bbox: bbox,
pixelHeight: pixelHeight,
pixelWidth: pixelWidth,
};
}
} else {
const currentBasicAspect: number = frame.state.currentTransform.basicAspect;
const previousBasicAspect: number = frame.state.previousTransform.basicAspect;
const [[cornerMinX, cornerMinY], [cornerMaxX, cornerMaxY]]: number[][] =
this._getBasicCorners(currentBasicAspect, previousBasicAspect);
const basicWidth: number = cornerMaxX - cornerMinX;
const basicHeight: number = cornerMaxY - cornerMinY;
const pixelWidth: number = roi.pixelWidth / basicWidth;
const pixelHeight: number = roi.pixelHeight / basicHeight;
const minX: number = (basicWidth - 1) / (2 * basicWidth) + roi.bbox.minX / basicWidth;
const maxX: number = (basicWidth - 1) / (2 * basicWidth) + roi.bbox.maxX / basicWidth;
const minY: number = (basicHeight - 1) / (2 * basicHeight) + roi.bbox.minY / basicHeight;
const maxY: number = (basicHeight - 1) / (2 * basicHeight) + roi.bbox.maxY / basicHeight;
const bbox: TileBoundingBox = {
maxX: maxX,
maxY: maxY,
minX: minX,
minY: minY,
};
this._clipBoundingBox(bbox);
shiftedRoi = {
bbox: bbox,
pixelHeight: pixelHeight,
pixelWidth: pixelWidth,
};
}
provider.setRegionOfInterest(shiftedRoi);
}));
const hasTexturePrev$ = textureProviderPrev$.pipe(
switchMap(
(provider: TextureProvider): Observable<boolean> => {
return provider.hasTexture$;
}),
startWith(false),
publishReplay(1),
refCount());
subs.push(hasTexturePrev$.subscribe(() => { /*noop*/ }));
}