├── .gitignore ├── LICENSE.md ├── README.md ├── dist └── tsm.js ├── package.json ├── src ├── constants.ts ├── mat2.ts ├── mat3.ts ├── mat4.ts ├── quat.ts ├── tsm.ts ├── vec2.ts ├── vec3.ts └── vec4.ts ├── test ├── mat2.spec.ts ├── mat3.spec.ts ├── mat4.spec.ts ├── quat.spec.ts ├── vec2.spec.ts ├── vec3.spec.ts └── vec4.spec.ts ├── tsconfig.json ├── tslint.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | node_modules/ 3 | package-lock.json -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, 2018 Matthias Ferch 2 | 3 | Project homepage: https://github.com/matthiasferch/tsm 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 18 | 2. Altered source versions must be plainly marked as such, and must not 19 | be misrepresented as being the original software. 20 | 21 | 3. This notice may not be removed or altered from any source 22 | distribution. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | tsm: A Typescript vector and matrix math library 2 | ================================================= 3 | 4 | tsm is a a collection of vector, matrix and quaternion classes written in Typescript. 5 | 6 | The library's design is influenced by both [gl-matrix](https://github.com/toji/gl-matrix) and [glm](https://github.com/g-truc/glm). 7 | 8 | What's special about tsm? 9 | ------------------------- 10 | 11 | - tsm makes use of Javascript's new property definitions to enable GLSL-style swizzle operators: 12 | 13 | let v1 = new vec2(); 14 | let q1 = new quat(); 15 | 16 | v1.xy = [0, 1]; 17 | q1.w = 1.0; 18 | 19 | - tsm offers both non-static and static methods for many operations: 20 | 21 | let v1 = new vec3([1, 2, 3]); 22 | let v2 = new vec3([4, 5, 6]); 23 | 24 | let v3 = vec3.sum(v1, v2); 25 | let v4 = v1.copy().add(v2); 26 | 27 | console.log(v3.equals(v4)); // output: "true" 28 | 29 | 30 | General design notes 31 | -------------------- 32 | 33 | Swizzle operators return numeric arrays, not vector instances: 34 | 35 | let v = new vec4([1, 2, 3, 4]); 36 | let n = v.xyz; // n = [1, 2, 3] 37 | 38 | If, instead, you want to create a new instance of a vector or a matrix, use the copy() method: 39 | 40 | let v1 = new vec4([1, 2, 3, 4]); 41 | let v2 = v1.copy(); 42 | 43 | You can also initialize a new vector with the values of another: 44 | 45 | let v1 = new vec4([1, 2, 3, 4]); 46 | let v2 = new vec4(v1.xyzw); 47 | 48 | Or copy the values of one vector to another using the swizzle operators or the copy() method: 49 | 50 | v2.xyzw = v1.xyzw; // same as v1.copy(v2) 51 | 52 | The four basic arithmetic operations can be performed on vector instances or using static methods: 53 | 54 | let v1 = new vec4([1, 2, 3, 4]); 55 | let v2 = new vec4([5, 6, 7, 8]); 56 | 57 | let v3 = vec4.product(v1, v2); // returns a new vec4 instance 58 | 59 | v1.multiply(v2); // writes the result of the multiplication into v1 60 | v2.multiply(v1); // writes the result of the multiplication into v2 61 | 62 | The reason for all of these different ways of doing the same thing is that object allocation in Javascript is slow and dynamic allocation should therefore be reduced to a minimum. 63 | 64 | For this reason, static methods offer an optional destination parameter: 65 | 66 | let v3 = vec3.cross(v1, v2) // allocates a new instance of vec3 67 | 68 | is the same as: 69 | 70 | let v3 = new vec3(); 71 | vec3.cross(v1, v2, v3) // writes into the existing instance 72 | 73 | Matrices do not have swizzle operators. Instead, they provide the all(), row() and col() methods: 74 | 75 | let m = new mat2([1, 2, 3, 4]); 76 | 77 | let all = m.all(); // [1, 2, 3, 4] 78 | let row = m.row(0); // [1, 2] 79 | let col = m.col(0); // [1, 3] 80 | 81 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tsm", 3 | "version": "0.8.0", 4 | "description": "A TypeScript vector and matrix math library", 5 | "author": "Matthias Ferch", 6 | "module": "src/tsm.js", 7 | "scripts": { 8 | "test": "mocha -r ts-node/register test/**/*.spec.ts", 9 | "lint": "tslint src/*.ts -t verbose", 10 | "build": "webpack", 11 | "watch": "webpack --watch" 12 | }, 13 | "devDependencies": { 14 | "@types/chai": "^4.1.4", 15 | "@types/mocha": "^5.2.5", 16 | "chai": "^4.1.2", 17 | "mocha": "^5.2.0", 18 | "ts-loader": "^5.1.0", 19 | "ts-node": "^7.0.1", 20 | "tslint": "^5.11.0", 21 | "typescript": "^3.0.3", 22 | "webpack": "^4.18.0", 23 | "webpack-cli": "^3.1.0" 24 | }, 25 | "dependencies": {} 26 | } 27 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export const epsilon = 0.00001 2 | -------------------------------------------------------------------------------- /src/mat2.ts: -------------------------------------------------------------------------------- 1 | import vec2 from './vec2' 2 | 3 | import { epsilon } from './constants' 4 | 5 | export default class mat2 { 6 | 7 | constructor(values?: number[]) { 8 | if (values !== undefined) { 9 | this.init(values) 10 | } 11 | } 12 | 13 | private values = new Float32Array(4) 14 | 15 | static readonly identity = new mat2().setIdentity() 16 | 17 | at(index: number): number { 18 | return this.values[index] 19 | } 20 | 21 | init(values: number[]): mat2 { 22 | for (let i = 0; i < 4; i++) { 23 | this.values[i] = values[i] 24 | } 25 | 26 | return this 27 | } 28 | 29 | reset(): void { 30 | for (let i = 0; i < 4; i++) { 31 | this.values[i] = 0 32 | } 33 | } 34 | 35 | copy(dest?: mat2): mat2 { 36 | if (!dest) { dest = new mat2() } 37 | 38 | for (let i = 0; i < 4; i++) { 39 | dest.values[i] = this.values[i] 40 | } 41 | 42 | return dest 43 | } 44 | 45 | all(): number[] { 46 | const data: number[] = [] 47 | for (let i = 0; i < 4; i++) { 48 | data[i] = this.values[i] 49 | } 50 | 51 | return data 52 | } 53 | 54 | row(index: number): number[] { 55 | return [ 56 | this.values[index * 2 + 0], 57 | this.values[index * 2 + 1], 58 | ] 59 | } 60 | 61 | col(index: number): number[] { 62 | return [ 63 | this.values[index], 64 | this.values[index + 2], 65 | ] 66 | } 67 | 68 | equals(matrix: mat2, threshold = epsilon): boolean { 69 | for (let i = 0; i < 4; i++) { 70 | if (Math.abs(this.values[i] - matrix.at(i)) > threshold) { 71 | return false 72 | } 73 | } 74 | 75 | return true 76 | } 77 | 78 | determinant(): number { 79 | return this.values[0] * this.values[3] - this.values[2] * this.values[1] 80 | } 81 | 82 | setIdentity(): mat2 { 83 | this.values[0] = 1 84 | this.values[1] = 0 85 | this.values[2] = 0 86 | this.values[3] = 1 87 | 88 | return this 89 | } 90 | 91 | transpose(): mat2 { 92 | const temp = this.values[1] 93 | 94 | this.values[1] = this.values[2] 95 | this.values[2] = temp 96 | 97 | return this 98 | } 99 | 100 | inverse(): mat2 { 101 | let det = this.determinant() 102 | 103 | if (!det) { return null } 104 | 105 | det = 1.0 / det 106 | 107 | const a11 = this.values[0] 108 | 109 | this.values[0] = det * (this.values[3]) 110 | this.values[1] = det * (-this.values[1]) 111 | this.values[2] = det * (-this.values[2]) 112 | this.values[3] = det * a11 113 | 114 | return this 115 | } 116 | 117 | multiply(matrix: mat2): mat2 { 118 | const a11 = this.values[0] 119 | const a12 = this.values[1] 120 | const a21 = this.values[2] 121 | const a22 = this.values[3] 122 | 123 | this.values[0] = a11 * matrix.at(0) + a12 * matrix.at(2) 124 | this.values[1] = a11 * matrix.at(1) + a12 * matrix.at(3) 125 | this.values[2] = a21 * matrix.at(0) + a22 * matrix.at(2) 126 | this.values[3] = a21 * matrix.at(1) + a22 * matrix.at(3) 127 | 128 | return this 129 | } 130 | 131 | rotate(angle: number): mat2 { 132 | const a11 = this.values[0] 133 | const a12 = this.values[1] 134 | const a21 = this.values[2] 135 | const a22 = this.values[3] 136 | 137 | const sin = Math.sin(angle) 138 | const cos = Math.cos(angle) 139 | 140 | this.values[0] = a11 * cos + a12 * sin 141 | this.values[1] = a11 * -sin + a12 * cos 142 | this.values[2] = a21 * cos + a22 * sin 143 | this.values[3] = a21 * -sin + a22 * cos 144 | 145 | return this 146 | } 147 | 148 | multiplyVec2(vector: vec2, result: vec2): vec2 { 149 | const x = vector.x 150 | const y = vector.y 151 | 152 | if (result) { 153 | result.xy = [ 154 | x * this.values[0] + y * this.values[1], 155 | x * this.values[2] + y * this.values[3], 156 | ] 157 | 158 | return result 159 | } else { 160 | return new vec2([ 161 | x * this.values[0] + y * this.values[1], 162 | x * this.values[2] + y * this.values[3], 163 | ]) 164 | } 165 | } 166 | 167 | scale(vector: vec2): mat2 { 168 | const a11 = this.values[0] 169 | const a12 = this.values[1] 170 | const a21 = this.values[2] 171 | const a22 = this.values[3] 172 | 173 | const x = vector.x 174 | const y = vector.y 175 | 176 | this.values[0] = a11 * x 177 | this.values[1] = a12 * y 178 | this.values[2] = a21 * x 179 | this.values[3] = a22 * y 180 | 181 | return this 182 | } 183 | 184 | static product(m1: mat2, m2: mat2, result: mat2): mat2 { 185 | const a11 = m1.at(0) 186 | const a12 = m1.at(1) 187 | const a21 = m1.at(2) 188 | const a22 = m1.at(3) 189 | 190 | if (result) { 191 | result.init([ 192 | a11 * m2.at(0) + a12 * m2.at(2), 193 | a11 * m2.at(1) + a12 * m2.at(3), 194 | a21 * m2.at(0) + a22 * m2.at(2), 195 | a21 * m2.at(1) + a22 * m2.at(3), 196 | ]) 197 | 198 | return result 199 | } else { 200 | return new mat2([ 201 | a11 * m2.at(0) + a12 * m2.at(2), 202 | a11 * m2.at(1) + a12 * m2.at(3), 203 | a21 * m2.at(0) + a22 * m2.at(2), 204 | a21 * m2.at(1) + a22 * m2.at(3), 205 | ]) 206 | } 207 | } 208 | 209 | } 210 | -------------------------------------------------------------------------------- /src/mat3.ts: -------------------------------------------------------------------------------- 1 | import mat4 from './mat4' 2 | import quat from './quat' 3 | import vec2 from './vec2' 4 | import vec3 from './vec3' 5 | 6 | import { epsilon } from './constants' 7 | 8 | export default class mat3 { 9 | 10 | constructor(values?: number[]) { 11 | if (values !== undefined) { 12 | this.init(values) 13 | } 14 | } 15 | 16 | private values = new Float32Array(9) 17 | 18 | static readonly identity = new mat3().setIdentity() 19 | 20 | at(index: number): number { 21 | return this.values[index] 22 | } 23 | 24 | init(values: number[]): mat3 { 25 | for (let i = 0; i < 9; i++) { 26 | this.values[i] = values[i] 27 | } 28 | 29 | return this 30 | } 31 | 32 | reset(): void { 33 | for (let i = 0; i < 9; i++) { 34 | this.values[i] = 0 35 | } 36 | } 37 | 38 | copy(dest?: mat3): mat3 { 39 | if (!dest) { dest = new mat3() } 40 | 41 | for (let i = 0; i < 9; i++) { 42 | dest.values[i] = this.values[i] 43 | } 44 | 45 | return dest 46 | } 47 | 48 | all(): number[] { 49 | const data: number[] = [] 50 | for (let i = 0; i < 9; i++) { 51 | data[i] = this.values[i] 52 | } 53 | 54 | return data 55 | } 56 | 57 | row(index: number): number[] { 58 | return [ 59 | this.values[index * 3 + 0], 60 | this.values[index * 3 + 1], 61 | this.values[index * 3 + 2], 62 | ] 63 | } 64 | 65 | col(index: number): number[] { 66 | return [ 67 | this.values[index], 68 | this.values[index + 3], 69 | this.values[index + 6], 70 | ] 71 | } 72 | 73 | equals(matrix: mat3, threshold = epsilon): boolean { 74 | for (let i = 0; i < 9; i++) { 75 | if (Math.abs(this.values[i] - matrix.at(i)) > threshold) { 76 | return false 77 | } 78 | } 79 | 80 | return true 81 | } 82 | 83 | determinant(): number { 84 | const a00 = this.values[0] 85 | const a01 = this.values[1] 86 | const a02 = this.values[2] 87 | const a10 = this.values[3] 88 | const a11 = this.values[4] 89 | const a12 = this.values[5] 90 | const a20 = this.values[6] 91 | const a21 = this.values[7] 92 | const a22 = this.values[8] 93 | 94 | const det01 = a22 * a11 - a12 * a21 95 | const det11 = -a22 * a10 + a12 * a20 96 | const det21 = a21 * a10 - a11 * a20 97 | 98 | return a00 * det01 + a01 * det11 + a02 * det21 99 | } 100 | 101 | setIdentity(): mat3 { 102 | this.values[0] = 1 103 | this.values[1] = 0 104 | this.values[2] = 0 105 | this.values[3] = 0 106 | this.values[4] = 1 107 | this.values[5] = 0 108 | this.values[6] = 0 109 | this.values[7] = 0 110 | this.values[8] = 1 111 | 112 | return this 113 | } 114 | 115 | transpose(): mat3 { 116 | const temp01 = this.values[1] 117 | const temp02 = this.values[2] 118 | const temp12 = this.values[5] 119 | 120 | this.values[1] = this.values[3] 121 | this.values[2] = this.values[6] 122 | this.values[3] = temp01 123 | this.values[5] = this.values[7] 124 | this.values[6] = temp02 125 | this.values[7] = temp12 126 | 127 | return this 128 | } 129 | 130 | inverse(): mat3 { 131 | const a00 = this.values[0] 132 | const a01 = this.values[1] 133 | const a02 = this.values[2] 134 | const a10 = this.values[3] 135 | const a11 = this.values[4] 136 | const a12 = this.values[5] 137 | const a20 = this.values[6] 138 | const a21 = this.values[7] 139 | const a22 = this.values[8] 140 | 141 | const det01 = a22 * a11 - a12 * a21 142 | const det11 = -a22 * a10 + a12 * a20 143 | const det21 = a21 * a10 - a11 * a20 144 | 145 | let det = a00 * det01 + a01 * det11 + a02 * det21 146 | 147 | if (!det) { 148 | return null 149 | } 150 | 151 | det = 1.0 / det 152 | 153 | this.values[0] = det01 * det 154 | this.values[1] = (-a22 * a01 + a02 * a21) * det 155 | this.values[2] = (a12 * a01 - a02 * a11) * det 156 | this.values[3] = det11 * det 157 | this.values[4] = (a22 * a00 - a02 * a20) * det 158 | this.values[5] = (-a12 * a00 + a02 * a10) * det 159 | this.values[6] = det21 * det 160 | this.values[7] = (-a21 * a00 + a01 * a20) * det 161 | this.values[8] = (a11 * a00 - a01 * a10) * det 162 | 163 | return this 164 | } 165 | 166 | multiply(matrix: mat3): mat3 { 167 | const a00 = this.values[0] 168 | const a01 = this.values[1] 169 | const a02 = this.values[2] 170 | const a10 = this.values[3] 171 | const a11 = this.values[4] 172 | const a12 = this.values[5] 173 | const a20 = this.values[6] 174 | const a21 = this.values[7] 175 | const a22 = this.values[8] 176 | 177 | const b00 = matrix.at(0) 178 | const b01 = matrix.at(1) 179 | const b02 = matrix.at(2) 180 | const b10 = matrix.at(3) 181 | const b11 = matrix.at(4) 182 | const b12 = matrix.at(5) 183 | const b20 = matrix.at(6) 184 | const b21 = matrix.at(7) 185 | const b22 = matrix.at(8) 186 | 187 | this.values[0] = b00 * a00 + b01 * a10 + b02 * a20 188 | this.values[1] = b00 * a01 + b01 * a11 + b02 * a21 189 | this.values[2] = b00 * a02 + b01 * a12 + b02 * a22 190 | 191 | this.values[3] = b10 * a00 + b11 * a10 + b12 * a20 192 | this.values[4] = b10 * a01 + b11 * a11 + b12 * a21 193 | this.values[5] = b10 * a02 + b11 * a12 + b12 * a22 194 | 195 | this.values[6] = b20 * a00 + b21 * a10 + b22 * a20 196 | this.values[7] = b20 * a01 + b21 * a11 + b22 * a21 197 | this.values[8] = b20 * a02 + b21 * a12 + b22 * a22 198 | 199 | return this 200 | } 201 | 202 | multiplyVec2(vector: vec2, result: vec2): vec2 { 203 | const x = vector.x 204 | const y = vector.y 205 | 206 | if (result) { 207 | result.xy = [ 208 | x * this.values[0] + y * this.values[3] + this.values[6], 209 | x * this.values[1] + y * this.values[4] + this.values[7], 210 | ] 211 | 212 | return result 213 | } else { 214 | return new vec2([ 215 | x * this.values[0] + y * this.values[3] + this.values[6], 216 | x * this.values[1] + y * this.values[4] + this.values[7], 217 | ]) 218 | } 219 | } 220 | 221 | multiplyVec3(vector: vec3, result: vec3): vec3 { 222 | const x = vector.x 223 | const y = vector.y 224 | const z = vector.z 225 | 226 | if (result) { 227 | result.xyz = [ 228 | x * this.values[0] + y * this.values[3] + z * this.values[6], 229 | x * this.values[1] + y * this.values[4] + z * this.values[7], 230 | x * this.values[2] + y * this.values[5] + z * this.values[8], 231 | ] 232 | 233 | return result 234 | } else { 235 | return new vec3([ 236 | x * this.values[0] + y * this.values[3] + z * this.values[6], 237 | x * this.values[1] + y * this.values[4] + z * this.values[7], 238 | x * this.values[2] + y * this.values[5] + z * this.values[8], 239 | ]) 240 | } 241 | } 242 | 243 | toMat4(result: mat4): mat4 { 244 | if (result) { 245 | result.init([ 246 | this.values[0], 247 | this.values[1], 248 | this.values[2], 249 | 0, 250 | 251 | this.values[3], 252 | this.values[4], 253 | this.values[5], 254 | 0, 255 | 256 | this.values[6], 257 | this.values[7], 258 | this.values[8], 259 | 0, 260 | 261 | 0, 262 | 0, 263 | 0, 264 | 1, 265 | ]) 266 | 267 | return result 268 | } else { 269 | return new mat4([ 270 | this.values[0], 271 | this.values[1], 272 | this.values[2], 273 | 0, 274 | 275 | this.values[3], 276 | this.values[4], 277 | this.values[5], 278 | 0, 279 | 280 | this.values[6], 281 | this.values[7], 282 | this.values[8], 283 | 0, 284 | 285 | 0, 286 | 0, 287 | 0, 288 | 1, 289 | ]) 290 | } 291 | } 292 | 293 | toQuat(): quat { 294 | const m00 = this.values[0] 295 | const m01 = this.values[1] 296 | const m02 = this.values[2] 297 | const m10 = this.values[3] 298 | const m11 = this.values[4] 299 | const m12 = this.values[5] 300 | const m20 = this.values[6] 301 | const m21 = this.values[7] 302 | const m22 = this.values[8] 303 | 304 | const fourXSquaredMinus1 = m00 - m11 - m22 305 | const fourYSquaredMinus1 = m11 - m00 - m22 306 | const fourZSquaredMinus1 = m22 - m00 - m11 307 | const fourWSquaredMinus1 = m00 + m11 + m22 308 | 309 | let biggestIndex = 0 310 | 311 | let fourBiggestSquaredMinus1 = fourWSquaredMinus1 312 | 313 | if (fourXSquaredMinus1 > fourBiggestSquaredMinus1) { 314 | fourBiggestSquaredMinus1 = fourXSquaredMinus1 315 | biggestIndex = 1 316 | } 317 | 318 | if (fourYSquaredMinus1 > fourBiggestSquaredMinus1) { 319 | fourBiggestSquaredMinus1 = fourYSquaredMinus1 320 | biggestIndex = 2 321 | } 322 | 323 | if (fourZSquaredMinus1 > fourBiggestSquaredMinus1) { 324 | fourBiggestSquaredMinus1 = fourZSquaredMinus1 325 | biggestIndex = 3 326 | } 327 | 328 | const biggestVal = Math.sqrt(fourBiggestSquaredMinus1 + 1) * 0.5 329 | const mult = 0.25 / biggestVal 330 | 331 | const result = new quat() 332 | 333 | switch (biggestIndex) { 334 | case 0: 335 | 336 | result.w = biggestVal 337 | result.x = (m12 - m21) * mult 338 | result.y = (m20 - m02) * mult 339 | result.z = (m01 - m10) * mult 340 | 341 | break 342 | 343 | case 1: 344 | 345 | result.w = (m12 - m21) * mult 346 | result.x = biggestVal 347 | result.y = (m01 + m10) * mult 348 | result.z = (m20 + m02) * mult 349 | 350 | break 351 | 352 | case 2: 353 | 354 | result.w = (m20 - m02) * mult 355 | result.x = (m01 + m10) * mult 356 | result.y = biggestVal 357 | result.z = (m12 + m21) * mult 358 | 359 | break 360 | 361 | case 3: 362 | 363 | result.w = (m01 - m10) * mult 364 | result.x = (m20 + m02) * mult 365 | result.y = (m12 + m21) * mult 366 | result.z = biggestVal 367 | 368 | break 369 | } 370 | 371 | return result 372 | } 373 | 374 | rotate(angle: number, axis: vec3): mat3 { 375 | let x = axis.x 376 | let y = axis.y 377 | let z = axis.z 378 | 379 | let length = Math.sqrt(x * x + y * y + z * z) 380 | 381 | if (!length) { 382 | return null 383 | } 384 | 385 | if (length !== 1) { 386 | length = 1 / length 387 | x *= length 388 | y *= length 389 | z *= length 390 | } 391 | 392 | const s = Math.sin(angle) 393 | const c = Math.cos(angle) 394 | 395 | const t = 1.0 - c 396 | 397 | const a00 = this.values[0] 398 | const a01 = this.values[1] 399 | const a02 = this.values[2] 400 | const a10 = this.values[4] 401 | const a11 = this.values[5] 402 | const a12 = this.values[6] 403 | const a20 = this.values[8] 404 | const a21 = this.values[9] 405 | const a22 = this.values[10] 406 | 407 | const b00 = x * x * t + c 408 | const b01 = y * x * t + z * s 409 | const b02 = z * x * t - y * s 410 | const b10 = x * y * t - z * s 411 | const b11 = y * y * t + c 412 | const b12 = z * y * t + x * s 413 | const b20 = x * z * t + y * s 414 | const b21 = y * z * t - x * s 415 | const b22 = z * z * t + c 416 | 417 | this.values[0] = a00 * b00 + a10 * b01 + a20 * b02 418 | this.values[1] = a01 * b00 + a11 * b01 + a21 * b02 419 | this.values[2] = a02 * b00 + a12 * b01 + a22 * b02 420 | 421 | this.values[3] = a00 * b10 + a10 * b11 + a20 * b12 422 | this.values[4] = a01 * b10 + a11 * b11 + a21 * b12 423 | this.values[5] = a02 * b10 + a12 * b11 + a22 * b12 424 | 425 | this.values[6] = a00 * b20 + a10 * b21 + a20 * b22 426 | this.values[7] = a01 * b20 + a11 * b21 + a21 * b22 427 | this.values[8] = a02 * b20 + a12 * b21 + a22 * b22 428 | 429 | return this 430 | } 431 | 432 | static product(m1: mat3, m2: mat3, result: mat3): mat3 { 433 | const a00 = m1.at(0) 434 | const a01 = m1.at(1) 435 | const a02 = m1.at(2) 436 | const a10 = m1.at(3) 437 | const a11 = m1.at(4) 438 | const a12 = m1.at(5) 439 | const a20 = m1.at(6) 440 | const a21 = m1.at(7) 441 | const a22 = m1.at(8) 442 | 443 | const b00 = m2.at(0) 444 | const b01 = m2.at(1) 445 | const b02 = m2.at(2) 446 | const b10 = m2.at(3) 447 | const b11 = m2.at(4) 448 | const b12 = m2.at(5) 449 | const b20 = m2.at(6) 450 | const b21 = m2.at(7) 451 | const b22 = m2.at(8) 452 | 453 | if (result) { 454 | result.init([ 455 | b00 * a00 + b01 * a10 + b02 * a20, 456 | b00 * a01 + b01 * a11 + b02 * a21, 457 | b00 * a02 + b01 * a12 + b02 * a22, 458 | 459 | b10 * a00 + b11 * a10 + b12 * a20, 460 | b10 * a01 + b11 * a11 + b12 * a21, 461 | b10 * a02 + b11 * a12 + b12 * a22, 462 | 463 | b20 * a00 + b21 * a10 + b22 * a20, 464 | b20 * a01 + b21 * a11 + b22 * a21, 465 | b20 * a02 + b21 * a12 + b22 * a22, 466 | ]) 467 | 468 | return result 469 | } else { 470 | return new mat3([ 471 | b00 * a00 + b01 * a10 + b02 * a20, 472 | b00 * a01 + b01 * a11 + b02 * a21, 473 | b00 * a02 + b01 * a12 + b02 * a22, 474 | 475 | b10 * a00 + b11 * a10 + b12 * a20, 476 | b10 * a01 + b11 * a11 + b12 * a21, 477 | b10 * a02 + b11 * a12 + b12 * a22, 478 | 479 | b20 * a00 + b21 * a10 + b22 * a20, 480 | b20 * a01 + b21 * a11 + b22 * a21, 481 | b20 * a02 + b21 * a12 + b22 * a22, 482 | ]) 483 | } 484 | } 485 | 486 | } 487 | -------------------------------------------------------------------------------- /src/mat4.ts: -------------------------------------------------------------------------------- 1 | import mat3 from './mat3' 2 | import vec3 from './vec3' 3 | import vec4 from './vec4' 4 | 5 | import { epsilon } from './constants' 6 | 7 | export default class mat4 { 8 | 9 | constructor(values?: number[]) { 10 | if (values !== undefined) { 11 | this.init(values) 12 | } 13 | } 14 | 15 | private values = new Float32Array(16) 16 | 17 | static readonly identity = new mat4().setIdentity() 18 | 19 | at(index: number): number { 20 | return this.values[index] 21 | } 22 | 23 | init(values: number[]): mat4 { 24 | for (let i = 0; i < 16; i++) { 25 | this.values[i] = values[i] 26 | } 27 | 28 | return this 29 | } 30 | 31 | reset(): void { 32 | for (let i = 0; i < 16; i++) { 33 | this.values[i] = 0 34 | } 35 | } 36 | 37 | copy(dest?: mat4): mat4 { 38 | if (!dest) { dest = new mat4() } 39 | 40 | for (let i = 0; i < 16; i++) { 41 | dest.values[i] = this.values[i] 42 | } 43 | 44 | return dest 45 | } 46 | 47 | all(): number[] { 48 | const data: number[] = [] 49 | for (let i = 0; i < 16; i++) { 50 | data[i] = this.values[i] 51 | } 52 | 53 | return data 54 | } 55 | 56 | row(index: number): number[] { 57 | return [ 58 | this.values[index * 4 + 0], 59 | this.values[index * 4 + 1], 60 | this.values[index * 4 + 2], 61 | this.values[index * 4 + 3], 62 | ] 63 | } 64 | 65 | col(index: number): number[] { 66 | return [ 67 | this.values[index], 68 | this.values[index + 4], 69 | this.values[index + 8], 70 | this.values[index + 12], 71 | ] 72 | } 73 | 74 | equals(matrix: mat4, threshold = epsilon): boolean { 75 | for (let i = 0; i < 16; i++) { 76 | if (Math.abs(this.values[i] - matrix.at(i)) > threshold) { 77 | return false 78 | } 79 | } 80 | 81 | return true 82 | } 83 | 84 | determinant(): number { 85 | const a00 = this.values[0] 86 | const a01 = this.values[1] 87 | const a02 = this.values[2] 88 | const a03 = this.values[3] 89 | const a10 = this.values[4] 90 | const a11 = this.values[5] 91 | const a12 = this.values[6] 92 | const a13 = this.values[7] 93 | const a20 = this.values[8] 94 | const a21 = this.values[9] 95 | const a22 = this.values[10] 96 | const a23 = this.values[11] 97 | const a30 = this.values[12] 98 | const a31 = this.values[13] 99 | const a32 = this.values[14] 100 | const a33 = this.values[15] 101 | 102 | const det00 = a00 * a11 - a01 * a10 103 | const det01 = a00 * a12 - a02 * a10 104 | const det02 = a00 * a13 - a03 * a10 105 | const det03 = a01 * a12 - a02 * a11 106 | const det04 = a01 * a13 - a03 * a11 107 | const det05 = a02 * a13 - a03 * a12 108 | const det06 = a20 * a31 - a21 * a30 109 | const det07 = a20 * a32 - a22 * a30 110 | const det08 = a20 * a33 - a23 * a30 111 | const det09 = a21 * a32 - a22 * a31 112 | const det10 = a21 * a33 - a23 * a31 113 | const det11 = a22 * a33 - a23 * a32 114 | 115 | return (det00 * det11 - det01 * det10 + det02 * det09 + det03 * det08 - det04 * det07 + det05 * det06) 116 | } 117 | 118 | setIdentity(): mat4 { 119 | this.values[0] = 1 120 | this.values[1] = 0 121 | this.values[2] = 0 122 | this.values[3] = 0 123 | this.values[4] = 0 124 | this.values[5] = 1 125 | this.values[6] = 0 126 | this.values[7] = 0 127 | this.values[8] = 0 128 | this.values[9] = 0 129 | this.values[10] = 1 130 | this.values[11] = 0 131 | this.values[12] = 0 132 | this.values[13] = 0 133 | this.values[14] = 0 134 | this.values[15] = 1 135 | 136 | return this 137 | } 138 | 139 | transpose(): mat4 { 140 | const temp01 = this.values[1] 141 | const temp02 = this.values[2] 142 | const temp03 = this.values[3] 143 | const temp12 = this.values[6] 144 | const temp13 = this.values[7] 145 | const temp23 = this.values[11] 146 | 147 | this.values[1] = this.values[4] 148 | this.values[2] = this.values[8] 149 | this.values[3] = this.values[12] 150 | this.values[4] = temp01 151 | this.values[6] = this.values[9] 152 | this.values[7] = this.values[13] 153 | this.values[8] = temp02 154 | this.values[9] = temp12 155 | this.values[11] = this.values[14] 156 | this.values[12] = temp03 157 | this.values[13] = temp13 158 | this.values[14] = temp23 159 | 160 | return this 161 | } 162 | 163 | inverse(): mat4 { 164 | const a00 = this.values[0] 165 | const a01 = this.values[1] 166 | const a02 = this.values[2] 167 | const a03 = this.values[3] 168 | const a10 = this.values[4] 169 | const a11 = this.values[5] 170 | const a12 = this.values[6] 171 | const a13 = this.values[7] 172 | const a20 = this.values[8] 173 | const a21 = this.values[9] 174 | const a22 = this.values[10] 175 | const a23 = this.values[11] 176 | const a30 = this.values[12] 177 | const a31 = this.values[13] 178 | const a32 = this.values[14] 179 | const a33 = this.values[15] 180 | 181 | const det00 = a00 * a11 - a01 * a10 182 | const det01 = a00 * a12 - a02 * a10 183 | const det02 = a00 * a13 - a03 * a10 184 | const det03 = a01 * a12 - a02 * a11 185 | const det04 = a01 * a13 - a03 * a11 186 | const det05 = a02 * a13 - a03 * a12 187 | const det06 = a20 * a31 - a21 * a30 188 | const det07 = a20 * a32 - a22 * a30 189 | const det08 = a20 * a33 - a23 * a30 190 | const det09 = a21 * a32 - a22 * a31 191 | const det10 = a21 * a33 - a23 * a31 192 | const det11 = a22 * a33 - a23 * a32 193 | 194 | let det = (det00 * det11 - det01 * det10 + det02 * det09 + det03 * det08 - det04 * det07 + det05 * det06) 195 | 196 | if (!det) { 197 | return null 198 | } 199 | 200 | det = 1.0 / det 201 | 202 | this.values[0] = (a11 * det11 - a12 * det10 + a13 * det09) * det 203 | this.values[1] = (-a01 * det11 + a02 * det10 - a03 * det09) * det 204 | this.values[2] = (a31 * det05 - a32 * det04 + a33 * det03) * det 205 | this.values[3] = (-a21 * det05 + a22 * det04 - a23 * det03) * det 206 | this.values[4] = (-a10 * det11 + a12 * det08 - a13 * det07) * det 207 | this.values[5] = (a00 * det11 - a02 * det08 + a03 * det07) * det 208 | this.values[6] = (-a30 * det05 + a32 * det02 - a33 * det01) * det 209 | this.values[7] = (a20 * det05 - a22 * det02 + a23 * det01) * det 210 | this.values[8] = (a10 * det10 - a11 * det08 + a13 * det06) * det 211 | this.values[9] = (-a00 * det10 + a01 * det08 - a03 * det06) * det 212 | this.values[10] = (a30 * det04 - a31 * det02 + a33 * det00) * det 213 | this.values[11] = (-a20 * det04 + a21 * det02 - a23 * det00) * det 214 | this.values[12] = (-a10 * det09 + a11 * det07 - a12 * det06) * det 215 | this.values[13] = (a00 * det09 - a01 * det07 + a02 * det06) * det 216 | this.values[14] = (-a30 * det03 + a31 * det01 - a32 * det00) * det 217 | this.values[15] = (a20 * det03 - a21 * det01 + a22 * det00) * det 218 | 219 | return this 220 | } 221 | 222 | multiply(matrix: mat4): mat4 { 223 | const a00 = this.values[0] 224 | const a01 = this.values[1] 225 | const a02 = this.values[2] 226 | const a03 = this.values[3] 227 | const a10 = this.values[4] 228 | const a11 = this.values[5] 229 | const a12 = this.values[6] 230 | const a13 = this.values[7] 231 | const a20 = this.values[8] 232 | const a21 = this.values[9] 233 | const a22 = this.values[10] 234 | const a23 = this.values[11] 235 | const a30 = this.values[12] 236 | const a31 = this.values[13] 237 | const a32 = this.values[14] 238 | const a33 = this.values[15] 239 | 240 | let b0 = matrix.at(0) 241 | let b1 = matrix.at(1) 242 | let b2 = matrix.at(2) 243 | let b3 = matrix.at(3) 244 | 245 | this.values[0] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30 246 | this.values[1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31 247 | this.values[2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32 248 | this.values[3] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33 249 | 250 | b0 = matrix.at(4) 251 | b1 = matrix.at(5) 252 | b2 = matrix.at(6) 253 | b3 = matrix.at(7) 254 | 255 | this.values[4] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30 256 | this.values[5] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31 257 | this.values[6] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32 258 | this.values[7] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33 259 | 260 | b0 = matrix.at(8) 261 | b1 = matrix.at(9) 262 | b2 = matrix.at(10) 263 | b3 = matrix.at(11) 264 | 265 | this.values[8] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30 266 | this.values[9] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31 267 | this.values[10] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32 268 | this.values[11] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33 269 | 270 | b0 = matrix.at(12) 271 | b1 = matrix.at(13) 272 | b2 = matrix.at(14) 273 | b3 = matrix.at(15) 274 | 275 | this.values[12] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30 276 | this.values[13] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31 277 | this.values[14] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32 278 | this.values[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33 279 | 280 | return this 281 | } 282 | 283 | multiplyVec3(vector: vec3): vec3 { 284 | const x = vector.x 285 | const y = vector.y 286 | const z = vector.z 287 | 288 | return new vec3([ 289 | this.values[0] * x + this.values[4] * y + this.values[8] * z + this.values[12], 290 | this.values[1] * x + this.values[5] * y + this.values[9] * z + this.values[13], 291 | this.values[2] * x + this.values[6] * y + this.values[10] * z + this.values[14], 292 | ]) 293 | } 294 | 295 | multiplyVec4(vector: vec4, dest?: vec4): vec4 { 296 | if (!dest) { dest = new vec4() } 297 | 298 | const x = vector.x 299 | const y = vector.y 300 | const z = vector.z 301 | const w = vector.w 302 | 303 | dest.x = this.values[0] * x + this.values[4] * y + this.values[8] * z + this.values[12] * w 304 | dest.y = this.values[1] * x + this.values[5] * y + this.values[9] * z + this.values[13] * w 305 | dest.z = this.values[2] * x + this.values[6] * y + this.values[10] * z + this.values[14] * w 306 | dest.w = this.values[3] * x + this.values[7] * y + this.values[11] * z + this.values[15] * w 307 | 308 | return dest 309 | } 310 | 311 | toMat3(): mat3 { 312 | return new mat3([ 313 | this.values[0], 314 | this.values[1], 315 | this.values[2], 316 | this.values[4], 317 | this.values[5], 318 | this.values[6], 319 | this.values[8], 320 | this.values[9], 321 | this.values[10], 322 | ]) 323 | } 324 | 325 | toInverseMat3(): mat3 { 326 | const a00 = this.values[0] 327 | const a01 = this.values[1] 328 | const a02 = this.values[2] 329 | const a10 = this.values[4] 330 | const a11 = this.values[5] 331 | const a12 = this.values[6] 332 | const a20 = this.values[8] 333 | const a21 = this.values[9] 334 | const a22 = this.values[10] 335 | 336 | const det01 = a22 * a11 - a12 * a21 337 | const det11 = -a22 * a10 + a12 * a20 338 | const det21 = a21 * a10 - a11 * a20 339 | 340 | let det = a00 * det01 + a01 * det11 + a02 * det21 341 | 342 | if (!det) { 343 | return null 344 | } 345 | 346 | det = 1.0 / det 347 | 348 | return new mat3([ 349 | det01 * det, 350 | (-a22 * a01 + a02 * a21) * det, 351 | (a12 * a01 - a02 * a11) * det, 352 | det11 * det, 353 | (a22 * a00 - a02 * a20) * det, 354 | (-a12 * a00 + a02 * a10) * det, 355 | det21 * det, 356 | (-a21 * a00 + a01 * a20) * det, 357 | (a11 * a00 - a01 * a10) * det, 358 | ]) 359 | } 360 | 361 | translate(vector: vec3): mat4 { 362 | const x = vector.x 363 | const y = vector.y 364 | const z = vector.z 365 | 366 | this.values[12] += this.values[0] * x + this.values[4] * y + this.values[8] * z 367 | this.values[13] += this.values[1] * x + this.values[5] * y + this.values[9] * z 368 | this.values[14] += this.values[2] * x + this.values[6] * y + this.values[10] * z 369 | this.values[15] += this.values[3] * x + this.values[7] * y + this.values[11] * z 370 | 371 | return this 372 | } 373 | 374 | scale(vector: vec3): mat4 { 375 | const x = vector.x 376 | const y = vector.y 377 | const z = vector.z 378 | 379 | this.values[0] *= x 380 | this.values[1] *= x 381 | this.values[2] *= x 382 | this.values[3] *= x 383 | 384 | this.values[4] *= y 385 | this.values[5] *= y 386 | this.values[6] *= y 387 | this.values[7] *= y 388 | 389 | this.values[8] *= z 390 | this.values[9] *= z 391 | this.values[10] *= z 392 | this.values[11] *= z 393 | 394 | return this 395 | } 396 | 397 | rotate(angle: number, axis: vec3): mat4 { 398 | let x = axis.x 399 | let y = axis.y 400 | let z = axis.z 401 | 402 | let length = Math.sqrt(x * x + y * y + z * z) 403 | 404 | if (!length) { 405 | return null 406 | } 407 | 408 | if (length !== 1) { 409 | length = 1 / length 410 | x *= length 411 | y *= length 412 | z *= length 413 | } 414 | 415 | const s = Math.sin(angle) 416 | const c = Math.cos(angle) 417 | 418 | const t = 1.0 - c 419 | 420 | const a00 = this.values[0] 421 | const a01 = this.values[1] 422 | const a02 = this.values[2] 423 | const a03 = this.values[3] 424 | 425 | const a10 = this.values[4] 426 | const a11 = this.values[5] 427 | const a12 = this.values[6] 428 | const a13 = this.values[7] 429 | 430 | const a20 = this.values[8] 431 | const a21 = this.values[9] 432 | const a22 = this.values[10] 433 | const a23 = this.values[11] 434 | 435 | const b00 = x * x * t + c 436 | const b01 = y * x * t + z * s 437 | const b02 = z * x * t - y * s 438 | 439 | const b10 = x * y * t - z * s 440 | const b11 = y * y * t + c 441 | const b12 = z * y * t + x * s 442 | 443 | const b20 = x * z * t + y * s 444 | const b21 = y * z * t - x * s 445 | const b22 = z * z * t + c 446 | 447 | this.values[0] = a00 * b00 + a10 * b01 + a20 * b02 448 | this.values[1] = a01 * b00 + a11 * b01 + a21 * b02 449 | this.values[2] = a02 * b00 + a12 * b01 + a22 * b02 450 | this.values[3] = a03 * b00 + a13 * b01 + a23 * b02 451 | 452 | this.values[4] = a00 * b10 + a10 * b11 + a20 * b12 453 | this.values[5] = a01 * b10 + a11 * b11 + a21 * b12 454 | this.values[6] = a02 * b10 + a12 * b11 + a22 * b12 455 | this.values[7] = a03 * b10 + a13 * b11 + a23 * b12 456 | 457 | this.values[8] = a00 * b20 + a10 * b21 + a20 * b22 458 | this.values[9] = a01 * b20 + a11 * b21 + a21 * b22 459 | this.values[10] = a02 * b20 + a12 * b21 + a22 * b22 460 | this.values[11] = a03 * b20 + a13 * b21 + a23 * b22 461 | 462 | return this 463 | } 464 | 465 | static frustum(left: number, right: number, bottom: number, top: number, near: number, far: number): mat4 { 466 | const rl = (right - left) 467 | const tb = (top - bottom) 468 | const fn = (far - near) 469 | 470 | return new mat4([ 471 | (near * 2) / rl, 472 | 0, 473 | 0, 474 | 0, 475 | 476 | 0, 477 | (near * 2) / tb, 478 | 0, 479 | 0, 480 | 481 | (right + left) / rl, 482 | (top + bottom) / tb, 483 | -(far + near) / fn, 484 | -1, 485 | 486 | 0, 487 | 0, 488 | -(far * near * 2) / fn, 489 | 0, 490 | ]) 491 | } 492 | 493 | static perspective(fov: number, aspect: number, near: number, far: number): mat4 { 494 | const top = near * Math.tan(fov * Math.PI / 360.0) 495 | const right = top * aspect 496 | 497 | return mat4.frustum(-right, right, -top, top, near, far) 498 | } 499 | 500 | static orthographic(left: number, right: number, bottom: number, top: number, near: number, far: number): mat4 { 501 | const rl = (right - left) 502 | const tb = (top - bottom) 503 | const fn = (far - near) 504 | 505 | return new mat4([ 506 | 2 / rl, 507 | 0, 508 | 0, 509 | 0, 510 | 511 | 0, 512 | 2 / tb, 513 | 0, 514 | 0, 515 | 516 | 0, 517 | 0, 518 | -2 / fn, 519 | 0, 520 | 521 | -(left + right) / rl, 522 | -(top + bottom) / tb, 523 | -(far + near) / fn, 524 | 1, 525 | ]) 526 | } 527 | 528 | static lookAt(position: vec3, target: vec3, up: vec3 = vec3.up): mat4 { 529 | if (position.equals(target)) { 530 | return this.identity 531 | } 532 | 533 | const z = vec3.difference(position, target).normalize() 534 | 535 | const x = vec3.cross(up, z).normalize() 536 | const y = vec3.cross(z, x).normalize() 537 | 538 | return new mat4([ 539 | x.x, 540 | y.x, 541 | z.x, 542 | 0, 543 | 544 | x.y, 545 | y.y, 546 | z.y, 547 | 0, 548 | 549 | x.z, 550 | y.z, 551 | z.z, 552 | 0, 553 | 554 | -vec3.dot(x, position), 555 | -vec3.dot(y, position), 556 | -vec3.dot(z, position), 557 | 1, 558 | ]) 559 | } 560 | 561 | static product(m1: mat4, m2: mat4, result: mat4): mat4 { 562 | const a00 = m1.at(0) 563 | const a01 = m1.at(1) 564 | const a02 = m1.at(2) 565 | const a03 = m1.at(3) 566 | const a10 = m1.at(4) 567 | const a11 = m1.at(5) 568 | const a12 = m1.at(6) 569 | const a13 = m1.at(7) 570 | const a20 = m1.at(8) 571 | const a21 = m1.at(9) 572 | const a22 = m1.at(10) 573 | const a23 = m1.at(11) 574 | const a30 = m1.at(12) 575 | const a31 = m1.at(13) 576 | const a32 = m1.at(14) 577 | const a33 = m1.at(15) 578 | 579 | const b00 = m2.at(0) 580 | const b01 = m2.at(1) 581 | const b02 = m2.at(2) 582 | const b03 = m2.at(3) 583 | const b10 = m2.at(4) 584 | const b11 = m2.at(5) 585 | const b12 = m2.at(6) 586 | const b13 = m2.at(7) 587 | const b20 = m2.at(8) 588 | const b21 = m2.at(9) 589 | const b22 = m2.at(10) 590 | const b23 = m2.at(11) 591 | const b30 = m2.at(12) 592 | const b31 = m2.at(13) 593 | const b32 = m2.at(14) 594 | const b33 = m2.at(15) 595 | 596 | if (result) { 597 | result.init([ 598 | b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30, 599 | b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31, 600 | b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32, 601 | b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33, 602 | 603 | b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30, 604 | b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31, 605 | b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32, 606 | b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33, 607 | 608 | b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30, 609 | b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31, 610 | b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32, 611 | b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33, 612 | 613 | b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30, 614 | b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31, 615 | b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32, 616 | b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33, 617 | ]) 618 | 619 | return result 620 | } else { 621 | return new mat4([ 622 | b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30, 623 | b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31, 624 | b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32, 625 | b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33, 626 | 627 | b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30, 628 | b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31, 629 | b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32, 630 | b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33, 631 | 632 | b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30, 633 | b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31, 634 | b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32, 635 | b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33, 636 | 637 | b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30, 638 | b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31, 639 | b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32, 640 | b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33, 641 | ]) 642 | } 643 | } 644 | 645 | } 646 | -------------------------------------------------------------------------------- /src/quat.ts: -------------------------------------------------------------------------------- 1 | import mat3 from './mat3' 2 | import mat4 from './mat4' 3 | import vec3 from './vec3' 4 | 5 | import { epsilon } from './constants' 6 | 7 | export default class quat { 8 | 9 | get x(): number { 10 | return this.values[0] 11 | } 12 | 13 | get y(): number { 14 | return this.values[1] 15 | } 16 | 17 | get z(): number { 18 | return this.values[2] 19 | } 20 | 21 | get w(): number { 22 | return this.values[3] 23 | } 24 | 25 | get xy(): [number, number] { 26 | return [ 27 | this.values[0], 28 | this.values[1], 29 | ] 30 | } 31 | 32 | get xyz(): [number, number, number] { 33 | return [ 34 | this.values[0], 35 | this.values[1], 36 | this.values[2], 37 | ] 38 | } 39 | 40 | get xyzw(): [number, number, number, number] { 41 | return [ 42 | this.values[0], 43 | this.values[1], 44 | this.values[2], 45 | this.values[3], 46 | ] 47 | } 48 | 49 | set x(value: number) { 50 | this.values[0] = value 51 | } 52 | 53 | set y(value: number) { 54 | this.values[1] = value 55 | } 56 | 57 | set z(value: number) { 58 | this.values[2] = value 59 | } 60 | 61 | set w(value: number) { 62 | this.values[3] = value 63 | } 64 | 65 | set xy(values: [number, number]) { 66 | this.values[0] = values[0] 67 | this.values[1] = values[1] 68 | } 69 | 70 | set xyz(values: [number, number, number]) { 71 | this.values[0] = values[0] 72 | this.values[1] = values[1] 73 | this.values[2] = values[2] 74 | } 75 | 76 | set xyzw(values: [number, number, number, number]) { 77 | this.values[0] = values[0] 78 | this.values[1] = values[1] 79 | this.values[2] = values[2] 80 | this.values[3] = values[3] 81 | } 82 | 83 | constructor(values?: [number, number, number, number]) { 84 | if (values !== undefined) { 85 | this.xyzw = values 86 | } 87 | } 88 | 89 | private values = new Float32Array(4) 90 | 91 | static readonly identity = new quat().setIdentity() 92 | 93 | at(index: number): number { 94 | return this.values[index] 95 | } 96 | 97 | reset(): void { 98 | for (let i = 0; i < 4; i++) { 99 | this.values[i] = 0 100 | } 101 | } 102 | 103 | copy(dest?: quat): quat { 104 | if (!dest) { dest = new quat() } 105 | 106 | for (let i = 0; i < 4; i++) { 107 | dest.values[i] = this.values[i] 108 | } 109 | 110 | return dest 111 | } 112 | 113 | roll(): number { 114 | const x = this.x 115 | const y = this.y 116 | const z = this.z 117 | const w = this.w 118 | 119 | return Math.atan2(2.0 * (x * y + w * z), w * w + x * x - y * y - z * z) 120 | } 121 | 122 | pitch(): number { 123 | const x = this.x 124 | const y = this.y 125 | const z = this.z 126 | const w = this.w 127 | 128 | return Math.atan2(2.0 * (y * z + w * x), w * w - x * x - y * y + z * z) 129 | } 130 | 131 | yaw(): number { 132 | return Math.asin(2.0 * (this.x * this.z - this.w * this.y)) 133 | } 134 | 135 | equals(vector: quat, threshold = epsilon): boolean { 136 | for (let i = 0; i < 4; i++) { 137 | if (Math.abs(this.values[i] - vector.at(i)) > threshold) { 138 | return false 139 | } 140 | } 141 | 142 | return true 143 | } 144 | 145 | setIdentity(): quat { 146 | this.x = 0 147 | this.y = 0 148 | this.z = 0 149 | this.w = 1 150 | 151 | return this 152 | } 153 | 154 | calculateW(): quat { 155 | const x = this.x 156 | const y = this.y 157 | const z = this.z 158 | 159 | this.w = -(Math.sqrt(Math.abs(1.0 - x * x - y * y - z * z))) 160 | 161 | return this 162 | } 163 | 164 | inverse(): quat { 165 | const dot = quat.dot(this, this) 166 | 167 | if (!dot) { 168 | this.xyzw = [0, 0, 0, 0] 169 | 170 | return this 171 | } 172 | 173 | const invDot = dot ? 1.0 / dot : 0 174 | 175 | this.x *= -invDot 176 | this.y *= -invDot 177 | this.z *= -invDot 178 | this.w *= invDot 179 | 180 | return this 181 | } 182 | 183 | conjugate(): quat { 184 | this.values[0] *= -1 185 | this.values[1] *= -1 186 | this.values[2] *= -1 187 | 188 | return this 189 | } 190 | 191 | length(): number { 192 | const x = this.x 193 | const y = this.y 194 | const z = this.z 195 | const w = this.w 196 | 197 | return Math.sqrt(x * x + y * y + z * z + w * w) 198 | } 199 | 200 | normalize(dest?: quat): quat { 201 | if (!dest) { dest = this } 202 | 203 | const x = this.x 204 | const y = this.y 205 | const z = this.z 206 | const w = this.w 207 | 208 | let length = Math.sqrt(x * x + y * y + z * z + w * w) 209 | 210 | if (!length) { 211 | dest.x = 0 212 | dest.y = 0 213 | dest.z = 0 214 | dest.w = 0 215 | 216 | return dest 217 | } 218 | 219 | length = 1 / length 220 | 221 | dest.x = x * length 222 | dest.y = y * length 223 | dest.z = z * length 224 | dest.w = w * length 225 | 226 | return dest 227 | } 228 | 229 | add(other: quat): quat { 230 | for (let i = 0; i < 4; i++) { 231 | this.values[i] += other.at(i) 232 | } 233 | 234 | return this 235 | } 236 | 237 | multiply(other: quat): quat { 238 | const q1x = this.values[0] 239 | const q1y = this.values[1] 240 | const q1z = this.values[2] 241 | const q1w = this.values[3] 242 | 243 | const q2x = other.x 244 | const q2y = other.y 245 | const q2z = other.z 246 | const q2w = other.w 247 | 248 | this.x = q1x * q2w + q1w * q2x + q1y * q2z - q1z * q2y 249 | this.y = q1y * q2w + q1w * q2y + q1z * q2x - q1x * q2z 250 | this.z = q1z * q2w + q1w * q2z + q1x * q2y - q1y * q2x 251 | this.w = q1w * q2w - q1x * q2x - q1y * q2y - q1z * q2z 252 | 253 | return this 254 | } 255 | 256 | multiplyVec3(vector: vec3, dest?: vec3): vec3 { 257 | if (!dest) { dest = new vec3() } 258 | 259 | const x = vector.x 260 | const y = vector.y 261 | const z = vector.z 262 | 263 | const qx = this.x 264 | const qy = this.y 265 | const qz = this.z 266 | const qw = this.w 267 | 268 | const ix = qw * x + qy * z - qz * y 269 | const iy = qw * y + qz * x - qx * z 270 | const iz = qw * z + qx * y - qy * x 271 | const iw = -qx * x - qy * y - qz * z 272 | 273 | dest.x = ix * qw + iw * -qx + iy * -qz - iz * -qy 274 | dest.y = iy * qw + iw * -qy + iz * -qx - ix * -qz 275 | dest.z = iz * qw + iw * -qz + ix * -qy - iy * -qx 276 | 277 | return dest 278 | } 279 | 280 | toMat3(dest?: mat3): mat3 { 281 | if (!dest) { dest = new mat3() } 282 | 283 | const x = this.x 284 | const y = this.y 285 | const z = this.z 286 | const w = this.w 287 | 288 | const x2 = x + x 289 | const y2 = y + y 290 | const z2 = z + z 291 | 292 | const xx = x * x2 293 | const xy = x * y2 294 | const xz = x * z2 295 | const yy = y * y2 296 | const yz = y * z2 297 | const zz = z * z2 298 | const wx = w * x2 299 | const wy = w * y2 300 | const wz = w * z2 301 | 302 | dest.init([ 303 | 1 - (yy + zz), 304 | xy + wz, 305 | xz - wy, 306 | 307 | xy - wz, 308 | 1 - (xx + zz), 309 | yz + wx, 310 | 311 | xz + wy, 312 | yz - wx, 313 | 1 - (xx + yy), 314 | ]) 315 | 316 | return dest 317 | } 318 | 319 | toMat4(dest?: mat4): mat4 { 320 | if (!dest) { dest = new mat4() } 321 | 322 | const x = this.x 323 | const y = this.y 324 | const z = this.z 325 | const w = this.w 326 | 327 | const x2 = x + x 328 | const y2 = y + y 329 | const z2 = z + z 330 | 331 | const xx = x * x2 332 | const xy = x * y2 333 | const xz = x * z2 334 | const yy = y * y2 335 | const yz = y * z2 336 | const zz = z * z2 337 | const wx = w * x2 338 | const wy = w * y2 339 | const wz = w * z2 340 | 341 | dest.init([ 342 | 1 - (yy + zz), 343 | xy + wz, 344 | xz - wy, 345 | 0, 346 | 347 | xy - wz, 348 | 1 - (xx + zz), 349 | yz + wx, 350 | 0, 351 | 352 | xz + wy, 353 | yz - wx, 354 | 1 - (xx + yy), 355 | 0, 356 | 357 | 0, 358 | 0, 359 | 0, 360 | 1, 361 | ]) 362 | 363 | return dest 364 | } 365 | 366 | static dot(q1: quat, q2: quat): number { 367 | return q1.x * q2.x + q1.y * q2.y + q1.z * q2.z + q1.w * q2.w 368 | } 369 | 370 | static sum(q1: quat, q2: quat, dest?: quat): quat { 371 | if (!dest) { dest = new quat() } 372 | 373 | dest.x = q1.x + q2.x 374 | dest.y = q1.y + q2.y 375 | dest.z = q1.z + q2.z 376 | dest.w = q1.w + q2.w 377 | 378 | return dest 379 | } 380 | 381 | static product(q1: quat, q2: quat, dest?: quat): quat { 382 | if (!dest) { dest = new quat() } 383 | 384 | const q1x = q1.x 385 | const q1y = q1.y 386 | const q1z = q1.z 387 | const q1w = q1.w 388 | 389 | const q2x = q2.x 390 | const q2y = q2.y 391 | const q2z = q2.z 392 | const q2w = q2.w 393 | 394 | dest.x = q1x * q2w + q1w * q2x + q1y * q2z - q1z * q2y 395 | dest.y = q1y * q2w + q1w * q2y + q1z * q2x - q1x * q2z 396 | dest.z = q1z * q2w + q1w * q2z + q1x * q2y - q1y * q2x 397 | dest.w = q1w * q2w - q1x * q2x - q1y * q2y - q1z * q2z 398 | 399 | return dest 400 | } 401 | 402 | static cross(q1: quat, q2: quat, dest?: quat): quat { 403 | if (!dest) { dest = new quat() } 404 | 405 | const q1x = q1.x 406 | const q1y = q1.y 407 | const q1z = q1.z 408 | const q1w = q1.w 409 | 410 | const q2x = q2.x 411 | const q2y = q2.y 412 | const q2z = q2.z 413 | const q2w = q2.w 414 | 415 | dest.x = q1w * q2z + q1z * q2w + q1x * q2y - q1y * q2x 416 | dest.y = q1w * q2w - q1x * q2x - q1y * q2y - q1z * q2z 417 | dest.z = q1w * q2x + q1x * q2w + q1y * q2z - q1z * q2y 418 | dest.w = q1w * q2y + q1y * q2w + q1z * q2x - q1x * q2z 419 | 420 | return dest 421 | } 422 | 423 | static shortMix(q1: quat, q2: quat, time: number, dest?: quat): quat { 424 | if (!dest) { dest = new quat() } 425 | 426 | if (time <= 0.0) { 427 | dest.xyzw = q1.xyzw 428 | 429 | return dest 430 | } else if (time >= 1.0) { 431 | dest.xyzw = q2.xyzw 432 | 433 | return dest 434 | } 435 | 436 | let cos = quat.dot(q1, q2) 437 | const q2a = q2.copy() 438 | 439 | if (cos < 0.0) { 440 | q2a.inverse() 441 | cos = -cos 442 | } 443 | 444 | let k0: number 445 | let k1: number 446 | 447 | if (cos > 0.9999) { 448 | k0 = 1 - time 449 | k1 = 0 + time 450 | } else { 451 | const sin: number = Math.sqrt(1 - cos * cos) 452 | const angle: number = Math.atan2(sin, cos) 453 | 454 | const oneOverSin: number = 1 / sin 455 | 456 | k0 = Math.sin((1 - time) * angle) * oneOverSin 457 | k1 = Math.sin((0 + time) * angle) * oneOverSin 458 | } 459 | 460 | dest.x = k0 * q1.x + k1 * q2a.x 461 | dest.y = k0 * q1.y + k1 * q2a.y 462 | dest.z = k0 * q1.z + k1 * q2a.z 463 | dest.w = k0 * q1.w + k1 * q2a.w 464 | 465 | return dest 466 | } 467 | 468 | static mix(q1: quat, q2: quat, time: number, dest?: quat): quat { 469 | if (!dest) { dest = new quat() } 470 | 471 | const cosHalfTheta = q1.x * q2.x + q1.y * q2.y + q1.z * q2.z + q1.w * q2.w 472 | 473 | if (Math.abs(cosHalfTheta) >= 1.0) { 474 | dest.xyzw = q1.xyzw 475 | 476 | return dest 477 | } 478 | 479 | const halfTheta = Math.acos(cosHalfTheta) 480 | const sinHalfTheta = Math.sqrt(1.0 - cosHalfTheta * cosHalfTheta) 481 | 482 | if (Math.abs(sinHalfTheta) < 0.001) { 483 | dest.x = q1.x * 0.5 + q2.x * 0.5 484 | dest.y = q1.y * 0.5 + q2.y * 0.5 485 | dest.z = q1.z * 0.5 + q2.z * 0.5 486 | dest.w = q1.w * 0.5 + q2.w * 0.5 487 | 488 | return dest 489 | } 490 | 491 | const ratioA = Math.sin((1 - time) * halfTheta) / sinHalfTheta 492 | const ratioB = Math.sin(time * halfTheta) / sinHalfTheta 493 | 494 | dest.x = q1.x * ratioA + q2.x * ratioB 495 | dest.y = q1.y * ratioA + q2.y * ratioB 496 | dest.z = q1.z * ratioA + q2.z * ratioB 497 | dest.w = q1.w * ratioA + q2.w * ratioB 498 | 499 | return dest 500 | } 501 | 502 | static fromAxisAngle(axis: vec3, angle: number, dest?: quat): quat { 503 | if (!dest) { dest = new quat() } 504 | 505 | angle *= 0.5 506 | const sin = Math.sin(angle) 507 | 508 | dest.x = axis.x * sin 509 | dest.y = axis.y * sin 510 | dest.z = axis.z * sin 511 | dest.w = Math.cos(angle) 512 | 513 | return dest 514 | } 515 | 516 | } 517 | -------------------------------------------------------------------------------- /src/tsm.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012, 2018 Matthias Ferch 3 | * 4 | * Project homepage: https://github.com/matthiasferch/tsm 5 | * 6 | * This software is provided 'as-is', without any express or implied 7 | * warranty. In no event will the authors be held liable for any damages 8 | * arising from the use of this software. 9 | * 10 | * Permission is granted to anyone to use this software for any purpose, 11 | * including commercial applications, and to alter it and redistribute it 12 | * freely, subject to the following restrictions: 13 | * 14 | * 1. The origin of this software must not be misrepresented; you must not 15 | * claim that you wrote the original software. If you use this software 16 | * in a product, an acknowledgment in the product documentation would be 17 | * appreciated but is not required. 18 | * 19 | * 2. Altered source versions must be plainly marked as such, and must not 20 | * be misrepresented as being the original software. 21 | * 22 | * 3. This notice may not be removed or altered from any source 23 | * distribution. 24 | */ 25 | 26 | import mat2 from './mat2' 27 | import mat3 from './mat3' 28 | import mat4 from './mat4' 29 | import quat from './quat' 30 | import vec2 from './vec2' 31 | import vec3 from './vec3' 32 | import vec4 from './vec4' 33 | 34 | export default { 35 | vec2, vec3, vec4, 36 | mat2, mat3, mat4, 37 | quat, 38 | } 39 | -------------------------------------------------------------------------------- /src/vec2.ts: -------------------------------------------------------------------------------- 1 | import mat2 from './mat2' 2 | import mat3 from './mat3' 3 | import vec3 from './vec3' 4 | 5 | import { epsilon } from './constants' 6 | 7 | export default class vec2 { 8 | 9 | get x(): number { 10 | return this.values[0] 11 | } 12 | 13 | get y(): number { 14 | return this.values[1] 15 | } 16 | 17 | get xy(): [number, number] { 18 | return [ 19 | this.values[0], 20 | this.values[1], 21 | ] 22 | } 23 | 24 | set x(value: number) { 25 | this.values[0] = value 26 | } 27 | 28 | set y(value: number) { 29 | this.values[1] = value 30 | } 31 | 32 | set xy(values: [number, number]) { 33 | this.values[0] = values[0] 34 | this.values[1] = values[1] 35 | } 36 | 37 | constructor(values?: [number, number]) { 38 | if (values !== undefined) { 39 | this.xy = values 40 | } 41 | } 42 | 43 | private values = new Float32Array(2) 44 | 45 | static readonly zero = new vec2([0, 0]) 46 | static readonly one = new vec2([1, 1]) 47 | 48 | at(index: number): number { 49 | return this.values[index] 50 | } 51 | 52 | reset(): void { 53 | this.x = 0 54 | this.y = 0 55 | } 56 | 57 | copy(dest?: vec2): vec2 { 58 | if (!dest) { dest = new vec2() } 59 | 60 | dest.x = this.x 61 | dest.y = this.y 62 | 63 | return dest 64 | } 65 | 66 | negate(dest?: vec2): vec2 { 67 | if (!dest) { dest = this } 68 | 69 | dest.x = -this.x 70 | dest.y = -this.y 71 | 72 | return dest 73 | } 74 | 75 | equals(vector: vec2, threshold = epsilon): boolean { 76 | if (Math.abs(this.x - vector.x) > threshold) { 77 | return false 78 | } 79 | 80 | if (Math.abs(this.y - vector.y) > threshold) { 81 | return false 82 | } 83 | 84 | return true 85 | } 86 | 87 | length(): number { 88 | return Math.sqrt(this.squaredLength()) 89 | } 90 | 91 | squaredLength(): number { 92 | const x = this.x 93 | const y = this.y 94 | 95 | return (x * x + y * y) 96 | } 97 | 98 | add(vector: vec2): vec2 { 99 | this.x += vector.x 100 | this.y += vector.y 101 | 102 | return this 103 | } 104 | 105 | subtract(vector: vec2): vec2 { 106 | this.x -= vector.x 107 | this.y -= vector.y 108 | 109 | return this 110 | } 111 | 112 | multiply(vector: vec2): vec2 { 113 | this.x *= vector.x 114 | this.y *= vector.y 115 | 116 | return this 117 | } 118 | 119 | divide(vector: vec2): vec2 { 120 | this.x /= vector.x 121 | this.y /= vector.y 122 | 123 | return this 124 | } 125 | 126 | scale(value: number, dest?: vec2): vec2 { 127 | if (!dest) { dest = this } 128 | 129 | dest.x *= value 130 | dest.y *= value 131 | 132 | return dest 133 | } 134 | 135 | normalize(dest?: vec2): vec2 { 136 | if (!dest) { dest = this } 137 | 138 | let length = this.length() 139 | 140 | if (length === 1) { 141 | return this 142 | } 143 | 144 | if (length === 0) { 145 | dest.x = 0 146 | dest.y = 0 147 | 148 | return dest 149 | } 150 | 151 | length = 1.0 / length 152 | 153 | dest.x *= length 154 | dest.y *= length 155 | 156 | return dest 157 | } 158 | 159 | multiplyMat2(matrix: mat2, dest?: vec2): vec2 { 160 | if (!dest) { dest = this } 161 | 162 | return matrix.multiplyVec2(this, dest) 163 | } 164 | 165 | multiplyMat3(matrix: mat3, dest?: vec2): vec2 { 166 | if (!dest) { dest = this } 167 | 168 | return matrix.multiplyVec2(this, dest) 169 | } 170 | 171 | static cross(vector: vec2, vector2: vec2, dest?: vec3): vec3 { 172 | if (!dest) { dest = new vec3() } 173 | 174 | const x = vector.x 175 | const y = vector.y 176 | 177 | const x2 = vector2.x 178 | const y2 = vector2.y 179 | 180 | const z = x * y2 - y * x2 181 | 182 | dest.x = 0 183 | dest.y = 0 184 | dest.z = z 185 | 186 | return dest 187 | } 188 | 189 | static dot(vector: vec2, vector2: vec2): number { 190 | return (vector.x * vector2.x + vector.y * vector2.y) 191 | } 192 | 193 | static distance(vector: vec2, vector2: vec2): number { 194 | return Math.sqrt(this.squaredDistance(vector, vector2)) 195 | } 196 | 197 | static squaredDistance(vector: vec2, vector2: vec2): number { 198 | const x = vector2.x - vector.x 199 | const y = vector2.y - vector.y 200 | 201 | return (x * x + y * y) 202 | } 203 | 204 | static direction(vector: vec2, vector2: vec2, dest?: vec2): vec2 { 205 | if (!dest) { dest = new vec2() } 206 | 207 | const x = vector.x - vector2.x 208 | const y = vector.y - vector2.y 209 | 210 | let length = Math.sqrt(x * x + y * y) 211 | 212 | if (length === 0) { 213 | dest.x = 0 214 | dest.y = 0 215 | 216 | return dest 217 | } 218 | 219 | length = 1 / length 220 | 221 | dest.x = x * length 222 | dest.y = y * length 223 | 224 | return dest 225 | } 226 | 227 | static mix(vector: vec2, vector2: vec2, time: number, dest?: vec2): vec2 { 228 | if (!dest) { dest = new vec2() } 229 | 230 | const x = vector.x 231 | const y = vector.y 232 | 233 | const x2 = vector2.x 234 | const y2 = vector2.y 235 | 236 | dest.x = x + time * (x2 - x) 237 | dest.y = y + time * (y2 - y) 238 | 239 | return dest 240 | } 241 | 242 | static sum(vector: vec2, vector2: vec2, dest?: vec2): vec2 { 243 | if (!dest) { dest = new vec2() } 244 | 245 | dest.x = vector.x + vector2.x 246 | dest.y = vector.y + vector2.y 247 | 248 | return dest 249 | } 250 | 251 | static difference(vector: vec2, vector2: vec2, dest?: vec2): vec2 { 252 | if (!dest) { dest = new vec2() } 253 | 254 | dest.x = vector.x - vector2.x 255 | dest.y = vector.y - vector2.y 256 | 257 | return dest 258 | } 259 | 260 | static product(vector: vec2, vector2: vec2, dest?: vec2): vec2 { 261 | if (!dest) { dest = new vec2() } 262 | 263 | dest.x = vector.x * vector2.x 264 | dest.y = vector.y * vector2.y 265 | 266 | return dest 267 | } 268 | 269 | static quotient(vector: vec2, vector2: vec2, dest?: vec2): vec2 { 270 | if (!dest) { dest = new vec2() } 271 | 272 | dest.x = vector.x / vector2.x 273 | dest.y = vector.y / vector2.y 274 | 275 | return dest 276 | } 277 | 278 | } 279 | -------------------------------------------------------------------------------- /src/vec3.ts: -------------------------------------------------------------------------------- 1 | import mat3 from './mat3' 2 | import quat from './quat' 3 | 4 | import { epsilon } from './constants' 5 | 6 | export default class vec3 { 7 | 8 | get x(): number { 9 | return this.values[0] 10 | } 11 | 12 | get y(): number { 13 | return this.values[1] 14 | } 15 | 16 | get z(): number { 17 | return this.values[2] 18 | } 19 | 20 | get xy(): [number, number] { 21 | return [ 22 | this.values[0], 23 | this.values[1], 24 | ] 25 | } 26 | 27 | get xyz(): [number, number, number] { 28 | return [ 29 | this.values[0], 30 | this.values[1], 31 | this.values[2], 32 | ] 33 | } 34 | 35 | set x(value: number) { 36 | this.values[0] = value 37 | } 38 | 39 | set y(value: number) { 40 | this.values[1] = value 41 | } 42 | 43 | set z(value: number) { 44 | this.values[2] = value 45 | } 46 | 47 | set xy(values: [number, number]) { 48 | this.values[0] = values[0] 49 | this.values[1] = values[1] 50 | } 51 | 52 | set xyz(values: [number, number, number]) { 53 | this.values[0] = values[0] 54 | this.values[1] = values[1] 55 | this.values[2] = values[2] 56 | } 57 | 58 | constructor(values?: [number, number, number]) { 59 | if (values !== undefined) { 60 | this.xyz = values 61 | } 62 | } 63 | 64 | private values = new Float32Array(3) 65 | 66 | static readonly zero = new vec3([0, 0, 0]) 67 | static readonly one = new vec3([1, 1, 1]) 68 | 69 | static readonly up = new vec3([0, 1, 0]) 70 | static readonly right = new vec3([1, 0, 0]) 71 | static readonly forward = new vec3([0, 0, 1]) 72 | 73 | at(index: number): number { 74 | return this.values[index] 75 | } 76 | 77 | reset(): void { 78 | this.x = 0 79 | this.y = 0 80 | this.z = 0 81 | } 82 | 83 | copy(dest?: vec3): vec3 { 84 | if (!dest) { dest = new vec3() } 85 | 86 | dest.x = this.x 87 | dest.y = this.y 88 | dest.z = this.z 89 | 90 | return dest 91 | } 92 | 93 | negate(dest?: vec3): vec3 { 94 | if (!dest) { dest = this } 95 | 96 | dest.x = -this.x 97 | dest.y = -this.y 98 | dest.z = -this.z 99 | 100 | return dest 101 | } 102 | 103 | equals(vector: vec3, threshold = epsilon): boolean { 104 | if (Math.abs(this.x - vector.x) > threshold) { 105 | return false 106 | } 107 | 108 | if (Math.abs(this.y - vector.y) > threshold) { 109 | return false 110 | } 111 | 112 | if (Math.abs(this.z - vector.z) > threshold) { 113 | return false 114 | } 115 | 116 | return true 117 | } 118 | 119 | length(): number { 120 | return Math.sqrt(this.squaredLength()) 121 | } 122 | 123 | squaredLength(): number { 124 | const x = this.x 125 | const y = this.y 126 | const z = this.z 127 | 128 | return (x * x + y * y + z * z) 129 | } 130 | 131 | add(vector: vec3): vec3 { 132 | this.x += vector.x 133 | this.y += vector.y 134 | this.z += vector.z 135 | 136 | return this 137 | } 138 | 139 | subtract(vector: vec3): vec3 { 140 | this.x -= vector.x 141 | this.y -= vector.y 142 | this.z -= vector.z 143 | 144 | return this 145 | } 146 | 147 | multiply(vector: vec3): vec3 { 148 | this.x *= vector.x 149 | this.y *= vector.y 150 | this.z *= vector.z 151 | 152 | return this 153 | } 154 | 155 | divide(vector: vec3): vec3 { 156 | this.x /= vector.x 157 | this.y /= vector.y 158 | this.z /= vector.z 159 | 160 | return this 161 | } 162 | 163 | scale(value: number, dest?: vec3): vec3 { 164 | if (!dest) { dest = this } 165 | 166 | dest.x *= value 167 | dest.y *= value 168 | dest.z *= value 169 | 170 | return dest 171 | } 172 | 173 | normalize(dest?: vec3): vec3 { 174 | if (!dest) { dest = this } 175 | 176 | let length = this.length() 177 | 178 | if (length === 1) { 179 | return this 180 | } 181 | 182 | if (length === 0) { 183 | dest.x = 0 184 | dest.y = 0 185 | dest.z = 0 186 | 187 | return dest 188 | } 189 | 190 | length = 1.0 / length 191 | 192 | dest.x *= length 193 | dest.y *= length 194 | dest.z *= length 195 | 196 | return dest 197 | } 198 | 199 | multiplyByMat3(matrix: mat3, dest?: vec3): vec3 { 200 | if (!dest) { dest = this } 201 | 202 | return matrix.multiplyVec3(this, dest) 203 | } 204 | 205 | multiplyByQuat(quaternion: quat, dest?: vec3): vec3 { 206 | if (!dest) { dest = this } 207 | 208 | return quaternion.multiplyVec3(this, dest) 209 | } 210 | 211 | toQuat(dest?: quat): quat { 212 | if (!dest) { dest = new quat() } 213 | 214 | const c = new vec3() 215 | const s = new vec3() 216 | 217 | c.x = Math.cos(this.x * 0.5) 218 | s.x = Math.sin(this.x * 0.5) 219 | 220 | c.y = Math.cos(this.y * 0.5) 221 | s.y = Math.sin(this.y * 0.5) 222 | 223 | c.z = Math.cos(this.z * 0.5) 224 | s.z = Math.sin(this.z * 0.5) 225 | 226 | dest.x = s.x * c.y * c.z - c.x * s.y * s.z 227 | dest.y = c.x * s.y * c.z + s.x * c.y * s.z 228 | dest.z = c.x * c.y * s.z - s.x * s.y * c.z 229 | dest.w = c.x * c.y * c.z + s.x * s.y * s.z 230 | 231 | return dest 232 | } 233 | 234 | static cross(vector: vec3, vector2: vec3, dest?: vec3): vec3 { 235 | if (!dest) { dest = new vec3() } 236 | 237 | const x = vector.x 238 | const y = vector.y 239 | const z = vector.z 240 | 241 | const x2 = vector2.x 242 | const y2 = vector2.y 243 | const z2 = vector2.z 244 | 245 | dest.x = y * z2 - z * y2 246 | dest.y = z * x2 - x * z2 247 | dest.z = x * y2 - y * x2 248 | 249 | return dest 250 | } 251 | 252 | static dot(vector: vec3, vector2: vec3): number { 253 | const x = vector.x 254 | const y = vector.y 255 | const z = vector.z 256 | 257 | const x2 = vector2.x 258 | const y2 = vector2.y 259 | const z2 = vector2.z 260 | 261 | return (x * x2 + y * y2 + z * z2) 262 | } 263 | 264 | static distance(vector: vec3, vector2: vec3): number { 265 | const x = vector2.x - vector.x 266 | const y = vector2.y - vector.y 267 | const z = vector2.z - vector.z 268 | 269 | return Math.sqrt(this.squaredDistance(vector, vector2)) 270 | } 271 | 272 | static squaredDistance(vector: vec3, vector2: vec3): number { 273 | const x = vector2.x - vector.x 274 | const y = vector2.y - vector.y 275 | const z = vector2.z - vector.z 276 | 277 | return (x * x + y * y + z * z) 278 | } 279 | 280 | static direction(vector: vec3, vector2: vec3, dest?: vec3): vec3 { 281 | if (!dest) { dest = new vec3() } 282 | 283 | const x = vector.x - vector2.x 284 | const y = vector.y - vector2.y 285 | const z = vector.z - vector2.z 286 | 287 | let length = Math.sqrt(x * x + y * y + z * z) 288 | 289 | if (length === 0) { 290 | dest.x = 0 291 | dest.y = 0 292 | dest.z = 0 293 | 294 | return dest 295 | } 296 | 297 | length = 1 / length 298 | 299 | dest.x = x * length 300 | dest.y = y * length 301 | dest.z = z * length 302 | 303 | return dest 304 | } 305 | 306 | static mix(vector: vec3, vector2: vec3, time: number, dest?: vec3): vec3 { 307 | if (!dest) { dest = new vec3() } 308 | 309 | dest.x = vector.x + time * (vector2.x - vector.x) 310 | dest.y = vector.y + time * (vector2.y - vector.y) 311 | dest.z = vector.z + time * (vector2.z - vector.z) 312 | 313 | return dest 314 | } 315 | 316 | static sum(vector: vec3, vector2: vec3, dest?: vec3): vec3 { 317 | if (!dest) { dest = new vec3() } 318 | 319 | dest.x = vector.x + vector2.x 320 | dest.y = vector.y + vector2.y 321 | dest.z = vector.z + vector2.z 322 | 323 | return dest 324 | } 325 | 326 | static difference(vector: vec3, vector2: vec3, dest?: vec3): vec3 { 327 | if (!dest) { dest = new vec3() } 328 | 329 | dest.x = vector.x - vector2.x 330 | dest.y = vector.y - vector2.y 331 | dest.z = vector.z - vector2.z 332 | 333 | return dest 334 | } 335 | 336 | static product(vector: vec3, vector2: vec3, dest?: vec3): vec3 { 337 | if (!dest) { dest = new vec3() } 338 | 339 | dest.x = vector.x * vector2.x 340 | dest.y = vector.y * vector2.y 341 | dest.z = vector.z * vector2.z 342 | 343 | return dest 344 | } 345 | 346 | static quotient(vector: vec3, vector2: vec3, dest?: vec3): vec3 { 347 | if (!dest) { dest = new vec3() } 348 | 349 | dest.x = vector.x / vector2.x 350 | dest.y = vector.y / vector2.y 351 | dest.z = vector.z / vector2.z 352 | 353 | return dest 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /src/vec4.ts: -------------------------------------------------------------------------------- 1 | import mat4 from './mat4' 2 | 3 | import { epsilon } from './constants' 4 | 5 | export default class vec4 { 6 | 7 | get x(): number { 8 | return this.values[0] 9 | } 10 | 11 | get y(): number { 12 | return this.values[1] 13 | } 14 | 15 | get z(): number { 16 | return this.values[2] 17 | } 18 | 19 | get w(): number { 20 | return this.values[3] 21 | } 22 | 23 | get xy(): [number, number] { 24 | return [ 25 | this.values[0], 26 | this.values[1], 27 | ] 28 | } 29 | 30 | get xyz(): [number, number, number] { 31 | return [ 32 | this.values[0], 33 | this.values[1], 34 | this.values[2], 35 | ] 36 | } 37 | 38 | get xyzw(): [number, number, number, number] { 39 | return [ 40 | this.values[0], 41 | this.values[1], 42 | this.values[2], 43 | this.values[3], 44 | ] 45 | } 46 | 47 | set x(value: number) { 48 | this.values[0] = value 49 | } 50 | 51 | set y(value: number) { 52 | this.values[1] = value 53 | } 54 | 55 | set z(value: number) { 56 | this.values[2] = value 57 | } 58 | 59 | set w(value: number) { 60 | this.values[3] = value 61 | } 62 | 63 | set xy(values: [number, number]) { 64 | this.values[0] = values[0] 65 | this.values[1] = values[1] 66 | } 67 | 68 | set xyz(values: [number, number, number]) { 69 | this.values[0] = values[0] 70 | this.values[1] = values[1] 71 | this.values[2] = values[2] 72 | } 73 | 74 | set xyzw(values: [number, number, number, number]) { 75 | this.values[0] = values[0] 76 | this.values[1] = values[1] 77 | this.values[2] = values[2] 78 | this.values[3] = values[3] 79 | } 80 | 81 | get r(): number { 82 | return this.values[0] 83 | } 84 | 85 | get g(): number { 86 | return this.values[1] 87 | } 88 | 89 | get b(): number { 90 | return this.values[2] 91 | } 92 | 93 | get a(): number { 94 | return this.values[3] 95 | } 96 | 97 | get rg(): [number, number] { 98 | return [ 99 | this.values[0], 100 | this.values[1], 101 | ] 102 | } 103 | 104 | get rgb(): [number, number, number] { 105 | return [ 106 | this.values[0], 107 | this.values[1], 108 | this.values[2], 109 | ] 110 | } 111 | 112 | get rgba(): [number, number, number, number] { 113 | return [ 114 | this.values[0], 115 | this.values[1], 116 | this.values[2], 117 | this.values[3], 118 | ] 119 | } 120 | 121 | set r(value: number) { 122 | this.values[0] = value 123 | } 124 | 125 | set g(value: number) { 126 | this.values[1] = value 127 | } 128 | 129 | set b(value: number) { 130 | this.values[2] = value 131 | } 132 | 133 | set a(value: number) { 134 | this.values[3] = value 135 | } 136 | 137 | set rg(values: [number, number]) { 138 | this.values[0] = values[0] 139 | this.values[1] = values[1] 140 | } 141 | 142 | set rgb(values: [number, number, number]) { 143 | this.values[0] = values[0] 144 | this.values[1] = values[1] 145 | this.values[2] = values[2] 146 | } 147 | 148 | set rgba(values: [number, number, number, number]) { 149 | this.values[0] = values[0] 150 | this.values[1] = values[1] 151 | this.values[2] = values[2] 152 | this.values[3] = values[3] 153 | } 154 | 155 | constructor(values?: [number, number, number, number]) { 156 | if (values !== undefined) { 157 | this.xyzw = values 158 | } 159 | } 160 | 161 | private values = new Float32Array(4) 162 | 163 | static readonly zero = new vec4([0, 0, 0, 1]) 164 | static readonly one = new vec4([1, 1, 1, 1]) 165 | 166 | at(index: number): number { 167 | return this.values[index] 168 | } 169 | 170 | reset(): void { 171 | this.x = 0 172 | this.y = 0 173 | this.z = 0 174 | this.w = 0 175 | } 176 | 177 | copy(dest?: vec4): vec4 { 178 | if (!dest) { dest = new vec4() } 179 | 180 | dest.x = this.x 181 | dest.y = this.y 182 | dest.z = this.z 183 | dest.w = this.w 184 | 185 | return dest 186 | } 187 | 188 | negate(dest?: vec4): vec4 { 189 | if (!dest) { dest = this } 190 | 191 | dest.x = -this.x 192 | dest.y = -this.y 193 | dest.z = -this.z 194 | dest.w = -this.w 195 | 196 | return dest 197 | } 198 | 199 | equals(vector: vec4, threshold = epsilon): boolean { 200 | if (Math.abs(this.x - vector.x) > threshold) { 201 | return false 202 | } 203 | 204 | if (Math.abs(this.y - vector.y) > threshold) { 205 | return false 206 | } 207 | 208 | if (Math.abs(this.z - vector.z) > threshold) { 209 | return false 210 | } 211 | 212 | if (Math.abs(this.w - vector.w) > threshold) { 213 | return false 214 | } 215 | 216 | return true 217 | } 218 | 219 | length(): number { 220 | return Math.sqrt(this.squaredLength()) 221 | } 222 | 223 | squaredLength(): number { 224 | const x = this.x 225 | const y = this.y 226 | const z = this.z 227 | const w = this.w 228 | 229 | return (x * x + y * y + z * z + w * w) 230 | } 231 | 232 | add(vector: vec4): vec4 { 233 | this.x += vector.x 234 | this.y += vector.y 235 | this.z += vector.z 236 | this.w += vector.w 237 | 238 | return this 239 | } 240 | 241 | subtract(vector: vec4): vec4 { 242 | this.x -= vector.x 243 | this.y -= vector.y 244 | this.z -= vector.z 245 | this.w -= vector.w 246 | 247 | return this 248 | } 249 | 250 | multiply(vector: vec4): vec4 { 251 | this.x *= vector.x 252 | this.y *= vector.y 253 | this.z *= vector.z 254 | this.w *= vector.w 255 | 256 | return this 257 | } 258 | 259 | divide(vector: vec4): vec4 { 260 | this.x /= vector.x 261 | this.y /= vector.y 262 | this.z /= vector.z 263 | this.w /= vector.w 264 | 265 | return this 266 | } 267 | 268 | scale(value: number, dest?: vec4): vec4 { 269 | if (!dest) { dest = this } 270 | 271 | dest.x *= value 272 | dest.y *= value 273 | dest.z *= value 274 | dest.w *= value 275 | 276 | return dest 277 | } 278 | 279 | normalize(dest?: vec4): vec4 { 280 | if (!dest) { dest = this } 281 | 282 | let length = this.length() 283 | 284 | if (length === 1) { 285 | return this 286 | } 287 | 288 | if (length === 0) { 289 | dest.x *= 0 290 | dest.y *= 0 291 | dest.z *= 0 292 | dest.w *= 0 293 | 294 | return dest 295 | } 296 | 297 | length = 1.0 / length 298 | 299 | dest.x *= length 300 | dest.y *= length 301 | dest.z *= length 302 | dest.w *= length 303 | 304 | return dest 305 | } 306 | 307 | multiplyMat4(matrix: mat4, dest?: vec4): vec4 { 308 | if (!dest) { dest = this } 309 | 310 | return matrix.multiplyVec4(this, dest) 311 | } 312 | 313 | static mix(vector: vec4, vector2: vec4, time: number, dest?: vec4): vec4 { 314 | if (!dest) { dest = new vec4() } 315 | 316 | dest.x = vector.x + time * (vector2.x - vector.x) 317 | dest.y = vector.y + time * (vector2.y - vector.y) 318 | dest.z = vector.z + time * (vector2.z - vector.z) 319 | dest.w = vector.w + time * (vector2.w - vector.w) 320 | 321 | return dest 322 | } 323 | 324 | static sum(vector: vec4, vector2: vec4, dest?: vec4): vec4 { 325 | if (!dest) { dest = new vec4() } 326 | 327 | dest.x = vector.x + vector2.x 328 | dest.y = vector.y + vector2.y 329 | dest.z = vector.z + vector2.z 330 | dest.w = vector.w + vector2.w 331 | 332 | return dest 333 | } 334 | 335 | static difference(vector: vec4, vector2: vec4, dest?: vec4): vec4 { 336 | if (!dest) { dest = new vec4() } 337 | 338 | dest.x = vector.x - vector2.x 339 | dest.y = vector.y - vector2.y 340 | dest.z = vector.z - vector2.z 341 | dest.w = vector.w - vector2.w 342 | 343 | return dest 344 | } 345 | 346 | static product(vector: vec4, vector2: vec4, dest?: vec4): vec4 { 347 | if (!dest) { dest = new vec4() } 348 | 349 | dest.x = vector.x * vector2.x 350 | dest.y = vector.y * vector2.y 351 | dest.z = vector.z * vector2.z 352 | dest.w = vector.w * vector2.w 353 | 354 | return dest 355 | } 356 | 357 | static quotient(vector: vec4, vector2: vec4, dest?: vec4): vec4 { 358 | if (!dest) { dest = new vec4() } 359 | 360 | dest.x = vector.x / vector2.x 361 | dest.y = vector.y / vector2.y 362 | dest.z = vector.z / vector2.z 363 | dest.w = vector.w / vector2.w 364 | 365 | return dest 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /test/mat2.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import 'mocha' 3 | 4 | import mat2 from '../src/mat2' 5 | 6 | import { epsilon } from '../src/constants' 7 | 8 | describe('mat2', () => { 9 | 10 | it('transposes', () => { 11 | const matrix = new mat2([ 12 | 1.0, 2.0, 13 | 3.0, 4.0, 14 | ]) 15 | 16 | matrix.transpose() 17 | 18 | expect(matrix.at(0)).to.equal(1.0) 19 | expect(matrix.at(1)).to.equal(3.0) 20 | expect(matrix.at(2)).to.equal(2.0) 21 | expect(matrix.at(3)).to.equal(4.0) 22 | }) 23 | 24 | it('inverses', () => { 25 | const matrix = new mat2([ 26 | 1.0, 2.0, 27 | 3.0, 4.0, 28 | ]) 29 | 30 | matrix.inverse() 31 | 32 | expect(matrix.at(0)).to.equal(-2.0) 33 | expect(matrix.at(1)).to.equal(1.0) 34 | expect(matrix.at(2)).to.equal(1.5) 35 | expect(matrix.at(3)).to.equal(-0.5) 36 | }) 37 | 38 | }) 39 | -------------------------------------------------------------------------------- /test/mat3.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import 'mocha' 3 | 4 | import mat3 from '../src/mat3' 5 | 6 | describe('mat3', () => { 7 | 8 | it('transposes', () => { 9 | const matrix = new mat3([ 10 | 1.0, 2.0, 3.0, 11 | 4.0, 5.0, 6.0, 12 | 7.0, 8.0, 9.0, 13 | 10.0, 11.0, 12.0, 14 | ]) 15 | 16 | matrix.transpose() 17 | 18 | expect(matrix.at(0)).to.equal(1.0) 19 | expect(matrix.at(1)).to.equal(4.0) 20 | expect(matrix.at(2)).to.equal(7.0) 21 | 22 | expect(matrix.at(3)).to.equal(2.0) 23 | expect(matrix.at(4)).to.equal(5.0) 24 | expect(matrix.at(5)).to.equal(8.0) 25 | 26 | expect(matrix.at(6)).to.equal(3.0) 27 | expect(matrix.at(7)).to.equal(6.0) 28 | expect(matrix.at(8)).to.equal(9.0) 29 | 30 | }) 31 | 32 | }) 33 | -------------------------------------------------------------------------------- /test/mat4.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import 'mocha' 3 | 4 | import mat4 from '../src/mat4' 5 | 6 | import { epsilon } from '../src/constants' 7 | 8 | describe('mat4', () => { 9 | 10 | it('transposes', () => { 11 | const matrix = new mat4([ 12 | 1.0, 2.0, 3.0, 4.0, 13 | 5.0, 6.0, 7.0, 8.0, 14 | 9.0, 10.0, 11.0, 12.0, 15 | 13.0, 14.0, 15.0, 16.0, 16 | ]) 17 | 18 | matrix.transpose() 19 | 20 | expect(matrix.at(0)).to.equal(1.0) 21 | expect(matrix.at(1)).to.equal(5.0) 22 | expect(matrix.at(2)).to.equal(9.0) 23 | expect(matrix.at(3)).to.equal(13.0) 24 | 25 | expect(matrix.at(4)).to.equal(2.0) 26 | expect(matrix.at(5)).to.equal(6.0) 27 | expect(matrix.at(6)).to.equal(10.0) 28 | expect(matrix.at(7)).to.equal(14.0) 29 | 30 | expect(matrix.at(8)).to.equal(3.0) 31 | expect(matrix.at(9)).to.equal(7.0) 32 | expect(matrix.at(10)).to.equal(11.0) 33 | expect(matrix.at(11)).to.equal(15.0) 34 | 35 | expect(matrix.at(12)).to.equal(4.0) 36 | expect(matrix.at(13)).to.equal(8.0) 37 | expect(matrix.at(14)).to.equal(12.0) 38 | expect(matrix.at(15)).to.equal(16.0) 39 | 40 | }) 41 | 42 | it('computes perspective projection', () => { 43 | const matrix = mat4.perspective(45, 1, 1, 100) 44 | 45 | expect(matrix.at(0)).to.be.approximately(2.414213, epsilon) 46 | expect(matrix.at(1)).to.equal(0.0) 47 | expect(matrix.at(2)).to.equal(0.0) 48 | expect(matrix.at(3)).to.equal(0.0) 49 | 50 | expect(matrix.at(4)).to.equal(0.0) 51 | expect(matrix.at(5)).to.be.approximately(2.414213, epsilon) 52 | expect(matrix.at(6)).to.equal(0.0) 53 | expect(matrix.at(7)).to.equal(0.0) 54 | 55 | expect(matrix.at(8)).to.equal(0.0) 56 | expect(matrix.at(9)).to.equal(0.0) 57 | expect(matrix.at(10)).to.be.approximately(-1.02020, epsilon) 58 | expect(matrix.at(11)).to.equal(-1.0) 59 | 60 | expect(matrix.at(12)).to.equal(0.0) 61 | expect(matrix.at(13)).to.equal(0.0) 62 | expect(matrix.at(14)).to.be.approximately(-2.02020, epsilon) 63 | expect(matrix.at(15)).to.equal(0.0) 64 | 65 | }) 66 | 67 | it('computes orthographic projection', () => { 68 | const matrix = mat4.orthographic(0, 800, 0, 600, 1, 100) 69 | 70 | expect(matrix.at(0)).to.be.approximately(0.002499, epsilon) 71 | expect(matrix.at(1)).to.equal(0.0) 72 | expect(matrix.at(2)).to.equal(0.0) 73 | expect(matrix.at(3)).to.equal(0.0) 74 | 75 | expect(matrix.at(4)).to.equal(0.0) 76 | expect(matrix.at(5)).to.be.approximately(0.003333, epsilon) 77 | expect(matrix.at(6)).to.equal(0.0) 78 | expect(matrix.at(7)).to.equal(0.0) 79 | 80 | expect(matrix.at(8)).to.equal(0.0) 81 | expect(matrix.at(9)).to.equal(0.0) 82 | expect(matrix.at(10)).to.be.approximately(-0.020202, epsilon) 83 | expect(matrix.at(11)).to.equal(0.0) 84 | 85 | expect(matrix.at(12)).to.equal(-1.0) 86 | expect(matrix.at(13)).to.equal(-1.0) 87 | expect(matrix.at(14)).to.be.approximately(-1.020202, epsilon) 88 | expect(matrix.at(15)).to.equal(1.0) 89 | 90 | }) 91 | 92 | }) 93 | -------------------------------------------------------------------------------- /test/quat.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import 'mocha' 3 | 4 | import quat from './../src/quat' 5 | 6 | import { epsilon } from '../src/constants' 7 | 8 | describe('quat', () => { 9 | 10 | it('resets', () => { 11 | const q = new quat([1.0, 2.0, 3.0, 4.0]) 12 | 13 | q.reset() 14 | 15 | expect(q.x).to.equal(0) 16 | expect(q.y).to.equal(0) 17 | expect(q.z).to.equal(0) 18 | }) 19 | 20 | it('copies', () => { 21 | const q1 = new quat([1.0, 2.0, 3.0, 4.0]) 22 | const q2 = q1.copy() 23 | 24 | expect(q2.x).to.equal(q1.x) 25 | expect(q2.y).to.equal(q1.y) 26 | expect(q2.z).to.equal(q1.z) 27 | expect(q2.w).to.equal(q1.w) 28 | }) 29 | 30 | it('compares', () => { 31 | const q1 = new quat([1.0, 2.0, 3.0, 4.0]) 32 | const q2 = new quat([1.0, 2.0, 3.0, 4.0]) 33 | const q3 = new quat([2.0, 3.0, 4.0, 5.0]) 34 | 35 | expect(q1.equals(q2)).to.equal(true) 36 | expect(q1.equals(q3)).to.equal(false) 37 | }) 38 | 39 | it('adds', () => { 40 | const q1 = new quat([1.0, 2.0, 3.0, 4.0]) 41 | const q2 = new quat([2.0, 3.0, 4.0, 5.0]) 42 | 43 | const result = q1.add(q2) 44 | 45 | expect(result.x).to.be.approximately(3.0, epsilon) 46 | expect(result.y).to.be.approximately(5.0, epsilon) 47 | expect(result.z).to.be.approximately(7.0, epsilon) 48 | expect(result.w).to.be.approximately(9.0, epsilon) 49 | }) 50 | 51 | it('multiplies', () => { 52 | const q1 = new quat([1.0, 3.0, 4.0, 5.0]) 53 | const q2 = new quat([5.0, 6.0, 7.0, 8.0]) 54 | 55 | const result = q1.multiply(q2) 56 | 57 | expect(result.x).to.be.approximately(30.0, epsilon) 58 | expect(result.y).to.be.approximately(67.0, epsilon) 59 | expect(result.z).to.be.approximately(58.0, epsilon) 60 | expect(result.w).to.be.approximately(-11.0, epsilon) 61 | }) 62 | 63 | it('normalizes', () => { 64 | const quaternion = new quat([1.0, 2.0, 3.0, 4.0]) 65 | 66 | quaternion.normalize() 67 | 68 | expect(quaternion.x).to.be.approximately(0.18257, epsilon) 69 | expect(quaternion.y).to.be.approximately(0.36515, epsilon) 70 | expect(quaternion.z).to.be.approximately(0.54772, epsilon) 71 | expect(quaternion.w).to.be.approximately(0.73029, epsilon) 72 | }) 73 | 74 | }) 75 | -------------------------------------------------------------------------------- /test/vec2.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import 'mocha' 3 | 4 | import vec2 from './../src/vec2' 5 | 6 | import { epsilon } from '../src/constants' 7 | 8 | describe('vec2', () => { 9 | 10 | it('resets', () => { 11 | const vector = new vec2([1.0, 2.0]) 12 | 13 | vector.reset() 14 | 15 | expect(vector.x).to.equal(0) 16 | expect(vector.y).to.equal(0) 17 | }) 18 | 19 | it('copies', () => { 20 | const vector1 = new vec2([1.0, 2.0]) 21 | const vector2 = vector1.copy() 22 | 23 | expect(vector2.x).to.equal(vector1.x) 24 | expect(vector2.y).to.equal(vector1.y) 25 | }) 26 | 27 | it('negates', () => { 28 | const vector = new vec2([1.0, 2.0]) 29 | 30 | vector.negate() 31 | 32 | expect(vector.x).to.equal(-1.0) 33 | expect(vector.y).to.equal(-2.0) 34 | }) 35 | 36 | it('compares', () => { 37 | const vector1 = new vec2([1.0, 2.0]) 38 | const vector2 = new vec2([1.0, 2.0]) 39 | const vector3 = new vec2([2.0, 3.0]) 40 | 41 | expect(vector1.equals(vector2)).to.equal(true) 42 | expect(vector1.equals(vector3)).to.equal(false) 43 | }) 44 | 45 | it('adds', () => { 46 | const vector1 = new vec2([1.0, 2.0]) 47 | const vector2 = new vec2([2.0, 3.0]) 48 | 49 | const result = vector1.add(vector2) 50 | 51 | expect(result.x).to.be.approximately(3.0, epsilon) 52 | expect(result.y).to.be.approximately(5.0, epsilon) 53 | }) 54 | 55 | it('subtracts', () => { 56 | const vector1 = new vec2([1.0, 2.0]) 57 | const vector2 = new vec2([2.0, 4.0]) 58 | 59 | const result = vector1.subtract(vector2) 60 | 61 | expect(result.x).to.be.approximately(-1.0, epsilon) 62 | expect(result.y).to.be.approximately(-2.0, epsilon) 63 | }) 64 | 65 | it('multiplies', () => { 66 | const vector1 = new vec2([2.0, 3.0]) 67 | const vector2 = new vec2([5.0, 6.0]) 68 | 69 | const result = vector1.multiply(vector2) 70 | 71 | expect(result.x).to.be.approximately(10.0, epsilon) 72 | expect(result.y).to.be.approximately(18.0, epsilon) 73 | }) 74 | 75 | it('divides', () => { 76 | const vector1 = new vec2([2.0, 3.0]) 77 | const vector2 = new vec2([5.0, 6.0]) 78 | 79 | const result = vector1.divide(vector2) 80 | 81 | expect(result.x).to.be.approximately(0.4, epsilon) 82 | expect(result.y).to.be.approximately(0.5, epsilon) 83 | }) 84 | 85 | it('scales', () => { 86 | const vector = new vec2([1.0, 2.0]) 87 | 88 | vector.scale(2.0) 89 | 90 | expect(vector.x).to.be.approximately(2.0, epsilon) 91 | expect(vector.y).to.be.approximately(4.0, epsilon) 92 | }) 93 | 94 | it('normalizes', () => { 95 | const vector = new vec2([1.0, 2.0]) 96 | 97 | vector.normalize() 98 | 99 | expect(vector.x).to.be.approximately(0.44721, epsilon) 100 | expect(vector.y).to.be.approximately(0.89443, epsilon) 101 | }) 102 | 103 | }) 104 | -------------------------------------------------------------------------------- /test/vec3.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import 'mocha' 3 | 4 | import vec3 from './../src/vec3' 5 | 6 | import { epsilon } from '../src/constants' 7 | 8 | describe('vec3', () => { 9 | 10 | it('resets', () => { 11 | const vector = new vec3([1.0, 2.0, 3.0]) 12 | 13 | vector.reset() 14 | 15 | expect(vector.x).to.equal(0) 16 | expect(vector.y).to.equal(0) 17 | expect(vector.z).to.equal(0) 18 | }) 19 | 20 | it('copies', () => { 21 | const vector1 = new vec3([1.0, 2.0, 3.0]) 22 | const vector2 = vector1.copy() 23 | 24 | expect(vector2.x).to.equal(vector1.x) 25 | expect(vector2.y).to.equal(vector1.y) 26 | expect(vector2.z).to.equal(vector1.z) 27 | }) 28 | 29 | it('negates', () => { 30 | const vector = new vec3([1.0, 2.0, 3.0]) 31 | 32 | vector.negate() 33 | 34 | expect(vector.x).to.equal(-1.0) 35 | expect(vector.y).to.equal(-2.0) 36 | expect(vector.z).to.equal(-3.0) 37 | }) 38 | 39 | it('compares', () => { 40 | const vector1 = new vec3([1.0, 2.0, 3.0]) 41 | const vector2 = new vec3([1.0, 2.0, 3.0]) 42 | const vector3 = new vec3([2.0, 3.0, 4.0]) 43 | 44 | expect(vector1.equals(vector2)).to.equal(true) 45 | expect(vector1.equals(vector3)).to.equal(false) 46 | }) 47 | 48 | it('adds', () => { 49 | const vector1 = new vec3([1.0, 2.0, 3.0]) 50 | const vector2 = new vec3([2.0, 3.0, 4.0]) 51 | 52 | const result = vector1.add(vector2) 53 | 54 | expect(result.x).to.be.approximately(3.0, epsilon) 55 | expect(result.y).to.be.approximately(5.0, epsilon) 56 | expect(result.z).to.be.approximately(7.0, epsilon) 57 | }) 58 | 59 | it('subtracts', () => { 60 | const vector1 = new vec3([1.0, 2.0, 3.0]) 61 | const vector2 = new vec3([2.0, 4.0, 6.0]) 62 | 63 | const result = vector1.subtract(vector2) 64 | 65 | expect(result.x).to.be.approximately(-1.0, epsilon) 66 | expect(result.y).to.be.approximately(-2.0, epsilon) 67 | expect(result.z).to.be.approximately(-3.0, epsilon) 68 | }) 69 | 70 | it('multiplies', () => { 71 | const vector1 = new vec3([2.0, 3.0, 4.0]) 72 | const vector2 = new vec3([5.0, 6.0, 7.0]) 73 | 74 | const result = vector1.multiply(vector2) 75 | 76 | expect(result.x).to.be.approximately(10.0, epsilon) 77 | expect(result.y).to.be.approximately(18.0, epsilon) 78 | expect(result.z).to.be.approximately(28.0, epsilon) 79 | }) 80 | 81 | it('divides', () => { 82 | const vector1 = new vec3([2.0, 3.0, 0.8]) 83 | const vector2 = new vec3([5.0, 6.0, 4.0]) 84 | 85 | const result = vector1.divide(vector2) 86 | 87 | expect(result.x).to.be.approximately(0.4, epsilon) 88 | expect(result.y).to.be.approximately(0.5, epsilon) 89 | expect(result.z).to.be.approximately(0.2, epsilon) 90 | }) 91 | 92 | it('scales', () => { 93 | const vector = new vec3([1.0, 2.0, 3.0]) 94 | 95 | vector.scale(2.0) 96 | 97 | expect(vector.x).to.be.approximately(2.0, epsilon) 98 | expect(vector.y).to.be.approximately(4.0, epsilon) 99 | expect(vector.z).to.be.approximately(6.0, epsilon) 100 | }) 101 | 102 | it('normalizes', () => { 103 | const vector = new vec3([1.0, 2.0, 3.0]) 104 | 105 | vector.normalize() 106 | 107 | expect(vector.x).to.be.approximately(0.26726, epsilon) 108 | expect(vector.y).to.be.approximately(0.53452, epsilon) 109 | expect(vector.z).to.be.approximately(0.80178, epsilon) 110 | }) 111 | 112 | }) 113 | -------------------------------------------------------------------------------- /test/vec4.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import 'mocha' 3 | 4 | import vec4 from './../src/vec4' 5 | 6 | import { epsilon } from '../src/constants' 7 | 8 | describe('vec4', () => { 9 | 10 | it('resets', () => { 11 | const vector = new vec4([1.0, 2.0, 3.0, 4.0]) 12 | 13 | vector.reset() 14 | 15 | expect(vector.x).to.equal(0) 16 | expect(vector.y).to.equal(0) 17 | expect(vector.z).to.equal(0) 18 | expect(vector.w).to.equal(0) 19 | }) 20 | 21 | it('copies', () => { 22 | const vector1 = new vec4([1.0, 2.0, 3.0, 4.0]) 23 | const vector2 = vector1.copy() 24 | 25 | expect(vector2.x).to.equal(vector1.x) 26 | expect(vector2.y).to.equal(vector1.y) 27 | expect(vector2.z).to.equal(vector1.z) 28 | expect(vector2.w).to.equal(vector1.w) 29 | }) 30 | 31 | it('negates', () => { 32 | const vector = new vec4([1.0, 2.0, 3.0, 4.0]) 33 | 34 | vector.negate() 35 | 36 | expect(vector.x).to.equal(-1.0) 37 | expect(vector.y).to.equal(-2.0) 38 | expect(vector.z).to.equal(-3.0) 39 | expect(vector.w).to.equal(-4.0) 40 | }) 41 | 42 | it('compares', () => { 43 | const vector1 = new vec4([1.0, 2.0, 3.0, 4.0]) 44 | const vector2 = new vec4([1.0, 2.0, 3.0, 4.0]) 45 | const vector3 = new vec4([2.0, 3.0, 4.0, 5.0]) 46 | 47 | expect(vector1.equals(vector2)).to.equal(true) 48 | expect(vector1.equals(vector3)).to.equal(false) 49 | }) 50 | 51 | it('adds', () => { 52 | const vector1 = new vec4([1.0, 2.0, 3.0, 4.0]) 53 | const vector2 = new vec4([2.0, 3.0, 4.0, 5.0]) 54 | 55 | const result = vector1.add(vector2) 56 | 57 | expect(result.x).to.be.approximately(3.0, epsilon) 58 | expect(result.y).to.be.approximately(5.0, epsilon) 59 | expect(result.z).to.be.approximately(7.0, epsilon) 60 | expect(result.w).to.be.approximately(9.0, epsilon) 61 | }) 62 | 63 | it('subtracts', () => { 64 | const vector1 = new vec4([1.0, 2.0, 3.0, 4.0]) 65 | const vector2 = new vec4([2.0, 4.0, 6.0, 8.0]) 66 | 67 | const result = vector1.subtract(vector2) 68 | 69 | expect(result.x).to.be.approximately(-1.0, epsilon) 70 | expect(result.y).to.be.approximately(-2.0, epsilon) 71 | expect(result.z).to.be.approximately(-3.0, epsilon) 72 | expect(result.w).to.be.approximately(-4.0, epsilon) 73 | }) 74 | 75 | it('multiplies', () => { 76 | const vector1 = new vec4([2.0, 3.0, 4.0, 5.0]) 77 | const vector2 = new vec4([5.0, 6.0, 7.0, 8.0]) 78 | 79 | const result = vector1.multiply(vector2) 80 | 81 | expect(result.x).to.be.approximately(10.0, epsilon) 82 | expect(result.y).to.be.approximately(18.0, epsilon) 83 | expect(result.z).to.be.approximately(28.0, epsilon) 84 | expect(result.w).to.be.approximately(40.0, epsilon) 85 | }) 86 | 87 | it('divides', () => { 88 | const vector1 = new vec4([2.0, 3.0, 0.8, 3.0]) 89 | const vector2 = new vec4([5.0, 6.0, 4.0, 2.0]) 90 | 91 | const result = vector1.divide(vector2) 92 | 93 | expect(result.x).to.be.approximately(0.4, epsilon) 94 | expect(result.y).to.be.approximately(0.5, epsilon) 95 | expect(result.z).to.be.approximately(0.2, epsilon) 96 | expect(result.w).to.be.approximately(1.5, epsilon) 97 | }) 98 | 99 | it('scales', () => { 100 | const vector = new vec4([1.0, 2.0, 3.0, 4.0]) 101 | 102 | vector.scale(2.0) 103 | 104 | expect(vector.x).to.be.approximately(2.0, epsilon) 105 | expect(vector.y).to.be.approximately(4.0, epsilon) 106 | expect(vector.z).to.be.approximately(6.0, epsilon) 107 | expect(vector.w).to.be.approximately(8.0, epsilon) 108 | }) 109 | 110 | it('normalizes', () => { 111 | const vector = new vec4([1.0, 2.0, 3.0, 4.0]) 112 | 113 | vector.normalize() 114 | 115 | expect(vector.x).to.be.approximately(0.18257, epsilon) 116 | expect(vector.y).to.be.approximately(0.36515, epsilon) 117 | expect(vector.z).to.be.approximately(0.54772, epsilon) 118 | expect(vector.w).to.be.approximately(0.73029, epsilon) 119 | }) 120 | 121 | }) 122 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "./src/**/*" 4 | ], 5 | "exclude": [ 6 | "./test/**/*.spec.ts" 7 | ], 8 | "compilerOptions": { 9 | "outDir": "./dist/", 10 | "module": "commonjs", 11 | "target": "es5", 12 | "sourceMap": true, 13 | } 14 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:latest", 3 | "rules": { 4 | "class-name": false, 5 | "prefer-const": true, 6 | "no-require-imports": true, 7 | "semicolon": [ 8 | true, 9 | "never" 10 | ], 11 | "quotemark": [ 12 | true, 13 | "single", 14 | "avoid-escape" 15 | ], 16 | "member-access": [ 17 | true, 18 | "no-public" 19 | ], 20 | "member-ordering": [ 21 | true, 22 | { 23 | "order": [ 24 | "constructor", 25 | "public-instance-field", 26 | "private-instance-field", 27 | "public-static-field", 28 | "private-static-field", 29 | "public-instance-method", 30 | "protected-instance-method", 31 | "private-instance-method", 32 | "public-static-method", 33 | "private-static-method" 34 | ] 35 | } 36 | ], 37 | "object-literal-sort-keys": false, 38 | "adjacent-overload-signatures": false, 39 | "no-implicit-dependencies": [ 40 | true, 41 | "dev" 42 | ] 43 | } 44 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: './src/tsm.ts', 5 | devtool: 'inline-source-map', 6 | module: { 7 | rules: [ 8 | { 9 | test: /\.ts$/, 10 | use: 'ts-loader', 11 | exclude: /node_modules/ 12 | } 13 | ] 14 | }, 15 | resolve: { 16 | extensions: ['.ts'] 17 | }, 18 | output: { 19 | library: 'tsm', 20 | filename: 'tsm.js', 21 | path: path.resolve(__dirname, 'dist') 22 | } 23 | }; --------------------------------------------------------------------------------