@pixi-essentials/cull
Version:
Optimized recursive display-object culling for scene graphs
263 lines (221 loc) • 7.04 kB
JavaScript
/* eslint-disable */
/*!
* @pixi-essentials/cull - v1.1.0
* Compiled Sun, 05 Mar 2023 03:07:48 UTC
*
* @pixi-essentials/cull is licensed under the MIT License.
* http://www.opensource.org/licenses/mit-license
*
* Copyright 2019-2020, Shukant Pal <shukantpal@outlook.com>, All Rights Reserved
*/
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var math = require('@pixi/math');
const tempRect = new math.Rectangle();
/**
* The culling options for {@code Cull}.
*
* @ignore
* @public
*/
/**
* Provides a simple, configurable mechanism for culling a subtree of your scene graph.
*
* If your scene graph is not static, culling needs to be done before rendering. You
* can run it on the `prerender` event fired by the renderer.
*
* @public
*/
class Cull
{
/**
* @param options
* @param [options.recursive] - whether culling should be recursive
* @param [options.toggle='renderable'] - which property of display-object was be set to indicate
* its culling state. It should be one of `renderable`, `visible`.
*/
constructor(options = {})
{
this._recursive = typeof options.recursive === 'boolean' ? options.recursive : true;
this._toggle = options.toggle || 'visible';
this._targetList = new Set();
}
/**
* Adds a display-object to the culling list
*
* @param target - the display-object to be culled
* @return this
*/
add(target)
{
this._targetList.add(target);
return this;
}
/**
* Adds all the display-objects to the culling list
*
* @param targets - the display-objects to be culled
* @return this
*/
addAll(targets)
{
for (let i = 0, j = targets.length; i < j; i++)
{
this._targetList.add(targets[i]);
}
return this;
}
/**
* Removes the display-object from the culling list
*
* @param target - the display-object to be removed
* @return this
*/
remove(target)
{
this._targetList.delete(target);
return this;
}
/**
* Removes all the passed display-objects from the culling list
*
* @param targets - the display-objects to be removed
* @return this
*/
removeAll(targets)
{
for (let i = 0, j = targets.length; i < j; i++)
{
this._targetList.delete(targets[i]);
}
return this;
}
/**
* Clears the culling list
*
* @return this
*/
clear()
{
this._targetList.clear();
return this;
}
/**
* @param rect - the rectangle outside of which display-objects should be culled
* @param skipUpdate - whether to skip unculling, transform update, bounds calculation. It is
* highly recommended you enable this by calling _this.uncull()_ and _root.getBounds(false)_ manually
* before your render loop.
* @return this
*/
cull(rect, skipUpdate = false)
{
if (!skipUpdate)
{
this.uncull();
}
this._targetList.forEach((target) =>
{
if (!skipUpdate)
{
// Update transforms, bounds of display-objects in this target's subtree
target.getBounds(false, tempRect);
}
if (this._recursive)
{
this.cullRecursive(rect, target, skipUpdate);
}
else
{
// NOTE: If skipUpdate is false, then tempRect already contains the bounds of the target
if (skipUpdate)
{
target._bounds.getRectangle(rect);
}
target[this._toggle] = tempRect.right > rect.left
&& tempRect.left < rect.right
&& tempRect.bottom > rect.top
&& tempRect.top < rect.bottom;
}
});
return this;
}
/**
* Sets all display-objects to the unculled state.
*
* This happens regardless of whether the culling toggle was set by {@code this.cull} or manually. This
* is why it is recommended to one of `visible` or `renderable` for normal use and the other for culling.
*
* @return this
*/
uncull()
{
this._targetList.forEach((target) =>
{
if (this._recursive)
{
this.uncullRecursive(target);
}
else
{
target[this._toggle] = false;
}
});
return this;
}
/**
* Recursively culls the subtree of {@code displayObject}.
*
* @param rect - the visiblity rectangle
* @param displayObject - the root of the subtree to cull
* @param skipUpdate - whether to skip bounds calculation. However, transforms are expected to be updated by the caller.
*/
cullRecursive(rect, displayObject, skipUpdate)
{
// NOTE: getBounds can skipUpdate because updateTransform is invoked before culling.
const bounds = skipUpdate
? displayObject._bounds.getRectangle(tempRect)
: displayObject.getBounds(true, tempRect);
displayObject[this._toggle] = bounds.right > rect.left
&& bounds.left < rect.right
&& bounds.bottom > rect.top
&& bounds.top < rect.bottom;
const fullyVisible = bounds.left >= rect.left
&& bounds.top >= rect.top
&& bounds.right <= rect.right
&& bounds.bottom <= rect.bottom;
// Only cull children if this display-object is *not* fully-visible. It is expected that the bounds
// of children lie inside of its own. Hence, further culling is only required if the display-object
// intersects with the boundaries of "rect". Otherwise, if the object is fully outside/inside the
// screen, the children don't need to be evaluated as they are presumed to be unculled.
if (!fullyVisible
&& displayObject[this._toggle]
&& (displayObject ).children
&& (displayObject ).children.length)
{
const children = (displayObject ).children;
for (let i = 0, j = children.length; i < j; i++)
{
this.cullRecursive(rect, children[i]);
}
}
}
/**
* Recursively unculls the subtree of {@code displayObject}.
*
* @param displayObject
*/
uncullRecursive(displayObject)
{
displayObject[this._toggle] = true;
if ((displayObject ).children && (displayObject ).children.length)
{
const children = (displayObject ).children;
for (let i = 0, j = children.length; i < j; i++)
{
this.uncullRecursive(children[i]);
}
}
}
}
exports.Cull = Cull;
//# sourceMappingURL=cull.js.map