blaze-2d
Version:
A fast and simple WebGL 2 2D game engine written in TypeScript
119 lines • 4.27 kB
JavaScript
import { vec2 } from "gl-matrix";
import Logger from "../logger";
import Physics from "./physics";
/**
* Performs EPA collision response algorithm between 2 colliders.
*
* For a detailed explanation on how this algorithm works:
* - @see [dyn4j EPA Post](https://dyn4j.org/2010/05/epa-expanding-polytope-algorithm/)
* - @see [hamaluik EPA Post](https://blog.hamaluik.ca/posts/building-a-collision-engine-part-2-2d-penetration-vectors/)
* - @see [WinterDev EPA Explanation](https://blog.winter.dev/2020/epa-algorithm/)
* - @see [EPA Visualisation](https://winter.dev/lilapps/gjk/index.html)
*
* @param polytope The final simplex from the GJK algorithm between a and b.
* @param a The first collider
* @param c The second collider
* @returns A {@link EPAResult} object containing the results of the EPA algorithm
*/
export default function EPA(polytope, a, b) {
if (polytope.length < 3)
throw Logger.error("EPA", "Initial polytope must have atleast 3 vertices.");
// console.log([...polytope]);
// console.log(polytope);
const winding = calculateSimplexWinding(polytope);
for (let i = 0; i < Physics.G_CONF.EPA_MAX_ITERATIONS; i++) {
const edge = findClosestEdge(polytope, winding);
const support = a.supportPoint(b, edge.normal);
// calculate distance of support along edge.normal
const d = vec2.dot(support, edge.normal);
if (Math.abs(d - edge.dist) <= Physics.G_CONF.EPA_TOLERANCE) {
// if the difference is less than the tolerance then we can
// assume that we cannot expand the polytope any further and
// we have our solution
return {
normal: edge.normal,
depth: d + Physics.G_CONF.EPA_TOLERANCE,
};
}
else {
polytope.splice(edge.index, 0, support);
}
if (i === Physics.G_CONF.EPA_MAX_ITERATIONS - 1) {
// console.log("EPA: Iteration limit hit.");
// iteration limit hit
// return current most accurate values
return {
normal: edge.normal,
depth: d + Physics.G_CONF.EPA_TOLERANCE,
};
}
}
}
const ab = vec2.create();
const normal = vec2.create();
/**
* Finds the edge in the polytope which is closest to the origin.
*
* @param polytope The polytope to analyse
* @return The edge in the polytope which is closest to the origin
*/
function findClosestEdge(polytope, winding) {
const edge = {
normal,
dist: Infinity,
index: -1,
};
for (let i = 0; i < polytope.length; i++) {
// calculate next point index in polytope
const j = i + 1 >= polytope.length ? 0 : i + 1;
const a = polytope[i];
const b = polytope[j];
// edge vector
vec2.sub(ab, b, a);
// get normal of edge towards origin
if (winding === Winding.CLOCKWISE) {
normal[0] = ab[1];
normal[1] = -ab[0];
}
else {
normal[0] = -ab[1];
normal[1] = ab[0];
}
vec2.normalize(normal, normal);
// distance from edge to origin
const dist = vec2.dot(normal, a);
// update closest edge if the new edge is closer to the origin
if (dist < edge.dist) {
edge.normal = vec2.clone(normal);
edge.dist = dist;
edge.index = j;
}
}
return edge;
}
/**
* Indicates the winding of the vertices in a polygon.
*/
var Winding;
(function (Winding) {
Winding[Winding["CLOCKWISE"] = 0] = "CLOCKWISE";
Winding[Winding["COUNTER_CLOCKWISE"] = 1] = "COUNTER_CLOCKWISE";
})(Winding || (Winding = {}));
/**
* Calculates the winding order of a simplex (triangle).
*
* @param simplex The array of 3 vertices that make up the simplex
*/
function calculateSimplexWinding(simplex) {
const a = simplex[0];
const b = simplex[1];
const c = simplex[2];
const e0 = (b[0] - a[0]) * (b[1] + a[1]);
const e1 = (c[0] - b[0]) * (c[1] + b[1]);
const e2 = (a[0] - c[0]) * (a[1] + c[1]);
if (e0 + e1 + e2 >= 0)
return Winding.CLOCKWISE;
else
return Winding.COUNTER_CLOCKWISE;
}
//# sourceMappingURL=epa.js.map