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() { 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) {}

View File

@ -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;

View File

@ -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();
}
}; };
} }

View File

@ -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;