Skip to content

Instantly share code, notes, and snippets.

@dwaard
Last active November 19, 2021 14:18
Show Gist options
  • Save dwaard/294d58440d5e48910cdec9ffe22efd27 to your computer and use it in GitHub Desktop.
Save dwaard/294d58440d5e48910cdec9ffe22efd27 to your computer and use it in GitHub Desktop.
Typescript class that represents a 2D Vector
/**
* This class represents a mathematical (Euclidian) Vector mainly used in
* physics and engineering to represent directed quantities. Basically it's
* responsible for holding a x- and y-coordinate, length and angle. It can also
* perform basic vector operations like adding, subtracting and scaling.
*
* The state of a vector object is immutable. This means that it is by design
* not allowed to change the internal state of the vector. The different
* computation methods that perform different computations like adding,
* subtracting, scaling and mirroring all return a new Vector object or just a
* number.
*
* @see https://en.wikipedia.org/wiki/Euclidean_vector
* @author BugSlayer
*/
export default class Vector {
/**
* The x-coordinate of the vector in a Cartesian Coordinate Space.
*/
public readonly x: number;
/**
* The y-coordinate of the vector in a Cartesian Coordinate Space.
*/
public readonly y: number;
// Cached computed values, so we only need expensive calculations once.
private cachedLength: number = null;
private cachedAngle: number = null;
/**
* Constructs a new Vector.
*
* @param x the x-coordinate of the endpoint of this vector an a Cartesian
* Coordinate Space.
* @param y the y-coordinate of the endpoint of this vector an a Cartesian
* Coordinate Space.
*/
public constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
}
/**
* Factory method that constructs a vector from the given size and angle
*
* @param size the length of the vector
* @param angle the angle of the vector (in radians)
* @returns a vector from the given size and angle
*/
public static fromSizeAndAngle(size: number, angle: number): Vector {
const x = size * Math.cos(angle);
const y = size * Math.sin(angle);
return new Vector(x, y);
}
/**
* Returns the size or length of this Vector
*
* @returns the size or length of this Vector
*/
public get length(): number {
if (!this.cachedLength) {
// Calculate only once for performance reasons
this.cachedLength = Math.sqrt(this.x ** 2 + this.y ** 2);
}
return this.cachedLength;
}
/**
* Returns the angle between this vector and the X-axis in radians as a
* value between -PI/2 and PI/2 radians.
*
* @returns the angle between this vector and the X-axis in radians as a
* value between -PI/2 and PI/2 radians.
*/
public get angle(): number {
if (!this.cachedAngle) {
// Calculate only once for performance reasons
this.cachedAngle = Math.atan2(this.y, this.x);
}
return this.cachedAngle;
}
/**
* Returns `true` if and only if this vector is equal to the specified
* other vector.
*
* @see https://en.wikipedia.org/wiki/Euclidean_vector#Equality
* @param input the other vector
* @returns `true` if and only if this vector is equal to the specified
* other vector.
*/
public isEqualTo(input: Vector): boolean {
return this.x === input.x && this.y === input.y;
}
/**
* Returns `true` if and only if this vector is opposite to the specified
* other vector.
*
* @see https://en.wikipedia.org/wiki/Euclidean_vector#Opposite,_parallel,_and_antiparallel_vectors
* @param input the other vector
* @returns `true` if and only if this vector is opposite to the specified
* other vector
*/
public isOppositeTo(input: Vector): boolean {
return this.x === -input.x && this.y === -input.y;
}
/**
* Returns `true` if and only if this vector is parallel to the specified
* other vector.
*
* @see https://en.wikipedia.org/wiki/Euclidean_vector#Opposite,_parallel,_and_antiparallel_vectors
* @param input the other vector
* @returns `true` if and only if this vector is parallel to the specified
* other vector
*/
public isParallelTo(input: Vector): boolean {
return this.angle === input.angle;
}
/**
* Returns `true` if and only if this vector is antiparallel to the
* specified other vector.
*
* @see https://en.wikipedia.org/wiki/Euclidean_vector#Opposite,_parallel,_and_antiparallel_vectors
* @param input the other vector
* @returns `true` if and only if this vector is antiparallel to the specified other vector
*/
public isAntiParallelTo(input: Vector): boolean {
const scaled = input.scale(-1);
return this.angle === scaled.angle;
}
/**
* Returns a new Vector representing the sum of this Vector and the given
* input.
*
* @see https://en.wikipedia.org/wiki/Euclidean_vector#Addition_and_subtraction
* @param input the Vector that must be subtracted to this Vector
* @returns a new Vector representing the sum of this Vector and the given input
*/
public add(input: Vector): Vector {
return new Vector(
this.x + input.x,
this.y + input.y,
);
}
/**
* Returns a new Vector representing the difference between this Vector and
* the given input.
*
* @see https://en.wikipedia.org/wiki/Euclidean_vector#Addition_and_subtraction
* @param input the Vector that must be subtracted to this Vector
* @returns a new Vector representing the difference between this Vector and
* the input.
*/
public subtract(input: Vector): Vector {
return new Vector(
this.x - input.x,
this.y - input.y,
);
}
/**
* Returns a new Vector representing the result of the multiplication of
* this vector and the specified scalar.
*
* @see https://en.wikipedia.org/wiki/Euclidean_vector#Scalar_multiplication
* @param scalar the scalar that should be used in the calculation
* @returns a new Vector representing the result of the multiplication of
* this vector and the specified scalar
*/
public scale(scalar: number): Vector {
return new Vector(
this.x * scalar,
this.y * scalar,
);
}
/**
* Returns a new Vector representing a unit vector with the same angle.
*
* @see https://en.wikipedia.org/wiki/Euclidean_vector#Length
* @returns a new Vector representing a unit vector with the same angle
*/
public normalize(): Vector {
return Vector.fromSizeAndAngle(1, this.angle);
}
/**
* Returns the dot product (sometimes called the inner product, or, since
* its result is a scalar, the scalar product) of this vector and the
* specified other vector.
*
* @see https://en.wikipedia.org/wiki/Euclidean_vector#Dot_product
* @param input the other vector
* @returns the dot product of this vector and the specified other vector
*/
public dotProduct(input: Vector): number {
return this.length * input.length * Math.cos(this.angle - input.angle);
}
/**
* Returns a new Vector representing the mirrored version of this vector
* with respect to the X-axis. This means that the
* Y-portion of this vector will be multiplied by -1.
*
* @returns a new Vector representing the mirrored version of this vector
* with respect to the X-axis
*/
public mirrorX(): Vector {
return new Vector(this.x, this.y * -1);
}
/**
* Returns a new Vector representing the mirrored version of this vector
* with respect to the Y-axis. This means that the
* X-portion of this vector will be multiplied by -1.
*
* @returns a new Vector representing the mirrored version of this vector
* with respect to the Y-axis
*/
public mirrorY(): Vector {
return new Vector(this.x * -1, this.y);
}
/**
* Returns the distance between the endpoints of this vector and the given
* input.
*
* @param input the Vector that must be subtracted to this Vector
* @returns the distance between the endpoints of this vector and the given input
*/
public distance(input: Vector): number {
return this.subtract(input).length;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment