112 lines
3.6 KiB
TypeScript
112 lines
3.6 KiB
TypeScript
// Copyright (c) 2012 Elf M. Sternberg
|
|
//
|
|
// Much of the code here I would never have understood if it hadn't
|
|
// been for the patient work of Caleb Helbling
|
|
// (http://www.propulsionjs.com/), as well as the Wikipedia pages for
|
|
// the Separating Axis Theorem. It took me a week to wrap my head
|
|
// around these ideas.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
// THE SOFTWARE.
|
|
|
|
class Vector {
|
|
x: number;
|
|
y: number;
|
|
|
|
constructor(x: number, y: number) {
|
|
this.x = x;
|
|
this.y = y;
|
|
}
|
|
|
|
add(v2: Vector) {
|
|
return new Vector(this.x + v2.x, this.y + v2.y);
|
|
}
|
|
|
|
scalar(s: number) {
|
|
return new Vector(this.x * s, this.y * s);
|
|
}
|
|
|
|
dot(v2: Vector) {
|
|
return this.x * v2.x + this.y * v2.y;
|
|
}
|
|
|
|
magnitude2() {
|
|
return this.x * this.x + this.y * this.y;
|
|
}
|
|
|
|
magnitude() {
|
|
return Math.sqrt(this.magnitude2());
|
|
}
|
|
|
|
normal() {
|
|
const mag = this.magnitude();
|
|
return new Vector(this.x / mag, this.y / mag);
|
|
}
|
|
|
|
leftNormal() {
|
|
return new Vector(-1 * this.y, this.x);
|
|
}
|
|
}
|
|
|
|
const vec = (x: number, y: number) => new Vector(x, y);
|
|
|
|
type Shape = Vector[];
|
|
|
|
type Projection = { min: number; max: number };
|
|
|
|
function colliding(shape1: Shape, shape2: Shape) {
|
|
const genAxes = (shape: Shape) => {
|
|
if (shape.length < 3) {
|
|
throw new Error("A Shape must be a polygon.");
|
|
}
|
|
|
|
const axis = (idx: number) => {
|
|
const p1 = shape[idx];
|
|
const p2 = shape[idx === shape.length - 1 ? 0 : idx + 1];
|
|
return vec(p1.x - p2.x, p1.y - p2.y)
|
|
.normal()
|
|
.leftNormal();
|
|
};
|
|
|
|
return shape.map((_: Vector, idx: number) => axis(idx));
|
|
};
|
|
|
|
const genProjection = (shape: Shape, axis: Vector) =>
|
|
shape.reduce(
|
|
({ min, max }: Projection, v: Vector) => {
|
|
const p = axis.dot(v);
|
|
return { min: p < min ? p : min, max: p > max ? p : max };
|
|
},
|
|
{ min: axis.dot(shape[0]), max: axis.dot(shape[0]) }
|
|
);
|
|
|
|
const axes = [...genAxes(shape1), ...genAxes(shape2)];
|
|
|
|
// The logic here may be wrong. I may have to invert the return on the whole expression.
|
|
return axes.some((axis) => {
|
|
const proj1 = genProjection(shape1, axis);
|
|
const proj2 = genProjection(shape2, axis);
|
|
return !(
|
|
(proj1.min >= proj2.min && proj1.min <= proj2.max) ||
|
|
(proj1.max >= proj2.min && proj1.max <= proj2.max) ||
|
|
(proj2.min >= proj1.min && proj2.min <= proj1.max) ||
|
|
(proj2.max >= proj1.min && proj2.max <= proj1.max)
|
|
);
|
|
});
|
|
}
|