refactor: Listed handles quadtree nodes, 4 points for bounds
This commit is contained in:
parent
006b9a742e
commit
bb02bed6a5
|
@ -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) {}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue
Block a user