@awayjs/view
Version:
View for AwayJS
801 lines (622 loc) • 22.2 kB
text/typescript
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);
}
}