UNPKG

check2d

Version:

Polygons, Ellipses, Circles, Boxes, Lines, Points. Ray-Casting, offsets, rotation, scaling, padding, groups.

214 lines (213 loc) 8.11 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.System = void 0; const model_1 = require("./model"); const utils_1 = require("./utils"); const intersect_1 = require("./intersect"); const optimized_1 = require("./optimized"); const base_system_1 = require("./base-system"); const line_1 = require("./bodies/line"); /** * collision system */ class System extends base_system_1.BaseSystem { constructor() { super(...arguments); /** * the last collision result */ this.response = new model_1.Response(); } /** * re-insert body into collision tree and update its bbox * every body can be part of only one system */ insert(body) { const insertResult = super.insert(body); // set system for later body.check2d.updateBody(body) body.system = this; return insertResult; } /** * separate (move away) bodies */ separate(callback = utils_1.returnTrue, response = this.response) { (0, optimized_1.forEach)(this.all(), (body) => { this.separateBody(body, callback, response); }); } /** * separate (move away) 1 body, with optional callback before collision */ separateBody(body, callback = utils_1.returnTrue, response = this.response) { if (body.isStatic && !body.isTrigger) { return; } const offsets = { x: 0, y: 0 }; const addOffsets = (collision) => { // when is not trigger and callback returns true it continues if (callback(collision) && !body.isTrigger && !collision.b.isTrigger) { offsets.x += collision.overlapV.x; offsets.y += collision.overlapV.y; } }; this.checkOne(body, addOffsets, response); if (offsets.x || offsets.y) { body.setPosition(body.x - offsets.x, body.y - offsets.y); } } /** * check one body collisions with callback */ checkOne(body, callback = utils_1.returnTrue, response = this.response) { // no need to check static body collision if (body.isStatic && !body.isTrigger) { return false; } const bodies = this.search(body); const checkCollision = (candidate) => { if (candidate !== body && this.checkCollision(body, candidate, response)) { return callback(response); } }; return (0, optimized_1.some)(bodies, checkCollision); } /** * check all bodies collisions in area with callback */ checkArea(area, callback = utils_1.returnTrue, response = this.response) { const checkOne = (body) => { return this.checkOne(body, callback, response); }; return (0, optimized_1.some)(this.search(area), checkOne); } /** * check all bodies collisions with callback */ checkAll(callback = utils_1.returnTrue, response = this.response) { const checkOne = (body) => { return this.checkOne(body, callback, response); }; return (0, optimized_1.some)(this.all(), checkOne); } /** * check do 2 objects collide */ checkCollision(bodyA, bodyB, response = this.response) { const { bbox: bboxA, padding: paddingA } = bodyA; const { bbox: bboxB, padding: paddingB } = bodyB; // assess the bodies real aabb without padding /* tslint:disable-next-line:cyclomatic-complexity */ if (!bboxA || !bboxB || !(0, utils_1.canInteract)(bodyA, bodyB) || ((paddingA || paddingB) && (0, utils_1.notIntersectAABB)(bboxA, bboxB))) { return false; } const sat = (0, utils_1.getSATTest)(bodyA, bodyB); // 99% of cases if (bodyA.isConvex && bodyB.isConvex) { // always first clear response response.clear(); return sat(bodyA, bodyB, response); } // more complex (non convex) cases const convexBodiesA = (0, intersect_1.ensureConvex)(bodyA); const convexBodiesB = (0, intersect_1.ensureConvex)(bodyB); let overlapX = 0; let overlapY = 0; let collided = false; (0, optimized_1.forEach)(convexBodiesA, (convexBodyA) => { (0, optimized_1.forEach)(convexBodiesB, (convexBodyB) => { // always first clear response response.clear(); if (sat(convexBodyA, convexBodyB, response)) { collided = true; overlapX += response.overlapV.x; overlapY += response.overlapV.y; } }); }); if (collided) { const vector = new model_1.SATVector(overlapX, overlapY); response.a = bodyA; response.b = bodyB; response.overlapV.x = overlapX; response.overlapV.y = overlapY; response.overlapN = vector.normalize(); response.overlap = vector.len(); response.aInB = (0, utils_1.checkAInB)(bodyA, bodyB); response.bInA = (0, utils_1.checkAInB)(bodyB, bodyA); } return collided; } /** * raycast to get collider of ray from start to end */ raycast(start, end, allow = utils_1.returnTrue) { let minDistance = Infinity; let result; if (!this.ray) { this.ray = new line_1.Line(start, end, { isTrigger: true }); } else { this.ray.start = start; this.ray.end = end; } this.insert(this.ray); this.checkOne(this.ray, ({ b: body }) => { if (!allow(body, this.ray)) { return false; } const points = body.typeGroup === model_1.BodyGroup.Circle ? (0, intersect_1.intersectLineCircle)(this.ray, body) : (0, intersect_1.intersectLinePolygon)(this.ray, body); (0, optimized_1.forEach)(points, (point) => { const pointDistance = (0, utils_1.distance)(start, point); if (pointDistance < minDistance) { minDistance = pointDistance; result = { point, body }; } }); }); this.remove(this.ray); return result; } /** * find collisions points between 2 bodies */ getCollisionPoints(a, b) { const collisionPoints = []; if (a.typeGroup === model_1.BodyGroup.Circle && b.typeGroup === model_1.BodyGroup.Circle) { collisionPoints.push(...(0, intersect_1.intersectCircleCircle)(a, b)); } if (a.typeGroup === model_1.BodyGroup.Circle && b.typeGroup !== model_1.BodyGroup.Circle) { for (let indexB = 0; indexB < b.calcPoints.length; indexB++) { const lineB = b.getEdge(indexB); collisionPoints.push(...(0, intersect_1.intersectLineCircle)(lineB, a)); } } if (a.typeGroup !== model_1.BodyGroup.Circle) { for (let indexA = 0; indexA < a.calcPoints.length; indexA++) { const lineA = a.getEdge(indexA); if (b.typeGroup === model_1.BodyGroup.Circle) { collisionPoints.push(...(0, intersect_1.intersectLineCircle)(lineA, b)); } else { for (let indexB = 0; indexB < b.calcPoints.length; indexB++) { const lineB = b.getEdge(indexB); const hit = (0, intersect_1.intersectLineLine)(lineA, lineB); if (hit) { collisionPoints.push(hit); } } } } } // unique return collisionPoints.filter(({ x, y }, index) => index === collisionPoints.findIndex((collisionPoint) => (0, utils_1.pointsEqual)(collisionPoint, { x, y }))); } } exports.System = System;