import EventEmitter from 'eventemitter3';
import { Color, type ColorSource } from '../../color/Color';
import { cullingMixin } from '../../culling/cullingMixin';
import { Matrix } from '../../maths/matrix/Matrix';
import { DEG_TO_RAD, RAD_TO_DEG } from '../../maths/misc/const';
import { ObservablePoint } from '../../maths/point/ObservablePoint';
import { uid } from '../../utils/data/uid';
import { deprecation, v8_0_0 } from '../../utils/logging/deprecation';
import { BigPool } from '../../utils/pool/PoolGroup';
import { childrenHelperMixin } from './container-mixins/childrenHelperMixin';
import { effectsMixin } from './container-mixins/effectsMixin';
import { findMixin } from './container-mixins/findMixin';
import { measureMixin } from './container-mixins/measureMixin';
import { onRenderMixin } from './container-mixins/onRenderMixin';
import { sortMixin } from './container-mixins/sortMixin';
import { toLocalGlobalMixin } from './container-mixins/toLocalGlobalMixin';
import { RenderGroup } from './RenderGroup';
import { assignWithIgnore } from './utils/assignWithIgnore';
import type { Size } from '../../maths/misc/Size';
import type { PointData } from '../../maths/point/PointData';
import type { Rectangle } from '../../maths/shapes/Rectangle';
import type { BLEND_MODES } from '../../rendering/renderers/shared/state/const';
import type { Dict } from '../../utils/types';
import type { Optional } from './container-mixins/measureMixin';
import type { DestroyOptions } from './destroyTypes';
export type ContainerChild = Container;
/**
* This is where you'll find all the display objects available in Pixi.
*
* All display objects inherit from the Container class. You can use a `Container` for simple grouping of
* other display objects. Here's all the available display object classes.
*
* - Container is the base class for all display objects that act as a container for other objects.
* - Sprite is a display object that uses a texture
* - AnimatedSprite is a sprite that can play animations
* - TilingSprite a fast way of rendering a tiling image
* - NineSliceSprite allows you to stretch a texture using 9-slice scaling
* - Graphics is a graphic object that can be drawn to the screen.
* - Mesh empowers you to have maximum flexibility to render any kind of visuals you can think of
* - MeshSimple mimics Mesh, providing easy-to-use constructor arguments
* - MeshPlane allows you to draw a texture across several points and then manipulate these points
* - MeshRope allows you to draw a texture across several points and then manipulate these points
* - Text render text using custom fonts
* - BitmapText render text using a bitmap font
* - HTMLText render text using HTML and CSS
* @namespace scene
*/
// as pivot and skew are the least used properties of a container, we can use this optimisation
// to avoid allocating lots of unnecessary objects for them.
const defaultSkew = new ObservablePoint(null);
const defaultPivot = new ObservablePoint(null);
const defaultScale = new ObservablePoint(null, 1, 1);
export interface ContainerEvents<C extends ContainerChild> extends PixiMixins.ContainerEvents
{
added: [container: Container];
childAdded: [child: C, container: Container, index: number];
removed: [container: Container];
childRemoved: [child: C, container: Container, index: number];
destroyed: [container: Container];
}
type AnyEvent = {
// The following is a hack to allow any custom event while maintaining type safety.
// For some reason, the tsc compiler gets angry about error TS1023
// "An index signature parameter type must be either 'string' or 'number'."
// This is really odd since ({}&string) should interpret as string, but then again
// there is some black magic behind why this works in the first place.
// Closest thing to an explanation:
// https://stackoverflow.com/questions/70144348/why-does-a-union-of-type-literals-and-string-cause-ide-code-completion-wh
//
// Side note, we disable @typescript-eslint/ban-types since {}&string is the only syntax that works.
// Nor of the Record/unknown/never alternatives work.
// eslint-disable-next-line @typescript-eslint/ban-types
[K: ({} & string) | ({} & symbol)]: any;
};
export const UPDATE_COLOR = 0b0001;
export const UPDATE_BLEND = 0b0010;
export const UPDATE_VISIBLE = 0b0100;
export const UPDATE_TRANSFORM = 0b1000;
export interface UpdateTransformOptions
{
x: number;
y: number;
scaleX: number;
scaleY: number;
rotation: number;
skewX: number;
skewY: number;
pivotX: number;
pivotY: number;
}
/**
* Constructor options used for `Container` instances.
* ```js
* const container = new Container({
* position: new Point(100, 200),
* scale: new Point(2, 2),
* rotation: Math.PI / 2,
* });
* ```
* @memberof scene
* @see scene.Container
*/
export interface ContainerOptions<C extends ContainerChild = ContainerChild> extends PixiMixins.ContainerOptions
{
/** @see scene.Container#isRenderGroup */
isRenderGroup?: boolean;
/** @see scene.Container#blendMode */
blendMode?: BLEND_MODES;
/** @see scene.Container#tint */
tint?: ColorSource;
/** @see scene.Container#alpha */
alpha?: number;
/** @see scene.Container#angle */
angle?: number;
/** @see scene.Container#children */
children?: C[];
/** @see scene.Container#parent */
parent?: Container;
/** @see scene.Container#renderable */
renderable?: boolean;
/** @see scene.Container#rotation */
rotation?: number;
/** @see scene.Container#scale */
scale?: PointData | number;
/** @see scene.Container#pivot */
pivot?: PointData | number;
/** @see scene.Container#position */
position?: PointData;
/** @see scene.Container#skew */
skew?: PointData;
/** @see scene.Container#visible */
visible?: boolean;
/** @see scene.Container#culled */
culled?: boolean;
/** @see scene.Container#x */
x?: number;
/** @see scene.Container#y */
y?: number;
/** @see scene.Container#boundArea */
boundsArea?: Rectangle;
}
export interface Container<C extends ContainerChild>
extends PixiMixins.Container<C>, EventEmitter<ContainerEvents<C> & AnyEvent> {}
/**
* Container is a general-purpose display object that holds children. It also adds built-in support for advanced
* rendering features like masking and filtering.
*
* It is the base class of all display objects that act as a container for other objects, including Graphics
* and Sprite.
*
* <details id="transforms">
*
* <summary>Transforms</summary>
*
* The transform of a display object describes the projection from its
* local coordinate space to its parent's local coordinate space. The following properties are derived
* from the transform:
*
* <table>
* <thead>
* <tr>
* <th>Property</th>
* <th>Description</th>
* </tr>
* </thead>
* <tbody>
* <tr>
* <td>pivot</td>
* <td>
* Invariant under rotation, scaling, and skewing. The projection of into the parent's space of the pivot
* is equal to position, regardless of the other three transformations. In other words, It is the center of
* rotation, scaling, and skewing.
* </td>
* </tr>
* <tr>
* <td>position</td>
* <td>
* Translation. This is the position of the pivot in the parent's local
* space. The default value of the pivot is the origin (0,0). If the top-left corner of your display object
* is (0,0) in its local space, then the position will be its top-left corner in the parent's local space.
* </td>
* </tr>
* <tr>
* <td>scale</td>
* <td>
* Scaling. This will stretch (or compress) the display object's projection. The scale factors are along the
* local coordinate axes. In other words, the display object is scaled before rotated or skewed. The center
* of scaling is the pivot.
* </td>
* </tr>
* <tr>
* <td>rotation</td>
* <td>
* Rotation. This will rotate the display object's projection by this angle (in radians).
* </td>
* </tr>
* <tr>
* <td>skew</td>
* <td>
* <p>Skewing. This can be used to deform a rectangular display object into a parallelogram.</p>
* <p>
* In PixiJS, skew has a slightly different behaviour than the conventional meaning. It can be
* thought of the net rotation applied to the coordinate axes (separately). For example, if "skew.x" is
* ⍺ and "skew.y" is β, then the line x = 0 will be rotated by ⍺ (y = -x*cot⍺) and the line y = 0 will be
* rotated by β (y = x*tanβ). A line y = x*tanϴ (i.e. a line at angle ϴ to the x-axis in local-space) will
* be rotated by an angle between ⍺ and β.
* </p>
* <p>
* It can be observed that if skew is applied equally to both axes, then it will be equivalent to applying
* a rotation. Indeed, if "skew.x" = -ϴ and "skew.y" = ϴ, it will produce an equivalent of "rotation" = ϴ.
* </p>
* <p>
* Another quite interesting observation is that "skew.x", "skew.y", rotation are commutative operations. Indeed,
* because rotation is essentially a careful combination of the two.
* </p>
* </td>
* </tr>
* <tr>
* <td>angle</td>
* <td>Rotation. This is an alias for rotation, but in degrees.</td>
* </tr>
* <tr>
* <td>x</td>
* <td>Translation. This is an alias for position.x!</td>
* </tr>
* <tr>
* <td>y</td>
* <td>Translation. This is an alias for position.y!</td>
* </tr>
* <tr>
* <td>width</td>
* <td>
* Implemented in Container. Scaling. The width property calculates scale.x by dividing
* the "requested" width by the local bounding box width. It is indirectly an abstraction over scale.x, and there
* is no concept of user-defined width.
* </td>
* </tr>
* <tr>
* <td>height</td>
* <td>
* Implemented in Container. Scaling. The height property calculates scale.y by dividing
* the "requested" height by the local bounding box height. It is indirectly an abstraction over scale.y, and there
* is no concept of user-defined height.
* </td>
* </tr>
* </tbody>
* </table>
* </details>
*
* <details id="alpha">
* <summary>Alpha</summary>
*
* This alpha sets a display object's **relative opacity** w.r.t its parent. For example, if the alpha of a display
* object is 0.5 and its parent's alpha is 0.5, then it will be rendered with 25% opacity (assuming alpha is not
* applied on any ancestor further up the chain).
* </details>
*
* <details id="visible">
* <summary>Renderable vs Visible</summary>
*
* The `renderable` and `visible` properties can be used to prevent a display object from being rendered to the
* screen. However, there is a subtle difference between the two. When using `renderable`, the transforms of the display
* object (and its children subtree) will continue to be calculated. When using `visible`, the transforms will not
* be calculated.
* ```ts
* import { BlurFilter, Container, Graphics, Sprite } from 'pixi.js';
*
* const container = new Container();
* const sprite = Sprite.from('https://s3-us-west-2.amazonaws.com/s.cdpn.io/693612/IaUrttj.png');
*
* sprite.width = 512;
* sprite.height = 512;
*
* // Adds a sprite as a child to this container. As a result, the sprite will be rendered whenever the container
* // is rendered.
* container.addChild(sprite);
*
* // Blurs whatever is rendered by the container
* container.filters = [new BlurFilter()];
*
* // Only the contents within a circle at the center should be rendered onto the screen.
* container.mask = new Graphics()
* .beginFill(0xffffff)
* .drawCircle(sprite.width / 2, sprite.height / 2, Math.min(sprite.width, sprite.height) / 2)
* .endFill();
* ```
*
* </details>
*
* <details id="renderGroup">
* <summary>RenderGroup</summary>
*
* In PixiJS v8, containers can be set to operate in 'render group mode',
* transforming them into entities akin to a stage in traditional rendering paradigms.
* A render group is a root renderable entity, similar to a container,
* but it's rendered in a separate pass with its own unique set of rendering instructions.
* This approach enhances rendering efficiency and organization, particularly in complex scenes.
*
* You can enable render group mode on any container using container.enableRenderGroup()
* or by initializing a new container with the render group property set to true (new Container({isRenderGroup: true})).
* The method you choose depends on your specific use case and setup requirements.
*
* An important aspect of PixiJS’s rendering process is the automatic treatment of rendered scenes as render groups.
* This conversion streamlines the rendering process, but understanding when and how this happens is crucial
* to fully leverage its benefits.
*
* One of the key advantages of using render groups is the performance efficiency in moving them. Since transformations
* are applied at the GPU level, moving a render group, even one with complex and numerous children,
* doesn't require recalculating the rendering instructions or performing transformations on each child.
* This makes operations like panning a large game world incredibly efficient.
*
* However, it's crucial to note that render groups do not batch together.
* This means that turning every container into a render group could actually slow things down,
* as each render group is processed separately. It's best to use render groups judiciously, at a broader level,
* rather than on a per-child basis.
* This approach ensures you get the performance benefits without overburdening the rendering process.
*
* RenderGroups maintain their own set of rendering instructions,
* ensuring that changes or updates within a render group don't affect the rendering
* instructions of its parent or other render groups.
* This isolation ensures more stable and predictable rendering behavior.
*
* Additionally, renderGroups can be nested, allowing for powerful options in organizing different aspects of your scene.
* This feature is particularly beneficial for separating complex game graphics from UI elements,
* enabling intricate and efficient scene management in complex applications.
*
* This means that Containers have 3 levels of matrix to be mindful of:
*
* 1. localTransform, this is the transform of the container based on its own properties
* 2. groupTransform, this it the transform of the container relative to the renderGroup it belongs too
* 3. worldTransform, this is the transform of the container relative to the Scene being rendered
* </details>
* @memberof scene
*/
export class Container<C extends ContainerChild = ContainerChild> extends EventEmitter<ContainerEvents<C> & AnyEvent>
{
/**
* Mixes all enumerable properties and methods from a source object to Container.
* @param source - The source of properties and methods to mix in.
*/
public static mixin(source: Dict<any>): void
{
Object.defineProperties(Container.prototype, Object.getOwnPropertyDescriptors(source));
}
/** @private */
public uid: number = uid('renderable');
/** @private */
public _updateFlags = 0b1111;
// the render group this container owns
/** @private */
public renderGroup: RenderGroup = null;
// the render group this container belongs to
/** @private */
public parentRenderGroup: RenderGroup = null;
// the index of the container in the render group
/** @private */
public parentRenderGroupIndex: number = 0;
// set to true if the container has changed. It is reset once the changes have been applied
// by the transform system
// its here to stop ensure that when things change, only one update gets registers with the transform system
/** @private */
public didChange = false;
// same as above, but for the renderable
/** @private */
public didViewUpdate = false;
// how deep is the container relative to its render group..
// unless the element is the root render group - it will be relative to its parent
/** @private */
public relativeRenderGroupDepth = 0;
/**
* The array of children of this container.
* @readonly
*/
public children: C[] = [];
/** The display object container that contains this display object. */
public parent: Container = null;
// used internally for changing up the render order.. mainly for masks and filters
// TODO setting this should cause a rebuild??
/** @private */
public includeInBuild = true;
/** @private */
public measurable = true;
/** @private */
public isSimple = true;
// / /////////////Transform related props//////////////
// used by the transform system to check if a container needs to be updated that frame
// if the tick matches the current transform system tick, it is not updated again
/**
* @internal
* @ignore
*/
public updateTick = -1;
/**
* Current transform of the object based on local factors: position, scale, other stuff.
* @readonly
*/
public localTransform: Matrix = new Matrix();
/**
* The relative group transform is a transform relative to the render group it belongs too. It will include all parent
* transforms and up to the render group (think of it as kind of like a stage - but the stage can be nested).
* If this container is is self a render group matrix will be relative to its parent render group
* @readonly
*/
public relativeGroupTransform: Matrix = new Matrix();
/**
* The group transform is a transform relative to the render group it belongs too.
* If this container is render group then this will be an identity matrix. other wise it
* will be the same as the relativeGroupTransform.
* Use this value when actually rendering things to the screen
* @readonly
*/
public groupTransform: Matrix = this.relativeGroupTransform;
// the global transform taking into account the render group and all parents
private _worldTransform: Matrix;
/** If the object has been destroyed via destroy(). If true, it should not be used. */
public destroyed = false;
// transform data..
/**
* The coordinate of the object relative to the local coordinates of the parent.
* @internal
* @ignore
*/
public _position: ObservablePoint = new ObservablePoint(this, 0, 0);
/**
* The scale factor of the object.
* @internal
* @ignore
*/
public _scale: ObservablePoint = defaultScale;
/**
* The pivot point of the container that it rotates around.
* @internal
* @ignore
*/
public _pivot: ObservablePoint = defaultPivot;
/**
* The skew amount, on the x and y axis.
* @internal
* @ignore
*/
public _skew: ObservablePoint = defaultSkew;
/**
* The X-coordinate value of the normalized local X axis,
* the first column of the local transformation matrix without a scale.
* @internal
* @ignore
*/
public _cx = 1;
/**
* The Y-coordinate value of the normalized local X axis,
* the first column of the local transformation matrix without a scale.
* @internal
* @ignore
*/
public _sx = 0;
/**
* The X-coordinate value of the normalized local Y axis,
* the second column of the local transformation matrix without a scale.
* @internal
* @ignore
*/
public _cy = 0;
/**
* The Y-coordinate value of the normalized local Y axis,
* the second column of the local transformation matrix without a scale.
* @internal
* @ignore
*/
public _sy = 1;
/**
* The rotation amount.
* @internal
* @ignore
*/
private _rotation = 0;
// / COLOR related props //////////////
// color stored as ABGR
public localColor = 0xFFFFFF;
public localAlpha = 1;
public groupAlpha = 1; // A
public groupColor = 0xFFFFFF; // BGR
public groupColorAlpha = 0xFFFFFFFF; // ABGR
// / BLEND related props //////////////
/**
* @internal
* @ignore
*/
public localBlendMode: BLEND_MODES = 'inherit';
/**
* @internal
* @ignore
*/
public groupBlendMode: BLEND_MODES = 'normal';
// / VISIBILITY related props //////////////
// visibility
// 0b11
// first bit is visible, second bit is renderable
/**
* This property holds three bits: culled, visible, renderable
* the third bit represents culling (0 = culled, 1 = not culled) 0b100
* the second bit represents visibility (0 = not visible, 1 = visible) 0b010
* the first bit represents renderable (0 = renderable, 1 = not renderable) 0b001
* @internal
* @ignore
*/
public localDisplayStatus = 0b111; // 0b11 | 0b10 | 0b01 | 0b00
/**
* @internal
* @ignore
*/
public globalDisplayStatus = 0b111; // 0b11 | 0b10 | 0b01 | 0b00
public renderPipeId: string;
/**
* An optional bounds area for this container. Setting this rectangle will stop the renderer
* from recursively measuring the bounds of each children and instead use this single boundArea.
* This is great for optimisation! If for example you have a 1000 spinning particles and you know they all sit
* within a specific bounds, then setting it will mean the renderer will not need to measure the
* 1000 children to find the bounds. Instead it will just use the bounds you set.
*/
public boundsArea: Rectangle;
/**
* A value that increments each time the container is modified
* the first 12 bits represent the container changes (eg transform, alpha, visible etc)
* the second 12 bits represent:
* - for view changes (eg texture swap, geometry change etc)
* - containers changes (eg children added, removed etc)
*
* view container
* [000000000000][00000000000]
* @ignore
*/
public _didChangeId = 0;
/**
* property that tracks if the container transform has changed
* @ignore
*/
private _didLocalTransformChangeId = -1;
constructor(options: ContainerOptions<C> = {})
{
super();
assignWithIgnore(this, options, {
children: true,
parent: true,
effects: true,
});
options.children?.forEach((child) => this.addChild(child));
this.effects = [];
options.parent?.addChild(this);
}
/**
* Adds one or more children to the container.
*
* Multiple items can be added like so: `myContainer.addChild(thingOne, thingTwo, thingThree)`
* @param {...Container} children - The Container(s) to add to the container
* @returns {Container} - The first child that was added.
*/
public addChild<U extends C[]>(...children: U): U[0]
{
// #if _DEBUG
if (!this.allowChildren)
{
deprecation(v8_0_0, 'addChild: Only Containers will be allowed to add children in v8.0.0');
}
// #endif
if (children.length > 1)
{
// loop through the array and add all children
for (let i = 0; i < children.length; i++)
{
this.addChild(children[i]);
}
return children[0];
}
const child = children[0];
if (child.parent === this)
{
this.children.splice(this.children.indexOf(child), 1);
this.children.push(child);
if (this.parentRenderGroup)
{
this.parentRenderGroup.structureDidChange = true;
}
return child;
}
if (child.parent)
{
// TODO Optimisation...if the parent has the same render group, this does not need to change!
child.parent.removeChild(child);
}
this.children.push(child);
if (this.sortableChildren) this.sortDirty = true;
child.parent = this;
child.didChange = true;
child.didViewUpdate = false;
// TODO - OPtimise this? could check what the parent has set?
child._updateFlags = 0b1111;
const renderGroup = this.renderGroup || this.parentRenderGroup;
if (renderGroup)
{
renderGroup.addChild(child);
}
this.emit('childAdded', child, this, this.children.length - 1);
child.emit('added', this);
this._didChangeId += 1 << 12;
if (child._zIndex !== 0)
{
child.depthOfChildModified();
}
return child;
}
/**
* Removes one or more children from the container.
* @param {...Container} children - The Container(s) to remove
* @returns {Container} The first child that was removed.
*/
public removeChild<U extends C[]>(...children: U): U[0]
{
// if there is only one argument we can bypass looping through the them
if (children.length > 1)
{
// loop through the arguments property and remove all children
for (let i = 0; i < children.length; i++)
{
this.removeChild(children[i]);
}
return children[0];
}
const child = children[0];
const index = this.children.indexOf(child);
if (index > -1)
{
this._didChangeId += 1 << 12;
this.children.splice(index, 1);
if (this.renderGroup)
{
this.renderGroup.removeChild(child);
}
else if (this.parentRenderGroup)
{
this.parentRenderGroup.removeChild(child);
}
child.parent = null;
this.emit('childRemoved', child, this, index);
child.emit('removed', this);
}
return child;
}
/** @ignore */
public _onUpdate(point?: ObservablePoint)
{
if (point)
{
// this.updateFlags |= UPDATE_TRANSFORM;
if (point === this._skew)
{
this._updateSkew();
}
}
this._didChangeId++;
if (this.didChange) return;
this.didChange = true;
if (this.parentRenderGroup)
{
this.parentRenderGroup.onChildUpdate(this);
}
}
set isRenderGroup(value: boolean)
{
if (!!this.renderGroup === value) return;
if (value)
{
this.enableRenderGroup();
}
else
{
this.disableRenderGroup();
}
}
/**
* Returns true if this container is a render group.
* This means that it will be rendered as a separate pass, with its own set of instructions
*/
get isRenderGroup(): boolean
{
return !!this.renderGroup;
}
/**
* Calling this enables a render group for this container.
* This means it will be rendered as a separate set of instructions.
* The transform of the container will also be handled on the GPU rather than the CPU.
*/
public enableRenderGroup(): void
{
if (this.renderGroup) return;
const parentRenderGroup = this.parentRenderGroup;
parentRenderGroup?.removeChild(this);
this.renderGroup = BigPool.get(RenderGroup, this);
// this group matrix will now be an identity matrix,
// as its own transform will be passed to the GPU
this.groupTransform = Matrix.IDENTITY;
parentRenderGroup?.addChild(this);
this._updateIsSimple();
}
/** This will disable the render group for this container. */
public disableRenderGroup(): void
{
if (!this.renderGroup) return;
const parentRenderGroup = this.parentRenderGroup;
parentRenderGroup?.removeChild(this);
BigPool.return(this.renderGroup);
this.renderGroup = null;
this.groupTransform = this.relativeGroupTransform;
parentRenderGroup?.addChild(this);
this._updateIsSimple();
}
/** @ignore */
public _updateIsSimple()
{
this.isSimple = !(this.renderGroup) && (this.effects.length === 0);
}
/**
* Current transform of the object based on world (parent) factors.
* @readonly
*/
get worldTransform()
{
this._worldTransform ||= new Matrix();
if (this.renderGroup)
{
this._worldTransform.copyFrom(this.renderGroup.worldTransform);
}
else if (this.parentRenderGroup)
{
this._worldTransform.appendFrom(this.relativeGroupTransform, this.parentRenderGroup.worldTransform);
}
return this._worldTransform;
}
// / ////// transform related stuff
/**
* The position of the container on the x axis relative to the local coordinates of the parent.
* An alias to position.x
*/
get x(): number
{
return this._position.x;
}
set x(value: number)
{
this._position.x = value;
}
/**
* The position of the container on the y axis relative to the local coordinates of the parent.
* An alias to position.y
*/
get y(): number
{
return this._position.y;
}
set y(value: number)
{
this._position.y = value;
}
/**
* The coordinate of the object relative to the local coordinates of the parent.
* @since 4.0.0
*/
get position(): ObservablePoint
{
return this._position;
}
set position(value: PointData)
{
this._position.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(): number
{
return this._rotation;
}
set rotation(value: number)
{
if (this._rotation !== value)
{
this._rotation = value;
this._onUpdate(this._skew);
}
}
/**
* 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.
*/
get angle(): number
{
return this.rotation * RAD_TO_DEG;
}
set angle(value: number)
{
this.rotation = value * DEG_TO_RAD;
}
/**
* The center of rotation, scaling, and skewing for this display object in its local space. The `position`
* is the projection of `pivot` in the parent's local space.
*
* By default, the pivot is the origin (0, 0).
* @since 4.0.0
*/
get pivot(): ObservablePoint
{
if (this._pivot === defaultPivot)
{
this._pivot = new ObservablePoint(this, 0, 0);
}
return this._pivot;
}
set pivot(value: PointData | number)
{
if (this._pivot === defaultPivot)
{
this._pivot = new ObservablePoint(this, 0, 0);
}
typeof value === 'number' ? this._pivot.set(value) : this._pivot.copyFrom(value);
}
/**
* The skew factor for the object in radians.
* @since 4.0.0
*/
get skew(): ObservablePoint
{
if (this._skew === defaultSkew)
{
this._skew = new ObservablePoint(this, 0, 0);
}
return this._skew;
}
set skew(value: PointData)
{
if (this._skew === defaultSkew)
{
this._skew = new ObservablePoint(this, 0, 0);
}
this._skew.copyFrom(value);
}
/**
* The scale factors of this object along the local coordinate axes.
*
* The default scale is (1, 1).
* @since 4.0.0
*/
get scale(): ObservablePoint
{
if (this._scale === defaultScale)
{
this._scale = new ObservablePoint(this, 1, 1);
}
return this._scale;
}
set scale(value: PointData | number)
{
if (this._scale === defaultScale)
{
this._scale = new ObservablePoint(this, 0, 0);
}
typeof value === 'number' ? this._scale.set(value) : this._scale.copyFrom(value);
}
/**
* The width of the Container, setting this will actually modify the scale to achieve the value set.
* @memberof scene.Container#
*/
get width(): number
{
return Math.abs(this.scale.x * this.getLocalBounds().width);
}
set width(value: number)
{
const localWidth = this.getLocalBounds().width;
this._setWidth(value, localWidth);
}
/**
* The height of the Container, setting this will actually modify the scale to achieve the value set.
* @memberof scene.Container#
*/
get height(): number
{
return Math.abs(this.scale.y * this.getLocalBounds().height);
}
set height(value: number)
{
const localHeight = this.getLocalBounds().height;
this._setHeight(value, localHeight);
}
/**
* Retrieves the size of the container as a Size object.
* This is faster than get the width and height separately.
* @param out - Optional object to store the size in.
* @returns - The size of the container.
* @memberof scene.Container#
*/
public getSize(out?: Size): Size
{
if (!out)
{
out = {} as Size;
}
const bounds = this.getLocalBounds();
out.width = Math.abs(this.scale.x * bounds.width);
out.height = Math.abs(this.scale.y * bounds.height);
return out;
}
/**
* Sets the size of the container to the specified width and height.
* This is faster than setting the width and height separately.
* @param value - This can be either a number or a Size object.
* @param height - The height to set. Defaults to the value of `width` if not provided.
* @memberof scene.Container#
*/
public setSize(value: number | Optional<Size, 'height'>, height?: number)
{
const size = this.getLocalBounds();
let convertedWidth: number;
let convertedHeight: number;
if (typeof value !== 'object')
{
convertedWidth = value;
convertedHeight = height ?? value;
}
else
{
convertedWidth = value.width;
convertedHeight = value.height ?? value.width;
}
if (convertedWidth !== undefined)
{
this._setWidth(convertedWidth, size.width);
}
if (convertedHeight !== undefined)
{
this._setHeight(convertedHeight, size.height);
}
}
/** Called when the skew or the rotation changes. */
private _updateSkew(): void
{
const rotation = this._rotation;
const skew = this._skew;
this._cx = Math.cos(rotation + skew._y);
this._sx = Math.sin(rotation + skew._y);
this._cy = -Math.sin(rotation - skew._x); // cos, added PI/2
this._sy = Math.cos(rotation - skew._x); // sin, added PI/2
}
/**
* Updates the transform properties of the container (accepts partial values).
* @param {object} opts - The options for updating the transform.
* @param {number} opts.x - The x position of the container.
* @param {number} opts.y - The y position of the container.
* @param {number} opts.scaleX - The scale factor on the x-axis.
* @param {number} opts.scaleY - The scale factor on the y-axis.
* @param {number} opts.rotation - The rotation of the container, in radians.
* @param {number} opts.skewX - The skew factor on the x-axis.
* @param {number} opts.skewY - The skew factor on the y-axis.
* @param {number} opts.pivotX - The x coordinate of the pivot point.
* @param {number} opts.pivotY - The y coordinate of the pivot point.
*/
public updateTransform(opts: Partial<UpdateTransformOptions>): this
{
this.position.set(
typeof opts.x === 'number' ? opts.x : this.position.x,
typeof opts.y === 'number' ? opts.y : this.position.y
);
this.scale.set(
typeof opts.scaleX === 'number' ? opts.scaleX || 1 : this.scale.x,
typeof opts.scaleY === 'number' ? opts.scaleY || 1 : this.scale.y
);
this.rotation = typeof opts.rotation === 'number' ? opts.rotation : this.rotation;
this.skew.set(
typeof opts.skewX === 'number' ? opts.skewX : this.skew.x,
typeof opts.skewY === 'number' ? opts.skewY : this.skew.y
);
this.pivot.set(
typeof opts.pivotX === 'number' ? opts.pivotX : this.pivot.x,
typeof opts.pivotY === 'number' ? opts.pivotY : this.pivot.y
);
return this;
}
/**
* Updates the local transform using the given matrix.
* @param matrix - The matrix to use for updating the transform.
*/
public setFromMatrix(matrix: Matrix): void
{
matrix.decompose(this);
}
/** Updates the local transform. */
public updateLocalTransform(): void
{
if ((this._didLocalTransformChangeId & 0b1111) === this._didChangeId) return;
this._didLocalTransformChangeId = this._didChangeId;
// this.didChange = false;
const lt = this.localTransform;
const scale = this._scale;
const pivot = this._pivot;
const position = this._position;
const sx = scale._x;
const sy = scale._y;
const px = pivot._x;
const py = pivot._y;
// get the matrix values of the container based on its this properties..
lt.a = this._cx * sx;
lt.b = this._sx * sx;
lt.c = this._cy * sy;
lt.d = this._sy * sy;
lt.tx = position._x - ((px * lt.a) + (py * lt.c));
lt.ty = position._y - ((px * lt.b) + (py * lt.d));
}
// / ///// color related stuff
set alpha(value: number)
{
if (value === this.localAlpha) return;
this.localAlpha = value;
this._updateFlags |= UPDATE_COLOR;
this._onUpdate();
}
/** The opacity of the object. */
get alpha(): number
{
return this.localAlpha;
}
set tint(value: ColorSource)
{
const tempColor = Color.shared.setValue(value ?? 0xFFFFFF);
const bgr = tempColor.toBgrNumber();
if (bgr === this.localColor) return;
this.localColor = bgr;
this._updateFlags |= UPDATE_COLOR;
this._onUpdate();
}
/**
* The tint applied to the sprite. This is a hex value.
*
* A value of 0xFFFFFF will remove any tint effect.
* @default 0xFFFFFF
*/
get tint(): number
{
const bgr = this.localColor;
// convert bgr to rgb..
return ((bgr & 0xFF) << 16) + (bgr & 0xFF00) + ((bgr >> 16) & 0xFF);
}
// / //////////////// blend related stuff
set blendMode(value: BLEND_MODES)
{
if (this.localBlendMode === value) return;
if (this.parentRenderGroup)
{
this.parentRenderGroup.structureDidChange = true;
}
this._updateFlags |= UPDATE_BLEND;
this.localBlendMode = value;
this._onUpdate();
}
/**
* The blend mode to be applied to the sprite. Apply a value of `'normal'` to reset the blend mode.
* @default 'normal'
*/
get blendMode(): BLEND_MODES
{
return this.localBlendMode;
}
// / ///////// VISIBILITY / RENDERABLE /////////////////
/** The visibility of the object. If false the object will not be drawn, and the transform will not be updated. */
get visible()
{
return !!(this.localDisplayStatus & 0b010);
}
set visible(value: boolean)
{
const valueNumber = value ? 1 : 0;
if ((this.localDisplayStatus & 0b010) >> 1 === valueNumber) return;
if (this.parentRenderGroup)
{
this.parentRenderGroup.structureDidChange = true;
}
this._updateFlags |= UPDATE_VISIBLE;
this.localDisplayStatus ^= 0b010;
this._onUpdate();
}
/** @ignore */
get culled()
{
return !(this.localDisplayStatus & 0b100);
}
/** @ignore */
set culled(value: boolean)
{
const valueNumber = value ? 1 : 0;
if ((this.localDisplayStatus & 0b100) >> 2 === valueNumber) return;
if (this.parentRenderGroup)
{
this.parentRenderGroup.structureDidChange = true;
}
this._updateFlags |= UPDATE_VISIBLE;
this.localDisplayStatus ^= 0b100;
this._onUpdate();
}
/** Can this object be rendered, if false the object will not be drawn but the transform will still be updated. */
get renderable()
{
return !!(this.localDisplayStatus & 0b001);
}
set renderable(value: boolean)
{
const valueNumber = value ? 1 : 0;
if ((this.localDisplayStatus & 0b001) === valueNumber) return;
this._updateFlags |= UPDATE_VISIBLE;
this.localDisplayStatus ^= 0b001;
if (this.parentRenderGroup)
{
this.parentRenderGroup.structureDidChange = true;
}
this._onUpdate();
}
/** Whether or not the object should be rendered. */
get isRenderable(): boolean
{
return (this.localDisplayStatus === 0b111 && this.groupAlpha > 0);
}
/**
* Removes all internal references and listeners as well as removes children from the display list.
* Do not use a Container after calling `destroy`.
* @param options - Options parameter. A boolean will act as if all options
* have been set to that value
* @param {boolean} [options.children=false] - if set to true, all the children will have their destroy
* method called as well. 'options' will be passed on to those calls.
* @param {boolean} [options.texture=false] - Only used for children with textures e.g. Sprites. If options.children
* is set to true it should destroy the texture of the child sprite
* @param {boolean} [options.textureSource=false] - Only used for children with textures e.g. Sprites.
* If options.children is set to true it should destroy the texture source of the child sprite
* @param {boolean} [options.context=false] - Only used for children with graphicsContexts e.g. Graphics.
* If options.children is set to true it should destroy the context of the child graphics
*/
public destroy(options: DestroyOptions = false): void
{
if (this.destroyed) return;
this.destroyed = true;
// remove children is faster than removeChild..
const oldChildren = this.removeChildren(0, this.children.length);
this.removeFromParent();
this.parent = null;
this._maskEffect = null;
this._filterEffect = null;
this.effects = null;
this._position = null;
this._scale = null;
this._pivot = null;
this._skew = null;
this.emit('destroyed', this);
this.removeAllListeners();
const destroyChildren = typeof options === 'boolean' ? options : options?.children;
if (destroyChildren)
{
for (let i = 0; i < oldChildren.length; ++i)
{
oldChildren[i].destroy(options);
}
}
this.renderGroup?.destroy();
this.renderGroup = null;
}
}
Container.mixin(childrenHelperMixin);
Container.mixin(toLocalGlobalMixin);
Container.mixin(onRenderMixin);
Container.mixin(measureMixin);
Container.mixin(effectsMixin);
Container.mixin(findMixin);
Container.mixin(sortMixin);
Container.mixin(cullingMixin);