import { System } from 'detect-collisions';
import { isVector } from '../math/Utils.js';
import Vector from '../math/Vector.js';
/**
* wrapper for detect-collisions
* @see https://sinova.github.io/Collisions/
* @example
* import { Box } from "detect-collisions";
*
* let s = new CollisionSystem();
*
* //inserting a new game object
* let a = {pos: { x: 0, y: 0 }};
* a.physicsBody = new Box(a.pos, 10, 10);
* a.physicsBody.gameObj = a;
*
* s.insert(a.physicsBody);
*
* //handle and resolve collisions
* s.handleCollisions();
*/
class CollisionSystem {
constructor() {
this.collisions = new System();
}
/**
* @readonly
*/
get bodies() {
return this.collisions.all()
}
/**
* @param {Óbject} body detect-collisions body
* @returns {Body}
*/
insert(body) {
return this.collisions.insert(body);
}
/**
* update the physics bodies and handle collisions
*/
update() {
//update collision system
this.collisions.update();
//handle collisions
for (let index = 0; index < 5; index++) {
this.handleCollisions();
}
}
/**
* get all overlapping bodies
* @param {Body} b
* @param {boolean} [check_layers=false]
* @return {Body[]}
*/
getOverlapping(b, check_layers = false) {
let out = [];
let potentials = this.collisions.getPotentials(b);
for (const body2 of potentials) {
if (check_layers && b.collide_with && body2.collide_layers) {
let overlap = b.collide_with.filter(l => body2.collide_layers.includes(l));
if (!overlap.length) continue;
}
if (system.checkCollision(b, body2)) out.push(body2);
}
return out;
}
/**
* check collisions in the system and resolve them.
*
* This assumes the bodies have a `gameObj` property with a vector `pos`, a vector `v`
*/
handleCollisions() {
this.collisions.checkAll((result) => {
if (result.overlap == 0) return;
let body = result.a;
let body2 = result.b;
if (body.collide_with && body2.collide_layers) {
let overlap = body.collide_with.filter(l => body2.collide_layers.includes(l));
if (!overlap.length) return;
}
let obj = body.gameObj;
let obj2 = body2.gameObj;
if (body2.isStatic) {
body.setPosition(body.x - result.overlapV.x, body.y - result.overlapV.y);
} else if (body.isStatic) {
body2.setPosition(body2.x - result.overlapV.x, body2.y - result.overlapV.y);
} else {
//both object are non-static
let resulting_direction = result.overlapV;
//split it equally
let a = 0.5;
let b = 0.5;
// if the objects have velocities we can go into more detail
if ((obj.v instanceof Vector) && (obj2.v instanceof Vector)) {
let a_v = obj.v.length();
let b_v = obj2.v.length();
let v_sum_mag = a_v + b_v; //sum of the velocity length
if (v_sum_mag !== 0) {
//there was some velocity at the point of collision, split it proportionate
//not that the variables are flipped because
b = a_v / v_sum_mag;
a = b_v / v_sum_mag;
}
}
//the resulting correction vectors
let a_result = Vector.multiply(resulting_direction, a);
let b_result = Vector.multiply(resulting_direction, b);
body.setPosition(body.x - a_result.x, body.y - a_result.y);
body2.setPosition(body2.x + b_result.x, body2.y + b_result.y);
//TODO at this point we could calculate the resulting velocities based on weight and bounce
//https://gamedevelopment.tutsplus.com/tutorials/how-to-create-a-custom-2d-physics-engine-the-basics-and-impulse-resolution--gamedev-6331
}
//set pos for non static objects
if (!body.isStatic) {
obj.pos.x = body.x;
obj.pos.y = body.y;
}
if (!body2.isStatic) {
obj2.pos.x = body2.x;
obj2.pos.y = body2.y;
}
//set velocity to 0 for non static objects in the overlapping axis
if (!body.isStatic && isVector(obj.v)) {
if (result.overlapV.y != 0) obj.v.y = 0;
if (result.overlapV.x != 0) obj.v.x = 0;
}
if (!body2.isStatic && isVector(obj2.v)) {
if (result.overlapV.y != 0) obj2.v.y = 0;
if (result.overlapV.x != 0) obj2.v.x = 0;
}
});
}
/**
* Add a rectangle
*
* @param {Rectangle} r
* @param {Number[]} collide_layers Layers this body is a member of
* @param {Number[]} collide_with layers this body collides with
* @return {Body}
*/
addRectangle(r, collide_layers = [], collide_with = []) {
let s = this.collisions.createPolygon(r.x, r.y, [
[0, 0],
[r.w, 0],
[r.w, r.h],
[0, r.h]
]);
s.isStatic = true;
s.collide_layers = collide_layers;
s.collide_with = collide_with;
return s;
}
/**
* remove all bodies
*/
cleanup() {
for (const o in this.bodies) {
this.bvh.remove(this.bodies[o], false);
}
}
}
export default CollisionSystem;