refactor: optimize visibleBoundingBox

This commit is contained in:
cha0s 2019-04-16 09:26:01 -05:00
parent 9ff9fca1e9
commit c9260a2cca
4 changed files with 78 additions and 15 deletions

View File

@ -107,7 +107,13 @@ export class EntityList extends decorate(Synchronized) {
visibleEntities(query) {
const quadTree = this._quadTree;
const entities = quadTree.search(query).map((node) => {
// Make sure they're actually in the query.
const nodes = quadTree.search(query).filter((node) => {
const [x, y, entity] = node.data;
const aabb = entity.visibleBoundingBox;
return Rectangle.intersects(query, aabb);
});
let entities = nodes.map((node) => {
return node.data[2];
});
// Hitting multiple points for each entity can return duplicates.

View File

@ -6,6 +6,7 @@ export class Listed extends Trait {
initialize() {
this._list = undefined;
this.quadTreeAabb = undefined;
this.quadTreeNodes = undefined;
}
@ -35,7 +36,13 @@ export class Listed extends Trait {
return;
}
const aabb = this.entity.visibleBoundingBox;
const points = Rectangle.toPoints(aabb);
if (Rectangle.isNull(aabb)) {
return;
}
// Expand the AABB so we don't have to update it every single tick.
const expandedAabb = Rectangle.expand(aabb, [32, 32]);
this.quadTreeAabb = expandedAabb;
const points = Rectangle.toPoints(expandedAabb);
this.quadTreeNodes = points.map((point) => [...point, this.entity]);
// Add points to quad tree.
for (const node of this.quadTreeNodes) {
@ -43,6 +50,14 @@ export class Listed extends Trait {
}
}
get doesNeedReset() {
if (!this.quadTreeAabb) {
return true;
}
const aabb = this.entity.visibleBoundingBox;
return !Rectangle.isInside(this.quadTreeAabb, aabb);
}
removeQuadTreeNodes() {
const list = this._list;
if (!list) {
@ -53,11 +68,15 @@ export class Listed extends Trait {
for (const node of this.quadTreeNodes) {
quadTree.remove(node);
}
this.quadTreeAabb = undefined;
this.quadTreeNodes = undefined;
}
}
resetQuadTreeNodes() {
if (!this.doesNeedReset) {
return;
}
this.removeQuadTreeNodes();
this.addQuadTreeNodes();
}

View File

@ -31,6 +31,7 @@ export class Visible extends decorate(Trait) {
this._container.isVisible = this.entity.isVisible;
}
this.trackPosition = this.params.get('trackPosition');
this._visibleBoundingBox = [0, 0, 0, 0];
}
destroy() {
@ -44,21 +45,24 @@ export class Visible extends decorate(Trait) {
}
get visibleBoundingBox() {
// Collect all bounding boxes.
const visibleBoundingBoxes = this.entity.invokeHookFlat(
'visibleBoundingBoxes'
);
if (0 === visibleBoundingBoxes.length) {
return [0, 0, 0, 0];
}
let unifiedBoundingBox = [0, 0, 0, 0];
for (const visibleBoundingBox of visibleBoundingBoxes) {
unifiedBoundingBox = Rectangle.united(
unifiedBoundingBox,
visibleBoundingBox,
if (Rectangle.isNull(this._visibleBoundingBox)) {
// Collect all bounding boxes.
const visibleBoundingBoxes = this.entity.invokeHookFlat(
'visibleBoundingBoxes'
);
if (0 === visibleBoundingBoxes.length) {
return [0, 0, 0, 0];
}
let unifiedBoundingBox = [0, 0, 0, 0];
for (const visibleBoundingBox of visibleBoundingBoxes) {
unifiedBoundingBox = Rectangle.united(
unifiedBoundingBox,
visibleBoundingBox,
);
}
this._visibleBoundingBox = unifiedBoundingBox;
}
return unifiedBoundingBox;
return this._visibleBoundingBox;
}
shouldSynchronizePosition() {
@ -80,6 +84,10 @@ export class Visible extends decorate(Trait) {
this.entity.container.visible = this.entity.isVisible;
},
visibleBoundingBoxesUpdated: () => {
this._visibleBoundingBox = [0, 0, 0, 0];
}
};
if (this.shouldSynchronizePosition()) {
listeners.traitAdded = (type) => {

View File

@ -6,6 +6,20 @@
import * as Vector from '../vector';
export function expand(rectangle, expandBy) {
const halfExpand = Vector.scale(expandBy, 0.5);
// return compose(
// Vector.sub(position(rectangle), halfExpand),
// Vector.add(size(rectangle), halfExpand),
// );
return [
rectangle[0] - halfExpand[0],
rectangle[1] - halfExpand[1],
rectangle[2] + expandBy[0],
rectangle[3] + expandBy[1],
];
}
// Check if a rectangle intersects with another rectangle.
//
// avocado> Rectangle.intersects [0, 0, 16, 16], [8, 8, 24, 24]
@ -29,6 +43,22 @@ export function intersects (l, r) {
return true;
}
export function isInside(outer, inner) {
if (inner[0] < outer[0]) {
return false;
}
if (inner[1] < outer[1]) {
return false;
}
if (inner[0] + inner[2] > outer[0] + outer[2]) {
return false;
}
if (inner[1] + inner[3] > outer[1] + outer[3]) {
return false;
}
return true;
}
// Check if a rectangle is touching a vector.
//
// avocado> Rectangle.isTouching [0, 0, 16, 16], [0, 0]