import {System} from '@/ecs/index.js'; import {intersects} from '@/util/math.js'; import SpatialHash from '@/util/spatial-hash.js'; export default class Colliders 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.Collider) { return; } this.hash.update(entity.Collider.aabb, entity.id); } tick() { const collisions = new Map(); for (const entity of this.ecs.changed(['Position'])) { this.updateHash(entity); } for (const entity of this.ecs.changed(['Position'])) { if (!entity.Collider) { continue; } collisions.set(entity, new Set()); for (const other of this.within(entity.Collider.aabb)) { if (entity === other) { continue; } if (!collisions.has(other)) { collisions.set(other, new Set()); } if (!other.Collider || collisions.get(other).has(entity)) { continue; } const intersections = entity.Collider.isCollidingWith(other.Collider); if (intersections.length > 0) { collisions.get(entity).add(other); if (!entity.Collider.collidingWith[other.id]) { entity.Collider.collidingWith[other.id] = true; other.Collider.collidingWith[entity.id] = true; if (entity.Collider.collisionStartScriptInstance) { const script = entity.Collider.collisionStartScriptInstance.clone(); script.context.intersections = intersections; script.context.other = other; entity.Ticking.add(script.ticker()); } if (other.Collider.collisionStartScriptInstance) { const script = other.Collider.collisionStartScriptInstance.clone(); script.context.intersections = intersections .map(([l, r]) => [r, l]); script.context.other = entity; other.Ticking.add(script.ticker()); } } for (const i in intersections) { const [body, otherBody] = intersections[i]; const {impassable} = otherBody; if (impassable) { const j = entity.Collider.bodies.indexOf(body); const oj = other.Collider.bodies.indexOf(otherBody); const aabb = entity.Collider.$$aabbs[j]; const otherAabb = other.Collider.aabbs[oj]; if (!intersects( { x0: aabb.x0 + entity.Position.lastX, x1: aabb.x1 + entity.Position.lastX, y0: aabb.y0 + entity.Position.y, y1: aabb.y1 + entity.Position.y, }, otherAabb, )) { entity.Position.x = entity.Position.lastX } else if (!intersects( { x0: aabb.x0 + entity.Position.x, x1: aabb.x1 + entity.Position.x, y0: aabb.y0 + entity.Position.lastY, y1: aabb.y1 + entity.Position.lastY, }, otherAabb, )) { entity.Position.y = entity.Position.lastY } else { entity.Position.x = entity.Position.lastX entity.Position.y = entity.Position.lastY } break; } } } else { if (entity.Collider.collidingWith[other.id]) { if (entity.Collider.collisionEndScriptInstance) { const script = entity.Collider.collisionEndScriptInstance.clone(); script.context.other = other; entity.Ticking.add(script.ticker()); } if (other.Collider.collisionEndScriptInstance) { const script = other.Collider.collisionEndScriptInstance.clone(); script.context.other = entity; other.Ticking.add(script.ticker()); } delete entity.Collider.collidingWith[other.id]; delete other.Collider.collidingWith[entity.id]; } } } } } within(query) { const within = new Set(); for (const id of this.hash.within(query)) { within.add(this.ecs.get(id)); } return within; } }