UNPKG

@awayjs/view

Version:
801 lines (622 loc) 22.2 kB
import { Plane3D, Vector3D, AbstractionBase, Matrix3D, ColorTransform, Point, Transform, Rectangle, PerspectiveProjection, CoordinateSystem } 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(); public 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 _transformDisabled: boolean = false; private _invisible: boolean; private _maskId: number = -1; private _mouseChildrenDisabled: boolean; private _maskOwners: ContainerNode[]; private _masks: ContainerNode[] = []; private _parent: ContainerNode; private _root: 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 this._useWeak ? (<WeakRef<IContainer>> this._asset).deref() : <IContainer> this._asset; } public get pickObjectNode(): ContainerNode { const container = this.container; if (this._pickObject != container.pickObject) { this._pickObject = container.pickObject; if (this._pickObject) { this._pickObjectNode = this._pool.abstractions.getAbstraction<ContainerNode>(this._pickObject); 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 } = this.container; const renderToImage = this.isRenderable() && ( cacheAsBitmap || filters && filters.length > 0 || (blendMode && 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 scrollRect(): Rectangle { const container = this.container; const scrollRect = container.scrollRect; if (!!this._scrollRect != !!scrollRect) { this._scrollRect = scrollRect; if (this._scrollRect) { this._scrollRectNode = (<View> this._pool).getNode(container.getScrollRectPrimitive()); //this._scrollRectNode.container.scrollRect = this._scrollRect; this._scrollRectNode.setParent(this); } else if (this._scrollRectNode) { this._scrollRectNode.setParent(null); this._scrollRectNode = null; } } return this._scrollRect; } public get boundsVisible(): boolean { return false; } public get view(): View { return <View> this._pool; } 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; } public get transformDisabled(): boolean { return this._transformDisabled; } public getRoot(local: boolean = false): ContainerNode { if (this._hierarchicalPropsDirty & HierarchicalProperty.ROOT) { this._root = this._transformDisabled && local || !this._parent ? this : this._parent.getRoot(local); } return this._root; } public getScale9Container(): IContainer { if (this._hierarchicalPropsDirty & HierarchicalProperty.SCALE9) { const container: IContainer = this.container; this._scale9Container = container.scale9Grid ? container : this._parent?.getScale9Container(); this._hierarchicalPropsDirty ^= HierarchicalProperty.SCALE9; } return this._scale9Container; } /** * */ public getPosition(): Vector3D { if (this._positionDirty) { const container: IContainer = this.container; if (container._registrationMatrix3D && container.alignmentMode === AlignmentMode.REGISTRATION_POINT ) { this._position.x = -container._registrationMatrix3D._rawData[12]; this._position.y = -container._registrationMatrix3D._rawData[13]; this._position.z = -container._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) { const container: IContainer = this.container; if (!this._transformDisabled) { this._matrix3D.copyFrom(container.transform.matrix3D); if (container._registrationMatrix3D) { this._matrix3D.prepend(container._registrationMatrix3D); if (container.alignmentMode != AlignmentMode.REGISTRATION_POINT) { this._matrix3D.appendTranslation( -container._registrationMatrix3D._rawData[12] * container.transform.scale.x, -container._registrationMatrix3D._rawData[13] * container.transform.scale.y, -container._registrationMatrix3D._rawData[14] * container.transform.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 (container.scrollRect) this._matrix3D.prependTranslation(-container.scrollRect.x, -container.scrollRect.y, 0); } else { this._matrix3D.copyFrom(ContainerNode._nullTransform.matrix3D); } this._hierarchicalPropsDirty ^= HierarchicalProperty.SCENE_TRANSFORM; //TODO: refactor controller API if (container['_iController']) container['_iController'].updateController(); } return this._matrix3D; } /** * */ public getRenderMatrix3D(cameraTransform: Matrix3D): Matrix3D { const container: IContainer = this.container; if (container.orientationMode == OrientationMode.CAMERA_PLANE) { const comps: Array<Vector3D> = cameraTransform.decompose(); comps[0].copyFrom(this.getPosition()); comps[2].copyFrom(container.transform.scale); (this._orientationMatrix || (this._orientationMatrix = new Matrix3D())).recompose(comps); //add in case of registration point if (container._registrationMatrix3D) { this._orientationMatrix.prepend(container._registrationMatrix3D); if (container.alignmentMode != AlignmentMode.REGISTRATION_POINT) this._orientationMatrix.appendTranslation( -container._registrationMatrix3D._rawData[12] * container.transform.scale.x, -container._registrationMatrix3D._rawData[13] * container.transform.scale.y, -container._registrationMatrix3D._rawData[14] * container.transform.scale.z); } return this._orientationMatrix; } return this.getMatrix3D(); } public getColorTransform(): ColorTransform { if (this._hierarchicalPropsDirty & HierarchicalProperty.COLOR_TRANSFORM) { const container: IContainer = this.container; this._hierarchicalPropsDirty ^= HierarchicalProperty.COLOR_TRANSFORM; 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(container.transform.colorTransform); } else { this._colorTransform.copyFrom(container.transform.colorTransform); } /* // if we will use getter - it return empty blend in USE_UNSAFE_BLEND = false if ((<any> container)._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) { const container: IContainer = this.container; this._maskId = (container.maskId != -1) ? container.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; const container: IContainer = this.container; if (container.masks) { const len = container.masks.length; this._masks.length = len; for (let i = 0; i < len; i++) { this._masks[i] = (<View> this._pool).getNode(container.masks[i]); } } else { this._masks.length = 0; } if (this.scrollRect) this._masks.push(this._scrollRectNode); 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, true); this._root = this; container._initNode(this); container._containerNodes[pool.id] = this; this._hierarchicalPropsDirty = HierarchicalProperty.ALL; } 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(this.container); this._localNode.transformDisabled = true; this._localNode.setParent(this); } return this._localNode; } public clearLocalNode(): void { if (this._localNode) { this._localNode.onClear(); this._localNode = null; } } public onClear(): void { const container: IContainer = this.container; if (container) delete container._containerNodes[this.view.id]; this._localNode = null; this._maskOwners = null; // for (let i: number = 0; i < this._masks.length; i++) // this._masks[i].onClear(); this._masks.length = 0; 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(); 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._transformDisabled = false; this._parent = null; this._root = null; super.clear(); super.onClear(); } public onInvalidate(): void { this.invalidate(); } public clear(): void { super.clear(); this._maskOwners = null; for (let i: number = 0; i < this._masks.length; i++) this._masks[i].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 : !this.container.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; 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 = this._pool.abstractions.getAbstraction<ContainerNode>(entity); 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() || !this.container.mouseEnabled || this.parent?.isMouseChildrenDisabled(); } public isMouseChildrenDisabled(): boolean { if (this._hierarchicalPropsDirty & HierarchicalProperty.MOUSE_ENABLED) { this._mouseChildrenDisabled = !this.container.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 { 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._scrollRectNode) this._scrollRectNode.invalidateHierarchicalProperty(property); if (property & HierarchicalProperty.COLOR_TRANSFORM) { this.dispatchEvent(this._invalidateColorTransformEvent || (this._invalidateColorTransformEvent = new ContainerNodeEvent(ContainerNodeEvent.INVALIDATE_COLOR_TRANSFORM))); } 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); } }