import {System} from '@/ecs/index.js'; import {intersects} from '@/util/math.js'; import SpatialHash from '@/util/spatial-hash.js'; export default class VisibleAabbs extends System { hash; deindex(entities) { super.deindex(entities); for (const id of entities) { this.hash.remove(id); } } static get priority() { return { after: 'IntegratePhysics', }; } reindex(entities) { for (const id of entities) { if (1 === id) { this.hash = new SpatialHash(this.ecs.get(1).AreaSize); } } super.reindex(entities); for (const id of entities) { this.updateHash(this.ecs.get(id)); } } updateHash(entity) { if (!entity.VisibleAabb) { return; } this.hash.update(entity.VisibleAabb, entity.id); } tick() { for (const entity of this.ecs.changed(['Position'])) { const {Position: {x, y}, Sprite, VisibleAabb} = entity; if (VisibleAabb) { let size = undefined; if (Sprite) { const frame = Sprite.animation ? Sprite.$$sourceJson.animations[Sprite.animation][Sprite.frame] : ''; size = Sprite.$$sourceJson.frames[frame].sourceSize; } /* v8 ignore next 3 */ if (!size) { throw new Error(`no size for aabb for entity ${entity.id}(${JSON.stringify(entity.toJSON(), null, 2)})`); } VisibleAabb.x0 = x - Sprite.anchor.x * size.w; VisibleAabb.x1 = x + (1 - Sprite.anchor.x) * size.w; VisibleAabb.y0 = y - Sprite.anchor.y * size.h; VisibleAabb.y1 = y + (1 - Sprite.anchor.y) * size.h; this.updateHash(entity); } } } within(query) { const {x0, x1, y0, y1} = query; const [cx0, cy0] = this.hash.chunkIndex(x0, y0); const [cx1, cy1] = this.hash.chunkIndex(x1, y1); const seen = {}; const within = new Set(); for (let cy = cy0; cy <= cy1; ++cy) { for (let cx = cx0; cx <= cx1; ++cx) { for (const id of this.hash.chunks[cx][cy]) { if (seen[id]) { continue; } seen[id] = true; const entity = this.ecs.get(id); if (intersects(query, entity.VisibleAabb)) { within.add(entity); } } } } return within; } }