@dominicstop/utils
Version:
Yet another event emitter written in typescript.
225 lines (224 loc) • 6.37 kB
JavaScript
import { Angle } from "./Angle";
import { Point } from "./Point";
export class Vector2D {
constructor(args) {
this.epsilon = 1e-10;
this.dx = args.dx;
this.dy = args.dy;
}
get asValue() {
return {
dx: this.dx,
dy: this.dy,
};
}
get asPoint() {
return new Point({
x: this.dx,
y: this.dy,
});
}
;
get asPointValue() {
return {
x: this.dx,
y: this.dy,
};
}
;
// MARK: - Computed Properties
// ---------------------------
get magnitude() {
return Math.sqrt(this.dx * this.dx + this.dy * this.dy);
}
get normalized() {
const magnitude = this.magnitude;
if (magnitude === 0) {
return Vector2D.zero;
}
;
return this.dividedByScalar(magnitude);
}
get isZero() {
return (Math.abs(this.dx) < this.epsilon &&
Math.abs(this.dy) < this.epsilon);
}
get isUnit() {
return Math.abs(this.magnitude - 1) < this.epsilon;
}
get perpendicular() {
return new Vector2D({
dx: -this.dy,
dy: this.dx
});
}
get inverse() {
return new Vector2D({
dx: -this.dx,
dy: -this.dy
});
}
/**
* Returns the angle in radians from the positive x-axis to the vector.
*/
get angle() {
const angle = Math.atan2(this.dy, this.dx);
return Angle.initFromRadians(angle);
}
;
// MARK: - Methods
// ---------------
clone() {
return new Vector2D(this.asValue);
}
;
computeDistanceFromOtherVector(otherVector) {
return Vector2D.distanceBetweenTwoVectors(this, otherVector);
}
isEqualToOtherVector(otherVector, tolerance = this.epsilon) {
return (Math.abs(this.dx - otherVector.dx) < tolerance &&
Math.abs(this.dy - otherVector.dy) < tolerance);
}
// MARK: - Mutate Methods (Math Operations)
// -------------------------------------
addWithOther(other) {
this.dx += other.dx;
this.dy += other.dy;
}
;
subtractFromOther(other) {
this.dx -= other.dx;
this.dy -= other.dy;
}
multiplyByScalar(scalar) {
this.dx *= scalar;
this.dy *= scalar;
}
divideByScalar(scalar) {
if (Math.abs(scalar) < this.epsilon) {
this.dx = 0;
this.dy = 0;
}
else {
this.dx /= scalar;
this.dy /= scalar;
}
}
normalizeInPlace() {
const mag = this.magnitude;
if (mag >= this.epsilon) {
this.divideByScalar(mag);
}
}
// MARK: - Methods (Math Operations)
// -----------------------------
addedWithOther(otherVector) {
return new Vector2D({
dx: this.dx + otherVector.dx,
dy: this.dy + otherVector.dy,
});
}
subtractedWithOther(otherVector) {
return new Vector2D({
dx: this.dx - otherVector.dx,
dy: this.dy - otherVector.dy,
});
}
multipliedByScalar(scalar) {
return new Vector2D({
dx: this.dx * scalar,
dy: this.dy * scalar,
});
}
dividedByScalar(scalar) {
if (scalar === 0) {
throw new Error("Cannot divide by zero.");
}
return new Vector2D({
dx: this.dx / scalar,
dy: this.dy / scalar,
});
}
dotProductWithOtherVector(otherVector) {
return this.dx * otherVector.dx + this.dy * otherVector.dy;
}
/**
* Rotates the vector by a given angle in radians.
*/
rotateByAngle(angle) {
const angleInRadians = angle.radians;
const cos = Math.cos(angleInRadians);
const sin = Math.sin(angleInRadians);
return new Vector2D({
dx: this.dx * cos - this.dy * sin,
dy: this.dx * sin + this.dy * cos,
});
}
projectOntoOtherVector(other) {
const scalar = this.dotProductWithOtherVector(other) / other.magnitude ** 2;
return other.multipliedByScalar(scalar);
}
angleBetweenOtherVector(other) {
const dot = this.dotProductWithOtherVector(other);
const mags = this.magnitude * other.magnitude;
const angle = Math.acos(Math.min(Math.max(dot / mags, -1), 1));
return Angle.initFromRadians(angle);
}
crossProductWithOtherVector(other) {
return this.dx * other.dy - this.dy * other.dx;
}
reflectOverOtherVector(vector) {
const vectorNormalized = vector.normalized;
const dot = this.dotProductWithOtherVector(vectorNormalized);
return vectorNormalized
.multipliedByScalar(2 * dot)
.subtractedWithOther(this);
}
limit(maxMagnitude) {
return this.magnitude > maxMagnitude
? this.normalized.multipliedByScalar(maxMagnitude)
: this.clone();
}
// MARK: - Static Alias
// --------------------
static get zero() {
return new Vector2D({ dx: 0, dy: 0 });
}
static get one() {
return new Vector2D({ dx: 1, dy: 1 });
}
static get unitX() {
return new Vector2D({ dx: 1, dy: 0 });
}
static get unitY() {
return new Vector2D({ dx: 0, dy: 1 });
}
// MARK: - Static Init
// -------------------
static initFromAngle(angle, magnitude = 1) {
const radians = angle.radians;
return new Vector2D({
dx: Math.cos(radians) * magnitude,
dy: Math.sin(radians) * magnitude,
});
}
static initFromPoints(p1, p2) {
return new Vector2D({
dx: p2.x - p1.x,
dy: p2.y - p1.y,
});
}
// MARK: Static Methods
// --------------------
static computeAverage(vectors) {
if (vectors.length === 0)
return Vector2D.zero;
const sum = vectors.reduce((acc, v) => acc.addedWithOther(v), Vector2D.zero);
return sum.dividedByScalar(vectors.length);
}
static distanceBetweenTwoVectors(vectorA, vectorB) {
const dx = vectorA.dx - vectorB.dx;
const dy = vectorA.dy - vectorB.dy;
return Math.sqrt(dx * dx + dy * dy);
}
}