Skip to content

Instantly share code, notes, and snippets.

@wilbo
Created March 8, 2018 12:29
Show Gist options
  • Save wilbo/aea832e3ee6d71bcec76da3106ff8466 to your computer and use it in GitHub Desktop.
Save wilbo/aea832e3ee6d71bcec76da3106ff8466 to your computer and use it in GitHub Desktop.
A Matrix and Vector class in TypeScript
import Vector2D from './Vector2D'
class Matrix2D {
private _matrix: number[][]
constructor(value?: number[][] | Vector2D) {
if (typeof value === 'undefined') {
this._matrix = Matrix2D.identity
} else if (value instanceof Vector2D) {
this._matrix = Matrix2D.identity
this._matrix[0][0] = value.x
this._matrix[1][0] = value.y
this._matrix[2][0] = value.w
} else {
this._matrix = value
}
}
/**
* Return the matrix values
*/
public get m(): number[][] {
return this._matrix
}
public static get empty(): number[][] {
return [[],[],[]]
}
public get toVector(): Vector2D {
return new Vector2D(this._matrix[0][0], this._matrix[1][0])
}
/**
* Initialize an identity matrix
*/
public static get identity(): number[][] {
return [
[1, 0, 0],
[0, 1, 0],
[0, 0, 1]]
}
public static add(matrix1: Matrix2D, matrix2: Matrix2D): Matrix2D {
var result = Matrix2D.empty
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
result[i][j] = matrix1.m[i][j] + matrix2.m[i][j]
}
}
return new Matrix2D(result);
}
public static subtract(matrix1: Matrix2D, matrix2: Matrix2D): Matrix2D {
var result = Matrix2D.empty
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
result[i][j] = matrix1.m[i][j] - matrix2.m[i][j]
}
}
return new Matrix2D(result);
}
public static multiply(matrix1: Matrix2D, matrix2: Matrix2D): Matrix2D {
var result = Matrix2D.empty
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
result[i][j] = 0;
for (let k = 0; k < 3; k++) {
result[i][j] += matrix1.m[i][k] * matrix2.m[k][j];
}
}
}
return new Matrix2D(result);
}
public static multiplyByValue(matrix: Matrix2D, value: number): Matrix2D {
var result = Matrix2D.empty
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
result[i][j] = matrix.m[i][j] * value
}
}
return new Matrix2D(result);
}
public static multiplyByVector(matrix: Matrix2D, vector: Vector2D): Vector2D {
return Matrix2D.multiply(matrix, new Matrix2D(vector)).toVector
}
public static view(width: number, height: number): Matrix2D {
const scaleStep = 1 // Scale every vector * scaleStep
const centerX = width / 2
const centerY = height / 2
const flipX = Math.cos(Math.PI) // rotate 180deg / 3.14radian around X-axis
return new Matrix2D([
[scaleStep, 0, centerX],
[0, flipX * scaleStep, centerY],
[0, 0, 1]])
}
public static scale(factor: number) {
return Matrix2D.multiplyByValue(new Matrix2D(), factor)
}
public static rotate(radians: number) {
const cos = Math.cos(radians)
const sin = Math.sin(radians)
return new Matrix2D([
[cos, -sin, 0],
[sin, cos, 0],
[0, 0, 1]])
}
public static translate(vector: Vector2D): Matrix2D {
return new Matrix2D([
[1, 0, vector.x],
[0, 1, vector.y],
[0, 0, vector.w]])
}
}
export default Matrix2D
import Matrix2D from './Matrix2D'
class Vector2D {
public static add(vector1: Vector2D, vector2: Vector2D): Vector2D {
return new Vector2D(vector1.x + vector2.x, vector1.y + vector2.y)
}
public static subtract(vector1: Vector2D, vector2: Vector2D): Vector2D {
return new Vector2D(vector1.x - vector2.x, vector1.y - vector2.y)
}
public static subtractValue(vector: Vector2D, value: number): Vector2D {
return new Vector2D(vector.x - value, vector.y - value)
}
public static multiply(vector: Vector2D, value: number): Vector2D {
return new Vector2D(vector.x * value, vector.y * value)
}
public static divide(vector: Vector2D, value: number): Vector2D {
return new Vector2D(vector.x / value, vector.y / value)
}
public static equals(vector1: Vector2D, vector2: Vector2D): boolean {
return vector1.x === vector2.x && vector1.y === vector2.y
}
public static equalsRounded(vector1: Vector2D, vector2: Vector2D, roundingFactor: number = 12): boolean {
const vector = Vector2D.abs(Vector2D.subtract(vector1, vector2))
if (vector.x < roundingFactor && vector.y < roundingFactor) {
return true
}
return false
}
/**
* Normalizes the vector if it matches a certain condition
*/
public static normalize(vector: Vector2D): Vector2D {
const length = vector.length
if (length > 2.220446049250313e-16) { // Epsilon
return Vector2D.divide(vector, length)
}
return vector
}
/**
* Adjusts x and y so that the length of the vector does not exceed max
*/
public static truncate(vector: Vector2D, max: number): Vector2D {
if (vector.length > max) {
return Vector2D.multiply(Vector2D.normalize(vector), max)
}
return vector
}
/**
* The vector that is perpendicular to this one
*/
public static perp(vector: Vector2D): Vector2D {
return new Vector2D(-vector.y, vector.x)
}
/**
* returns the vector that is the reverse of this vector
*/
public static reverse(vector: Vector2D): Vector2D {
return new Vector2D(-vector.x, -vector.y)
}
public static abs(vector: Vector2D): Vector2D {
return new Vector2D(Math.abs(vector.x), Math.abs(vector.y))
}
/**
* The dot product of v1 and v2
*/
public static dot(vector1: Vector2D, vector2: Vector2D): number {
return (vector1.x * vector2.x) + (vector1.y * vector2.y)
}
/**
* The distance between this and the vector
*/
public static distance(vector1: Vector2D, vector2: Vector2D): number {
const ySeparation = vector2.y - vector1.y
const xSeparation = vector2.x - vector1.x
return Math.sqrt((ySeparation * ySeparation) + (xSeparation * xSeparation))
}
/**
* The distance between this and the vector squared
*/
public static distanceSq(vector1: Vector2D, vector2: Vector2D): number {
const ySeparation = vector2.y - vector1.y
const xSeparation = vector2.x - vector1.x
return (ySeparation * ySeparation) + (xSeparation * xSeparation)
}
/**
* Returns positive if v2 is clockwise of this vector, negative if counterclockwise
* (assuming the Y axis is pointing down, X axis to right like a Window app)
*/
public static sign(vector1: Vector2D, vector2: Vector2D): number {
if (vector1.y * vector2.x > vector1.x * vector2.y) {
return -1
}
return 1
}
/**
* Returns the angle between origin and the given vector in radians
* @param vector
*/
public static angle(vector: Vector2D): number {
const origin = new Vector2D(0, -1)
const radian = Math.acos(Vector2D.dot(vector, origin) / (vector.length * origin.length))
return Vector2D.sign(vector, origin) === 1 ? ((Math.PI * 2) - radian) : radian
}
public static random(maxX: number, maxY: number): Vector2D {
const randX = Math.floor(Math.random() * maxX - (maxX / 2))
const randY = Math.floor(Math.random() * maxY - (maxY / 2))
return new Vector2D(randX, randY)
}
/**
* Transform vectors based on the current tranformation matrices: translation, rotation and scale
* @param vectors The vectors to transform
*/
public static transform(vector: Vector2D, transformation: Matrix2D): Vector2D {
return Matrix2D.multiplyByVector(transformation, vector)
}
/**
* Transform vectors based on the current tranformation matrices: translation, rotation and scale
* @param vectors The vectors to transform
*/
public static transformList(vectors: Vector2D[], transformation: Matrix2D): Vector2D[] {
return vectors.map(vector => Matrix2D.multiplyByVector(transformation, vector))
}
constructor(
public x: number = 0,
public y: number = 0,
public w: number = 1 // needed for matrix multiplication
) { }
/**
* Check wether both x and y are zero
*/
public zero(): void {
this.x = 0
this.y = 0
}
/**
* Set x and y both to zero
*/
public get isZero(): boolean {
return this.x === 0 && this.y === 0
}
/**
* The length / magnitude of the vector
*/
public get length(): number {
return Math.sqrt((this.x * this.x) + (this.y * this.y))
}
/**
* The squared length of the vector
*/
public get lengthSq(): number {
return (this.x * this.x) + (this.y * this.y)
}
/**
* Return the vector with rounded values
*/
public get rounded(): Vector2D {
return new Vector2D(Math.round(this.x), Math.round(this.y))
}
}
export default Vector2D
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment