Three types of collision and a polygon class

This commit is contained in:
2023-11-03 04:51:50 -06:00
parent a7e7cd139f
commit 601bc51233
10 changed files with 561 additions and 125 deletions

97
geometry/polygon.ts Normal file
View File

@@ -0,0 +1,97 @@
import { axisAlignedBoundingBox } from "../collision/aa.ts";
import { CircleLike } from "../collision/circular.ts";
import { Vector } from "../mod.ts";
import { Point } from "./vector.ts";
export class Polygon {
points: Vector[];
center: Vector;
constructor(points: Point[]) {
this.points = points.map((p) => new Vector(p));
this.center = this.calcCenter();
}
draw(color?: string) {
for (let i = 0; i < this.points.length; i++) {
const p1 = this.points[i];
const p2 = this.points.at(i - this.points.length + 1)!;
doodler.line(p1.copy().add(this.center), p2.copy().add(this.center), {
color,
});
const { x, y, w, h } = this.aaHitbox();
doodler.drawRect(new Vector(x, y), w, h, { color: "lime" });
}
}
calcCenter() {
if (!this.points.length) return new Vector();
const center = new Vector();
for (const point of this.points) {
center.add(point);
}
center.div(this.points.length);
return center;
}
circularHitbox(): CircleLike {
let greatestDistance = 0;
for (const p of this.points) {
greatestDistance = Math.max(
p.copy().add(this.center).dist(this.center),
greatestDistance,
);
}
return {
center: this.center.copy(),
radius: greatestDistance,
};
}
aaHitbox(): axisAlignedBoundingBox {
let smallestX, biggestX, smallestY, biggestY;
smallestX =
smallestY =
Infinity;
biggestX =
biggestY =
-Infinity;
for (const p of this.points) {
const temp = p.copy().add(this.center);
smallestX = Math.min(temp.x, smallestX);
biggestX = Math.max(temp.x, biggestX);
smallestY = Math.min(temp.y, smallestY);
biggestY = Math.max(temp.y, biggestY);
}
return {
x: smallestX,
y: smallestY,
w: biggestX - smallestX,
h: biggestY - smallestY,
};
}
static createPolygon(sides = 3, radius = 100) {
sides = Math.round(sides);
if (sides < 3) {
throw "You need at least 3 sides for a polygon";
}
const poly = new Polygon([]);
// figure out the angles required
const rotangle = (Math.PI * 2) / sides;
let angle = 0;
// loop through and generate each point
for (let i = 0; i < sides; i++) {
angle = (i * rotangle) + ((Math.PI - rotangle) * 0.5);
const pt = new Vector(Math.cos(angle) * radius, Math.sin(angle) * radius);
poly.points.push(pt);
}
poly.center = poly.calcCenter();
return poly;
}
}

View File

@@ -7,10 +7,19 @@ export class Vector implements Point {
y: number;
z: number;
constructor(x = 0, y = 0, z = 0) {
this.x = x;
this.y = y;
this.z = z;
constructor();
constructor(p: Point);
constructor(x: number, y: number, z?: number);
constructor(x: number | Point = 0, y = 0, z = 0) {
if (typeof x === "number") {
this.x = x;
this.y = y;
this.z = z;
} else {
this.x = x.x;
this.y = x.y || y;
this.z = x.z || z;
}
}
set(x: number, y: number, z?: number): void;
@@ -18,9 +27,11 @@ export class Vector implements Point {
set(v: [number, number, number]): void;
set(v: Vector | [number, number, number] | number, y?: number, z?: number) {
if (arguments.length === 1 && typeof v !== "number") {
this.set((v as Vector).x || (v as Array<number>)[0] || 0,
this.set(
(v as Vector).x || (v as Array<number>)[0] || 0,
(v as Vector).y || (v as Array<number>)[1] || 0,
(v as Vector).z || (v as Array<number>)[2] || 0);
(v as Vector).z || (v as Array<number>)[2] || 0,
);
} else {
this.x = v as number;
this.y = y || 0;
@@ -43,7 +54,7 @@ export class Vector implements Point {
return (x * x + y * y + z * z);
}
setMag(len: number): void;
setMag(v: Vector, len: number): Vector
setMag(v: Vector, len: number): Vector;
setMag(v_or_len: Vector | number, len?: number) {
if (len === undefined) {
len = v_or_len as number;
@@ -60,7 +71,7 @@ export class Vector implements Point {
add(x: number, y: number): Vector;
add(v: Vector): Vector;
add(v: Vector | number, y?: number, z?: number) {
if (arguments.length === 1 && typeof v !== 'number') {
if (arguments.length === 1 && typeof v !== "number") {
this.x += v.x;
this.y += v.y;
this.z += v.z;
@@ -79,7 +90,7 @@ export class Vector implements Point {
sub(x: number, y: number): Vector;
sub(v: Vector): Vector;
sub(v: Vector | number, y?: number, z?: number) {
if (arguments.length === 1 && typeof v !== 'number') {
if (arguments.length === 1 && typeof v !== "number") {
this.x -= v.x;
this.y -= v.y;
this.z -= v.z;
@@ -95,7 +106,7 @@ export class Vector implements Point {
return this;
}
mult(v: number | Vector) {
if (typeof v === 'number') {
if (typeof v === "number") {
this.x *= v;
this.y *= v;
this.z *= v;
@@ -107,7 +118,7 @@ export class Vector implements Point {
return this;
}
div(v: number | Vector) {
if (typeof v === 'number') {
if (typeof v === "number") {
this.x /= v;
this.y /= v;
this.z /= v;
@@ -135,18 +146,16 @@ export class Vector implements Point {
dot(x: number, y: number, z: number): number;
dot(v: Vector): number;
dot(v: Vector | number, y?: number, z?: number) {
if (arguments.length === 1 && typeof v !== 'number') {
return (this.x * v.x + this.y * v.y + this.z * v.z);
if (arguments.length === 1 && typeof v !== "number") {
return (this.x * v.x) + (this.y * v.y) + (this.z * v.z);
}
return (this.x * (v as number) + this.y * y! + this.z * z!);
return (this.x * (v as number)) + (this.y * y!) + (this.z * z!);
}
cross(v: Vector) {
const x = this.x,
y = this.y,
z = this.z;
return new Vector(y * v.z - v.y * z,
z * v.x - v.z * x,
x * v.y - v.x * y);
return new Vector(y * v.z - v.y * z, z * v.x - v.z * x, x * v.y - v.x * y);
}
lerp(x: number, y: number, z: number): void;
lerp(v: Vector, amt: number): void;
@@ -155,7 +164,7 @@ export class Vector implements Point {
return start + (stop - start) * amt;
};
let x, y: number;
if (arguments.length === 2 && typeof v_or_x !== 'number') {
if (arguments.length === 2 && typeof v_or_x !== "number") {
// given vector and amt
amt = amt_or_y;
x = v_or_x.x;
@@ -202,10 +211,32 @@ export class Vector implements Point {
return new Vector(this.x, this.y, this.z);
}
drawDot() {
drawDot(color?: string) {
if (!doodler) return;
doodler.dot(this, {weight: 2, color: 'red'});
doodler.dot(this, { weight: 2, color: color || "red" });
}
draw() {
if (!doodler) return;
const startPoint = new Vector();
doodler.dot(new Vector(), { weight: 4, color: "orange" });
doodler.line(
startPoint,
startPoint.copy().add(this.copy().normalize().mult(700)),
);
doodler.line(
startPoint,
startPoint.copy().sub(this.copy().normalize().mult(700)),
);
}
normal(v: Vector) {
const dx = v.x - this.x;
const dy = v.y - this.y;
return new Vector(-dy, dx);
}
static fromAngle(angle: number, v?: Vector) {
@@ -261,9 +292,9 @@ export class Vector implements Point {
static lerp(v1: Vector, v2: Vector, amt: number) {
// non-static lerp mutates object, but this version returns a new vector
const retval = new Vector(v1.x, v1.y, v1.z);
retval.lerp(v2, amt);
return retval;
const val = new Vector(v1.x, v1.y, v1.z);
val.lerp(v2, amt);
return val;
}
static vectorProjection(v1: Vector, v2: Vector) {
@@ -273,9 +304,16 @@ export class Vector implements Point {
v2.mult(sp);
return v2;
}
static vectorProjectionAndDot(v1: Vector, v2: Vector): [Vector, number] {
v2 = v2.copy();
v2.normalize();
const sp = v1.dot(v2);
v2.mult(sp);
return [v2, sp];
}
static hypot2(a: Vector, b: Vector) {
return Vector.dot(Vector.sub(a, b), Vector.sub(a, b))
return Vector.dot(Vector.sub(a, b), Vector.sub(a, b));
}
}
@@ -284,9 +322,9 @@ export class OriginVector extends Vector {
get halfwayPoint() {
return {
x: (this.mag()/2 * Math.sin(this.heading())) + this.origin.x,
y: (this.mag()/2 * Math.cos(this.heading())) + this.origin.y
}
x: (this.mag() / 2 * Math.sin(this.heading())) + this.origin.x,
y: (this.mag() / 2 * Math.cos(this.heading())) + this.origin.y,
};
}
constructor(origin: Point, p: Point) {