Created
March 8, 2018 12:29
-
-
Save wilbo/aea832e3ee6d71bcec76da3106ff8466 to your computer and use it in GitHub Desktop.
A Matrix and Vector class in TypeScript
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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