UNPKG

@awayjs/view

Version:
791 lines (623 loc) 23 kB
import { Plane3D, Vector3D, AbstractionBase, AssetEvent, Matrix3D, ColorTransform, Point, Transform, Rectangle, PerspectiveProjection, CoordinateSystem, IAbstractionPool, } from '@awayjs/core'; import { IPartitionTraverser } from './IPartitionTraverser'; import { INode } from './INode'; import { PickGroup } from '../PickGroup'; import { HierarchicalProperty } from '../base/HierarchicalProperty'; import { IContainer } from '../base/IContainer'; import { BlendMode, Settings as StageSettings, isNativeBlend } from '@awayjs/stage'; import { AlignmentMode } from '../base/AlignmentMode'; import { OrientationMode } from '../base/OrientationMode'; import { ContainerNodeEvent } from '../events/ContainerNodeEvent'; import { View } from '../View'; export class ContainerNode extends AbstractionBase implements INode { private static _nullTransform: Transform = new Transform(); private static _tempVector3D: Vector3D = new Vector3D(); private static _nullColorTransform = new ColorTransform(); private _invalidateMatrix3DEvent: ContainerNodeEvent; private _invalidateColorTransformEvent: ContainerNodeEvent; private _localNode: ContainerNode; private _pickObject: IContainer; private _pickObjectNode: ContainerNode; private _scrollRect: Rectangle; private _scrollRectNode: ContainerNode; private _renderToImage: boolean = false; private _isDragEntity: boolean; private _position: Vector3D = new Vector3D(); private _positionDirty: boolean; private _scale9Container: IContainer; private _matrix3D: Matrix3D = new Matrix3D(); private _colorTransform: ColorTransform; private _inverseMatrix3D: Matrix3D; private _inverseMatrix3DDirty: boolean = true; private _orientationMatrix: Matrix3D; private _maskDisabled: boolean = false; private _colorTransformDisabled: boolean = false; private _transformDisabled: boolean = false; private _activeTransform: Transform; private _invisible: boolean; private _maskId: number = -1; private _mouseChildrenDisabled: boolean; private _maskOwners: ContainerNode[]; private _masks: ContainerNode[] = []; protected _parent: ContainerNode; protected _childNodes: Array<ContainerNode> = new Array<ContainerNode>(); protected _numChildNodes: number = 0; public _hierarchicalPropsDirty: HierarchicalProperty = HierarchicalProperty.ALL; public _collectionMark: number;// = 0; public get parent(): ContainerNode { return this._parent; } public get numChildNodes(): number { return this._numChildNodes; } public get container(): IContainer { return <IContainer> this._asset; } public get pickObjectNode(): ContainerNode { if (this._pickObject != (<IContainer> this._asset).pickObject) { this._pickObject = (<IContainer> this._asset).pickObject; if (this._pickObject) { this._pickObjectNode = this._pickObject.getAbstraction<ContainerNode>(<IAbstractionPool> this._pool); if (this._pickObject.pickObjectFromTimeline) this._pickObjectNode.setParent(this); } else { this._pickObjectNode.setParent(null); this._pickObjectNode = null; } } return this._pickObjectNode; } public get renderToImage(): boolean { const { blendMode, filters, cacheAsBitmap } = <IContainer> this._asset; const renderToImage = this.isRenderable() && ( cacheAsBitmap || filters && filters.length > 0 || (blendMode && blendMode !== BlendMode.LAYER && blendMode !== BlendMode.NORMAL && (StageSettings.USE_NON_NATIVE_BLEND || isNativeBlend(blendMode))) ); if (this._renderToImage !== renderToImage) { this._renderToImage = renderToImage; if (!this._renderToImage) this.clearLocalNode(); } return this._renderToImage; } public get boundsVisible(): boolean { return false; } public get view(): View { return <View> this._pool; } /** * Allow disable/enable colorTransform for this node independent of transform, this required for cache phase * @param value */ public set colorTransformDisabled(value: boolean) { this._colorTransformDisabled = value; } public get colorTransformDisabled() { return this._colorTransformDisabled; } public set maskDisabled(value: boolean) { this._maskDisabled = value; } public get maskDisabled() { return this._maskDisabled; } public set transformDisabled(value: boolean) { if (this._transformDisabled == value) return; this._maskDisabled = value; this._transformDisabled = value; this._colorTransformDisabled = value; if (this._transformDisabled) { this._activeTransform = ContainerNode._nullTransform; } else { this._activeTransform = (<IContainer> this._asset).transform; } } public get transformDisabled(): boolean { return this._transformDisabled; } public getScale9Container(): IContainer { if (this._hierarchicalPropsDirty & HierarchicalProperty.SCALE9) { this._scale9Container = (<IContainer> this._asset).scale9Grid ? (<IContainer> this._asset) : this._parent?.getScale9Container(); this._hierarchicalPropsDirty ^= HierarchicalProperty.SCALE9; } return this._scale9Container; } /** * */ public getPosition(): Vector3D { if (this._positionDirty) { if ((<IContainer> this._asset)._registrationMatrix3D && (<IContainer> this._asset).alignmentMode === AlignmentMode.REGISTRATION_POINT ) { this._position.x = -(<IContainer> this._asset)._registrationMatrix3D._rawData[12]; this._position.y = -(<IContainer> this._asset)._registrationMatrix3D._rawData[13]; this._position.z = -(<IContainer> this._asset)._registrationMatrix3D._rawData[14]; this._position = this.getMatrix3D().transformVector( this._position, this._position); /* this._position.decrementBy( new Vector3D( this._registrationPoint.x*this._scaleX, this._registrationPoint.y*this._scaleY, this._registrationPoint.z*this._scaleZ)); */ } else { this.getMatrix3D().copyColumnTo(3, this._position); } this._positionDirty = false; } return this._position; } /** * */ public getInverseMatrix3D(): Matrix3D { if (this._inverseMatrix3DDirty) { if (!this._inverseMatrix3D) this._inverseMatrix3D = new Matrix3D(); this._inverseMatrix3DDirty = false; this._inverseMatrix3D.copyFrom(this.getMatrix3D()); this._inverseMatrix3D.invert(); } return this._inverseMatrix3D || (this._inverseMatrix3D = new Matrix3D()); } public getMatrix3D(): Matrix3D { if (this._hierarchicalPropsDirty & HierarchicalProperty.SCENE_TRANSFORM) { this._matrix3D.copyFrom(this._activeTransform.matrix3D); if (!this._transformDisabled) { if ((<IContainer> this._asset)._registrationMatrix3D) { this._matrix3D.prepend((<IContainer> this._asset)._registrationMatrix3D); if ((<IContainer> this._asset).alignmentMode != AlignmentMode.REGISTRATION_POINT) { this._matrix3D.appendTranslation( -(<IContainer> this._asset)._registrationMatrix3D._rawData[12] * this._activeTransform.scale.x, -(<IContainer> this._asset)._registrationMatrix3D._rawData[13] * this._activeTransform.scale.y, -(<IContainer> this._asset)._registrationMatrix3D._rawData[14] * this._activeTransform.scale.z); } } if (this._parent) this._matrix3D.append(this._parent.getMatrix3D()); // scrollRect-masks are childs of the object that have the scrollRect applied // to support scrolling we need to: // - move objects with scrollRect by negative scrollRect position // - move scrollRect-masks by positive scrollRect position if (!(<IContainer> this._asset).maskMode && (<IContainer> this._asset).scrollRect) this._matrix3D.prependTranslation(-(<IContainer> this._asset).scrollRect.x, -(<IContainer> this._asset).scrollRect.y, 0); else if ((<IContainer> this._asset).maskMode && (<IContainer> this._asset).scrollRect) this._matrix3D.prependTranslation((<IContainer> this._asset).scrollRect.x, (<IContainer> this._asset).scrollRect.y, 0); } this._hierarchicalPropsDirty ^= HierarchicalProperty.SCENE_TRANSFORM; //TODO: refactor controller API if ((<IContainer> this._asset)['_iController']) (<IContainer> this._asset)['_iController'].updateController(); } return this._matrix3D; } /** * */ public getRenderMatrix3D(cameraTransform: Matrix3D): Matrix3D { if ((<IContainer> this._asset).orientationMode == OrientationMode.CAMERA_PLANE) { const comps: Array<Vector3D> = cameraTransform.decompose(); comps[0].copyFrom(this.getPosition()); comps[3].copyFrom(this._activeTransform.scale); (this._orientationMatrix || (this._orientationMatrix = new Matrix3D())).recompose(comps); //add in case of registration point if ((<IContainer> this._asset)._registrationMatrix3D) { this._orientationMatrix.prepend((<IContainer> this._asset)._registrationMatrix3D); if ((<IContainer> this._asset).alignmentMode != AlignmentMode.REGISTRATION_POINT) this._orientationMatrix.appendTranslation( -(<IContainer> this._asset)._registrationMatrix3D._rawData[12] * this._activeTransform.scale.x, -(<IContainer> this._asset)._registrationMatrix3D._rawData[13] * this._activeTransform.scale.y, -(<IContainer> this._asset)._registrationMatrix3D._rawData[14] * this._activeTransform.scale.z); } return this._orientationMatrix; } return this.getMatrix3D(); } public getColorTransform(): ColorTransform { if (this._colorTransformDisabled) { return ContainerNode._nullColorTransform; } if (this._hierarchicalPropsDirty & HierarchicalProperty.COLOR_TRANSFORM) { this._hierarchicalPropsDirty ^= HierarchicalProperty.COLOR_TRANSFORM; if (this._colorTransformDisabled) { return ContainerNode._nullColorTransform; } if (!this._colorTransform) this._colorTransform = new ColorTransform(); if (this._parent && this._parent.getColorTransform()) { this._colorTransform.copyFrom(this._parent.getColorTransform()); // we MUST prepend real transform in cached phase, but reset in cached image render phase this._colorTransform.prepend((<IContainer> this._asset).transform.colorTransform); } else { this._colorTransform.copyFrom((<IContainer> this._asset).transform.colorTransform); } /* // if we will use getter - it return empty blend in USE_UNSAFE_BLEND = false if ((<any> (<IContainer> this._asset))._blendMode === BlendMode.OVERLAY) { // apply 0.5 alpha for object with `overlay` because we not support it now this._colorTransform.alphaMultiplier *= 0.5; }*/ } return this._colorTransform || ContainerNode._nullColorTransform; } /** * * @returns {number} */ public getMaskId(): number { if (this._hierarchicalPropsDirty & HierarchicalProperty.MASK_ID) { this._maskId = ((<IContainer> this._asset).maskId != -1) ? (<IContainer> this._asset).maskId : (this._parent) ? this._parent.getMaskId() : -1; this._hierarchicalPropsDirty ^= HierarchicalProperty.MASK_ID; } return this._maskId; } public getMasks(update: boolean = false): ContainerNode[] { if (!update) { return this._masks; } if ((<IContainer> this._asset).masks) { const len = (<IContainer> this._asset).masks.length; this._masks.length = len; for (let i = 0; i < len; i++) { this._masks[i] = (<View> this._pool).getNode((<IContainer> this._asset).masks[i]); } } else { this._masks.length = 0; } return this._masks; } public getMaskOwners(): ContainerNode[] { if (this._hierarchicalPropsDirty & HierarchicalProperty.MASKS) { const masks = this.getMasks(true); this._maskOwners = this._maskDisabled ? null : (this._parent?.getMaskOwners() && this.getMaskId() == -1) ? masks.length ? this._parent.getMaskOwners().concat([this]) : this._parent.getMaskOwners().concat() : masks.length ? [this] : null; this._hierarchicalPropsDirty ^= HierarchicalProperty.MASKS; } return this._maskOwners; } /** * Converts the `point` object from the Stage(global) coordinates to the * display object's(local) coordinates. * * To use this method, first create an instance of the Point class. The _x_ * and _y_ values that you assign represent global coordinates because they * relate to the origin(0,0) of the main display area. Then pass the Point * instance as the parameter to the `globalToLocal()` method. The method * returns a new Point object with _x_ and _y_ values that relate to the * origin of the display object instead of the origin of the Stage. * */ public globalToLocal(point: Point, target: Point = null): Point { const tmp = ContainerNode._tempVector3D; tmp.setTo(point.x, point.y, 0); const pos = this.getInverseMatrix3D().transformVector(tmp, tmp); if (!target) { target = new Point(); } target.x = pos.x; target.y = pos.y; return target; } /** * Converts a two-dimensional point from the Scene(global) coordinates to a * three-dimensional display object's(local) coordinates. * * <p>To use this method, first create an instance of the Vector3D class. The x, * y and z values that you assign to the Vector3D object represent global * coordinates because they are relative to the origin(0,0,0) of the scene. Then * pass the Vector3D object to the <code>globalToLocal3D()</code> method as the * <code>position</code> parameter. * The method returns three-dimensional coordinates as a Vector3D object * containing <code>x</code>, <code>y</code>, and <code>z</code> values that * are relative to the origin of the three-dimensional display object.</p> * */ public globalToLocal3D(position: Vector3D): Vector3D { return this.getInverseMatrix3D().transformVector(position); } /** * Converts the `point` object from the display object's(local) coordinates * to the Stage(global) coordinates. * * This method allows you to convert any given _x_ and _y_ coordinates from * values that are relative to the origin(0,0) of a specific display * object(local coordinates) to values that are relative to the origin of * the Stage(global coordinates). * * To use this method, first create an instance of the Point class. The _x_ * and _y_ values that you assign represent local coordinates because they * relate to the origin of the display object. * * You then pass the Point instance that you created as the parameter to the * `localToGlobal()` method. The method returns a new Point object with _x_ * and _y_ values that relate to the origin of the Stage instead of the * origin of the display object. * * @param point The name or identifier of a point created with the Point * class, specifying the _x_ and _y_ coordinates as properties. * @param target Result point * @return A Point object with coordinates relative to the Stage. */ public localToGlobal(point: Point, target: Point = null): Point { const tmp = ContainerNode._tempVector3D; tmp.setTo(point.x, point.y, 0); const pos = this.getMatrix3D().transformVector(tmp, tmp); if (!target) { target = new Point(); } target.x = pos.x; target.y = pos.y; return target; } public getBoundsPrimitive(_pickGroup: PickGroup): ContainerNode { return null; } public init(container: IContainer, pool: View) { super.init(container, pool); container._initNode(this); this._hierarchicalPropsDirty = HierarchicalProperty.ALL; this._activeTransform = (<IContainer> this._asset).transform; } public getLocalNode(): ContainerNode { if (!this._localNode) { /** * projection is not simple object * not needed spawn it for every cached partition * it has 3 matrices = 100 bytes + Transform, * that have 4 matrices + a lot of vectors (16 bytes) = 300 bytes, * And this is under heavy extending. 1 projection allocate more that 4kb per instance */ const projection = new PerspectiveProjection(); projection.coordinateSystem = CoordinateSystem.LEFT_HANDED; projection.originX = -1; projection.originY = -1; projection.transform = new Transform(); projection.transform.moveTo(0, 0, -1000); projection.transform.lookAt(new Vector3D()); const view = new View(projection, this.view.stage); view.backgroundAlpha = 0; this._localNode = view.getNode(<IContainer> this._asset); this._localNode.transformDisabled = true; this._localNode.setParent(this); } return this._localNode; } public clearLocalNode(): void { if (this._localNode) { this._localNode.onClear(null); this._localNode = null; } } public onClear(event: AssetEvent): void { this.clearLocalNode(); if (this._pickObject) { this._pickObject = null; this._pickObjectNode.setParent(null); this._pickObjectNode = null; } for (let i: number = 0; i < this._numChildNodes; i++) this._childNodes[i].onClear(event); this._childNodes.length = 0; this._numChildNodes = 0; this._scrollRect = null; this._scrollRectNode = null; this._renderToImage = false; this._isDragEntity = false; this._positionDirty = false; this._scale9Container = null; this._inverseMatrix3DDirty = true; this._maskDisabled = false; this._colorTransformDisabled = false; this._transformDisabled = false; this._maskOwners = null; this._masks.length = 0; this._parent = null; super.clear(); super.onClear(event); } public onInvalidate(event: AssetEvent): void { this.invalidate(); } public clear(): void { super.clear(); if (this._localNode) this._localNode.clear(); for (let i: number = 0; i < this._numChildNodes; i++) this._childNodes[i].clear(); if (this._pickObject) this._pickObjectNode.clear(); } public isInFrustum( _rootEntity: INode, _planes: Array<Plane3D>, _numPlanes: number, _pickGroup: PickGroup ): boolean { return !this.isInvisible(); } public invalidate(): void { if (this._invalid) return; this._invalid = true; super.invalidate(); if (this._parent) this._parent.invalidate(); } /** * @internal */ public isInvisible(): boolean { if (this._hierarchicalPropsDirty & HierarchicalProperty.VISIBLE) { this._invisible = this._transformDisabled ? false : !(<IContainer> this._asset).visible || this.parent?.isInvisible(); this._hierarchicalPropsDirty ^= HierarchicalProperty.VISIBLE; } return this._invisible; } public isIntersectingRay( _rootEntity: INode, _rayPosition: Vector3D, _rayDirection: Vector3D, _pickGroup: PickGroup ): boolean { return true; } /** * * @returns {boolean} */ public isRenderable(): boolean { // if container is invisible - all child nodes automatically invisible to return this.getMaskId() != -1 || !this.isInvisible() && this.getColorTransform()._isRenderable(); } /** * * @returns {boolean} */ public isCastingShadow(): boolean { return true; } /** * @param traverser */ public acceptTraverser(traverser: IPartitionTraverser): void { this._invalid = false; //get the sub-traverser for the partition, if different, terminate this traversal if (traverser.node != this && traverser !== traverser.getTraverser(this)) return; if (!traverser.enterNode(this)) return; if (!(<IContainer> this._asset).maskMode && this._scrollRect !== (<IContainer> this._asset).scrollRect) { this._scrollRect = (<IContainer> this._asset).scrollRect; if (this._scrollRectNode) { this._scrollRectNode.setParent(null); this._scrollRectNode = null; } if (this._scrollRect) { this._scrollRectNode = (<IContainer> this._asset).getScrollRectPrimitive() .getAbstraction<ContainerNode>(<IAbstractionPool> this._pool); this._scrollRectNode.container.scrollRect = this._scrollRect; this._scrollRectNode.setParent(this); } } traverser.applyEntity(this); for (let i: number = 0; i < this._numChildNodes; i++) this._childNodes[i].acceptTraverser(traverser); } public addChildAt(entity: IContainer, index: number): ContainerNode { const node = entity.getAbstraction<ContainerNode>(<IAbstractionPool> this._pool); node.setParent(this); if (index == this._numChildNodes) this._childNodes.push(node); else this._childNodes.splice(index, 0, node); this._numChildNodes++; this.invalidate(); return node; } public removeChildAt(index: number): ContainerNode { this._numChildNodes--; const node: ContainerNode = (index === this._numChildNodes) ? this._childNodes.pop() : this._childNodes.splice(index, 1)[0]; node.setParent(null); this.invalidate(); return node; } public getChildAt(index: number): ContainerNode { return this._childNodes.length > index ? this._childNodes[index] : null; } public startDrag(): void { this._isDragEntity = true; } public stopDrag(): void { this._isDragEntity = false; } public isDragEntity(): boolean { return this._isDragEntity; } public isMouseDisabled(): boolean { return this.isInvisible() || !(<IContainer> this._asset).mouseEnabled || this.parent?.isMouseChildrenDisabled(); } public isMouseChildrenDisabled(): boolean { if (this._hierarchicalPropsDirty & HierarchicalProperty.MOUSE_ENABLED) { this._mouseChildrenDisabled = !(<IContainer> this._asset).mouseChildren || this.parent?.isMouseChildrenDisabled(); this._hierarchicalPropsDirty ^= HierarchicalProperty.MOUSE_ENABLED; } return this._mouseChildrenDisabled; } public isDescendant(node: INode): boolean { let parent: INode = this; while (parent.parent) { parent = parent.parent; if (parent == node) return true; } return false; } public invalidateHierarchicalProperty(property: HierarchicalProperty): void { // property dirty check not working for ColorTransform // will emit every change // todo Fixme if (property & HierarchicalProperty.COLOR_TRANSFORM) { // eslint-disable-next-line max-len this.dispatchEvent(this._invalidateColorTransformEvent || (this._invalidateColorTransformEvent = new ContainerNodeEvent(ContainerNodeEvent.INVALIDATE_COLOR_TRANSFORM))); } const propertyDirty: number = (this._hierarchicalPropsDirty ^ property) & property; if (!propertyDirty) return; this._hierarchicalPropsDirty |= propertyDirty; for (let i = 0; i < this._childNodes.length; ++i) this._childNodes[i].invalidateHierarchicalProperty(property); if (this._pickObjectNode) this._pickObjectNode.invalidateHierarchicalProperty(property); if (this._localNode) this._localNode.invalidateHierarchicalProperty(property); if (property & HierarchicalProperty.SCENE_TRANSFORM) { this._positionDirty = true; this._inverseMatrix3DDirty = true; this.dispatchEvent(this._invalidateMatrix3DEvent || (this._invalidateMatrix3DEvent = new ContainerNodeEvent(ContainerNodeEvent.INVALIDATE_MATRIX3D))); this.invalidate(); } } public setParent(parent: ContainerNode): void { if (this._parent) this.clear(); this._parent = parent; this.invalidateHierarchicalProperty(HierarchicalProperty.ALL); } }