├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── commands.yml │ └── publish.yml ├── .gitignore ├── .jshintrc ├── .npmrc ├── HISTORY.md ├── README.md ├── index.d.ts ├── index.js ├── package.json ├── test ├── index.test-d.ts └── test.js └── wrapper.mjs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [22.x] 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v1 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | - run: npm install 25 | - run: npm test -------------------------------------------------------------------------------- /.github/workflows/commands.yml: -------------------------------------------------------------------------------- 1 | name: Repo Commands 2 | 3 | on: 4 | issue_comment: # Handle comment commands 5 | types: [created] 6 | pull_request: # Handle renamed PRs 7 | types: [edited] 8 | 9 | jobs: 10 | comment-trigger: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Check out repository 14 | uses: actions/checkout@v3 15 | - name: Run command handlers 16 | uses: PrismarineJS/prismarine-repo-actions@master 17 | with: 18 | # NOTE: You must specify a Personal Access Token (PAT) with repo access here. While you can use the default GITHUB_TOKEN, actions taken with it will not trigger other actions, so if you have a CI workflow, commits created by this action will not trigger it. 19 | token: ${{ secrets.PAT_PASSWORD }} 20 | # See `Options` section below for more info on these options 21 | install-command: npm install 22 | /fixlint.fix-command: npm run fix -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: npm-publish 2 | on: 3 | push: 4 | branches: 5 | - master # Change this to your default branch 6 | jobs: 7 | npm-publish: 8 | name: npm-publish 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout repository 12 | uses: actions/checkout@master 13 | - name: Set up Node.js 14 | uses: actions/setup-node@master 15 | with: 16 | node-version: 22.0.0 17 | - id: publish 18 | uses: JS-DevTools/npm-publish@v1 19 | with: 20 | token: ${{ secrets.NPM_AUTH_TOKEN }} 21 | - name: Create Release 22 | if: steps.publish.outputs.type != 'none' 23 | id: create_release 24 | uses: actions/create-release@v1 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | with: 28 | tag_name: ${{ steps.publish.outputs.version }} 29 | release_name: Release ${{ steps.publish.outputs.version }} 30 | body: ${{ steps.publish.outputs.version }} 31 | draft: false 32 | prerelease: false 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | package-lock.json -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // Settings 3 | "passfail" : false, // Stop on first error. 4 | "maxerr" : 100, // Maximum errors before stopping. 5 | 6 | 7 | // Predefined globals whom JSHint will ignore. 8 | "browser" : false, // Standard browser globals e.g. `window`, `document`. 9 | "predef" : [ // extra globals 10 | "describe", 11 | "beforeEach", 12 | "afterEach", 13 | "it" 14 | ], 15 | 16 | "node" : true, 17 | "rhino" : false, 18 | "couch" : false, 19 | "wsh" : false, // Windows Scripting Host. 20 | 21 | "jquery" : false, 22 | "prototypejs" : false, 23 | "mootools" : false, 24 | "dojo" : false, 25 | 26 | 27 | 28 | // Development. 29 | "debug" : true, // Allow debugger statements e.g. browser breakpoints. 30 | "devel" : true, // Allow development statements e.g. `console.log();`. 31 | 32 | 33 | // EcmaScript 5. 34 | "es5" : true, // Allow EcmaScript 5 syntax. 35 | "strict" : false, // Require `use strict` pragma in every file. 36 | "globalstrict" : true, // Allow global "use strict" (also enables 'strict'). 37 | 38 | 39 | // The Good Parts. 40 | "asi" : true, // Tolerate Automatic Semicolon Insertion (no semicolons). 41 | "laxbreak" : false, // Tolerate unsafe line breaks e.g. `return [\n] x` without semicolons. 42 | "laxcomma" : true, 43 | "bitwise" : false, // Prohibit bitwise operators (&, |, ^, etc.). 44 | "boss" : true, // Tolerate assignments inside if, for & while. Usually conditions & loops are for comparison, not assignments. 45 | "curly" : false, // Require {} for every new block or scope. 46 | "eqeqeq" : true, // Require triple equals i.e. `===`. 47 | "eqnull" : true, // Tolerate use of `== null`. 48 | "evil" : false, // Tolerate use of `eval`. 49 | "expr" : false, // Tolerate `ExpressionStatement` as Programs. 50 | "forin" : false, // Prohibt `for in` loops without `hasOwnProperty`. 51 | "immed" : true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );` 52 | "latedef" : false, // Prohibit variable use before definition. 53 | "loopfunc" : false, // Allow functions to be defined within loops. 54 | "noarg" : true, // Prohibit use of `arguments.caller` and `arguments.callee`. 55 | "regexp" : false, // Prohibit `.` and `[^...]` in regular expressions. 56 | "regexdash" : false, // Tolerate unescaped last dash i.e. `[-...]`. 57 | "scripturl" : false, // Tolerate script-targeted URLs. 58 | "shadow" : false, // Allows re-define variables later in code e.g. `var x=1; x=2;`. 59 | "supernew" : false, // Tolerate `new function () { ... };` and `new Object;`. 60 | "undef" : true, // Require all non-global variables be declared before they are used. 61 | 62 | 63 | // Persone styling prefrences. 64 | "newcap" : true, // Require capitalization of all constructor functions e.g. `new F()`. 65 | "noempty" : true, // Prohibit use of empty blocks. 66 | "nonew" : true, // Prohibit use of constructors for side-effects. 67 | "nomen" : false, // Prohibit use of initial or trailing underbars in names. 68 | "onevar" : false, // Allow only one `var` statement per function. 69 | "plusplus" : false, // Prohibit use of `++` & `--`. 70 | "sub" : false, // Tolerate all forms of subscript notation besides dot notation e.g. `dict['key']` instead of `dict.key`. 71 | "trailing" : true, // Prohibit trailing whitespaces. 72 | "white" : false // Check against strict whitespace and indentation rules. 73 | } 74 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | ## 0.1.10 2 | * [add new types export (#49)](https://github.com/PrismarineJS/node-vec3/commit/9a3c259a361de7b6053c5e60ac70ba8ce658aeb0) (thanks @zerozeynep) 3 | 4 | ## 0.1.9 5 | * [Add methods, fix .equals (#46)](https://github.com/PrismarineJS/node-vec3/commit/c6b94c4289cfba5fc460bed99d112cec85fe1cf3) (thanks @szdytom) 6 | * [Add command gh workflow allowing to use release command in comments (#45)](https://github.com/PrismarineJS/node-vec3/commit/8675f8ecf6065278c0d1f889a585a5febf446cc0) (thanks @rom1504) 7 | * [Update to node 18.0.0 (#44)](https://github.com/PrismarineJS/node-vec3/commit/358445025ff7d558c8f3fddec2d5786c8c468db5) (thanks @rom1504) 8 | * [New publish workflow (#38)](https://github.com/PrismarineJS/node-vec3/commit/ed6ba10a9b3d163f5c1ee8cb1b78108296b98477) (thanks @KTibow) 9 | 10 | ## 0.1.8 11 | 12 | * fix some typescript stuff 13 | * new methods: rounded, round, multiply, and divide 14 | 15 | ## 0.1.7 16 | * fix standard not being a dev dependency 17 | 18 | ## 0.1.6 19 | 20 | * add distance squared (thanks @TheDudeFromCI) 21 | * fix typings (thanks @iczero) 22 | 23 | ## 0.1.5 24 | 25 | * normalize() : like unit() but do not create a new vector (in-place) 26 | * dot(other) : return the dot product of the vector with other 27 | * cross(other) : return the cross product of the vector with other 28 | 29 | 30 | ## 0.1.4 31 | 32 | * add typescript declarations 33 | * add more methods 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vec3 2 | [![NPM version](https://img.shields.io/npm/v/vec3.svg)](http://npmjs.com/package/vec3) 3 | [![Build Status](https://github.com/PrismarineJS/node-vec3/workflows/CI/badge.svg)](https://github.com/PrismarineJS/node-vec3/actions?query=workflow%3A%22CI%22) 4 | 5 | 3D vector math with robust unit tests. 6 | 7 | ## Usage 8 | 9 | ```js 10 | var v = require('vec3'); 11 | 12 | var v1 = v(1, 2, 3); 13 | console.log(v1); // prints "(1, 2, 3)" 14 | var v2 = v1.offset(0, 0, 1); 15 | console.log(v2); // prints "(1, 2, 4)" 16 | ``` 17 | 18 | Or: 19 | 20 | ```js 21 | var Vec3 = require('vec3').Vec3; 22 | 23 | var v1 = new Vec3(1, 2, 3); 24 | // etc... 25 | ``` 26 | 27 | More available functions are listed below in Test Coverage. 28 | 29 | ## Test Coverage 30 | 31 | ``` 32 | v() 33 | ✔ no args 34 | ✔ x, y, z 35 | ✔ array 36 | ✔ object 37 | ✔ string coords 38 | ✔ deserialize 39 | ✔ invalid deserialize 40 | 41 | vec3 42 | ✔ isZero 43 | ✔ at 44 | ✔ xz 45 | ✔ xy 46 | ✔ yz 47 | ✔ xzy 48 | ✔ rounded 49 | ✔ round 50 | ✔ floored 51 | ✔ floor 52 | ✔ offset 53 | ✔ translate 54 | ✔ plus 55 | ✔ minus 56 | ✔ scaled 57 | ✔ abs 58 | ✔ distanceTo 59 | ✔ distanceSquared 60 | ✔ equals 61 | ✔ toString 62 | ✔ clone 63 | ✔ add 64 | ✔ subtract 65 | ✔ multiply 66 | ✔ divide 67 | ✔ set 68 | ✔ modulus 69 | ✔ volume 70 | ✔ min 71 | ✔ max 72 | ✔ update 73 | ✔ norm 74 | ✔ dot 75 | ✔ cross 76 | ✔ unit 77 | ✔ normalize 78 | ✔ scale 79 | ✔ xyDistanceTo 80 | ✔ xzDistanceTo 81 | ✔ yzDistanceTo 82 | ✔ innerProduct 83 | ✔ manhattanDistanceTo 84 | ✔ toArray 85 | 86 | 50 passing (14ms) 87 | ``` 88 | 89 | More functions welcome in the form of pull requests. 90 | 91 | ## History 92 | 93 | See [History](HISTORY.md) 94 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | export class Vec3 { 2 | constructor(x: number, y: number, z: number); 3 | 4 | x: number; 5 | y: number; 6 | z: number; 7 | 8 | /** 9 | * Returns true when it is a zero vector. 10 | */ 11 | isZero(): boolean; 12 | 13 | /** 14 | * Access component by index 15 | */ 16 | at(id: number): number; 17 | 18 | /** 19 | * Returns an array component x, z 20 | */ 21 | xz(): [number, number]; 22 | 23 | /** 24 | * Returns an array component x, y 25 | */ 26 | xy(): [number, number]; 27 | 28 | /** 29 | * Returns an array component y, z 30 | */ 31 | yz(): [number, number]; 32 | 33 | /** 34 | * Returns a vector with swapped y and z 35 | */ 36 | xzy(): Vec3; 37 | 38 | /** 39 | * Set own values to given x y z 40 | * If some components is given null, then those components won't change 41 | */ 42 | set(x: number, y: number, z: number): this; 43 | 44 | /** 45 | * Set own values to values given by other 46 | */ 47 | update(other: Vec3): this; 48 | 49 | /** 50 | * Return a new instance with copied values that are rounded 51 | */ 52 | rounded(): Vec3; 53 | 54 | /** 55 | * Round own values to nearest integer 56 | */ 57 | round(): this; 58 | 59 | /** 60 | * Return a new instance with copied values that are floored 61 | */ 62 | floored(): Vec3; 63 | 64 | /** 65 | * Floor own values 66 | */ 67 | floor(): this; 68 | 69 | /** 70 | * Return a new instance with copied values that are offset by dx dy and dz 71 | */ 72 | offset(dx: number, dy: number, dz: number): Vec3; 73 | 74 | /** 75 | * Translate own values by dx dy and dz 76 | */ 77 | translate(dx: number, dy: number, dz: number): this; 78 | 79 | /** 80 | * Add to own values by vector 81 | */ 82 | add(other: Vec3): this; 83 | 84 | /** 85 | * Subtract own values by vector 86 | */ 87 | subtract(other: Vec3): this; 88 | 89 | /** 90 | * Multiply own values by value from vector 91 | */ 92 | multiply(other: Vec3): this; 93 | 94 | /** 95 | * Divide own values by value from vector 96 | */ 97 | divide(other: Vec3): this; 98 | 99 | /** 100 | * Return a new instance with copied values that are added to by vector 101 | */ 102 | plus(other: Vec3): Vec3; 103 | 104 | /** 105 | * Return a new instance with copied values that are subtracted by vector 106 | */ 107 | minus(other: Vec3): Vec3; 108 | 109 | /** 110 | * Return a new instance with copied values that are scaled by number 111 | */ 112 | scaled(scalar: number): Vec3; 113 | 114 | /** 115 | * Return a new instance with copied values that are absolute 116 | */ 117 | abs(): Vec3; 118 | 119 | /** 120 | * Return the volume off the vector 121 | */ 122 | volume(): number; 123 | 124 | /** 125 | * Return a new instance with copied values that are modulated by value from a vector 126 | */ 127 | modulus(other: Vec3): Vec3; 128 | 129 | /** 130 | * Return the euclidean distance to another vector 131 | */ 132 | distanceTo(other: Vec3): number; 133 | 134 | /** 135 | * Return the squared euclidean distance to another vector 136 | */ 137 | distanceSquared(other: Vec3): number; 138 | 139 | /** 140 | * Check whether two vectors are equal 141 | * Returns true if each components have at most `error` difference 142 | */ 143 | equals(other: Vec3, error?: number): boolean; 144 | 145 | /** 146 | * Converts own values to a string representation in the format `(x, y, z)` 147 | */ 148 | toString(): string; 149 | 150 | /** 151 | * Return a new instance with the same values 152 | */ 153 | clone(): Vec3; 154 | 155 | /** 156 | * Return a new instance with the min values by value compared to another vector 157 | */ 158 | min(other: Vec3): Vec3; 159 | 160 | /** 161 | * Return a new instance with the max values by value compared to another vector 162 | */ 163 | max(other: Vec3): Vec3; 164 | 165 | /** 166 | * Returns its own euclidean norm 167 | */ 168 | norm(): number; 169 | 170 | /** 171 | * Returns the dot product with another vector 172 | */ 173 | dot(other: Vec3): number; 174 | 175 | /** 176 | * Returns a new instance off the cross product to another vector 177 | */ 178 | cross(other: Vec3): Vec3; 179 | 180 | /** 181 | * Returns a new instance with copied values normed to the unit vector 182 | */ 183 | unit(): Vec3; 184 | 185 | /** 186 | * Normalize own values 187 | */ 188 | normalize(): Vec3; 189 | 190 | /** 191 | * Scale own values by a number 192 | */ 193 | scale(scalar: number): this; 194 | 195 | /** 196 | * Returns the xy distance to another vector 197 | */ 198 | xyDistanceTo(other: Vec3): number; 199 | 200 | /** 201 | * Returns the xz distance to another vector 202 | */ 203 | xzDistanceTo(other: Vec3): number; 204 | 205 | /** 206 | * Returns the yz distance to another vector 207 | */ 208 | yzDistanceTo(other: Vec3): number; 209 | 210 | /** 211 | * Returns the inner product to another vector 212 | */ 213 | innerProduct(other: Vec3): number; 214 | 215 | /** 216 | * Returns the manhattan distance to another vector 217 | */ 218 | manhattanDistanceTo(other: Vec3): number; 219 | 220 | /** 221 | * Returns an array with x y z in array form ie [x, y, z] 222 | */ 223 | toArray(): [number, number, number]; 224 | } 225 | 226 | export default function v( 227 | coordinates: 228 | | null 229 | | string 230 | | [number | string, number | string, number | string] 231 | | { x: number | string; y: number | string; z: number | string } 232 | ): Vec3; 233 | export default function v( 234 | x: number | string, 235 | y: number | string, 236 | z: number | string 237 | ): Vec3; 238 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const re = /\((-?[.\d]+), (-?[.\d]+), (-?[.\d]+)\)/ 2 | 3 | class Vec3 { 4 | constructor (x, y, z) { 5 | this.x = x 6 | this.y = y 7 | this.z = z 8 | } 9 | 10 | isZero () { 11 | return this.x === 0 && this.y === 0 && this.z === 0 12 | } 13 | 14 | at (id) { 15 | return this.toArray()[id] 16 | } 17 | 18 | xz () { 19 | return [this.x, this.z] 20 | } 21 | 22 | xy () { 23 | return [this.x, this.y] 24 | } 25 | 26 | yz () { 27 | return [this.y, this.z] 28 | } 29 | 30 | xzy () { 31 | return new Vec3(this.x, this.z, this.y) 32 | } 33 | 34 | set (x, y, z) { 35 | this.x = x 36 | this.y = y 37 | this.z = z 38 | return this 39 | } 40 | 41 | update (other) { 42 | this.x = other.x 43 | this.y = other.y 44 | this.z = other.z 45 | return this 46 | } 47 | 48 | rounded () { 49 | return new Vec3(Math.round(this.x), Math.round(this.y), Math.round(this.z)) 50 | } 51 | 52 | round () { 53 | this.x = Math.round(this.x) 54 | this.y = Math.round(this.y) 55 | this.z = Math.round(this.z) 56 | return this 57 | } 58 | 59 | floored () { 60 | return new Vec3(Math.floor(this.x), Math.floor(this.y), Math.floor(this.z)) 61 | } 62 | 63 | floor () { 64 | this.x = Math.floor(this.x) 65 | this.y = Math.floor(this.y) 66 | this.z = Math.floor(this.z) 67 | return this 68 | } 69 | 70 | offset (dx, dy, dz) { 71 | return new Vec3(this.x + dx, this.y + dy, this.z + dz) 72 | } 73 | 74 | translate (dx, dy, dz) { 75 | this.x += dx 76 | this.y += dy 77 | this.z += dz 78 | return this 79 | } 80 | 81 | add (other) { 82 | this.x += other.x 83 | this.y += other.y 84 | this.z += other.z 85 | return this 86 | } 87 | 88 | subtract (other) { 89 | this.x -= other.x 90 | this.y -= other.y 91 | this.z -= other.z 92 | return this 93 | } 94 | 95 | multiply (other) { 96 | this.x *= other.x 97 | this.y *= other.y 98 | this.z *= other.z 99 | return this 100 | } 101 | 102 | divide (other) { 103 | this.x /= other.x 104 | this.y /= other.y 105 | this.z /= other.z 106 | return this 107 | } 108 | 109 | plus (other) { 110 | return this.offset(other.x, other.y, other.z) 111 | } 112 | 113 | minus (other) { 114 | return this.offset(-other.x, -other.y, -other.z) 115 | } 116 | 117 | scaled (scalar) { 118 | return new Vec3(this.x * scalar, this.y * scalar, this.z * scalar) 119 | } 120 | 121 | abs () { 122 | return new Vec3(Math.abs(this.x), Math.abs(this.y), Math.abs(this.z)) 123 | } 124 | 125 | volume () { 126 | return this.x * this.y * this.z 127 | } 128 | 129 | modulus (other) { 130 | return new Vec3( 131 | euclideanMod(this.x, other.x), 132 | euclideanMod(this.y, other.y), 133 | euclideanMod(this.z, other.z)) 134 | } 135 | 136 | distanceTo (other) { 137 | const dx = other.x - this.x 138 | const dy = other.y - this.y 139 | const dz = other.z - this.z 140 | return Math.sqrt(dx * dx + dy * dy + dz * dz) 141 | } 142 | 143 | distanceSquared (other) { 144 | const dx = other.x - this.x 145 | const dy = other.y - this.y 146 | const dz = other.z - this.z 147 | return dx * dx + dy * dy + dz * dz 148 | } 149 | 150 | equals (other, error = 0) { 151 | return Math.abs(this.x - other.x) <= error && 152 | Math.abs(this.y - other.y) <= error && 153 | Math.abs(this.z - other.z) <= error 154 | } 155 | 156 | toString () { 157 | return '(' + this.x + ', ' + this.y + ', ' + this.z + ')' 158 | } 159 | 160 | clone () { 161 | return this.offset(0, 0, 0) 162 | } 163 | 164 | min (other) { 165 | return new Vec3(Math.min(this.x, other.x), Math.min(this.y, other.y), Math.min(this.z, other.z)) 166 | } 167 | 168 | max (other) { 169 | return new Vec3(Math.max(this.x, other.x), Math.max(this.y, other.y), Math.max(this.z, other.z)) 170 | } 171 | 172 | norm () { 173 | return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z) 174 | } 175 | 176 | dot (other) { 177 | return this.x * other.x + this.y * other.y + this.z * other.z 178 | } 179 | 180 | cross (other) { 181 | return new Vec3(this.y * other.z - this.z * other.y, this.z * other.x - this.x * other.z, this.x * other.y - this.y * other.x) 182 | } 183 | 184 | unit () { 185 | const norm = this.norm() 186 | if (norm === 0) { 187 | return this.clone() 188 | } else { 189 | return this.scaled(1 / norm) 190 | } 191 | } 192 | 193 | normalize () { 194 | const norm = this.norm() 195 | if (norm !== 0) { 196 | this.x /= norm 197 | this.y /= norm 198 | this.z /= norm 199 | } 200 | return this 201 | } 202 | 203 | scale (scalar) { 204 | this.x *= scalar 205 | this.y *= scalar 206 | this.z *= scalar 207 | return this 208 | } 209 | 210 | xyDistanceTo (other) { 211 | const dx = other.x - this.x 212 | const dy = other.y - this.y 213 | return Math.sqrt(dx * dx + dy * dy) 214 | } 215 | 216 | xzDistanceTo (other) { 217 | const dx = other.x - this.x 218 | const dz = other.z - this.z 219 | return Math.sqrt(dx * dx + dz * dz) 220 | } 221 | 222 | yzDistanceTo (other) { 223 | const dy = other.y - this.y 224 | const dz = other.z - this.z 225 | return Math.sqrt(dy * dy + dz * dz) 226 | } 227 | 228 | innerProduct (other) { 229 | return this.x * other.x + this.y * other.y + this.z * other.z 230 | } 231 | 232 | manhattanDistanceTo (other) { 233 | return Math.abs(other.x - this.x) + Math.abs(other.y - this.y) + Math.abs(other.z - this.z) 234 | } 235 | 236 | toArray () { 237 | return [this.x, this.y, this.z] 238 | } 239 | } 240 | 241 | function v (x, y, z) { 242 | if (x == null) { 243 | return new Vec3(0, 0, 0) 244 | } else if (Array.isArray(x)) { 245 | return new Vec3(parseFloat(x[0]), parseFloat(x[1]), parseFloat(x[2])) 246 | } else if (typeof x === 'object') { 247 | return new Vec3(parseFloat(x.x), parseFloat(x.y), parseFloat(x.z)) 248 | } else if (typeof x === 'string' && y == null) { 249 | const match = x.match(re) 250 | if (match) { 251 | return new Vec3( 252 | parseFloat(match[1]), 253 | parseFloat(match[2]), 254 | parseFloat(match[3])) 255 | } else { 256 | throw new Error('vec3: cannot parse: ' + x) 257 | } 258 | } else { 259 | return new Vec3(parseFloat(x), parseFloat(y), parseFloat(z)) 260 | } 261 | } 262 | 263 | function euclideanMod (numerator, denominator) { 264 | const result = numerator % denominator 265 | return result < 0 ? result + denominator : result 266 | } 267 | 268 | module.exports = v 269 | v.Vec3 = Vec3 270 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vec3", 3 | "version": "0.1.10", 4 | "description": "3d vector math with good unit tests", 5 | "main": "index.js", 6 | "types": "index.d.ts", 7 | "scripts": { 8 | "test": "npm run test-js && npm run test-types", 9 | "test-js": "mocha --reporter spec", 10 | "test-types": "tsd", 11 | "pretest": "npm run lint", 12 | "lint": "standard", 13 | "fix": "standard --fix" 14 | }, 15 | "keywords": [ 16 | "point" 17 | ], 18 | "exports": { 19 | "types": "./index.d.ts", 20 | "require": "./index.js", 21 | "import": "./wrapper.mjs" 22 | }, 23 | "author": "Andrew Kelley", 24 | "license": "BSD", 25 | "devDependencies": { 26 | "mocha": "^11.0.1", 27 | "standard": "^17.0.0", 28 | "tsd": "^0.25.0" 29 | }, 30 | "dependencies": {}, 31 | "tsd": { 32 | "directory": "test" 33 | }, 34 | "repository": { 35 | "type": "git", 36 | "url": "https://github.com/PrismarineJS/node-vec3.git" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/index.test-d.ts: -------------------------------------------------------------------------------- 1 | import { expectType } from "tsd"; 2 | import vec3, { Vec3 } from ".."; 3 | 4 | const vec = vec3([1, 2, 3]); 5 | expectType(vec); 6 | expectType(vec3([1, 2, 3])); 7 | expectType(vec3({ x: 1, y: 2, z: 3 })); 8 | expectType(vec3("1, 2, 3")); 9 | expectType(vec3(1, 2, 3)); 10 | expectType(new Vec3(1,2,3)); 11 | // @ts-expect-error 12 | vec3(1, 2); 13 | // @ts-expect-error 14 | vec3("1", 2); 15 | 16 | expectType(vec.x); 17 | expectType(vec.y); 18 | expectType(vec.z); 19 | 20 | expectType(vec.isZero()); 21 | expectType(vec.at(1)); 22 | expectType<[number, number]>(vec.xz()); 23 | expectType<[number, number]>(vec.xy()); 24 | expectType<[number, number]>(vec.yz()); 25 | expectType(vec.xzy()); 26 | expectType(vec.set(4, 5, 6)); 27 | expectType(vec.update(vec)); 28 | expectType(vec.floored()); 29 | expectType(vec.floor()); 30 | expectType(vec.offset(0, 0, 0)); 31 | expectType(vec.translate(0, 0, 0)); 32 | expectType(vec.add(vec)); 33 | expectType(vec.subtract(vec)); 34 | expectType(vec.multiply(vec)); 35 | expectType(vec.divide(vec)); 36 | expectType(vec.plus(vec)); 37 | expectType(vec.minus(vec)); 38 | expectType(vec.scaled(2)); 39 | expectType(vec.abs()); 40 | expectType(vec.volume()); 41 | expectType(vec.modulus(vec)); 42 | expectType(vec.distanceTo(vec)); 43 | expectType(vec.distanceSquared(vec)); 44 | expectType(vec.equals(vec)); 45 | expectType(vec.toString()); 46 | expectType(vec.clone()); 47 | expectType(vec.min(vec)); 48 | expectType(vec.max(vec)); 49 | expectType(vec.norm()); 50 | expectType(vec.dot(vec)); 51 | expectType(vec.cross(vec)); 52 | expectType(vec.unit()); 53 | expectType(vec.normalize()); 54 | expectType(vec.scale(2)); 55 | expectType(vec.xyDistanceTo(vec)); 56 | expectType(vec.xzDistanceTo(vec)); 57 | expectType(vec.yzDistanceTo(vec)); 58 | expectType(vec.innerProduct(vec)); 59 | expectType(vec.manhattanDistanceTo(vec)); 60 | expectType<[number, number, number]>(vec.toArray()); 61 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 3 | const v = require('../') 4 | const Vec3 = v.Vec3 5 | const assert = require('assert') 6 | 7 | describe('v()', function () { 8 | it('no args', function () { 9 | const v1 = v() 10 | assert.strictEqual(v1.x, 0) 11 | assert.strictEqual(v1.y, 0) 12 | assert.strictEqual(v1.z, 0) 13 | }) 14 | it('x, y, z', function () { 15 | const v1 = v(-1, 5, 10.10) 16 | assert.strictEqual(v1.x, -1) 17 | assert.strictEqual(v1.y, 5) 18 | assert.strictEqual(v1.z, 10.10) 19 | }) 20 | it('array', function () { 21 | const v1 = v([4, 5, 6]) 22 | assert.strictEqual(v1.x, 4) 23 | assert.strictEqual(v1.y, 5) 24 | assert.strictEqual(v1.z, 6) 25 | }) 26 | it('object', function () { 27 | const v1 = v({ x: 9, y: 8, z: 7 }) 28 | assert.strictEqual(v1.x, 9) 29 | assert.strictEqual(v1.y, 8) 30 | assert.strictEqual(v1.z, 7) 31 | }) 32 | it('string coords', function () { 33 | const v1 = v('1', '1.5', '-30.2') 34 | assert.strictEqual(v1.x, 1) 35 | assert.strictEqual(v1.y, 1.5) 36 | assert.strictEqual(v1.z, -30.2) 37 | }) 38 | it('deserialize', function () { 39 | const v1 = v(v(1, -3.5, 0).toString()) 40 | assert.strictEqual(v1.x, 1) 41 | assert.strictEqual(v1.y, -3.5) 42 | assert.strictEqual(v1.z, 0) 43 | const v2 = v(v(-111, 222, 9876543210.12345).toString()) 44 | assert.strictEqual(v2.x, -111) 45 | assert.strictEqual(v2.y, 222) 46 | assert.strictEqual(v2.z, 9876543210.12345) 47 | }) 48 | it('invalid deserialize', function () { 49 | assert.throws(function () { 50 | return v('lol hax') 51 | }, /cannot parse/) 52 | }) 53 | }) 54 | describe('vec3', function () { 55 | it('isZero', function () { 56 | const v1 = new Vec3(0, 1, 2) 57 | const v2 = new Vec3(0, 0, 0) 58 | assert.ok(!v1.isZero()) 59 | assert.ok(v2.isZero()) 60 | }) 61 | it('at', function () { 62 | const v1 = new Vec3(0, 1, 2) 63 | assert.strictEqual(v1.at(0), 0) 64 | assert.strictEqual(v1.at(1), 1) 65 | assert.strictEqual(v1.at(2), 2) 66 | }) 67 | it('xz', function () { 68 | const v1 = new Vec3(0, 1, 2) 69 | const a = v1.xz() 70 | assert.strictEqual(a[0], 0) 71 | assert.strictEqual(a[1], 2) 72 | }) 73 | it('xy', function () { 74 | const v1 = new Vec3(0, 1, 2) 75 | const a = v1.xy() 76 | assert.strictEqual(a[0], 0) 77 | assert.strictEqual(a[1], 1) 78 | }) 79 | it('yz', function () { 80 | const v1 = new Vec3(0, 1, 2) 81 | const a = v1.yz() 82 | assert.strictEqual(a[0], 1) 83 | assert.strictEqual(a[1], 2) 84 | }) 85 | it('xzy', function () { 86 | const v1 = new Vec3(0, 1, 2) 87 | const v2 = v1.xzy() 88 | assert.strictEqual(v2.x, 0) 89 | assert.strictEqual(v2.y, 2) 90 | assert.strictEqual(v2.z, 1) 91 | }) 92 | it('rounded', function () { 93 | const v1 = new Vec3(1.1, -1.5, 1.9) 94 | const v2 = v1.rounded() 95 | v1.x = 10 96 | assert.strictEqual(v2.x, 1) 97 | assert.strictEqual(v2.y, -1) 98 | assert.strictEqual(v2.z, 2) 99 | }) 100 | it('round', function () { 101 | const v1 = new Vec3(1.1, -1.5, 1.9) 102 | const v2 = v1.round() 103 | assert.strictEqual(v2, v1) 104 | assert.strictEqual(v1.x, 1) 105 | assert.strictEqual(v1.y, -1) 106 | assert.strictEqual(v1.z, 2) 107 | }) 108 | it('floored', function () { 109 | const v1 = new Vec3(1.1, -1.5, 1.9) 110 | const v2 = v1.floored() 111 | v1.x = 10 112 | assert.strictEqual(v2.x, 1) 113 | assert.strictEqual(v2.y, -2) 114 | assert.strictEqual(v2.z, 1) 115 | }) 116 | it('floor', function () { 117 | const v1 = new Vec3(1.1, -1.5, 1.9) 118 | const v2 = v1.floor() 119 | assert.strictEqual(v2, v1) 120 | assert.strictEqual(v1.x, 1) 121 | assert.strictEqual(v1.y, -2) 122 | assert.strictEqual(v1.z, 1) 123 | }) 124 | it('offset', function () { 125 | const v1 = new Vec3(1, 2, 3) 126 | const v2 = v1.offset(10, -10, 20) 127 | v1.x = -100 128 | assert.strictEqual(v2.x, 11) 129 | assert.strictEqual(v2.y, -8) 130 | assert.strictEqual(v2.z, 23) 131 | }) 132 | it('translate', function () { 133 | const v1 = new Vec3(1, 2, 3) 134 | v1.translate(10, -10, 20) 135 | assert.strictEqual(v1.x, 11) 136 | assert.strictEqual(v1.y, -8) 137 | assert.strictEqual(v1.z, 23) 138 | }) 139 | it('plus', function () { 140 | const v1 = new Vec3(1, 2, 3) 141 | const v2 = new Vec3(-1, 0, 1) 142 | const v3 = v1.plus(v2) 143 | assert.strictEqual(v1.x, 1) 144 | assert.strictEqual(v1.y, 2) 145 | assert.strictEqual(v1.z, 3) 146 | assert.strictEqual(v2.x, -1) 147 | assert.strictEqual(v2.y, 0) 148 | assert.strictEqual(v2.z, 1) 149 | assert.strictEqual(v3.x, 0) 150 | assert.strictEqual(v3.y, 2) 151 | assert.strictEqual(v3.z, 4) 152 | }) 153 | it('minus', function () { 154 | const v1 = new Vec3(1, 2, 3) 155 | const v2 = new Vec3(-1, 0, 1) 156 | const v3 = v1.minus(v2) 157 | assert.strictEqual(v1.x, 1) 158 | assert.strictEqual(v1.y, 2) 159 | assert.strictEqual(v1.z, 3) 160 | assert.strictEqual(v2.x, -1) 161 | assert.strictEqual(v2.y, 0) 162 | assert.strictEqual(v2.z, 1) 163 | assert.strictEqual(v3.x, 2) 164 | assert.strictEqual(v3.y, 2) 165 | assert.strictEqual(v3.z, 2) 166 | }) 167 | it('scaled', function () { 168 | const v1 = new Vec3(1, 2, 3) 169 | const v2 = v1.scaled(2) 170 | assert.strictEqual(v1.x, 1) 171 | assert.strictEqual(v1.y, 2) 172 | assert.strictEqual(v1.z, 3) 173 | assert.strictEqual(v2.x, 2) 174 | assert.strictEqual(v2.y, 4) 175 | assert.strictEqual(v2.z, 6) 176 | }) 177 | it('abs', function () { 178 | const v1 = new Vec3(1.1, -1.5, 1.9) 179 | const v2 = v1.abs() 180 | v1.x = 10 181 | assert.strictEqual(v2.x, 1.1) 182 | assert.strictEqual(v2.y, 1.5) 183 | assert.strictEqual(v2.z, 1.9) 184 | }) 185 | it('distanceTo', function () { 186 | const v1 = new Vec3(1, 1, 1) 187 | const v2 = new Vec3(2, 2, 2) 188 | const dist1 = v1.distanceTo(v2) 189 | const dist2 = v2.distanceTo(v1) 190 | const expected = 1.7320508075688772 191 | assert.strictEqual(dist1, dist2) 192 | assert.strictEqual(Math.round(dist1 * 100000), Math.round(expected * 100000)) 193 | }) 194 | it('distanceSquared', function () { 195 | const v1 = new Vec3(1, 1, 1) 196 | const v2 = new Vec3(2, 2, 2) 197 | const dist1 = v1.distanceSquared(v2) 198 | const dist2 = v2.distanceSquared(v1) 199 | const expected = 3 200 | assert.strictEqual(dist1, dist2) 201 | assert.strictEqual(Math.round(dist1 * 100000), Math.round(expected * 100000)) 202 | }) 203 | it('equals', function () { 204 | const v1 = new Vec3(1, 2, 3) 205 | const v2 = v1.scaled(0.23424) 206 | const v3 = v1.scaled(0.23424) 207 | assert.ok(v2.equals(v3)) 208 | const v4 = new Vec3(0.1, 0, 0) 209 | const v5 = new Vec3(0.2, 0, 0) 210 | const v6 = new Vec3(0.3, 0, 0) 211 | assert.ok(v4.plus(v5).equals(v6, Number.EPSILON)) 212 | }) 213 | it('toString', function () { 214 | const v1 = new Vec3(1, -1, 3.14) 215 | assert.strictEqual(v1.toString(), '(1, -1, 3.14)') 216 | }) 217 | it('clone', function () { 218 | const v1 = new Vec3(1, 2, 3) 219 | const v2 = v1.clone() 220 | v2.x = 10 221 | assert.strictEqual(v1.x, 1) 222 | assert.strictEqual(v1.y, 2) 223 | assert.strictEqual(v1.z, 3) 224 | assert.strictEqual(v2.x, 10) 225 | assert.strictEqual(v2.y, 2) 226 | assert.strictEqual(v2.z, 3) 227 | }) 228 | it('add', function () { 229 | const v1 = new Vec3(1, 2, 3) 230 | const v2 = new Vec3(-1, -2, -3) 231 | const v3 = v1.add(v2) 232 | assert.strictEqual(v3, v1) 233 | assert.strictEqual(v1.x, 0) 234 | assert.strictEqual(v1.y, 0) 235 | assert.strictEqual(v1.z, 0) 236 | }) 237 | it('subtract', function () { 238 | const v1 = new Vec3(1, 2, 3) 239 | const v2 = new Vec3(-1, -2, -3) 240 | const v3 = v1.subtract(v2) 241 | assert.strictEqual(v3, v1) 242 | assert.strictEqual(v1.x, 2) 243 | assert.strictEqual(v1.y, 4) 244 | assert.strictEqual(v1.z, 6) 245 | }) 246 | it('multiply', function () { 247 | const v1 = new Vec3(1, 2, 3) 248 | const v2 = new Vec3(-1, -2, -5) 249 | const v3 = v1.multiply(v2) 250 | assert.strictEqual(v3, v1) 251 | assert.strictEqual(v1.x, -1) 252 | assert.strictEqual(v1.y, -4) 253 | assert.strictEqual(v1.z, -15) 254 | }) 255 | it('divide', function () { 256 | const v1 = new Vec3(10, 20, 30) 257 | const v2 = new Vec3(2, 5, 3) 258 | const v3 = v1.divide(v2) 259 | assert.strictEqual(v3, v1) 260 | assert.strictEqual(v1.x, 5) 261 | assert.strictEqual(v1.y, 4) 262 | assert.strictEqual(v1.z, 10) 263 | }) 264 | it('set', function () { 265 | const v1 = new Vec3(12, 32, 46) 266 | const v2 = v1.set(0, 10, 100) 267 | assert.strictEqual(v1, v2) 268 | assert.strictEqual(v1.x, 0) 269 | assert.strictEqual(v1.y, 10) 270 | assert.strictEqual(v1.z, 100) 271 | }) 272 | it('modulus', function () { 273 | const v1 = new Vec3(12, 32, -1) 274 | const v2 = new Vec3(14, 32, 16) 275 | const v3 = v1.modulus(v2) 276 | assert.strictEqual(v1.x, 12) 277 | assert.strictEqual(v1.y, 32) 278 | assert.strictEqual(v1.z, -1) 279 | assert.strictEqual(v2.x, 14) 280 | assert.strictEqual(v2.y, 32) 281 | assert.strictEqual(v2.z, 16) 282 | assert.strictEqual(v3.x, 12) 283 | assert.strictEqual(v3.y, 0) 284 | assert.strictEqual(v3.z, 15) 285 | }) 286 | it('volume', function () { 287 | const v1 = new Vec3(3, 4, 5) 288 | assert.strictEqual(v1.volume(), 60) 289 | }) 290 | it('min', function () { 291 | const v1 = new Vec3(-1, 0, 1) 292 | const v2 = new Vec3(10, -10, 1.1) 293 | const v3 = v1.min(v2) 294 | assert.strictEqual(v3.x, -1) 295 | assert.strictEqual(v3.y, -10) 296 | assert.strictEqual(v3.z, 1) 297 | }) 298 | it('max', function () { 299 | const v1 = new Vec3(-1, 0, 1) 300 | const v2 = new Vec3(10, -10, 1.1) 301 | const v3 = v1.max(v2) 302 | assert.strictEqual(v3.x, 10) 303 | assert.strictEqual(v3.y, 0) 304 | assert.strictEqual(v3.z, 1.1) 305 | }) 306 | it('update', function () { 307 | const v1 = new Vec3(-1, 0, 1) 308 | const v2 = new Vec3(10, -10, 1.1) 309 | const v3 = v1.update(v2) 310 | assert.strictEqual(v3, v1) 311 | assert.strictEqual(v1.x, 10) 312 | assert.strictEqual(v1.y, -10) 313 | assert.strictEqual(v1.z, 1.1) 314 | assert.strictEqual(v2.x, 10) 315 | assert.strictEqual(v2.y, -10) 316 | assert.strictEqual(v2.z, 1.1) 317 | }) 318 | it('norm', function () { 319 | const v1 = new Vec3(-10, 0, 10) 320 | assert.strictEqual(Math.round(v1.norm() * 100000), Math.round(14.1421356237 * 100000)) 321 | }) 322 | it('dot', function () { 323 | const v1 = new Vec3(-1, -1, -1) 324 | const v2 = new Vec3(1, 1, 1) 325 | assert.strictEqual(v1.dot(v2), -3) 326 | }) 327 | it('cross', function () { 328 | const v1 = new Vec3(1, 0, 0) 329 | const v2 = new Vec3(0, 1, 0) 330 | const v3 = new Vec3(0, 0, 1) 331 | assert.ok(v1.cross(v2).equals(v3)) 332 | }) 333 | it('unit', function () { 334 | const v1 = new Vec3(10, -10, 1.1) 335 | const v2 = v1.unit() 336 | assert.strictEqual(Math.round(v2.x * 100000), Math.round(0.70497744020 * 100000)) 337 | assert.strictEqual(Math.round(v2.y * 100000), Math.round(-0.7049774402 * 100000)) 338 | assert.strictEqual(Math.round(v2.z * 100000), Math.round(0.07754751842 * 100000)) 339 | const v3 = new Vec3(0, 0, 0) 340 | const v4 = v3.unit() 341 | assert.strictEqual(v4.x, 0) 342 | assert.strictEqual(v4.y, 0) 343 | assert.strictEqual(v4.z, 0) 344 | }) 345 | it('normalize', function () { 346 | const v1 = new Vec3(10, -10, 1.1) 347 | const v2 = v1.normalize() 348 | assert.strictEqual(Math.round(v2.x * 100000), Math.round(0.70497744020 * 100000)) 349 | assert.strictEqual(Math.round(v2.y * 100000), Math.round(-0.7049774402 * 100000)) 350 | assert.strictEqual(Math.round(v2.z * 100000), Math.round(0.07754751842 * 100000)) 351 | const v3 = new Vec3(0, 0, 0) 352 | const v4 = v3.normalize() 353 | assert.strictEqual(v4.x, 0) 354 | assert.strictEqual(v4.y, 0) 355 | assert.strictEqual(v4.z, 0) 356 | }) 357 | it('scale', function () { 358 | const v1 = new Vec3(10, -10, 1.1) 359 | const v2 = v1.scale(1.5) 360 | assert.strictEqual(v2.x, 15) 361 | assert.strictEqual(v2.y, -15) 362 | assert.strictEqual(Math.round(v2.z * 100000), Math.round(1.65 * 100000)) 363 | }) 364 | it('xyDistanceTo', function () { 365 | const v1 = new Vec3(1, 1, 1) 366 | const v2 = new Vec3(2, 2, 2) 367 | const dist1 = v1.xyDistanceTo(v2) 368 | const dist2 = v2.xyDistanceTo(v1) 369 | const expected = 1.414213562 370 | assert.strictEqual(dist1, dist2) 371 | assert.strictEqual(Math.round(dist1 * 100000), Math.round(expected * 100000)) 372 | }) 373 | it('xzDistanceTo', function () { 374 | const v1 = new Vec3(1, 1, 1) 375 | const v2 = new Vec3(2, 2, 2) 376 | const dist1 = v1.xzDistanceTo(v2) 377 | const dist2 = v2.xzDistanceTo(v1) 378 | const expected = 1.41421356237 379 | assert.strictEqual(dist1, dist2) 380 | assert.strictEqual(Math.round(dist1 * 100000), Math.round(expected * 100000)) 381 | }) 382 | it('yzDistanceTo', function () { 383 | const v1 = new Vec3(1, 1, 1) 384 | const v2 = new Vec3(2, 2, 2) 385 | const dist1 = v1.yzDistanceTo(v2) 386 | const dist2 = v2.yzDistanceTo(v1) 387 | const expected = 1.41421356237 388 | assert.strictEqual(dist1, dist2) 389 | assert.strictEqual(Math.round(dist1 * 100000), Math.round(expected * 100000)) 390 | }) 391 | it('innerProduct', function () { 392 | const v1 = new Vec3(-1, 0, 1) 393 | const v2 = new Vec3(0, 1, 0) 394 | const ip1 = v1.innerProduct(v2) 395 | const ip2 = v2.innerProduct(v1) 396 | assert.strictEqual(ip1, ip2) 397 | assert.strictEqual(ip1, 0) 398 | }) 399 | it('manhattanDistanceTo', function () { 400 | const v1 = new Vec3(-1, 0, 1) 401 | const v2 = new Vec3(10, -10, 1.1) 402 | const dist1 = v1.manhattanDistanceTo(v2) 403 | const dist2 = v2.manhattanDistanceTo(v1) 404 | assert.strictEqual(dist1, dist2) 405 | assert.strictEqual(dist1, 21.1) 406 | }) 407 | it('toArray', function () { 408 | const v1 = new Vec3(1, -1, 3.14) 409 | const array = v1.toArray() 410 | assert.strictEqual(v1.x, array[0]) 411 | assert.strictEqual(v1.y, array[1]) 412 | assert.strictEqual(v1.z, array[2]) 413 | }) 414 | }) 415 | -------------------------------------------------------------------------------- /wrapper.mjs: -------------------------------------------------------------------------------- 1 | import mod from './index.js' 2 | 3 | export default mod 4 | export const Vec3 = mod.Vec3 5 | --------------------------------------------------------------------------------