import { RAD_TO_DEG, DEG_TO_RAD } from "./const"; import { Bounds } from "./bounds"; import { Point } from "./point"; import { Transform } from "./transform"; import { Rectangle } from "./rectangle"; export class DisplayObject { tempDisplayObjectParent?:DisplayObject; transform = new Transform(); visible = true; parent?:any; alpha = 1; /** * The multiplied alpha of the displayObject. */ worldAlpha = 1; /** * Which index in the children array the display component was before the previous zIndex sort. * Used by containers to help sort objects with the same zIndex, by using previous array index as the decider. */ _lastSortedIndex = 0; /** * The zIndex of the displayObject. * A higher value will mean it will be rendered on top of other displayObjects within the same container. */ _zIndex = 0; /** * The bounds object, this is used to calculate and store the bounds of the displayObject. */ _bounds = new Bounds(); _boundsID = 0; _lastBoundsID = -1; _boundsRect?:Rectangle; _localBoundsRect?:Rectangle; /** * If the object has been destroyed via destroy(). If true, it should not be used. */ _destroyed = false; get _tempDisplayObjectParent() { if (!this.tempDisplayObjectParent) { this.tempDisplayObjectParent = new DisplayObject(); } return this.tempDisplayObjectParent as DisplayObject; } /** * Updates the object transform for rendering. * TODO - Optimization pass! */ updateTransform() { if (this.parent) { this.transform.updateTransform(this.parent.transform); // multiply the alphas.. this.worldAlpha = this.alpha * this.parent.worldAlpha; } else { this.transform.updateTransform(this._tempDisplayObjectParent.transform); this.worldAlpha = this.alpha; } this._boundsID++; } /** * Recursively updates transform of all objects from the root to this one * internal function for toLocal() */ _recursivePostUpdateTransform() { if (this.parent) { this.parent._recursivePostUpdateTransform(); this.transform.updateTransform(this.parent.transform); } else { this.transform.updateTransform(this._tempDisplayObjectParent.transform); } } calculateBounds() { console.log("please implement me") } getBounds(skipUpdate:boolean, rect?:Rectangle) { if (!skipUpdate) { if (!this.parent) { this.parent = this._tempDisplayObjectParent; this.updateTransform(); this.parent = undefined; } else { this._recursivePostUpdateTransform(); this.updateTransform(); } } if (this._boundsID !== this._lastBoundsID) { this.calculateBounds(); this._lastBoundsID = this._boundsID; } if (!rect) { if (!this._boundsRect) { this._boundsRect = new Rectangle(); } rect = this._boundsRect; } return this._bounds.getRectangle(rect); } getLocalBounds(rect?:Rectangle) { const transformRef = this.transform; const parentRef = this.parent; this.parent = undefined; this.transform = this._tempDisplayObjectParent.transform; if (!rect) { if (!this._localBoundsRect) { this._localBoundsRect = new Rectangle(); } rect = this._localBoundsRect; } const bounds = this.getBounds(false, rect); this.parent = parentRef; this.transform = transformRef; return bounds; } /** * Calculates the global position of the display object. */ toGlobal(position:Point, point:Point, skipUpdate = false) { if (!skipUpdate) { this._recursivePostUpdateTransform(); // this parent check is for just in case the item is a root object. // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) if (!this.parent) { this.parent = this._tempDisplayObjectParent; this.displayObjectUpdateTransform(); this.parent = undefined; } else { this.displayObjectUpdateTransform(); } } // don't need to update the lot return this.worldTransform.apply(position, point); } /** * Calculates the local position of the display object relative to another point. * * @param {PIXI.IPoint} position - The world origin to calculate from. * @param {PIXI.DisplayObject} [from] - The DisplayObject to calculate the global position from. * @param {PIXI.IPoint} [point] - A Point object in which to store the value, optional * (otherwise will create a new Point). * @param {boolean} [skipUpdate=false] - Should we skip the update transform * @return {PIXI.IPoint} A point object representing the position of this object */ toLocal(position:Point, from:DisplayObject, point:Point, skipUpdate:boolean) { if (from) { position = from.toGlobal(position, point, skipUpdate); } if (!skipUpdate) { this._recursivePostUpdateTransform(); // this parent check is for just in case the item is a root object. // If it is we need to give it a temporary parent so that displayObjectUpdateTransform works correctly // this is mainly to avoid a parent check in the main loop. Every little helps for performance :) if (!this.parent) { this.parent = this._tempDisplayObjectParent; this.displayObjectUpdateTransform(); this.parent = undefined; } else { this.displayObjectUpdateTransform(); } } // simply apply the matrix.. return this.worldTransform.applyInverse(position, point); } setParent(container:any) { if (!container || !container.addChild) { throw new Error("setParent: Argument must be a Container"); } container.addChild(this); return container; } setTransform( x = 0, y = 0, scaleX = 1, scaleY = 1, rotation = 0, skewX = 0, skewY = 0, pivotX = 0, pivotY = 0 ) { this.position.x = x; this.position.y = y; this.scale.x = !scaleX ? 1 : scaleX; this.scale.y = !scaleY ? 1 : scaleY; this.rotation = rotation; this.skew.x = skewX; this.skew.y = skewY; this.pivot.x = pivotX; this.pivot.y = pivotY; return this; } /** * Base destroy method for generic display objects. This will automatically * remove the display object from its parent Container as well as remove * all current event listeners and internal references. Do not use a DisplayObject * after calling `destroy()`. * */ sortDirty = false; destroy() { if (this.parent) { this.parent.removeChild(this); } this.parent = undefined; this._destroyed = true; } /** * The position of the displayObject on the x axis relative to the local coordinates of the parent. * An alias to position.x */ get x() { return this.position.x; } set x( value // eslint-disable-line require-jsdoc ) { this.transform.position.x = value; } /** * The position of the displayObject on the y axis relative to the local coordinates of the parent. * An alias to position.y * * @member {number} */ get y() { return this.position.y; } set y( value // eslint-disable-line require-jsdoc ) { this.transform.position.y = value; } /** * Current transform of the object based on world (parent) factors. * * @member {PIXI.Matrix} * @readonly */ get worldTransform() { return this.transform.worldTransform; } get localTransform() { return this.transform.localTransform; } /** * The coordinate of the object relative to the local coordinates of the parent. * Assignment by value since pixi-v4. * * @member {PIXI.IPoint} */ get position() { return this.transform.position; } set position( value // eslint-disable-line require-jsdoc ) { this.transform.position.copyFrom(value); } /** * The scale factor of the object. * Assignment by value since pixi-v4. * * @member {PIXI.IPoint} */ get scale() { return this.transform.scale; } set scale( value // eslint-disable-line require-jsdoc ) { this.transform.scale.copyFrom(value); } /** * The pivot point of the displayObject that it rotates around. * Assignment by value since pixi-v4. * * @member {PIXI.IPoint} */ get pivot() { return this.transform.pivot; } set pivot( value ) { this.transform.pivot.copyFrom(value); } //保持当前对象位置不变,修改pivot setPivotWorldNoChange(x:number, y:number) { const targetPivot = { x, y }; const point = { x: targetPivot.x, y: targetPivot.y }; this.worldTransform.apply(point as any, point as any); this.pivot = targetPivot as any; this.position.x = point.x; this.position.y = point.y; this.updateTransform(); } /** * The skew factor for the object in radians. * Assignment by value since pixi-v4. */ get skew() { return this.transform.skew; } set skew( value // eslint-disable-line require-jsdoc ) { this.transform.skew.copyFrom(value); } /** * The rotation of the object in radians. * 'rotation' and 'angle' have the same effect on a display object; rotation is in radians, angle is in degrees. */ get rotation() { return this.transform.rotation; } set rotation( value // eslint-disable-line require-jsdoc ) { this.transform.rotation = value; } /** * The angle of the object in degrees. * 'rotation' and 'angle' have the same effect on a display object; rotation is in radians, angle is in degrees. * * @member {number} */ get angle() { return this.transform.rotation * RAD_TO_DEG; } set angle( value // eslint-disable-line require-jsdoc ) { this.transform.rotation = value * DEG_TO_RAD; } /** * The zIndex of the displayObject. * If a container has the sortableChildren property set to true, children will be automatically * sorted by zIndex value; a higher value will mean it will be moved towards the end of the array, * and thus rendered on top of other displayObjects within the same container. * * @member {number} */ get zIndex() { return this._zIndex; } set zIndex( value // eslint-disable-line require-jsdoc ) { this._zIndex = value; if (this.parent) { this.parent.sortDirty = true; } } get worldVisible() { let item:any = this; do { if (!item.visible) { return false; } item = item.parent; } while (item); return true; } getGlobalPosition( point = new Point(),skipUpdate = false ) { if (this.parent) { this.parent.toGlobal(this.position, point, skipUpdate); } else { point.x = this.position.x; point.y = this.position.y; } return point; } displayObjectUpdateTransform = this.updateTransform; }