silphius/app/ecs-systems/colliders.js
2024-07-04 21:47:14 -05:00

151 lines
4.7 KiB
JavaScript

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 seen = {};
for (const entity of this.ecs.changed(['Position'])) {
if (seen[entity.id]) {
continue;
}
seen[entity.id] = true;
if (!entity.Collider) {
continue;
}
const {collidingWith: wasCollidingWith} = entity.Collider;
entity.Collider.collidingWith = {};
this.updateHash(entity);
for (const other of this.within(entity.Collider.aabb)) {
if (seen[other.id]) {
continue;
}
seen[other.id] = true;
if (!other.Collider) {
continue;
}
delete other.Collider.collidingWith[entity.id];
const intersections = entity.Collider.isCollidingWith(other.Collider);
if (intersections.length > 0) {
entity.Collider.collidingWith[other.id] = true;
other.Collider.collidingWith[entity.id] = true;
if (!wasCollidingWith[other.id]) {
if (entity.Collider.collisionStartScriptInstance) {
const script = entity.Collider.collisionStartScriptInstance.clone();
script.context.intersections = intersections;
script.context.other = other;
entity.Ticking.addTickingPromise(script.tickingPromise());
}
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.addTickingPromise(script.tickingPromise());
}
}
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;
}
}
}
}
for (const otherId in wasCollidingWith) {
if (!entity.Collider.collidingWith[otherId]) {
const other = this.ecs.get(otherId);
if (!other || !other.Collider) {
continue;
}
if (entity.Collider.collisionEndScriptInstance) {
const script = entity.Collider.collisionEndScriptInstance.clone();
script.context.other = other;
entity.Ticking.addTickingPromise(script.tickingPromise());
}
if (other.Collider.collisionEndScriptInstance) {
const script = other.Collider.collisionEndScriptInstance.clone();
script.context.other = entity;
other.Ticking.addTickingPromise(script.tickingPromise());
}
}
}
}
}
within(query) {
const within = new Set();
for (const id of this.hash.within(query)) {
within.add(this.ecs.get(id));
}
return within;
}
}