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() {
|
constructor() {
|
||||||
this.entities_PRIVATE = {};
|
this.entities_PRIVATE = {};
|
||||||
this.quadTree_PRIVATE = new QuadTree();
|
this.quadTree_PRIVATE = new QuadTree();
|
||||||
this.quadTreeData_PRIVATE = {};
|
|
||||||
this.state_PRIVATE = I.Map();
|
this.state_PRIVATE = I.Map();
|
||||||
this.uuidMap_PRIVATE = {};
|
this.uuidMap_PRIVATE = {};
|
||||||
}
|
}
|
||||||
|
@ -59,17 +58,10 @@ class EntityListBase {
|
||||||
this.state_PRIVATE = this.state_PRIVATE.set(uuid, entity.state());
|
this.state_PRIVATE = this.state_PRIVATE.set(uuid, entity.state());
|
||||||
entity.addTrait('listed');
|
entity.addTrait('listed');
|
||||||
entity.list = this;
|
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', () => {
|
entity.once('destroyed', () => {
|
||||||
this.removeEntity(entity);
|
this.removeEntity(entity);
|
||||||
entity.off('positionChanged', onEntityPositionChanged);
|
|
||||||
});
|
});
|
||||||
this.emit('entityAdded', entity);
|
this.emit('entityAdded', entity);
|
||||||
this.quadTree_PRIVATE.add(this.recomputeQuadTreePoint(entity));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
|
@ -86,17 +78,8 @@ class EntityListBase {
|
||||||
return this.uuidMap_PRIVATE[uuid];
|
return this.uuidMap_PRIVATE[uuid];
|
||||||
}
|
}
|
||||||
|
|
||||||
quadTreePoint(entity) {
|
quadTree() {
|
||||||
return this.quadTreeData_PRIVATE[entity.instanceUuid];
|
return this.quadTree_PRIVATE;
|
||||||
}
|
|
||||||
|
|
||||||
recomputeQuadTreePoint(entity) {
|
|
||||||
const point = this.quadTreeData_PRIVATE[entity.instanceUuid] = [
|
|
||||||
entity.x,
|
|
||||||
entity.y,
|
|
||||||
entity,
|
|
||||||
];
|
|
||||||
return point;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
recomputeState() {
|
recomputeState() {
|
||||||
|
@ -114,7 +97,6 @@ class EntityListBase {
|
||||||
if (entity.hasTrait('listed')) {
|
if (entity.hasTrait('listed')) {
|
||||||
entity.removeTrait('listed');
|
entity.removeTrait('listed');
|
||||||
}
|
}
|
||||||
this.quadTree_PRIVATE.remove(this.quadTreePoint(entity));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
state() {
|
state() {
|
||||||
|
@ -131,12 +113,6 @@ class EntityListBase {
|
||||||
this.recomputeState();
|
this.recomputeState();
|
||||||
}
|
}
|
||||||
|
|
||||||
within(rectangle) {
|
|
||||||
return this.quadTree_PRIVATE.search(rectangle).map((node) => {
|
|
||||||
return node.data[2];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EntityList extends decorate(EntityListBase) {}
|
export class EntityList extends decorate(EntityListBase) {}
|
||||||
|
|
|
@ -93,6 +93,8 @@ class AnimatedBase extends Trait {
|
||||||
};
|
};
|
||||||
animation.on('indexChanged', this.animationListeners[key]);
|
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.
|
// Store keyed animation views.
|
||||||
this.animationViews = {};
|
this.animationViews = {};
|
||||||
this.animationsPromise.then((animations) => {
|
this.animationsPromise.then((animations) => {
|
||||||
const animationParams = this.params.get('animations');
|
|
||||||
animations.forEach(({animation, key}) => {
|
animations.forEach(({animation, key}) => {
|
||||||
this.animationViews[key] = new AnimationView(animation);
|
this.animationViews[key] = new AnimationView(animation);
|
||||||
// Calculate any offset.
|
// Calculate any offset.
|
||||||
const animationView = this.animationViews[key];
|
const animationView = this.animationViews[key];
|
||||||
const size = Rectangle.size(animation.sourceRectangle);
|
animationView.position = this.viewPositionFor(key);
|
||||||
const halfway = Vector.scale(size, -0.5);
|
|
||||||
const {offset} = animationParams[key];
|
|
||||||
animationView.position = Vector.add(halfway, offset);
|
|
||||||
// Ensure animation is made visible upfront.
|
// Ensure animation is made visible upfront.
|
||||||
const isCurrentAnimation = key === this.entity.currentAnimation;
|
const isCurrentAnimation = key === this.entity.currentAnimation;
|
||||||
if (isCurrentAnimation) {
|
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) {
|
showAnimation(key) {
|
||||||
if (!this.animationViews) {
|
if (!this.animationViews) {
|
||||||
return;
|
return;
|
||||||
|
@ -138,6 +144,34 @@ class AnimatedBase extends Trait {
|
||||||
this.entity.container.addChild(animationView);
|
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() {
|
listeners() {
|
||||||
return {
|
return {
|
||||||
currentAnimationChanged: (oldKey) => {
|
currentAnimationChanged: (oldKey) => {
|
||||||
|
@ -146,6 +180,8 @@ class AnimatedBase extends Trait {
|
||||||
if (oldAnimation) {
|
if (oldAnimation) {
|
||||||
oldAnimation.reset();
|
oldAnimation.reset();
|
||||||
}
|
}
|
||||||
|
// Bounding box update.
|
||||||
|
this.entity.emit('boundingBoxesUpdated');
|
||||||
// Only client/graphics.
|
// Only client/graphics.
|
||||||
if (!this.animationViews) {
|
if (!this.animationViews) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -7,6 +7,13 @@ export class Listed extends Trait {
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
this.lastNearby = I.Set();
|
this.lastNearby = I.Set();
|
||||||
|
this._list = undefined;
|
||||||
|
this.quadTreeNodes = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.removeQuadTreeNodes();
|
||||||
|
delete this._list;
|
||||||
}
|
}
|
||||||
|
|
||||||
get list() {
|
get list() {
|
||||||
|
@ -15,16 +22,95 @@ export class Listed extends Trait {
|
||||||
|
|
||||||
set list(list) {
|
set list(list) {
|
||||||
this._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() {
|
methods() {
|
||||||
return {
|
return {
|
||||||
|
|
||||||
nearbyEntities: (size, position = this.entity.position) => {
|
nearbyEntities: (size, position = this.entity.position) => {
|
||||||
|
const quadTree = this.entity.list.quadTree();
|
||||||
position = Vector.add(position, Vector.scale(size, -0.5));
|
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 {compose} from '@avocado/core';
|
||||||
import {Image, Sprite} from '@avocado/graphics';
|
import {Image, Sprite} from '@avocado/graphics';
|
||||||
import {Vector} from '@avocado/math';
|
import {Rectangle, Vector} from '@avocado/math';
|
||||||
|
|
||||||
import {simpleState, Trait} from '../trait';
|
import {simpleState, Trait} from '../trait';
|
||||||
|
|
||||||
|
@ -26,6 +26,8 @@ class PicturedBase extends Trait {
|
||||||
|
|
||||||
initialize() {
|
initialize() {
|
||||||
this.sprites = undefined;
|
this.sprites = undefined;
|
||||||
|
// Bounding box update.
|
||||||
|
this.entity.emit('boundingBoxesUpdated');
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
|
@ -64,11 +66,7 @@ class PicturedBase extends Trait {
|
||||||
Image.load(uri).then((image) => {
|
Image.load(uri).then((image) => {
|
||||||
const sprite = this.sprites[key] = new Sprite(image);
|
const sprite = this.sprites[key] = new Sprite(image);
|
||||||
// Calculate any offset.
|
// Calculate any offset.
|
||||||
const images = this.params.get('images');
|
sprite.position = this.viewPositionFor(key);
|
||||||
const size = image.size;
|
|
||||||
const halfway = Vector.scale(size, -0.5);
|
|
||||||
const {offset} = images[key];
|
|
||||||
sprite.position = Vector.add(halfway, offset);
|
|
||||||
// Set current image upfront.
|
// Set current image upfront.
|
||||||
const isCurrentImage = key === this.entity.currentImage;
|
const isCurrentImage = key === this.entity.currentImage;
|
||||||
if (isCurrentImage) {
|
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) {
|
showImage(key) {
|
||||||
if (!this.sprites) {
|
if (!this.sprites) {
|
||||||
return;
|
return;
|
||||||
|
@ -89,9 +95,48 @@ class PicturedBase extends Trait {
|
||||||
this.entity.container.addChild(sprite);
|
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() {
|
listeners() {
|
||||||
return {
|
return {
|
||||||
currentImageChanged: (oldKey) => {
|
currentImageChanged: (oldKey) => {
|
||||||
|
// Bounding box update.
|
||||||
|
this.entity.emit('boundingBoxesUpdated');
|
||||||
// Only client/graphics.
|
// Only client/graphics.
|
||||||
if (!this.sprites) {
|
if (!this.sprites) {
|
||||||
return;
|
return;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user