import Component from '@/ecs/component.js'; import {distance, intersects, transform} from '@/util/math.js'; import vector2d from './helpers/vector-2d'; export default class Collider extends Component { instanceFromSchema() { const {ecs} = this; return class ColliderInstance extends super.instanceFromSchema() { $$aabb = {x0: Infinity, x1: -Infinity, y0: Infinity, y1: -Infinity}; $$aabbs = []; collidingWith = {}; get aabb() { const {Position: {x: px, y: py}} = ecs.get(this.entity); return { x0: this.$$aabb.x0 + px, x1: this.$$aabb.x1 + px, y0: this.$$aabb.y0 + py, y1: this.$$aabb.y1 + py, }; } get aabbs() { const {Position: {x: px, y: py}} = ecs.get(this.entity); const aabbs = []; for (const aabb of this.$$aabbs) { aabbs.push({ x0: aabb.x0 + px, x1: aabb.x1 + px, y0: aabb.y0 + py, y1: aabb.y1 + py, }) } return aabbs; } closest(aabb) { const entity = ecs.get(this.entity); return Array.from(ecs.system('Colliders').within(aabb)) .filter((other) => other !== entity) .sort(({Position: l}, {Position: r}) => { return distance(entity.Position, l) > distance(entity.Position, r) ? -1 : 1; }); } destroy() { const entity = ecs.get(this.entity); for (const otherId in this.collidingWith) { const other = ecs.get(otherId); delete entity.Collider.collidingWith[other.id]; delete other.Collider.collidingWith[entity.id]; } } isCollidingWith(other) { const {aabb, aabbs} = this; const {aabb: otherAabb, aabbs: otherAabbs} = other; const intersections = []; if (!intersects(aabb, otherAabb)) { return intersections; } for (const i in aabbs) { const aabb = aabbs[i]; for (const j in otherAabbs) { const otherAabb = otherAabbs[j]; if (intersects(aabb, otherAabb)) { intersections.push([this.bodies[i], other.bodies[j]]); } } } return intersections; } isWithin(query) { const {aabb, aabbs} = this; if (!intersects(aabb, query)) { return false; } for (const aabb of aabbs) { if (intersects(aabb, query)) { return true; } } return false; } updateAabbs() { this.$$aabb = {x0: Infinity, x1: -Infinity, y0: Infinity, y1: -Infinity}; this.$$aabbs = []; const {bodies} = this; const {Direction: {direction = 0} = {}} = ecs.get(this.entity); for (const body of bodies) { let x0 = Infinity, x1 = -Infinity, y0 = Infinity, y1 = -Infinity; for (const point of transform(body.points, {rotation: direction})) { const {x, y} = point; if (x < x0) x0 = x; if (x < this.$$aabb.x0) this.$$aabb.x0 = x; if (x > x1) x1 = x; if (x > this.$$aabb.x1) this.$$aabb.x1 = x; if (y < y0) y0 = y; if (y < this.$$aabb.y0) this.$$aabb.y0 = y; if (y > y1) y1 = y; if (y > this.$$aabb.y1) this.$$aabb.y1 = y; } this.$$aabbs.push({ x0: x0 > x1 ? x1 : x0, x1: x0 > x1 ? x0 : x1, y0: y0 > y1 ? y1 : y0, y1: y0 > y1 ? y0 : y1, }); } } } } async load(instance) { instance.updateAabbs(); // heavy handed... if ('undefined' !== typeof window) { return; } instance.collisionEndScriptInstance = await this.ecs.readScript( instance.collisionEndScript, { ecs: this.ecs, entity: this.ecs.get(instance.entity), }, ); instance.collisionStartScriptInstance = await this.ecs.readScript( instance.collisionStartScript, { ecs: this.ecs, entity: this.ecs.get(instance.entity), }, ); } static properties = { bodies: { type: 'array', subtype: { type: 'object', properties: { impassable: {type: 'uint8'}, points: { type: 'array', subtype: vector2d('int16'), }, tags: { type: 'array', subtype: {type: 'string'}, }, }, }, }, collisionEndScript: {type: 'string'}, collisionStartScript: {type: 'string'}, }; }