sat circle collision

This commit is contained in:
Emmaline Autumn 2023-11-03 05:46:50 -06:00
parent 62b13e49e7
commit 9d8a0fc7d2
4 changed files with 141 additions and 168 deletions

127
bundle.js
View File

@ -2,9 +2,6 @@
// deno-lint-ignore-file // deno-lint-ignore-file
// This code was bundled using `deno bundle` and it's not recommended to edit it manually // This code was bundled using `deno bundle` and it's not recommended to edit it manually
const axisAlignedContains = (aa1, aa2)=>{
return aa1.x < aa2.x && aa1.y < aa2.y && aa1.x + aa1.w > aa2.x + aa2.w && aa1.y + aa1.h > aa2.y + aa2.h;
};
const Constants = { const Constants = {
TWO_PI: Math.PI * 2 TWO_PI: Math.PI * 2
}; };
@ -74,7 +71,7 @@ class Vector {
if (arguments.length === 1 && typeof v !== "number") { if (arguments.length === 1 && typeof v !== "number") {
this.x -= v.x; this.x -= v.x;
this.y -= v.y; this.y -= v.y;
this.z -= v.z; this.z -= v.z || 0;
} else if (arguments.length === 2) { } else if (arguments.length === 2) {
this.x -= v; this.x -= v;
this.y -= y ?? 0; this.y -= y ?? 0;
@ -118,7 +115,7 @@ class Vector {
return this; return this;
} }
dist(v) { dist(v) {
const dx = this.x - v.x, dy = this.y - v.y, dz = this.z - v.z; const dx = this.x - v.x, dy = this.y - v.y, dz = this.z - (v.z || 0);
return Math.sqrt(dx * dx + dy * dy + dz * dz); return Math.sqrt(dx * dx + dy * dy + dz * dz);
} }
dot(v, y, z) { dot(v, y, z) {
@ -293,86 +290,45 @@ class OriginVector extends Vector {
return new OriginVector(origin, v); return new OriginVector(origin, v);
} }
} }
const satCollision = (s1, s2)=>{ const satCollisionCircle = (s, c)=>{
const shape1 = s1.points.map((p)=>new Vector(p).add(s1.center)); const shape = s.points.map((p)=>new Vector(p).add(s.center));
const shape2 = s2.points.map((p)=>new Vector(p).add(s2.center)); if (shape.length < 2) {
if (shape1.length < 2 || shape2.length < 2) { throw "Insufficient shape data in satCollisionCircle";
throw "Insufficient shape data in satCollision";
} }
new Vector(s1.center).sub(s2.center); for(let i = 0; i < shape.length; i++){
for(let i = 0; i < shape1.length; i++){ const axis = shape[i].normal(shape.at(i - 1));
const axis = shape1[i].normal(shape1.at(i - 1)); let [_, p1minDot] = Vector.vectorProjectionAndDot(shape[0], axis);
let [p1min, p1minDot] = Vector.vectorProjectionAndDot(shape1[0], axis); let p1maxDot = p1minDot;
let [p1max, p1maxDot] = [ for (const point of shape){
p1min, const [_, dot] = Vector.vectorProjectionAndDot(point, axis);
p1minDot
];
for (const point of shape1){
const [projected, dot] = Vector.vectorProjectionAndDot(point, axis);
p1min = dot < p1minDot ? projected : p1min;
p1minDot = Math.min(dot, p1minDot); p1minDot = Math.min(dot, p1minDot);
p1max = dot > p1maxDot ? projected : p1max;
p1maxDot = Math.max(dot, p1maxDot); p1maxDot = Math.max(dot, p1maxDot);
} }
let [p2min, p2minDot] = Vector.vectorProjectionAndDot(shape2[0], axis); const [__, circleDot] = Vector.vectorProjectionAndDot(new Vector(c.center), axis);
let [p2max, p2maxDot] = [ const p2minDot = circleDot - c.radius;
p2min, const p2maxDot = circleDot + c.radius;
p2minDot
];
for (const point of shape2){
const [projected, dot] = Vector.vectorProjectionAndDot(point, axis);
p2min = dot < p2minDot ? projected : p2min;
p2minDot = Math.min(dot, p2minDot);
p2max = dot > p2maxDot ? projected : p2max;
p2maxDot = Math.max(dot, p2maxDot);
}
if (p1minDot - p2maxDot > 0 || p2minDot - p1maxDot > 0) { if (p1minDot - p2maxDot > 0 || p2minDot - p1maxDot > 0) {
return false; return false;
} }
[
axis,
p1max,
p1min,
p2max,
p2min
];
} }
for(let i = 0; i < shape2.length; i++){ const center = new Vector(c.center);
const axis = shape2[i].normal(shape2.at(i - 1)); let nearest = shape[0];
let [p1min, p1minDot] = Vector.vectorProjectionAndDot(shape2[0], axis); for (const p of shape){
let [p1max, p1maxDot] = [ if (center.dist(p) < center.dist(nearest)) nearest = p;
p1min, }
p1minDot const axis = center.sub(nearest);
]; let [_, p1minDot] = Vector.vectorProjectionAndDot(shape[0], axis);
for (const point of shape2){ let p1maxDot = p1minDot;
const [projected, dot] = Vector.vectorProjectionAndDot(point, axis); for (const point of shape){
p1min = dot < p1minDot ? projected : p1min; const [_, dot] = Vector.vectorProjectionAndDot(point, axis);
p1minDot = Math.min(dot, p1minDot); p1minDot = Math.min(dot, p1minDot);
p1max = dot > p1maxDot ? projected : p1max; p1maxDot = Math.max(dot, p1maxDot);
p1maxDot = Math.max(dot, p1maxDot); }
} const [__, circleDot] = Vector.vectorProjectionAndDot(new Vector(c.center), axis);
let [p2min, p2minDot] = Vector.vectorProjectionAndDot(shape1[0], axis); const p2minDot = circleDot - c.radius;
let [p2max, p2maxDot] = [ const p2maxDot = circleDot + c.radius;
p2min, if (p1minDot - p2maxDot > 0 || p2minDot - p1maxDot > 0) {
p2minDot return false;
];
for (const point of shape1){
const [projected, dot] = Vector.vectorProjectionAndDot(point, axis);
p2min = dot < p2minDot ? projected : p2min;
p2minDot = Math.min(dot, p2minDot);
p2max = dot > p2maxDot ? projected : p2max;
p2maxDot = Math.max(dot, p2maxDot);
}
if (p1minDot - p2maxDot > 0 || p2minDot - p1maxDot > 0) {
return false;
}
[
axis,
p1max,
p1min,
p2max,
p2min
];
} }
return true; return true;
}; };
@ -1048,7 +1004,16 @@ const poly = new Polygon([
y: 25 y: 25
} }
]); ]);
const poly2 = Polygon.createPolygon(5, 75); const poly2 = new Polygon([
{
x: -250,
y: -25
},
{
x: 25,
y: 250
}
]);
poly2.center = p.copy().add(100, 100); poly2.center = p.copy().add(100, 100);
poly.center.add(p); poly.center.add(p);
doodler.createLayer((c)=>{ doodler.createLayer((c)=>{
@ -1059,7 +1024,7 @@ doodler.createLayer((c)=>{
}); });
} }
} }
const color = satCollision(poly, poly2) ? "red" : "aqua"; const color = satCollisionCircle(poly2, poly.circularHitbox) ? "red" : "aqua";
poly.draw(color); poly.draw(color);
poly2.draw(color); poly2.draw(color);
const [gamepad] = navigator.getGamepads(); const [gamepad] = navigator.getGamepads();
@ -1068,10 +1033,6 @@ doodler.createLayer((c)=>{
const leftY = gamepad.axes[1]; const leftY = gamepad.axes[1];
const rightX = gamepad.axes[2]; const rightX = gamepad.axes[2];
const rightY = gamepad.axes[3]; const rightY = gamepad.axes[3];
if (axisAlignedContains(poly2.aaHitbox, poly.aaHitbox)) {
poly.center.add(new Vector(Math.min(Math.max(rightX - 0.05, 0), rightX + 0.05), Math.min(Math.max(rightY - 0.05, 0), rightY + 0.05)).mult(10));
poly2.center.add(new Vector(Math.min(Math.max(leftX - 0.05, 0), leftX + 0.05), Math.min(Math.max(leftY - 0.05, 0), leftY + 0.05)).mult(10));
}
poly.center.add(new Vector(Math.min(Math.max(leftX - 0.05, 0), leftX + 0.05), Math.min(Math.max(leftY - 0.05, 0), leftY + 0.05)).mult(10)); poly.center.add(new Vector(Math.min(Math.max(leftX - 0.05, 0), leftX + 0.05), Math.min(Math.max(leftY - 0.05, 0), leftY + 0.05)).mult(10));
poly2.center.add(new Vector(Math.min(Math.max(rightX - 0.05, 0), rightX + 0.05), Math.min(Math.max(rightY - 0.05, 0), rightY + 0.05)).mult(10)); poly2.center.add(new Vector(Math.min(Math.max(rightX - 0.05, 0), rightX + 0.05), Math.min(Math.max(rightY - 0.05, 0), rightY + 0.05)).mult(10));
} }

View File

@ -1,5 +1,6 @@
import { Polygon } from "../geometry/polygon.ts"; import { Polygon } from "../geometry/polygon.ts";
import { Point, Vector } from "../geometry/vector.ts"; import { Point, Vector } from "../geometry/vector.ts";
import { CircleLike } from "./circular.ts";
export const satCollision = (s1: Polygon, s2: Polygon) => { export const satCollision = (s1: Polygon, s2: Polygon) => {
const shape1 = s1.points.map((p) => new Vector(p).add(s1.center)); const shape1 = s1.points.map((p) => new Vector(p).add(s1.center));
@ -8,98 +9,105 @@ export const satCollision = (s1: Polygon, s2: Polygon) => {
if (shape1.length < 2 || shape2.length < 2) { if (shape1.length < 2 || shape2.length < 2) {
throw "Insufficient shape data in satCollision"; throw "Insufficient shape data in satCollision";
} }
const offset = new Vector(s1.center).sub(s2.center);
// Take one side of the polygon and find the normal
let last: Vector[] = [];
for (let i = 0; i < shape1.length; i++) { for (let i = 0; i < shape1.length; i++) {
const axis = shape1[i].normal(shape1.at(i - 1)!); const axis = shape1[i].normal(shape1.at(i - 1)!);
// axis.draw(); let [_, p1minDot] = Vector.vectorProjectionAndDot(shape1[0], axis);
let [p1min, p1minDot] = Vector.vectorProjectionAndDot(shape1[0], axis); let p1maxDot = p1minDot;
let [p1max, p1maxDot] = [p1min, p1minDot];
for (const point of shape1) { for (const point of shape1) {
const [projected, dot] = Vector.vectorProjectionAndDot(point, axis); const [_, dot] = Vector.vectorProjectionAndDot(point, axis);
// projected.drawDot();
p1min = dot < p1minDot ? projected : p1min;
p1minDot = Math.min(dot, p1minDot); p1minDot = Math.min(dot, p1minDot);
p1max = dot > p1maxDot ? projected : p1max;
p1maxDot = Math.max(dot, p1maxDot); p1maxDot = Math.max(dot, p1maxDot);
} }
let [p2min, p2minDot] = Vector.vectorProjectionAndDot(shape2[0], axis); let [__, p2minDot] = Vector.vectorProjectionAndDot(shape2[0], axis);
let [p2max, p2maxDot] = [p2min, p2minDot]; let p2maxDot = p2minDot;
for (const point of shape2) { for (const point of shape2) {
const [projected, dot] = Vector.vectorProjectionAndDot(point, axis); const [_, dot] = Vector.vectorProjectionAndDot(point, axis);
// projected.drawDot();
p2min = dot < p2minDot ? projected : p2min;
p2minDot = Math.min(dot, p2minDot); p2minDot = Math.min(dot, p2minDot);
p2max = dot > p2maxDot ? projected : p2max;
p2maxDot = Math.max(dot, p2maxDot); p2maxDot = Math.max(dot, p2maxDot);
} }
// const scale = axis.dot(offset);
// p1max += scale;
// p1min += scale;
// let p2min = axis.dot(shape1[0]);
// let p2max = p2min;
// for (const point of shape2) {
// const dot = point.dot(axis);
// p2max = Math.max(p2max, dot);
// p2min = Math.min(p2min, dot);
// Vector.vectorProjection(point, axis).drawDot();
// }
if (p1minDot - p2maxDot > 0 || p2minDot - p1maxDot > 0) { if (p1minDot - p2maxDot > 0 || p2minDot - p1maxDot > 0) {
// axis.draw();
// p1max.drawDot("blue");
// p1min.drawDot("lime");
// p2max.drawDot("blue");
// p2min.drawDot("lime");
return false; return false;
} }
last = [axis, p1max, p1min, p2max, p2min];
} }
for (let i = 0; i < shape2.length; i++) { for (let i = 0; i < shape2.length; i++) {
const axis = shape2[i].normal(shape2.at(i - 1)!); const axis = shape2[i].normal(shape2.at(i - 1)!);
// axis.draw(); let [_, p1minDot] = Vector.vectorProjectionAndDot(shape2[0], axis);
let [p1min, p1minDot] = Vector.vectorProjectionAndDot(shape2[0], axis); let p1maxDot = p1minDot;
let [p1max, p1maxDot] = [p1min, p1minDot];
for (const point of shape2) { for (const point of shape2) {
const [projected, dot] = Vector.vectorProjectionAndDot(point, axis); const [_, dot] = Vector.vectorProjectionAndDot(point, axis);
// projected.drawDot(); // p1min = dot < p1minDot ? projected : p1min;
p1min = dot < p1minDot ? projected : p1min;
p1minDot = Math.min(dot, p1minDot); p1minDot = Math.min(dot, p1minDot);
p1max = dot > p1maxDot ? projected : p1max; // p1max = dot > p1maxDot ? projected : p1max;
p1maxDot = Math.max(dot, p1maxDot); p1maxDot = Math.max(dot, p1maxDot);
} }
let [p2min, p2minDot] = Vector.vectorProjectionAndDot(shape1[0], axis); let [__, p2minDot] = Vector.vectorProjectionAndDot(shape1[0], axis);
let [p2max, p2maxDot] = [p2min, p2minDot]; let p2maxDot = p2minDot;
for (const point of shape1) { for (const point of shape1) {
const [projected, dot] = Vector.vectorProjectionAndDot(point, axis); const [_, dot] = Vector.vectorProjectionAndDot(point, axis);
// projected.drawDot(); // p2min = dot < p2minDot ? projected : p2min;
p2min = dot < p2minDot ? projected : p2min;
p2minDot = Math.min(dot, p2minDot); p2minDot = Math.min(dot, p2minDot);
p2max = dot > p2maxDot ? projected : p2max; // p2max = dot > p2maxDot ? projected : p2max;
p2maxDot = Math.max(dot, p2maxDot); p2maxDot = Math.max(dot, p2maxDot);
} }
if (p1minDot - p2maxDot > 0 || p2minDot - p1maxDot > 0) { if (p1minDot - p2maxDot > 0 || p2minDot - p1maxDot > 0) {
// axis.draw();
// p1max.drawDot("blue");
// p1min.drawDot("lime");
// p2max.drawDot("blue");
// p2min.drawDot("lime");
return false; return false;
} }
last = [axis, p1max, p1min, p2max, p2min];
} }
// for (const [i, l] of last.entries()) { return true;
// if (i === 0) { };
// l.draw();
// } else { export const satCollisionCircle = (s: Polygon, c: CircleLike) => {
// l.drawDot(); const shape = s.points.map((p) => new Vector(p).add(s.center));
// }
// } if (shape.length < 2) {
throw "Insufficient shape data in satCollisionCircle";
}
for (let i = 0; i < shape.length; i++) {
const axis = shape[i].normal(shape.at(i - 1)!);
let [_, p1minDot] = Vector.vectorProjectionAndDot(shape[0], axis);
let p1maxDot = p1minDot;
for (const point of shape) {
const [_, dot] = Vector.vectorProjectionAndDot(point, axis);
p1minDot = Math.min(dot, p1minDot);
p1maxDot = Math.max(dot, p1maxDot);
}
const [__, circleDot] = Vector.vectorProjectionAndDot(
new Vector(c.center),
axis,
);
const p2minDot = circleDot - c.radius;
const p2maxDot = circleDot + c.radius;
if (p1minDot - p2maxDot > 0 || p2minDot - p1maxDot > 0) {
return false;
}
}
const center = new Vector(c.center);
let nearest = shape[0];
for (const p of shape) {
if (center.dist(p) < center.dist(nearest)) nearest = p;
}
const axis = center.sub(nearest);
let [_, p1minDot] = Vector.vectorProjectionAndDot(shape[0], axis);
let p1maxDot = p1minDot;
for (const point of shape) {
const [_, dot] = Vector.vectorProjectionAndDot(point, axis);
p1minDot = Math.min(dot, p1minDot);
p1maxDot = Math.max(dot, p1maxDot);
}
const [__, circleDot] = Vector.vectorProjectionAndDot(
new Vector(c.center),
axis,
);
const p2minDot = circleDot - c.radius;
const p2maxDot = circleDot + c.radius;
if (p1minDot - p2maxDot > 0 || p2minDot - p1maxDot > 0) {
return false;
}
return true; return true;
}; };

View File

@ -89,11 +89,12 @@ export class Vector implements Point {
sub(x: number, y: number, z: number): Vector; sub(x: number, y: number, z: number): Vector;
sub(x: number, y: number): Vector; sub(x: number, y: number): Vector;
sub(v: Vector): Vector; sub(v: Vector): Vector;
sub(v: Vector | number, y?: number, z?: number) { sub(v: Point): Vector;
sub(v: Vector | Point | number, y?: number, z?: number) {
if (arguments.length === 1 && typeof v !== "number") { if (arguments.length === 1 && typeof v !== "number") {
this.x -= v.x; this.x -= v.x;
this.y -= v.y; this.y -= v.y;
this.z -= v.z; this.z -= v.z || 0;
} else if (arguments.length === 2) { } else if (arguments.length === 2) {
// 2D Vector // 2D Vector
this.x -= v as number; this.x -= v as number;
@ -137,10 +138,10 @@ export class Vector implements Point {
this.y = s * prev_x + c * this.y; this.y = s * prev_x + c * this.y;
return this; return this;
} }
dist(v: Vector) { dist(v: Vector | Point) {
const dx = this.x - v.x, const dx = this.x - v.x,
dy = this.y - v.y, dy = this.y - v.y,
dz = this.z - v.z; dz = this.z - (v.z || 0);
return Math.sqrt(dx * dx + dy * dy + dz * dz); return Math.sqrt(dx * dx + dy * dy + dz * dz);
} }
dot(x: number, y: number, z: number): number; dot(x: number, y: number, z: number): number;

41
main.ts
View File

@ -2,7 +2,7 @@
import { axisAlignedCollision, axisAlignedContains } from "./collision/aa.ts"; import { axisAlignedCollision, axisAlignedContains } from "./collision/aa.ts";
import { circularCollision } from "./collision/circular.ts"; import { circularCollision } from "./collision/circular.ts";
import { satCollision } from "./collision/sat.ts"; import { satCollision, satCollisionCircle } from "./collision/sat.ts";
import { Polygon } from "./geometry/polygon.ts"; import { Polygon } from "./geometry/polygon.ts";
import { initializeDoodler, Vector } from "./mod.ts"; import { initializeDoodler, Vector } from "./mod.ts";
// import { ZoomableDoodler } from "./zoomableCanvas.ts"; // import { ZoomableDoodler } from "./zoomableCanvas.ts";
@ -39,9 +39,12 @@ const poly = new Polygon([
]); ]);
// poly.center = p.copy(); // poly.center = p.copy();
const poly2 = Polygon.createPolygon(5, 75); const poly2 = new Polygon([
poly2.center = p.copy().add(100, 100); { x: -250, y: -25 },
{ x: 25, y: 250 },
]);
poly2.center = p.copy().add(100, 100);
poly.center.add(p); poly.center.add(p);
doodler.createLayer((c) => { doodler.createLayer((c) => {
@ -50,9 +53,9 @@ doodler.createLayer((c) => {
doodler.drawSquare(new Vector(i, j), 50, { color: "#00000010" }); doodler.drawSquare(new Vector(i, j), 50, { color: "#00000010" });
} }
} }
const color = satCollision( const color = satCollisionCircle(
poly,
poly2, poly2,
poly.circularHitbox,
) )
? "red" ? "red"
: "aqua"; : "aqua";
@ -73,20 +76,20 @@ doodler.createLayer((c) => {
const rightX = gamepad.axes[2]; const rightX = gamepad.axes[2];
const rightY = gamepad.axes[3]; const rightY = gamepad.axes[3];
if (axisAlignedContains(poly2.aaHitbox, poly.aaHitbox)) { // if (axisAlignedContains(poly2.aaHitbox, poly.aaHitbox)) {
poly.center.add( // poly.center.add(
new Vector( // new Vector(
Math.min(Math.max(rightX - deadzone, 0), rightX + deadzone), // Math.min(Math.max(rightX - deadzone, 0), rightX + deadzone),
Math.min(Math.max(rightY - deadzone, 0), rightY + deadzone), // Math.min(Math.max(rightY - deadzone, 0), rightY + deadzone),
).mult(10), // ).mult(10),
); // );
poly2.center.add( // poly2.center.add(
new Vector( // new Vector(
Math.min(Math.max(leftX - deadzone, 0), leftX + deadzone), // Math.min(Math.max(leftX - deadzone, 0), leftX + deadzone),
Math.min(Math.max(leftY - deadzone, 0), leftY + deadzone), // Math.min(Math.max(leftY - deadzone, 0), leftY + deadzone),
).mult(10), // ).mult(10),
); // );
} // }
poly.center.add( poly.center.add(
new Vector( new Vector(