UNPKG

@awayjs/view

Version:
541 lines (429 loc) 13.3 kB
import { ProjectionBase, Vector3D, ProjectionEvent, Matrix3D, Rectangle, AssetEvent, PerspectiveProjection, ErrorBase, AssetBase, IAbstractionPool, AbstractionSet, } from '@awayjs/core'; import { Stage, StageEvent, ImageBase, Image2D, ImageCube, ContextGLClearMask, ContextGLProfile, ContextMode, StageManager, } from '@awayjs/stage'; import { IContainer } from './base/IContainer'; import { ViewEvent } from './events/ViewEvent'; import { ContainerNode } from './partition/ContainerNode'; import { INode } from './partition/INode'; export class View extends AssetBase implements IAbstractionPool { private static _store: INode[] = []; private _shareContext: boolean; private _rect: Rectangle = new Rectangle(); private _backgroundColor: number = 0; private _backgroundRed: number = 0; private _backgroundGreen: number = 0; private _backgroundBlue: number = 0; private _projection: ProjectionBase; private _stage: Stage; private _target: ImageBase; private _targetWidth: number; private _targetHeight: number; private _focalLength: number = 1000; private _pixelRatio: number = 1; private _frustumMatrix3D: Matrix3D = new Matrix3D(); private _viewMatrix3D: Matrix3D = new Matrix3D(); private _inverseViewMatrix3D: Matrix3D = new Matrix3D(); private _components: Array<Vector3D> = new Array<Vector3D>(4); private _offset: Vector3D = new Vector3D(); private _scale: Vector3D = new Vector3D(1, 1, 1); private _frustumMatrix3DDirty: boolean = true; private _viewMatrix3DDirty: boolean = true; private _inverseViewMatrix3DDirty: boolean = true; private _onInvalidateSizeDelegate: (event: StageEvent | AssetEvent) => void; private _onInvalidateViewMatrix3DDelegate: (event: ProjectionEvent) => void; private _onInvalidateFrustumMatrix3DDelegate: (event: ProjectionEvent) => void; public readonly abstractions: AbstractionSet; /** * */ public backgroundAlpha: number = 1; /** * */ public backgroundDepth: number = 1; /** * */ public backgroundStencil: number = 0; /** * */ public preservePixelRatio: boolean = true; /** * */ public preserveFocalLength: boolean = false; /** * */ public preserveDimensions: boolean = false; /** * */ public get shareContext(): boolean { return this._shareContext; } /** * */ public get x(): number { if (this._shareContext || this._target) return this._rect.x; return this._stage.x; } public set x(value: number) { if (this._shareContext || this._target) { if (this._rect.x == value) return; this._offset.x = (this._rect.x = value) / this._targetWidth; this._invalidateViewMatrix3D(); } else { this._stage.x = value; } } /** * */ public get y(): number { if (this._shareContext || this._target) return this._rect.y; return this._stage.y; } public set y(value: number) { if (this._shareContext || this._target) { if (this._rect.y == value) return; this._offset.y = (this._rect.y = value) / this._targetHeight; this._invalidateViewMatrix3D(); } else { this._stage.y = value; } } /** * */ public get width(): number { return this._rect.width; } public set width(value: number) { if (this._rect.width == value) return; this._rect.width = value; if (this._shareContext || this._target) { this._scale.x = value / this._targetWidth; this._updatePixelRatio(); this._invalidateViewMatrix3D(); } else { this._stage.width = value; } this._invalidateSize(); } /** * */ public get height(): number { return this._rect.height; } public set height(value: number) { if (this._rect.height == value) return; this._rect.height = value; if (this._shareContext || this._target) { this._scale.y = value / this._targetHeight; this._updateFocalLength(); this._updatePixelRatio(); this._invalidateViewMatrix3D(); } else { this._stage.height = value; } this._invalidateSize(); } /** * */ public get backgroundColor(): number { return this._backgroundColor; } public set backgroundColor(value: number) { if (this._backgroundColor == value) return; this._backgroundColor = value; this._backgroundRed = ((value >> 16) & 0xff) / 0xff; this._backgroundGreen = ((value >> 8) & 0xff) / 0xff; this._backgroundBlue = (value & 0xff) / 0xff; } /** * */ public get focalLength(): number { return this._focalLength; } public set focalLength(value: number) { if (this._focalLength == value) return; this._focalLength = value; this._updateFocalLength(); } /** * */ public get pixelRatio(): number { return this._pixelRatio; } public set pixelRatio(value: number) { if (this._pixelRatio == value) return; this._pixelRatio = value; this._updatePixelRatio(); } public get projection(): ProjectionBase { return this._projection; } public set projection(value: ProjectionBase) { if (value == null) throw new ErrorBase('projection cannot be null'); if (this._projection == value) return; this._projection.removeEventListener( ProjectionEvent.INVALIDATE_VIEW_MATRIX3D, this._onInvalidateViewMatrix3DDelegate ); this._projection.removeEventListener( ProjectionEvent.INVALIDATE_FRUSTUM_MATRIX3D, this._onInvalidateFrustumMatrix3DDelegate ); this._projection = value; this._projection.addEventListener( ProjectionEvent.INVALIDATE_VIEW_MATRIX3D, this._onInvalidateViewMatrix3DDelegate ); this._projection.addEventListener( ProjectionEvent.INVALIDATE_FRUSTUM_MATRIX3D, this._onInvalidateFrustumMatrix3DDelegate ); this._invalidateViewMatrix3D(); } public get target(): ImageBase { return this._target; } public set target(value: ImageBase) { if (this._target == value) return; this._updateTarget(value); } public get stage(): Stage { return this._stage; } public get frustumMatrix3D(): Matrix3D { if (this._frustumMatrix3DDirty) { this._frustumMatrix3DDirty = false; this._frustumMatrix3D.recompose(this._components); this._frustumMatrix3D.prepend(this._projection.frustumMatrix3D); } return this._frustumMatrix3D; } public get viewMatrix3D(): Matrix3D { if (this._viewMatrix3DDirty) { this._viewMatrix3DDirty = false; this._viewMatrix3D.recompose(this._components); this._viewMatrix3D.prepend(this._projection.viewMatrix3D); } return this._viewMatrix3D; } public get inverseViewMatrix3D(): Matrix3D { if (this._inverseViewMatrix3DDirty) { this._inverseViewMatrix3DDirty = false; this._inverseViewMatrix3D.copyFrom(this.viewMatrix3D); this._inverseViewMatrix3D.invert(); } return this._inverseViewMatrix3D; } constructor( projection: ProjectionBase = null, stage: Stage = null, forceSoftware: boolean = false, profile: ContextGLProfile = ContextGLProfile.BASELINE, mode: ContextMode = ContextMode.AUTO, alpha: boolean = false) { super(); this.abstractions = new AbstractionSet(this); this._components[0] = this._offset; this._components[2] = this._scale; this._onInvalidateSizeDelegate = (event: StageEvent | AssetEvent) => this._onInvalidateSize(event); this._onInvalidateViewMatrix3DDelegate = (event: ProjectionEvent) => this._onInvalidateViewMatrix3D(event); this._onInvalidateFrustumMatrix3DDelegate = (event: ProjectionEvent) => this._onInvalidateFrustumMatrix3D(event); this._projection = projection || new PerspectiveProjection(); this._projection.addEventListener( ProjectionEvent.INVALIDATE_VIEW_MATRIX3D, this._onInvalidateViewMatrix3DDelegate); this._projection.addEventListener( ProjectionEvent.INVALIDATE_FRUSTUM_MATRIX3D, this._onInvalidateFrustumMatrix3DDelegate); if (stage) this._shareContext = true; this._stage = stage || StageManager.getInstance().getFreeStage(forceSoftware, profile, mode, alpha); this._stage.addEventListener(StageEvent.INVALIDATE_SIZE, this._onInvalidateSizeDelegate); this._updateDimensions(); this._updateFocalLength(); this._updatePixelRatio(); } public requestAbstraction(_asset: IContainer): INode { return View._store.length ? View._store.pop() : new ContainerNode(); } public storeAbstraction(abstraction: INode): void { View._store.push(abstraction); } public getNode(entity: IContainer): ContainerNode { return this.abstractions.getAbstraction<ContainerNode>(entity); } public clear( enableClear = true, enableDepthAndStencil = true, surfaceSelector = 0, mipmapSelector = 0, clearMaskSelector: ContextGLClearMask = ContextGLClearMask.ALL): void { this._stage.setRenderTarget(this._target, enableDepthAndStencil, surfaceSelector, mipmapSelector); //TODO: make scissor compatible with image targets this._stage.setScissor((this._target == null) ? this._rect : null); if (enableClear) { this._stage.clear( this._backgroundRed, this._backgroundGreen, this._backgroundBlue, this.backgroundAlpha, this.backgroundDepth, this.backgroundStencil, clearMaskSelector); } } public present(): void { if (!this._shareContext && this._target == null) this._stage.present(); } /* */ public project(position: Vector3D, target: Vector3D = null): Vector3D { target = this._projection.project(position, target); target.x = (target.x + 1) * this.width / 2; target.y = (target.y + 1) * this.height / 2; return target; } public unproject(sX: number, sY: number, sZ: number, target: Vector3D = null): Vector3D { return this._projection.unproject(2 * sX / this.width - 1, 2 * sY / this.height - 1, sZ, target); } public dispose() { if (this._target) { this._target.removeEventListener(AssetEvent.INVALIDATE, this._onInvalidateSizeDelegate); this._target = null; } else { this._stage.removeEventListener(StageEvent.INVALIDATE_SIZE, this._onInvalidateSizeDelegate); if (!this._shareContext && this._target == null) this._stage.dispose(); this._stage = null; } if (this._projection) { this._projection.removeEventListener( ProjectionEvent.INVALIDATE_VIEW_MATRIX3D, this._onInvalidateViewMatrix3DDelegate); this._projection = null; } } private _onInvalidateSize(event: StageEvent | AssetEvent): void { this._updateDimensions(); this._updateFocalLength(); this._updatePixelRatio(); if (!this.preserveDimensions || (!this._shareContext && this._target == null)) this._invalidateSize(); } private _onInvalidateViewMatrix3D(event: ProjectionEvent): void { this._invalidateViewMatrix3D(); } private _onInvalidateFrustumMatrix3D(event: ProjectionEvent): void { this._frustumMatrix3DDirty = true; if (!this.preserveFocalLength) this._updateFocalLength(); } private _updateTarget(value: ImageBase): void { if (this._target) this._target.removeEventListener(AssetEvent.INVALIDATE, this._onInvalidateSizeDelegate); else this._stage.removeEventListener(StageEvent.INVALIDATE_SIZE, this._onInvalidateSizeDelegate); this._target = value; if (this._target) this._target.addEventListener(AssetEvent.INVALIDATE, this._onInvalidateSizeDelegate); else this._stage.addEventListener(StageEvent.INVALIDATE_SIZE, this._onInvalidateSizeDelegate); this._updateDimensions(); this._updateFocalLength(); this._updatePixelRatio(); if (!this.preserveDimensions || (!this._shareContext && this._target == null)) this._invalidateSize(); } private _updateDimensions(): void { if (this._target) { if (this._target instanceof Image2D) { this._targetWidth = (<Image2D> this._target).width; this._targetHeight = (<Image2D> this._target).height; } else if (this._target instanceof ImageCube) { this._targetWidth = (<ImageCube> this._target).size; this._targetHeight = (<ImageCube> this._target).size; } } else { this._targetWidth = this._stage.width; this._targetHeight = this._stage.height; } if (this.preserveDimensions && (this._shareContext || this._target)) { this._offset.x = this._rect.x / this._targetWidth; this._offset.y = this._rect.y / this._targetHeight; this._scale.x = this._rect.width / this._targetWidth; this._scale.y = this._rect.height / this._targetHeight; } else { this._rect.x = this._offset.x * this._targetWidth; this._rect.y = this._offset.y * this._targetHeight; this._rect.width = this._scale.x * this._targetWidth; this._rect.height = this._scale.y * this._targetHeight; } } private _updateFocalLength(): void { if (this.preserveFocalLength) this.projection.scale = this._focalLength / this._rect.height; else this._focalLength = this._projection.scale * this._rect.height; } private _updatePixelRatio(): void { if (this.preservePixelRatio) this._projection.ratio = this._pixelRatio * this._rect.width / this._rect.height; else this._pixelRatio = this._projection.ratio * this._rect.height / this._rect.width; } private _invalidateViewMatrix3D(): void { this._frustumMatrix3DDirty = true; this._viewMatrix3DDirty = true; this._inverseViewMatrix3DDirty = true; this.dispatchEvent(new ViewEvent(ViewEvent.INVALIDATE_VIEW_MATRIX3D, this)); } private _invalidateSize(): void { this.dispatchEvent(new ViewEvent(ViewEvent.INVALIDATE_SIZE, this)); } }