456 lines
13 KiB
JavaScript
456 lines
13 KiB
JavaScript
/**
|
|
* A mixin which decorates a DisplayObject with additional properties expected for rendering in the PrimaryCanvasGroup.
|
|
* @category - Mixins
|
|
* @param {typeof PIXI.DisplayObject} DisplayObject The parent DisplayObject class being mixed
|
|
* @returns {typeof PrimaryCanvasObject} A DisplayObject subclass mixed with PrimaryCanvasObject features
|
|
* @mixin
|
|
*/
|
|
function PrimaryCanvasObjectMixin(DisplayObject) {
|
|
|
|
/**
|
|
* A display object rendered in the PrimaryCanvasGroup.
|
|
* @param {...*} args The arguments passed to the base class constructor
|
|
*/
|
|
return class PrimaryCanvasObject extends CanvasTransformMixin(DisplayObject) {
|
|
constructor(...args) {
|
|
super(...args);
|
|
// Activate culling and initialize handlers
|
|
this.cullable = true;
|
|
this.on("added", this._onAdded);
|
|
this.on("removed", this._onRemoved);
|
|
}
|
|
|
|
/**
|
|
* An optional reference to the object that owns this PCO.
|
|
* This property does not affect the behavior of the PCO itself.
|
|
* @type {*}
|
|
* @default null
|
|
*/
|
|
object = null;
|
|
|
|
/**
|
|
* The entry in the quadtree.
|
|
* @type {QuadtreeObject|null}
|
|
*/
|
|
#quadtreeEntry = null;
|
|
|
|
/**
|
|
* Update the quadtree entry?
|
|
* @type {boolean}
|
|
*/
|
|
#quadtreeDirty = false;
|
|
|
|
/* -------------------------------------------- */
|
|
/* Properties */
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* The elevation of this object.
|
|
* @type {number}
|
|
*/
|
|
get elevation() {
|
|
return this.#elevation;
|
|
}
|
|
|
|
set elevation(value) {
|
|
if ( (typeof value !== "number") || Number.isNaN(value) ) {
|
|
throw new Error("PrimaryCanvasObject#elevation must be a numeric value.");
|
|
}
|
|
if ( value === this.#elevation ) return;
|
|
this.#elevation = value;
|
|
if ( this.parent ) {
|
|
this.parent.sortDirty = true;
|
|
if ( this.shouldRenderDepth ) canvas.masks.depth._elevationDirty = true;
|
|
}
|
|
}
|
|
|
|
#elevation = 0;
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* A key which resolves ties amongst objects at the same elevation within the same layer.
|
|
* @type {number}
|
|
*/
|
|
get sort() {
|
|
return this.#sort;
|
|
}
|
|
|
|
set sort(value) {
|
|
if ( (typeof value !== "number") || Number.isNaN(value) ) {
|
|
throw new Error("PrimaryCanvasObject#sort must be a numeric value.");
|
|
}
|
|
if ( value === this.#sort ) return;
|
|
this.#sort = value;
|
|
if ( this.parent ) this.parent.sortDirty = true;
|
|
}
|
|
|
|
#sort = 0;
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* A key which resolves ties amongst objects at the same elevation of different layers.
|
|
* @type {number}
|
|
*/
|
|
get sortLayer() {
|
|
return this.#sortLayer;
|
|
}
|
|
|
|
set sortLayer(value) {
|
|
if ( (typeof value !== "number") || Number.isNaN(value) ) {
|
|
throw new Error("PrimaryCanvasObject#sortLayer must be a numeric value.");
|
|
}
|
|
if ( value === this.#sortLayer ) return;
|
|
this.#sortLayer = value;
|
|
if ( this.parent ) this.parent.sortDirty = true;
|
|
}
|
|
|
|
#sortLayer = 0;
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* A key which resolves ties amongst objects at the same elevation within the same layer and same sort.
|
|
* @type {number}
|
|
*/
|
|
get zIndex() {
|
|
return this._zIndex;
|
|
}
|
|
|
|
set zIndex(value) {
|
|
if ( (typeof value !== "number") || Number.isNaN(value) ) {
|
|
throw new Error("PrimaryCanvasObject#zIndex must be a numeric value.");
|
|
}
|
|
if ( value === this._zIndex ) return;
|
|
this._zIndex = value;
|
|
if ( this.parent ) this.parent.sortDirty = true;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/* PIXI Events */
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Event fired when this display object is added to a parent.
|
|
* @param {PIXI.Container} parent The new parent container.
|
|
* @protected
|
|
*/
|
|
_onAdded(parent) {
|
|
if ( parent !== canvas.primary ) {
|
|
throw new Error("PrimaryCanvasObject instances may only be direct children of the PrimaryCanvasGroup");
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Event fired when this display object is removed from its parent.
|
|
* @param {PIXI.Container} parent Parent from which the PCO is removed.
|
|
* @protected
|
|
*/
|
|
_onRemoved(parent) {
|
|
this.#updateQuadtree(true);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/* Canvas Transform & Quadtree */
|
|
/* -------------------------------------------- */
|
|
|
|
/** @inheritdoc */
|
|
updateCanvasTransform() {
|
|
super.updateCanvasTransform();
|
|
this.#updateQuadtree();
|
|
this.#updateDepth();
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/** @inheritdoc */
|
|
_onCanvasBoundsUpdate() {
|
|
super._onCanvasBoundsUpdate();
|
|
this.#quadtreeDirty = true;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Update the quadtree.
|
|
* @param {boolean} [remove=false] Remove the quadtree entry?
|
|
*/
|
|
#updateQuadtree(remove=false) {
|
|
if ( !this.#quadtreeDirty && !remove ) return;
|
|
this.#quadtreeDirty = false;
|
|
if ( !remove && (this.canvasBounds.width > 0) && (this.canvasBounds.height > 0) ) {
|
|
this.#quadtreeEntry ??= {r: this.canvasBounds, t: this};
|
|
canvas.primary.quadtree.update(this.#quadtreeEntry);
|
|
} else if ( this.#quadtreeEntry ) {
|
|
this.#quadtreeEntry = null;
|
|
canvas.primary.quadtree.remove(this);
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/* PCO Properties */
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Does this object render to the depth buffer?
|
|
* @type {boolean}
|
|
*/
|
|
get shouldRenderDepth() {
|
|
return this.#shouldRenderDepth;
|
|
}
|
|
|
|
/** @type {boolean} */
|
|
#shouldRenderDepth = false;
|
|
|
|
/* -------------------------------------------- */
|
|
/* Depth Rendering */
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Flag the depth as dirty if necessary.
|
|
*/
|
|
#updateDepth() {
|
|
const shouldRenderDepth = this._shouldRenderDepth();
|
|
if ( this.#shouldRenderDepth === shouldRenderDepth ) return;
|
|
this.#shouldRenderDepth = shouldRenderDepth;
|
|
canvas.masks.depth._elevationDirty = true;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Does this object render to the depth buffer?
|
|
* @returns {boolean}
|
|
* @protected
|
|
*/
|
|
_shouldRenderDepth() {
|
|
return false;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Render the depth of this object.
|
|
* @param {PIXI.Renderer} renderer
|
|
*/
|
|
renderDepthData(renderer) {}
|
|
|
|
/* -------------------------------------------- */
|
|
/* Deprecations and Compatibility */
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* @deprecated since v11
|
|
* @ignore
|
|
*/
|
|
renderOcclusion(renderer) {
|
|
const msg = "PrimaryCanvasObject#renderOcclusion is deprecated in favor of PrimaryCanvasObject#renderDepthData";
|
|
foundry.utils.logCompatibilityWarning(msg, {since: 11, until: 13});
|
|
this.renderDepthData(renderer);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* @deprecated since v12
|
|
* @ignore
|
|
*/
|
|
get document() {
|
|
foundry.utils.logCompatibilityWarning("PrimaryCanvasObject#document is deprecated.", {since: 12, until: 14});
|
|
if ( !(this.object instanceof PlaceableObject) ) return null;
|
|
return this.object.document || null;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* @deprecated since v12
|
|
* @ignore
|
|
*/
|
|
updateBounds() {
|
|
const msg = "PrimaryCanvasObject#updateBounds is deprecated and has no effect.";
|
|
foundry.utils.logCompatibilityWarning(msg, {since: 12, until: 14});
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* A mixin which decorates a DisplayObject with additional properties for canvas transforms and bounds.
|
|
* @category - Mixins
|
|
* @param {typeof PIXI.Container} DisplayObject The parent DisplayObject class being mixed
|
|
* @returns {typeof CanvasTransformMixin} A DisplayObject subclass mixed with CanvasTransformMixin features
|
|
* @mixin
|
|
*/
|
|
function CanvasTransformMixin(DisplayObject) {
|
|
return class CanvasTransformMixin extends DisplayObject {
|
|
constructor(...args) {
|
|
super(...args);
|
|
this.on("added", this.#resetCanvasTransformParentID);
|
|
this.on("removed", this.#resetCanvasTransformParentID);
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/* Properties */
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* The transform matrix from local space to canvas space.
|
|
* @type {PIXI.Matrix}
|
|
*/
|
|
canvasTransform = new PIXI.Matrix();
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* The update ID of canvas transform matrix.
|
|
* @type {number}
|
|
* @internal
|
|
*/
|
|
_canvasTransformID = -1;
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* The update ID of the local transform of this object.
|
|
* @type {number}
|
|
*/
|
|
#canvasTransformLocalID = -1;
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* The update ID of the canvas transform of the parent.
|
|
* @type {number}
|
|
*/
|
|
#canvasTransformParentID = -1;
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* The canvas bounds of this object.
|
|
* @type {PIXI.Rectangle}
|
|
*/
|
|
canvasBounds = new PIXI.Rectangle();
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* The canvas bounds of this object.
|
|
* @type {PIXI.Bounds}
|
|
* @protected
|
|
*/
|
|
_canvasBounds = new PIXI.Bounds();
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* The update ID of the canvas bounds.
|
|
* Increment to force recalculation.
|
|
* @type {number}
|
|
* @protected
|
|
*/
|
|
_canvasBoundsID = 0;
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Reset the parent ID of the canvas transform.
|
|
*/
|
|
#resetCanvasTransformParentID() {
|
|
this.#canvasTransformParentID = -1;
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
/* Methods */
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Calculate the canvas bounds of this object.
|
|
* @protected
|
|
*/
|
|
_calculateCanvasBounds() {}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Recalculate the canvas transform and bounds of this object and its children, if necessary.
|
|
*/
|
|
updateCanvasTransform() {
|
|
this.transform.updateLocalTransform();
|
|
|
|
// If the local transform or the parent canvas transform has changed,
|
|
// recalculate the canvas transform of this object
|
|
if ( (this.#canvasTransformLocalID !== this.transform._localID)
|
|
|| (this.#canvasTransformParentID !== (this.parent._canvasTransformID ?? 0)) ) {
|
|
this.#canvasTransformLocalID = this.transform._localID;
|
|
this.#canvasTransformParentID = this.parent._canvasTransformID ?? 0;
|
|
this._canvasTransformID++;
|
|
this.canvasTransform.copyFrom(this.transform.localTransform);
|
|
|
|
// Prepend the parent canvas transform matrix (if exists)
|
|
if ( this.parent.canvasTransform ) this.canvasTransform.prepend(this.parent.canvasTransform);
|
|
this._canvasBoundsID++;
|
|
this._onCanvasTransformUpdate();
|
|
}
|
|
|
|
// Recalculate the canvas bounds of this object if necessary
|
|
if ( this._canvasBounds.updateID !== this._canvasBoundsID ) {
|
|
this._canvasBounds.updateID = this._canvasBoundsID;
|
|
this._canvasBounds.clear();
|
|
this._calculateCanvasBounds();
|
|
|
|
// Set the width and height of the canvas bounds rectangle to 0
|
|
// if the bounds are empty. PIXI.Bounds#getRectangle does not
|
|
// change the rectangle passed to it if the bounds are empty:
|
|
// so we need to handle the empty case here.
|
|
if ( this._canvasBounds.isEmpty() ) {
|
|
this.canvasBounds.x = this.x;
|
|
this.canvasBounds.y = this.y;
|
|
this.canvasBounds.width = 0;
|
|
this.canvasBounds.height = 0;
|
|
}
|
|
|
|
// Update the canvas bounds rectangle
|
|
else this._canvasBounds.getRectangle(this.canvasBounds);
|
|
this._onCanvasBoundsUpdate();
|
|
}
|
|
|
|
// Recursively update child canvas transforms
|
|
const children = this.children;
|
|
for ( let i = 0, n = children.length; i < n; i++ ) {
|
|
children[i].updateCanvasTransform?.();
|
|
}
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Called when the canvas transform changed.
|
|
* @protected
|
|
*/
|
|
_onCanvasTransformUpdate() {}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Called when the canvas bounds changed.
|
|
* @protected
|
|
*/
|
|
_onCanvasBoundsUpdate() {}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
/**
|
|
* Is the given point in canvas space contained in this object?
|
|
* @param {PIXI.IPointData} point The point in canvas space.
|
|
* @returns {boolean}
|
|
*/
|
|
containsCanvasPoint(point) {
|
|
return false;
|
|
}
|
|
};
|
|
}
|