349 lines
8.1 KiB
TypeScript
349 lines
8.1 KiB
TypeScript
/// <reference types="../global.d.ts" />
|
|
|
|
import { Constants } from "./constants.ts";
|
|
|
|
export class Vector implements Point {
|
|
x: number;
|
|
y: number;
|
|
z: number;
|
|
|
|
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;
|
|
set(v: Vector): void;
|
|
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,
|
|
(v as Vector).y || (v as Array<number>)[1] || 0,
|
|
(v as Vector).z || (v as Array<number>)[2] || 0,
|
|
);
|
|
} else {
|
|
this.x = v as number;
|
|
this.y = y || 0;
|
|
this.z = z || 0;
|
|
}
|
|
}
|
|
get() {
|
|
return new Vector(this.x, this.y, this.z);
|
|
}
|
|
mag() {
|
|
const x = this.x,
|
|
y = this.y,
|
|
z = this.z;
|
|
return Math.sqrt(x * x + y * y + z * z);
|
|
}
|
|
magSq() {
|
|
const x = this.x,
|
|
y = this.y,
|
|
z = this.z;
|
|
return (x * x + y * y + z * z);
|
|
}
|
|
setMag(len: number): void;
|
|
setMag(v: Vector, len: number): Vector;
|
|
setMag(v_or_len: Vector | number, len?: number) {
|
|
if (len === undefined) {
|
|
len = v_or_len as number;
|
|
this.normalize();
|
|
this.mult(len);
|
|
} else {
|
|
const v = v_or_len as Vector;
|
|
v.normalize();
|
|
v.mult(len);
|
|
return v;
|
|
}
|
|
}
|
|
add(x: number, y: number, z: number): Vector;
|
|
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") {
|
|
this.x += v.x;
|
|
this.y += v.y;
|
|
this.z += v.z;
|
|
} else if (arguments.length === 2) {
|
|
// 2D Vector
|
|
this.x += v as number;
|
|
this.y += y ?? 0;
|
|
} else {
|
|
this.x += v as number;
|
|
this.y += y ?? 0;
|
|
this.z += z ?? 0;
|
|
}
|
|
return this;
|
|
}
|
|
sub(x: number, y: number, z: number): Vector;
|
|
sub(x: number, y: number): Vector;
|
|
sub(v: Vector): Vector;
|
|
sub(v: Point): Vector;
|
|
sub(v: Vector | Point | number, y?: number, z?: number) {
|
|
if (arguments.length === 1 && typeof v !== "number") {
|
|
this.x -= v.x;
|
|
this.y -= v.y;
|
|
this.z -= v.z || 0;
|
|
} else if (arguments.length === 2) {
|
|
// 2D Vector
|
|
this.x -= v as number;
|
|
this.y -= y ?? 0;
|
|
} else {
|
|
this.x -= v as number;
|
|
this.y -= y ?? 0;
|
|
this.z -= z ?? 0;
|
|
}
|
|
return this;
|
|
}
|
|
mult(v: number | Vector) {
|
|
if (typeof v === "number") {
|
|
this.x *= v;
|
|
this.y *= v;
|
|
this.z *= v;
|
|
} else {
|
|
this.x *= v.x;
|
|
this.y *= v.y;
|
|
this.z *= v.z;
|
|
}
|
|
return this;
|
|
}
|
|
div(v: number | Vector) {
|
|
if (typeof v === "number") {
|
|
this.x /= v;
|
|
this.y /= v;
|
|
this.z /= v;
|
|
} else {
|
|
this.x /= v.x;
|
|
this.y /= v.y;
|
|
this.z /= v.z;
|
|
}
|
|
return this;
|
|
}
|
|
rotate(angle: number) {
|
|
const prev_x = this.x;
|
|
const c = Math.cos(angle);
|
|
const s = Math.sin(angle);
|
|
this.x = c * this.x - s * this.y;
|
|
this.y = s * prev_x + c * this.y;
|
|
return this;
|
|
}
|
|
dist(v: Vector | Point) {
|
|
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);
|
|
}
|
|
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);
|
|
}
|
|
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);
|
|
}
|
|
lerp(x: number, y: number, z: number): void;
|
|
lerp(v: Vector, amt: number): void;
|
|
lerp(v_or_x: Vector | number, amt_or_y: number, z?: number, amt?: number) {
|
|
const lerp_val = (start: number, stop: number, amt: number) => {
|
|
return start + (stop - start) * amt;
|
|
};
|
|
let x, y: number;
|
|
if (arguments.length === 2 && typeof v_or_x !== "number") {
|
|
// given vector and amt
|
|
amt = amt_or_y;
|
|
x = v_or_x.x;
|
|
y = v_or_x.y;
|
|
z = v_or_x.z;
|
|
} else {
|
|
// given x, y, z and amt
|
|
x = v_or_x as number;
|
|
y = amt_or_y;
|
|
}
|
|
this.x = lerp_val(this.x, x, amt!);
|
|
this.y = lerp_val(this.y, y, amt!);
|
|
this.z = lerp_val(this.z, z!, amt!);
|
|
return this;
|
|
}
|
|
normalize() {
|
|
const m = this.mag();
|
|
if (m > 0) {
|
|
this.div(m);
|
|
}
|
|
return this;
|
|
}
|
|
limit(high: number) {
|
|
if (this.mag() > high) {
|
|
this.normalize();
|
|
this.mult(high);
|
|
}
|
|
return this;
|
|
}
|
|
heading() {
|
|
return (-Math.atan2(-this.y, this.x));
|
|
}
|
|
heading2D() {
|
|
return this.heading();
|
|
}
|
|
toString() {
|
|
return "[" + this.x + ", " + this.y + ", " + this.z + "]";
|
|
}
|
|
array() {
|
|
return [this.x, this.y, this.z];
|
|
}
|
|
|
|
copy() {
|
|
return new Vector(this.x, this.y, this.z);
|
|
}
|
|
|
|
drawDot(color?: string) {
|
|
if (!doodler) return;
|
|
|
|
doodler.dot(this, { weight: 2, color: color || "red" });
|
|
}
|
|
|
|
draw(origin?: Point) {
|
|
if (!doodler) return;
|
|
|
|
const startPoint = origin ? new Vector(origin) : new Vector();
|
|
doodler.line(
|
|
startPoint,
|
|
startPoint.copy().add(this.copy().normalize().mult(100)),
|
|
);
|
|
}
|
|
|
|
normal(): Vector;
|
|
normal(v: Vector): Vector;
|
|
normal(v?: Vector) {
|
|
if (!v) return new Vector(-this.y, this.x);
|
|
const dx = v.x - this.x;
|
|
const dy = v.y - this.y;
|
|
|
|
return new Vector(-dy, dx);
|
|
}
|
|
|
|
static fromAngle(angle: number, v?: Vector) {
|
|
if (v === undefined || v === null) {
|
|
v = new Vector();
|
|
}
|
|
v.x = Math.cos(angle);
|
|
v.y = Math.sin(angle);
|
|
return v;
|
|
}
|
|
|
|
static random2D(v?: Vector) {
|
|
return Vector.fromAngle(Math.random() * (Math.PI * 2), v);
|
|
}
|
|
|
|
static random3D(v: Vector) {
|
|
const angle = Math.random() * Constants.TWO_PI;
|
|
const vz = Math.random() * 2 - 1;
|
|
const mult = Math.sqrt(1 - vz * vz);
|
|
const vx = mult * Math.cos(angle);
|
|
const vy = mult * Math.sin(angle);
|
|
if (v === undefined || v === null) {
|
|
v = new Vector(vx, vy, vz);
|
|
} else {
|
|
v.set(vx, vy, vz);
|
|
}
|
|
return v;
|
|
}
|
|
|
|
static dist(v1: Vector, v2: Vector) {
|
|
return v1.dist(v2);
|
|
}
|
|
|
|
static dot(v1: Vector, v2: Vector) {
|
|
return v1.dot(v2);
|
|
}
|
|
|
|
static cross(v1: Vector, v2: Vector) {
|
|
return v1.cross(v2);
|
|
}
|
|
|
|
static add(v1: Vector, v2: Vector) {
|
|
return new Vector(v1.x + v2.x, v1.y + v2.y, v1.z + v2.z);
|
|
}
|
|
|
|
static sub(v1: Vector, v2: Vector) {
|
|
return new Vector(v1.x - v2.x, v1.y - v2.y, v1.z - v2.z);
|
|
}
|
|
|
|
static angleBetween(v1: Vector, v2: Vector) {
|
|
return Math.acos(v1.dot(v2) / Math.sqrt(v1.magSq() * v2.magSq()));
|
|
}
|
|
|
|
static lerp(v1: Vector, v2: Vector, amt: number) {
|
|
// non-static lerp mutates object, but this version returns a new vector
|
|
const val = new Vector(v1.x, v1.y, v1.z);
|
|
val.lerp(v2, amt);
|
|
return val;
|
|
}
|
|
|
|
static vectorProjection(v1: Vector, v2: Vector) {
|
|
v2 = v2.copy();
|
|
v2.normalize();
|
|
const sp = v1.dot(v2);
|
|
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));
|
|
}
|
|
}
|
|
|
|
export class OriginVector extends Vector {
|
|
origin: Point;
|
|
|
|
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,
|
|
};
|
|
}
|
|
|
|
constructor(origin: Point, p: Point) {
|
|
super(p.x, p.y, p.z);
|
|
this.origin = origin;
|
|
}
|
|
|
|
static from(origin: Point, p: Point) {
|
|
const v = {
|
|
x: p.x - origin.x,
|
|
y: p.y - origin.y,
|
|
};
|
|
|
|
return new OriginVector(origin, v);
|
|
}
|
|
}
|
|
|
|
export interface Point {
|
|
x: number;
|
|
y: number;
|
|
z?: number;
|
|
}
|