refactor: Listed handles quadtree nodes, 4 points for bounds

This commit is contained in:
cha0s 2019-03-21 20:07:32 -05:00
parent 006b9a742e
commit bb02bed6a5
4 changed files with 181 additions and 38 deletions

View File

@ -16,7 +16,6 @@ class EntityListBase {
constructor() {
this.entities_PRIVATE = {};
this.quadTree_PRIVATE = new QuadTree();
this.quadTreeData_PRIVATE = {};
this.state_PRIVATE = I.Map();
this.uuidMap_PRIVATE = {};
}
@ -59,17 +58,10 @@ class EntityListBase {
this.state_PRIVATE = this.state_PRIVATE.set(uuid, entity.state());
entity.addTrait('listed');
entity.list = this;
const onEntityPositionChanged = () => {
this.quadTree_PRIVATE.remove(this.quadTreePoint(entity));
this.quadTree_PRIVATE.add(this.recomputeQuadTreePoint(entity));
};
entity.on('positionChanged', onEntityPositionChanged);
entity.once('destroyed', () => {
this.removeEntity(entity);
entity.off('positionChanged', onEntityPositionChanged);
});
this.emit('entityAdded', entity);
this.quadTree_PRIVATE.add(this.recomputeQuadTreePoint(entity));
}
destroy() {
@ -86,17 +78,8 @@ class EntityListBase {
return this.uuidMap_PRIVATE[uuid];
}
quadTreePoint(entity) {
return this.quadTreeData_PRIVATE[entity.instanceUuid];
}
recomputeQuadTreePoint(entity) {
const point = this.quadTreeData_PRIVATE[entity.instanceUuid] = [
entity.x,
entity.y,
entity,
];
return point;
quadTree() {
return this.quadTree_PRIVATE;
}
recomputeState() {
@ -114,7 +97,6 @@ class EntityListBase {
if (entity.hasTrait('listed')) {
entity.removeTrait('listed');
}
this.quadTree_PRIVATE.remove(this.quadTreePoint(entity));
}
state() {
@ -131,12 +113,6 @@ class EntityListBase {
this.recomputeState();
}
within(rectangle) {
return this.quadTree_PRIVATE.search(rectangle).map((node) => {
return node.data[2];
});
}
}
export class EntityList extends decorate(EntityListBase) {}

View File

@ -93,6 +93,8 @@ class AnimatedBase extends Trait {
};
animation.on('indexChanged', this.animationListeners[key]);
});
// Bounding box update.
this.entity.emit('boundingBoxesUpdated');
});
}
@ -109,15 +111,11 @@ class AnimatedBase extends Trait {
// Store keyed animation views.
this.animationViews = {};
this.animationsPromise.then((animations) => {
const animationParams = this.params.get('animations');
animations.forEach(({animation, key}) => {
this.animationViews[key] = new AnimationView(animation);
// Calculate any offset.
const animationView = this.animationViews[key];
const size = Rectangle.size(animation.sourceRectangle);
const halfway = Vector.scale(size, -0.5);
const {offset} = animationParams[key];
animationView.position = Vector.add(halfway, offset);
animationView.position = this.viewPositionFor(key);
// Ensure animation is made visible upfront.
const isCurrentAnimation = key === this.entity.currentAnimation;
if (isCurrentAnimation) {
@ -127,6 +125,14 @@ class AnimatedBase extends Trait {
});
}
offsetFor(key) {
const animations = this.params.get('animations');
if (!animations[key] || !animations[key].offset) {
return [0, 0];
}
return animations[key].offset;
}
showAnimation(key) {
if (!this.animationViews) {
return;
@ -138,6 +144,34 @@ class AnimatedBase extends Trait {
this.entity.container.addChild(animationView);
}
viewPositionFor(key) {
const animation = this.animations[key];
if (!animation) {
return [0, 0];
}
const size = animation.frameSize;
const halfway = Vector.scale(size, -0.5);
const offset = this.offsetFor(key);
return Vector.add(halfway, offset);
}
hooks() {
return {
boundingBoxes: () => {
const key = this.entity.currentAnimation;
const animation = this.animations[key];
if (!animation) {
return [0, 0, 0, 0];
}
const viewPosition = this.viewPositionFor(key);
const position = Vector.add(this.entity.position, viewPosition);
return Rectangle.compose(position, animation.frameSize);
},
}
}
listeners() {
return {
currentAnimationChanged: (oldKey) => {
@ -146,6 +180,8 @@ class AnimatedBase extends Trait {
if (oldAnimation) {
oldAnimation.reset();
}
// Bounding box update.
this.entity.emit('boundingBoxesUpdated');
// Only client/graphics.
if (!this.animationViews) {
return;

View File

@ -7,6 +7,13 @@ export class Listed extends Trait {
initialize() {
this.lastNearby = I.Set();
this._list = undefined;
this.quadTreeNodes = [];
}
destroy() {
this.removeQuadTreeNodes();
delete this._list;
}
get list() {
@ -15,16 +22,95 @@ export class Listed extends Trait {
set list(list) {
this._list = list;
this.addQuadTreeNodes();
}
addQuadTreeNodes() {
const list = this.entity.list;
if (!list) {
return;
}
const quadTree = list.quadTree();
// Collect all bounding boxes.
const boundingBoxes = this.entity.invokeHookFlat('boundingBoxes');
if (0 === boundingBoxes.length) {
return;
}
let unifiedBoundingBox = [0, 0, 0, 0];
for (const boundingBox of boundingBoxes) {
unifiedBoundingBox = Rectangle.united(unifiedBoundingBox, boundingBox);
}
if (Rectangle.isNull(unifiedBoundingBox)) {
return;
}
// Break into 4 points.
const width = unifiedBoundingBox[2] - .01;
const height = unifiedBoundingBox[3] - .01;
const upperLeft = Rectangle.position(unifiedBoundingBox);
const upperRight = Vector.add(upperLeft, [width, 0]);
const lowerLeft = Vector.add(upperLeft, [0, height]);
const lowerRight = Vector.add(upperLeft, [width, height]);
this.quadTreeNodes = [
upperLeft,
upperRight,
lowerLeft,
lowerRight,
].map((point) => [...point, this.entity]);
// Add points to quad tree.
for (const node of this.quadTreeNodes) {
quadTree.add(node);
}
}
removeQuadTreeNodes() {
const list = this.entity.list;
if (!list) {
return;
}
const quadTree = list.quadTree();
if (this.quadTreeNodes) {
for (const node of this.quadTreeNodes) {
quadTree.remove(node);
}
}
this.quadTreeNodes = [];
}
listeners() {
return {
boundingBoxesUpdated: () => {
this.entity.resetQuadTreeNodes();
},
positionChanged: () => {
this.entity.resetQuadTreeNodes();
},
traitAdded: () => {
this.entity.resetQuadTreeNodes();
},
}
}
methods() {
return {
nearbyEntities: (size, position = this.entity.position) => {
const quadTree = this.entity.list.quadTree();
position = Vector.add(position, Vector.scale(size, -0.5));
return this._list.within(Rectangle.compose(position, size));
const query = Rectangle.compose(position, size);
return quadTree.search(query).map((node) => {
return node.data[2];
});
},
resetQuadTreeNodes: () => {
this.removeQuadTreeNodes();
this.addQuadTreeNodes();
}
};
}

View File

@ -1,6 +1,6 @@
import {compose} from '@avocado/core';
import {Image, Sprite} from '@avocado/graphics';
import {Vector} from '@avocado/math';
import {Rectangle, Vector} from '@avocado/math';
import {simpleState, Trait} from '../trait';
@ -26,6 +26,8 @@ class PicturedBase extends Trait {
initialize() {
this.sprites = undefined;
// Bounding box update.
this.entity.emit('boundingBoxesUpdated');
}
destroy() {
@ -64,11 +66,7 @@ class PicturedBase extends Trait {
Image.load(uri).then((image) => {
const sprite = this.sprites[key] = new Sprite(image);
// Calculate any offset.
const images = this.params.get('images');
const size = image.size;
const halfway = Vector.scale(size, -0.5);
const {offset} = images[key];
sprite.position = Vector.add(halfway, offset);
sprite.position = this.viewPositionFor(key);
// Set current image upfront.
const isCurrentImage = key === this.entity.currentImage;
if (isCurrentImage) {
@ -78,6 +76,14 @@ class PicturedBase extends Trait {
}
}
offsetFor(key) {
const images = this.params.get('images');
if (!images[key] || !images[key].offset) {
return [0, 0];
}
return images[key].offset;
}
showImage(key) {
if (!this.sprites) {
return;
@ -89,9 +95,48 @@ class PicturedBase extends Trait {
this.entity.container.addChild(sprite);
}
sizeFor(key) {
const images = this.params.get('images');
if (!images[key] || !images[key].size) {
return [0, 0];
}
return images[key].size;
}
viewPositionFor(key) {
const images = this.params.get('images');
const image = images[key];
if (!image) {
return [0, 0];
}
const size = this.sizeFor(key);
const halfway = Vector.scale(size, -0.5);
const offset = this.offsetFor(key);
return Vector.add(halfway, offset);
}
hooks() {
return {
boundingBoxes: () => {
const key = this.entity.currentImage;
const images = this.params.get('images');
const image = images[key];
if (!image) {
return [0, 0, 0, 0];
}
const viewPosition = this.viewPositionFor(key);
const position = Vector.add(this.entity.position, viewPosition);
return Rectangle.compose(position, this.sizeFor(key));
}
}
}
listeners() {
return {
currentImageChanged: (oldKey) => {
// Bounding box update.
this.entity.emit('boundingBoxesUpdated');
// Only client/graphics.
if (!this.sprites) {
return;