@js-draw/math 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/Mat33.d.ts +18 -1
- package/dist/cjs/Mat33.fromCSSMatrix.test.d.ts +1 -0
- package/dist/cjs/Mat33.js +114 -24
- package/dist/cjs/Vec3.d.ts +7 -0
- package/dist/cjs/Vec3.js +9 -0
- package/dist/mjs/Mat33.d.ts +18 -1
- package/dist/mjs/Mat33.fromCSSMatrix.test.d.ts +1 -0
- package/dist/mjs/Mat33.mjs +114 -24
- package/dist/mjs/Vec3.d.ts +7 -0
- package/dist/mjs/Vec3.mjs +9 -0
- package/package.json +4 -4
- package/src/Mat33.fromCSSMatrix.test.ts +90 -0
- package/src/Mat33.test.ts +6 -42
- package/src/Mat33.ts +143 -32
- package/src/Vec3.ts +10 -0
- package/src/rounding.ts +1 -0
    
        package/dist/cjs/Mat33.d.ts
    CHANGED
    
    | @@ -107,14 +107,31 @@ export declare class Mat33 { | |
| 107 107 | 
             
                mapEntries(mapping: (component: number, rowcol: [number, number]) => number): Mat33;
         | 
| 108 108 | 
             
                /** Estimate the scale factor of this matrix (based on the first row). */
         | 
| 109 109 | 
             
                getScaleFactor(): number;
         | 
| 110 | 
            +
                /** Returns the `idx`-th column (`idx` is 0-indexed). */
         | 
| 111 | 
            +
                getColumn(idx: number): Vec3;
         | 
| 112 | 
            +
                /** Returns the magnitude of the entry with the largest entry */
         | 
| 113 | 
            +
                maximumEntryMagnitude(): number;
         | 
| 110 114 | 
             
                /**
         | 
| 111 115 | 
             
                 * Constructs a 3x3 translation matrix (for translating `Vec2`s) using
         | 
| 112 116 | 
             
                 * **transformVec2**.
         | 
| 117 | 
            +
                 *
         | 
| 118 | 
            +
                 * Creates a matrix in the form
         | 
| 119 | 
            +
                 * $$
         | 
| 120 | 
            +
                 * 	\begin{pmatrix}
         | 
| 121 | 
            +
                 * 		1 & 0 & {\tt amount.x}\\
         | 
| 122 | 
            +
                 * 		0 & 1 & {\tt amount.y}\\
         | 
| 123 | 
            +
                 * 		0 & 0 & 1
         | 
| 124 | 
            +
                 * 	\end{pmatrix}
         | 
| 125 | 
            +
                 * $$
         | 
| 113 126 | 
             
                 */
         | 
| 114 127 | 
             
                static translation(amount: Vec2): Mat33;
         | 
| 115 128 | 
             
                static zRotation(radians: number, center?: Point2): Mat33;
         | 
| 116 129 | 
             
                static scaling2D(amount: number | Vec2, center?: Point2): Mat33;
         | 
| 117 | 
            -
                /** | 
| 130 | 
            +
                /**
         | 
| 131 | 
            +
                 * **Note**: Assumes `this.c1 = this.c2 = 0` and `this.c3 = 1`.
         | 
| 132 | 
            +
                 *
         | 
| 133 | 
            +
                 * @see {@link fromCSSMatrix} and {@link toSafeCSSTransformList}
         | 
| 134 | 
            +
                 */
         | 
| 118 135 | 
             
                toCSSMatrix(): string;
         | 
| 119 136 | 
             
                /**
         | 
| 120 137 | 
             
                 * Converts a CSS-form `matrix(a, b, c, d, e, f)` to a Mat33.
         | 
| @@ -0,0 +1 @@ | |
| 1 | 
            +
            export {};
         | 
    
        package/dist/cjs/Mat33.js
    CHANGED
    
    | @@ -259,9 +259,30 @@ class Mat33 { | |
| 259 259 | 
             
                getScaleFactor() {
         | 
| 260 260 | 
             
                    return Math.hypot(this.a1, this.a2);
         | 
| 261 261 | 
             
                }
         | 
| 262 | 
            +
                /** Returns the `idx`-th column (`idx` is 0-indexed). */
         | 
| 263 | 
            +
                getColumn(idx) {
         | 
| 264 | 
            +
                    return Vec3_1.default.of(this.rows[0].at(idx), this.rows[1].at(idx), this.rows[2].at(idx));
         | 
| 265 | 
            +
                }
         | 
| 266 | 
            +
                /** Returns the magnitude of the entry with the largest entry */
         | 
| 267 | 
            +
                maximumEntryMagnitude() {
         | 
| 268 | 
            +
                    let greatestSoFar = Math.abs(this.a1);
         | 
| 269 | 
            +
                    for (const entry of this.toArray()) {
         | 
| 270 | 
            +
                        greatestSoFar = Math.max(greatestSoFar, Math.abs(entry));
         | 
| 271 | 
            +
                    }
         | 
| 272 | 
            +
                    return greatestSoFar;
         | 
| 273 | 
            +
                }
         | 
| 262 274 | 
             
                /**
         | 
| 263 275 | 
             
                 * Constructs a 3x3 translation matrix (for translating `Vec2`s) using
         | 
| 264 276 | 
             
                 * **transformVec2**.
         | 
| 277 | 
            +
                 *
         | 
| 278 | 
            +
                 * Creates a matrix in the form
         | 
| 279 | 
            +
                 * $$
         | 
| 280 | 
            +
                 * 	\begin{pmatrix}
         | 
| 281 | 
            +
                 * 		1 & 0 & {\tt amount.x}\\
         | 
| 282 | 
            +
                 * 		0 & 1 & {\tt amount.y}\\
         | 
| 283 | 
            +
                 * 		0 & 0 & 1
         | 
| 284 | 
            +
                 * 	\end{pmatrix}
         | 
| 285 | 
            +
                 * $$
         | 
| 265 286 | 
             
                 */
         | 
| 266 287 | 
             
                static translation(amount) {
         | 
| 267 288 | 
             
                    // When transforming Vec2s by a 3x3 matrix, we give the input
         | 
| @@ -296,7 +317,11 @@ class Mat33 { | |
| 296 317 | 
             
                    // Translate such that [center] goes to (0, 0)
         | 
| 297 318 | 
             
                    return result.rightMul(Mat33.translation(center.times(-1)));
         | 
| 298 319 | 
             
                }
         | 
| 299 | 
            -
                /** | 
| 320 | 
            +
                /**
         | 
| 321 | 
            +
                 * **Note**: Assumes `this.c1 = this.c2 = 0` and `this.c3 = 1`.
         | 
| 322 | 
            +
                 *
         | 
| 323 | 
            +
                 * @see {@link fromCSSMatrix} and {@link toSafeCSSTransformList}
         | 
| 324 | 
            +
                 */
         | 
| 300 325 | 
             
                toCSSMatrix() {
         | 
| 301 326 | 
             
                    return `matrix(${this.a1},${this.b1},${this.a2},${this.b2},${this.a3},${this.b3})`;
         | 
| 302 327 | 
             
                }
         | 
| @@ -314,30 +339,95 @@ class Mat33 { | |
| 314 339 | 
             
                    if (cssString === '' || cssString === 'none') {
         | 
| 315 340 | 
             
                        return Mat33.identity;
         | 
| 316 341 | 
             
                    }
         | 
| 317 | 
            -
                    const  | 
| 318 | 
            -
             | 
| 319 | 
            -
             | 
| 320 | 
            -
             | 
| 321 | 
            -
             | 
| 322 | 
            -
             | 
| 323 | 
            -
             | 
| 324 | 
            -
             | 
| 325 | 
            -
             | 
| 326 | 
            -
             | 
| 327 | 
            -
             | 
| 328 | 
            -
             | 
| 329 | 
            -
             | 
| 330 | 
            -
             | 
| 342 | 
            +
                    const parseArguments = (argumentString) => {
         | 
| 343 | 
            +
                        return argumentString.split(/[, \t\n]+/g).map(argString => {
         | 
| 344 | 
            +
                            let isPercentage = false;
         | 
| 345 | 
            +
                            if (argString.endsWith('%')) {
         | 
| 346 | 
            +
                                isPercentage = true;
         | 
| 347 | 
            +
                                argString = argString.substring(0, argString.length - 1);
         | 
| 348 | 
            +
                            }
         | 
| 349 | 
            +
                            // Remove trailing px units.
         | 
| 350 | 
            +
                            argString = argString.replace(/px$/ig, '');
         | 
| 351 | 
            +
                            const numberExp = /^[-]?\d*(?:\.\d*)?(?:[eE][-+]?\d+)?$/i;
         | 
| 352 | 
            +
                            if (!numberExp.exec(argString)) {
         | 
| 353 | 
            +
                                throw new Error(`All arguments to transform functions must be numeric (state: ${JSON.stringify({
         | 
| 354 | 
            +
                                    currentArgument: argString,
         | 
| 355 | 
            +
                                    allArguments: argumentString,
         | 
| 356 | 
            +
                                })})`);
         | 
| 357 | 
            +
                            }
         | 
| 358 | 
            +
                            let argNumber = parseFloat(argString);
         | 
| 359 | 
            +
                            if (isPercentage) {
         | 
| 360 | 
            +
                                argNumber /= 100;
         | 
| 361 | 
            +
                            }
         | 
| 362 | 
            +
                            return argNumber;
         | 
| 363 | 
            +
                        });
         | 
| 364 | 
            +
                    };
         | 
| 365 | 
            +
                    const keywordToAction = {
         | 
| 366 | 
            +
                        matrix: (matrixData) => {
         | 
| 367 | 
            +
                            if (matrixData.length !== 6) {
         | 
| 368 | 
            +
                                throw new Error(`Invalid matrix argument: ${matrixData}. Must have length 6`);
         | 
| 369 | 
            +
                            }
         | 
| 370 | 
            +
                            const a = matrixData[0];
         | 
| 371 | 
            +
                            const b = matrixData[1];
         | 
| 372 | 
            +
                            const c = matrixData[2];
         | 
| 373 | 
            +
                            const d = matrixData[3];
         | 
| 374 | 
            +
                            const e = matrixData[4];
         | 
| 375 | 
            +
                            const f = matrixData[5];
         | 
| 376 | 
            +
                            const transform = new Mat33(a, c, e, b, d, f, 0, 0, 1);
         | 
| 377 | 
            +
                            return transform;
         | 
| 378 | 
            +
                        },
         | 
| 379 | 
            +
                        scale: (scaleArgs) => {
         | 
| 380 | 
            +
                            let scaleX, scaleY;
         | 
| 381 | 
            +
                            if (scaleArgs.length === 1) {
         | 
| 382 | 
            +
                                scaleX = scaleArgs[0];
         | 
| 383 | 
            +
                                scaleY = scaleArgs[0];
         | 
| 384 | 
            +
                            }
         | 
| 385 | 
            +
                            else if (scaleArgs.length === 2) {
         | 
| 386 | 
            +
                                scaleX = scaleArgs[0];
         | 
| 387 | 
            +
                                scaleY = scaleArgs[1];
         | 
| 388 | 
            +
                            }
         | 
| 389 | 
            +
                            else {
         | 
| 390 | 
            +
                                throw new Error(`The scale() function only supports two arguments. Given: ${scaleArgs}`);
         | 
| 391 | 
            +
                            }
         | 
| 392 | 
            +
                            return Mat33.scaling2D(Vec2_1.Vec2.of(scaleX, scaleY));
         | 
| 393 | 
            +
                        },
         | 
| 394 | 
            +
                        translate: (translateArgs) => {
         | 
| 395 | 
            +
                            let translateX = 0;
         | 
| 396 | 
            +
                            let translateY = 0;
         | 
| 397 | 
            +
                            if (translateArgs.length === 1) {
         | 
| 398 | 
            +
                                // If no y translation is given, assume 0.
         | 
| 399 | 
            +
                                translateX = translateArgs[0];
         | 
| 400 | 
            +
                            }
         | 
| 401 | 
            +
                            else if (translateArgs.length === 2) {
         | 
| 402 | 
            +
                                translateX = translateArgs[0];
         | 
| 403 | 
            +
                                translateY = translateArgs[1];
         | 
| 404 | 
            +
                            }
         | 
| 405 | 
            +
                            else {
         | 
| 406 | 
            +
                                throw new Error(`The translate() function requires either 1 or 2 arguments. Given ${translateArgs}`);
         | 
| 407 | 
            +
                            }
         | 
| 408 | 
            +
                            return Mat33.translation(Vec2_1.Vec2.of(translateX, translateY));
         | 
| 409 | 
            +
                        },
         | 
| 410 | 
            +
                    };
         | 
| 411 | 
            +
                    // A command (\w+)
         | 
| 412 | 
            +
                    // followed by a set of arguments ([ \t\n0-9eE.,\-%]+)
         | 
| 413 | 
            +
                    const partRegex = /\s*(\w+)\s*\(([^)]*)\)/ig;
         | 
| 414 | 
            +
                    let match;
         | 
| 415 | 
            +
                    let matrix = null;
         | 
| 416 | 
            +
                    while ((match = partRegex.exec(cssString)) !== null) {
         | 
| 417 | 
            +
                        const action = match[1].toLowerCase();
         | 
| 418 | 
            +
                        if (!(action in keywordToAction)) {
         | 
| 419 | 
            +
                            throw new Error(`Unsupported CSS transform action: ${action}`);
         | 
| 420 | 
            +
                        }
         | 
| 421 | 
            +
                        const args = parseArguments(match[2]);
         | 
| 422 | 
            +
                        const currentMatrix = keywordToAction[action](args);
         | 
| 423 | 
            +
                        if (!matrix) {
         | 
| 424 | 
            +
                            matrix = currentMatrix;
         | 
| 425 | 
            +
                        }
         | 
| 426 | 
            +
                        else {
         | 
| 427 | 
            +
                            matrix = matrix.rightMul(currentMatrix);
         | 
| 428 | 
            +
                        }
         | 
| 331 429 | 
             
                    }
         | 
| 332 | 
            -
                     | 
| 333 | 
            -
                    const a = matrixData[0];
         | 
| 334 | 
            -
                    const b = matrixData[1];
         | 
| 335 | 
            -
                    const c = matrixData[2];
         | 
| 336 | 
            -
                    const d = matrixData[3];
         | 
| 337 | 
            -
                    const e = matrixData[4];
         | 
| 338 | 
            -
                    const f = matrixData[5];
         | 
| 339 | 
            -
                    const transform = new Mat33(a, c, e, b, d, f, 0, 0, 1);
         | 
| 340 | 
            -
                    return transform;
         | 
| 430 | 
            +
                    return matrix ?? Mat33.identity;
         | 
| 341 431 | 
             
                }
         | 
| 342 432 | 
             
            }
         | 
| 343 433 | 
             
            exports.Mat33 = Mat33;
         | 
    
        package/dist/cjs/Vec3.d.ts
    CHANGED
    
    | @@ -35,6 +35,13 @@ export declare class Vec3 { | |
| 35 35 | 
             
                length(): number;
         | 
| 36 36 | 
             
                magnitude(): number;
         | 
| 37 37 | 
             
                magnitudeSquared(): number;
         | 
| 38 | 
            +
                /**
         | 
| 39 | 
            +
                 * Returns the entry of this with the greatest magnitude.
         | 
| 40 | 
            +
                 *
         | 
| 41 | 
            +
                 * In other words, returns $\max \{ |x| : x \in {\bf v} \}$, where ${\bf v}$ is the set of
         | 
| 42 | 
            +
                 * all entries of this vector.
         | 
| 43 | 
            +
                 */
         | 
| 44 | 
            +
                maximumEntryMagnitude(): number;
         | 
| 38 45 | 
             
                /**
         | 
| 39 46 | 
             
                 * Return this' angle in the XY plane (treats this as a Vec2).
         | 
| 40 47 | 
             
                 *
         | 
    
        package/dist/cjs/Vec3.js
    CHANGED
    
    | @@ -58,6 +58,15 @@ class Vec3 { | |
| 58 58 | 
             
                magnitudeSquared() {
         | 
| 59 59 | 
             
                    return this.dot(this);
         | 
| 60 60 | 
             
                }
         | 
| 61 | 
            +
                /**
         | 
| 62 | 
            +
                 * Returns the entry of this with the greatest magnitude.
         | 
| 63 | 
            +
                 *
         | 
| 64 | 
            +
                 * In other words, returns $\max \{ |x| : x \in {\bf v} \}$, where ${\bf v}$ is the set of
         | 
| 65 | 
            +
                 * all entries of this vector.
         | 
| 66 | 
            +
                 */
         | 
| 67 | 
            +
                maximumEntryMagnitude() {
         | 
| 68 | 
            +
                    return Math.max(Math.abs(this.x), Math.max(Math.abs(this.y), Math.abs(this.z)));
         | 
| 69 | 
            +
                }
         | 
| 61 70 | 
             
                /**
         | 
| 62 71 | 
             
                 * Return this' angle in the XY plane (treats this as a Vec2).
         | 
| 63 72 | 
             
                 *
         | 
    
        package/dist/mjs/Mat33.d.ts
    CHANGED
    
    | @@ -107,14 +107,31 @@ export declare class Mat33 { | |
| 107 107 | 
             
                mapEntries(mapping: (component: number, rowcol: [number, number]) => number): Mat33;
         | 
| 108 108 | 
             
                /** Estimate the scale factor of this matrix (based on the first row). */
         | 
| 109 109 | 
             
                getScaleFactor(): number;
         | 
| 110 | 
            +
                /** Returns the `idx`-th column (`idx` is 0-indexed). */
         | 
| 111 | 
            +
                getColumn(idx: number): Vec3;
         | 
| 112 | 
            +
                /** Returns the magnitude of the entry with the largest entry */
         | 
| 113 | 
            +
                maximumEntryMagnitude(): number;
         | 
| 110 114 | 
             
                /**
         | 
| 111 115 | 
             
                 * Constructs a 3x3 translation matrix (for translating `Vec2`s) using
         | 
| 112 116 | 
             
                 * **transformVec2**.
         | 
| 117 | 
            +
                 *
         | 
| 118 | 
            +
                 * Creates a matrix in the form
         | 
| 119 | 
            +
                 * $$
         | 
| 120 | 
            +
                 * 	\begin{pmatrix}
         | 
| 121 | 
            +
                 * 		1 & 0 & {\tt amount.x}\\
         | 
| 122 | 
            +
                 * 		0 & 1 & {\tt amount.y}\\
         | 
| 123 | 
            +
                 * 		0 & 0 & 1
         | 
| 124 | 
            +
                 * 	\end{pmatrix}
         | 
| 125 | 
            +
                 * $$
         | 
| 113 126 | 
             
                 */
         | 
| 114 127 | 
             
                static translation(amount: Vec2): Mat33;
         | 
| 115 128 | 
             
                static zRotation(radians: number, center?: Point2): Mat33;
         | 
| 116 129 | 
             
                static scaling2D(amount: number | Vec2, center?: Point2): Mat33;
         | 
| 117 | 
            -
                /** | 
| 130 | 
            +
                /**
         | 
| 131 | 
            +
                 * **Note**: Assumes `this.c1 = this.c2 = 0` and `this.c3 = 1`.
         | 
| 132 | 
            +
                 *
         | 
| 133 | 
            +
                 * @see {@link fromCSSMatrix} and {@link toSafeCSSTransformList}
         | 
| 134 | 
            +
                 */
         | 
| 118 135 | 
             
                toCSSMatrix(): string;
         | 
| 119 136 | 
             
                /**
         | 
| 120 137 | 
             
                 * Converts a CSS-form `matrix(a, b, c, d, e, f)` to a Mat33.
         | 
| @@ -0,0 +1 @@ | |
| 1 | 
            +
            export {};
         | 
    
        package/dist/mjs/Mat33.mjs
    CHANGED
    
    | @@ -253,9 +253,30 @@ export class Mat33 { | |
| 253 253 | 
             
                getScaleFactor() {
         | 
| 254 254 | 
             
                    return Math.hypot(this.a1, this.a2);
         | 
| 255 255 | 
             
                }
         | 
| 256 | 
            +
                /** Returns the `idx`-th column (`idx` is 0-indexed). */
         | 
| 257 | 
            +
                getColumn(idx) {
         | 
| 258 | 
            +
                    return Vec3.of(this.rows[0].at(idx), this.rows[1].at(idx), this.rows[2].at(idx));
         | 
| 259 | 
            +
                }
         | 
| 260 | 
            +
                /** Returns the magnitude of the entry with the largest entry */
         | 
| 261 | 
            +
                maximumEntryMagnitude() {
         | 
| 262 | 
            +
                    let greatestSoFar = Math.abs(this.a1);
         | 
| 263 | 
            +
                    for (const entry of this.toArray()) {
         | 
| 264 | 
            +
                        greatestSoFar = Math.max(greatestSoFar, Math.abs(entry));
         | 
| 265 | 
            +
                    }
         | 
| 266 | 
            +
                    return greatestSoFar;
         | 
| 267 | 
            +
                }
         | 
| 256 268 | 
             
                /**
         | 
| 257 269 | 
             
                 * Constructs a 3x3 translation matrix (for translating `Vec2`s) using
         | 
| 258 270 | 
             
                 * **transformVec2**.
         | 
| 271 | 
            +
                 *
         | 
| 272 | 
            +
                 * Creates a matrix in the form
         | 
| 273 | 
            +
                 * $$
         | 
| 274 | 
            +
                 * 	\begin{pmatrix}
         | 
| 275 | 
            +
                 * 		1 & 0 & {\tt amount.x}\\
         | 
| 276 | 
            +
                 * 		0 & 1 & {\tt amount.y}\\
         | 
| 277 | 
            +
                 * 		0 & 0 & 1
         | 
| 278 | 
            +
                 * 	\end{pmatrix}
         | 
| 279 | 
            +
                 * $$
         | 
| 259 280 | 
             
                 */
         | 
| 260 281 | 
             
                static translation(amount) {
         | 
| 261 282 | 
             
                    // When transforming Vec2s by a 3x3 matrix, we give the input
         | 
| @@ -290,7 +311,11 @@ export class Mat33 { | |
| 290 311 | 
             
                    // Translate such that [center] goes to (0, 0)
         | 
| 291 312 | 
             
                    return result.rightMul(Mat33.translation(center.times(-1)));
         | 
| 292 313 | 
             
                }
         | 
| 293 | 
            -
                /** | 
| 314 | 
            +
                /**
         | 
| 315 | 
            +
                 * **Note**: Assumes `this.c1 = this.c2 = 0` and `this.c3 = 1`.
         | 
| 316 | 
            +
                 *
         | 
| 317 | 
            +
                 * @see {@link fromCSSMatrix} and {@link toSafeCSSTransformList}
         | 
| 318 | 
            +
                 */
         | 
| 294 319 | 
             
                toCSSMatrix() {
         | 
| 295 320 | 
             
                    return `matrix(${this.a1},${this.b1},${this.a2},${this.b2},${this.a3},${this.b3})`;
         | 
| 296 321 | 
             
                }
         | 
| @@ -308,30 +333,95 @@ export class Mat33 { | |
| 308 333 | 
             
                    if (cssString === '' || cssString === 'none') {
         | 
| 309 334 | 
             
                        return Mat33.identity;
         | 
| 310 335 | 
             
                    }
         | 
| 311 | 
            -
                    const  | 
| 312 | 
            -
             | 
| 313 | 
            -
             | 
| 314 | 
            -
             | 
| 315 | 
            -
             | 
| 316 | 
            -
             | 
| 317 | 
            -
             | 
| 318 | 
            -
             | 
| 319 | 
            -
             | 
| 320 | 
            -
             | 
| 321 | 
            -
             | 
| 322 | 
            -
             | 
| 323 | 
            -
             | 
| 324 | 
            -
             | 
| 336 | 
            +
                    const parseArguments = (argumentString) => {
         | 
| 337 | 
            +
                        return argumentString.split(/[, \t\n]+/g).map(argString => {
         | 
| 338 | 
            +
                            let isPercentage = false;
         | 
| 339 | 
            +
                            if (argString.endsWith('%')) {
         | 
| 340 | 
            +
                                isPercentage = true;
         | 
| 341 | 
            +
                                argString = argString.substring(0, argString.length - 1);
         | 
| 342 | 
            +
                            }
         | 
| 343 | 
            +
                            // Remove trailing px units.
         | 
| 344 | 
            +
                            argString = argString.replace(/px$/ig, '');
         | 
| 345 | 
            +
                            const numberExp = /^[-]?\d*(?:\.\d*)?(?:[eE][-+]?\d+)?$/i;
         | 
| 346 | 
            +
                            if (!numberExp.exec(argString)) {
         | 
| 347 | 
            +
                                throw new Error(`All arguments to transform functions must be numeric (state: ${JSON.stringify({
         | 
| 348 | 
            +
                                    currentArgument: argString,
         | 
| 349 | 
            +
                                    allArguments: argumentString,
         | 
| 350 | 
            +
                                })})`);
         | 
| 351 | 
            +
                            }
         | 
| 352 | 
            +
                            let argNumber = parseFloat(argString);
         | 
| 353 | 
            +
                            if (isPercentage) {
         | 
| 354 | 
            +
                                argNumber /= 100;
         | 
| 355 | 
            +
                            }
         | 
| 356 | 
            +
                            return argNumber;
         | 
| 357 | 
            +
                        });
         | 
| 358 | 
            +
                    };
         | 
| 359 | 
            +
                    const keywordToAction = {
         | 
| 360 | 
            +
                        matrix: (matrixData) => {
         | 
| 361 | 
            +
                            if (matrixData.length !== 6) {
         | 
| 362 | 
            +
                                throw new Error(`Invalid matrix argument: ${matrixData}. Must have length 6`);
         | 
| 363 | 
            +
                            }
         | 
| 364 | 
            +
                            const a = matrixData[0];
         | 
| 365 | 
            +
                            const b = matrixData[1];
         | 
| 366 | 
            +
                            const c = matrixData[2];
         | 
| 367 | 
            +
                            const d = matrixData[3];
         | 
| 368 | 
            +
                            const e = matrixData[4];
         | 
| 369 | 
            +
                            const f = matrixData[5];
         | 
| 370 | 
            +
                            const transform = new Mat33(a, c, e, b, d, f, 0, 0, 1);
         | 
| 371 | 
            +
                            return transform;
         | 
| 372 | 
            +
                        },
         | 
| 373 | 
            +
                        scale: (scaleArgs) => {
         | 
| 374 | 
            +
                            let scaleX, scaleY;
         | 
| 375 | 
            +
                            if (scaleArgs.length === 1) {
         | 
| 376 | 
            +
                                scaleX = scaleArgs[0];
         | 
| 377 | 
            +
                                scaleY = scaleArgs[0];
         | 
| 378 | 
            +
                            }
         | 
| 379 | 
            +
                            else if (scaleArgs.length === 2) {
         | 
| 380 | 
            +
                                scaleX = scaleArgs[0];
         | 
| 381 | 
            +
                                scaleY = scaleArgs[1];
         | 
| 382 | 
            +
                            }
         | 
| 383 | 
            +
                            else {
         | 
| 384 | 
            +
                                throw new Error(`The scale() function only supports two arguments. Given: ${scaleArgs}`);
         | 
| 385 | 
            +
                            }
         | 
| 386 | 
            +
                            return Mat33.scaling2D(Vec2.of(scaleX, scaleY));
         | 
| 387 | 
            +
                        },
         | 
| 388 | 
            +
                        translate: (translateArgs) => {
         | 
| 389 | 
            +
                            let translateX = 0;
         | 
| 390 | 
            +
                            let translateY = 0;
         | 
| 391 | 
            +
                            if (translateArgs.length === 1) {
         | 
| 392 | 
            +
                                // If no y translation is given, assume 0.
         | 
| 393 | 
            +
                                translateX = translateArgs[0];
         | 
| 394 | 
            +
                            }
         | 
| 395 | 
            +
                            else if (translateArgs.length === 2) {
         | 
| 396 | 
            +
                                translateX = translateArgs[0];
         | 
| 397 | 
            +
                                translateY = translateArgs[1];
         | 
| 398 | 
            +
                            }
         | 
| 399 | 
            +
                            else {
         | 
| 400 | 
            +
                                throw new Error(`The translate() function requires either 1 or 2 arguments. Given ${translateArgs}`);
         | 
| 401 | 
            +
                            }
         | 
| 402 | 
            +
                            return Mat33.translation(Vec2.of(translateX, translateY));
         | 
| 403 | 
            +
                        },
         | 
| 404 | 
            +
                    };
         | 
| 405 | 
            +
                    // A command (\w+)
         | 
| 406 | 
            +
                    // followed by a set of arguments ([ \t\n0-9eE.,\-%]+)
         | 
| 407 | 
            +
                    const partRegex = /\s*(\w+)\s*\(([^)]*)\)/ig;
         | 
| 408 | 
            +
                    let match;
         | 
| 409 | 
            +
                    let matrix = null;
         | 
| 410 | 
            +
                    while ((match = partRegex.exec(cssString)) !== null) {
         | 
| 411 | 
            +
                        const action = match[1].toLowerCase();
         | 
| 412 | 
            +
                        if (!(action in keywordToAction)) {
         | 
| 413 | 
            +
                            throw new Error(`Unsupported CSS transform action: ${action}`);
         | 
| 414 | 
            +
                        }
         | 
| 415 | 
            +
                        const args = parseArguments(match[2]);
         | 
| 416 | 
            +
                        const currentMatrix = keywordToAction[action](args);
         | 
| 417 | 
            +
                        if (!matrix) {
         | 
| 418 | 
            +
                            matrix = currentMatrix;
         | 
| 419 | 
            +
                        }
         | 
| 420 | 
            +
                        else {
         | 
| 421 | 
            +
                            matrix = matrix.rightMul(currentMatrix);
         | 
| 422 | 
            +
                        }
         | 
| 325 423 | 
             
                    }
         | 
| 326 | 
            -
                     | 
| 327 | 
            -
                    const a = matrixData[0];
         | 
| 328 | 
            -
                    const b = matrixData[1];
         | 
| 329 | 
            -
                    const c = matrixData[2];
         | 
| 330 | 
            -
                    const d = matrixData[3];
         | 
| 331 | 
            -
                    const e = matrixData[4];
         | 
| 332 | 
            -
                    const f = matrixData[5];
         | 
| 333 | 
            -
                    const transform = new Mat33(a, c, e, b, d, f, 0, 0, 1);
         | 
| 334 | 
            -
                    return transform;
         | 
| 424 | 
            +
                    return matrix ?? Mat33.identity;
         | 
| 335 425 | 
             
                }
         | 
| 336 426 | 
             
            }
         | 
| 337 427 | 
             
            Mat33.identity = new Mat33(1, 0, 0, 0, 1, 0, 0, 0, 1);
         | 
    
        package/dist/mjs/Vec3.d.ts
    CHANGED
    
    | @@ -35,6 +35,13 @@ export declare class Vec3 { | |
| 35 35 | 
             
                length(): number;
         | 
| 36 36 | 
             
                magnitude(): number;
         | 
| 37 37 | 
             
                magnitudeSquared(): number;
         | 
| 38 | 
            +
                /**
         | 
| 39 | 
            +
                 * Returns the entry of this with the greatest magnitude.
         | 
| 40 | 
            +
                 *
         | 
| 41 | 
            +
                 * In other words, returns $\max \{ |x| : x \in {\bf v} \}$, where ${\bf v}$ is the set of
         | 
| 42 | 
            +
                 * all entries of this vector.
         | 
| 43 | 
            +
                 */
         | 
| 44 | 
            +
                maximumEntryMagnitude(): number;
         | 
| 38 45 | 
             
                /**
         | 
| 39 46 | 
             
                 * Return this' angle in the XY plane (treats this as a Vec2).
         | 
| 40 47 | 
             
                 *
         | 
    
        package/dist/mjs/Vec3.mjs
    CHANGED
    
    | @@ -55,6 +55,15 @@ export class Vec3 { | |
| 55 55 | 
             
                magnitudeSquared() {
         | 
| 56 56 | 
             
                    return this.dot(this);
         | 
| 57 57 | 
             
                }
         | 
| 58 | 
            +
                /**
         | 
| 59 | 
            +
                 * Returns the entry of this with the greatest magnitude.
         | 
| 60 | 
            +
                 *
         | 
| 61 | 
            +
                 * In other words, returns $\max \{ |x| : x \in {\bf v} \}$, where ${\bf v}$ is the set of
         | 
| 62 | 
            +
                 * all entries of this vector.
         | 
| 63 | 
            +
                 */
         | 
| 64 | 
            +
                maximumEntryMagnitude() {
         | 
| 65 | 
            +
                    return Math.max(Math.abs(this.x), Math.max(Math.abs(this.y), Math.abs(this.z)));
         | 
| 66 | 
            +
                }
         | 
| 58 67 | 
             
                /**
         | 
| 59 68 | 
             
                 * Return this' angle in the XY plane (treats this as a Vec2).
         | 
| 60 69 | 
             
                 *
         | 
    
        package/package.json
    CHANGED
    
    | @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            {
         | 
| 2 2 | 
             
            	"name": "@js-draw/math",
         | 
| 3 | 
            -
            	"version": "1. | 
| 3 | 
            +
            	"version": "1.4.0",
         | 
| 4 4 | 
             
            	"description": "A math library for js-draw. ",
         | 
| 5 5 | 
             
            	"types": "./dist/mjs/lib.d.ts",
         | 
| 6 6 | 
             
            	"main": "./dist/cjs/lib.js",
         | 
| @@ -22,13 +22,13 @@ | |
| 22 22 | 
             
            		"dist-test": "npm run build && cd dist-test/test_imports && npm install && npm run test",
         | 
| 23 23 | 
             
            		"dist": "npm run build && npm run dist-test",
         | 
| 24 24 | 
             
            		"build": "rm -rf ./dist && mkdir dist && build-tool build",
         | 
| 25 | 
            -
            		"watch": "rm -rf ./dist && mkdir dist && build-tool watch"
         | 
| 25 | 
            +
            		"watch": "rm -rf ./dist/* && mkdir -p dist && build-tool watch"
         | 
| 26 26 | 
             
            	},
         | 
| 27 27 | 
             
            	"dependencies": {
         | 
| 28 28 | 
             
            		"bezier-js": "6.1.3"
         | 
| 29 29 | 
             
            	},
         | 
| 30 30 | 
             
            	"devDependencies": {
         | 
| 31 | 
            -
            		"@js-draw/build-tool": "^1.0 | 
| 31 | 
            +
            		"@js-draw/build-tool": "^1.4.0",
         | 
| 32 32 | 
             
            		"@types/bezier-js": "4.1.0",
         | 
| 33 33 | 
             
            		"@types/jest": "29.5.3",
         | 
| 34 34 | 
             
            		"@types/jsdom": "21.1.1"
         | 
| @@ -45,5 +45,5 @@ | |
| 45 45 | 
             
            		"svg",
         | 
| 46 46 | 
             
            		"math"
         | 
| 47 47 | 
             
            	],
         | 
| 48 | 
            -
            	"gitHead": " | 
| 48 | 
            +
            	"gitHead": "b520078c16a4d23d9bed4531eafda87bfce3f6b1"
         | 
| 49 49 | 
             
            }
         | 
| @@ -0,0 +1,90 @@ | |
| 1 | 
            +
            import Mat33 from './Mat33';
         | 
| 2 | 
            +
            import { Vec2 } from './Vec2';
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            describe('Mat33.fromCSSMatrix', () => {
         | 
| 5 | 
            +
            	it('should convert CSS matrix(...) strings to matricies', () => {
         | 
| 6 | 
            +
            		// From MDN:
         | 
| 7 | 
            +
            		// 		⎡ a c e ⎤
         | 
| 8 | 
            +
            		// 		⎢ b d f ⎥  =  matrix(a,b,c,d,e,f)
         | 
| 9 | 
            +
            		// 		⎣ 0 0 1 ⎦
         | 
| 10 | 
            +
            		const identity = Mat33.fromCSSMatrix('matrix(1, 0, 0, 1, 0, 0)');
         | 
| 11 | 
            +
            		expect(identity).objEq(Mat33.identity);
         | 
| 12 | 
            +
            		expect(Mat33.fromCSSMatrix('matrix(1, 2, 3, 4, 5, 6)')).objEq(new Mat33(
         | 
| 13 | 
            +
            			1, 3, 5,
         | 
| 14 | 
            +
            			2, 4, 6,
         | 
| 15 | 
            +
            			0, 0, 1,
         | 
| 16 | 
            +
            		));
         | 
| 17 | 
            +
            		expect(Mat33.fromCSSMatrix('matrix(1e2, 2, 3, 4, 5, 6)')).objEq(new Mat33(
         | 
| 18 | 
            +
            			1e2, 3, 5,
         | 
| 19 | 
            +
            			2, 4, 6,
         | 
| 20 | 
            +
            			0, 0, 1,
         | 
| 21 | 
            +
            		));
         | 
| 22 | 
            +
            		expect(Mat33.fromCSSMatrix('matrix(1.6, 2, .3, 4, 5, 6)')).objEq(new Mat33(
         | 
| 23 | 
            +
            			1.6, .3, 5,
         | 
| 24 | 
            +
            			2, 4, 6,
         | 
| 25 | 
            +
            			0, 0, 1,
         | 
| 26 | 
            +
            		));
         | 
| 27 | 
            +
            		expect(Mat33.fromCSSMatrix('matrix(-1, 2, 3.E-2, 4, -5.123, -6.5)')).objEq(new Mat33(
         | 
| 28 | 
            +
            			-1, 0.03, -5.123,
         | 
| 29 | 
            +
            			2, 4, -6.5,
         | 
| 30 | 
            +
            			0, 0, 1,
         | 
| 31 | 
            +
            		));
         | 
| 32 | 
            +
            		expect(Mat33.fromCSSMatrix('matrix(1.6,\n\t2, .3, 4, 5, 6)')).objEq(new Mat33(
         | 
| 33 | 
            +
            			1.6, .3, 5,
         | 
| 34 | 
            +
            			2, 4, 6,
         | 
| 35 | 
            +
            			0, 0, 1,
         | 
| 36 | 
            +
            		));
         | 
| 37 | 
            +
            		expect(Mat33.fromCSSMatrix('matrix(1.6,2, .3E-2, 4, 5, 6)')).objEq(new Mat33(
         | 
| 38 | 
            +
            			1.6, 3e-3, 5,
         | 
| 39 | 
            +
            			2, 4, 6,
         | 
| 40 | 
            +
            			0, 0, 1,
         | 
| 41 | 
            +
            		));
         | 
| 42 | 
            +
            		expect(Mat33.fromCSSMatrix('matrix(-1, 2e6,	3E-2,-5.123, -6.5e-1, 0.01)')).objEq(new Mat33(
         | 
| 43 | 
            +
            			-1, 	3E-2,  	-6.5e-1,
         | 
| 44 | 
            +
            			2e6,	-5.123,	0.01,
         | 
| 45 | 
            +
            			0,  	0,     	1,
         | 
| 46 | 
            +
            		));
         | 
| 47 | 
            +
            	});
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            	it('should convert multi-matrix arguments into a single CSS matrix', () => {
         | 
| 50 | 
            +
            		const identity = Mat33.fromCSSMatrix('matrix(1, 0, 0, 1, 0, 0) matrix(1, 0, 0, 1, 0, 0)');
         | 
| 51 | 
            +
            		expect(identity).objEq(Mat33.identity);
         | 
| 52 | 
            +
             | 
| 53 | 
            +
            		expect(Mat33.fromCSSMatrix('matrix(1, 0, 0, 1, 0, 0) matrix(1, 2, 3, 4, 5, 6) matrix(1, 0, 0, 1, 0, 0)')).objEq(new Mat33(
         | 
| 54 | 
            +
            			1, 3, 5,
         | 
| 55 | 
            +
            			2, 4, 6,
         | 
| 56 | 
            +
            			0, 0, 1,
         | 
| 57 | 
            +
            		));
         | 
| 58 | 
            +
             | 
| 59 | 
            +
            		expect(Mat33.fromCSSMatrix('matrix(2,\n\t 0, 0, 2, 0, 0) matrix(1, 2, 3, 4, 5, 6) matrix(1, 0, 0, 1, 0, 0)')).objEq(new Mat33(
         | 
| 60 | 
            +
            			2, 6, 10,
         | 
| 61 | 
            +
            			4, 8, 12,
         | 
| 62 | 
            +
            			0, 0, 1,
         | 
| 63 | 
            +
            		));
         | 
| 64 | 
            +
            	});
         | 
| 65 | 
            +
             | 
| 66 | 
            +
            	it('should convert scale()s with a single argument', () => {
         | 
| 67 | 
            +
            		expect(Mat33.fromCSSMatrix('scale(1)')).objEq(Mat33.identity);
         | 
| 68 | 
            +
            		expect(Mat33.fromCSSMatrix('scale(0.4)')).objEq(Mat33.scaling2D(0.4));
         | 
| 69 | 
            +
            		expect(Mat33.fromCSSMatrix('scale(-0.4 )')).objEq(Mat33.scaling2D(-0.4));
         | 
| 70 | 
            +
            		expect(Mat33.fromCSSMatrix('scale(100%)')).objEq(Mat33.identity);
         | 
| 71 | 
            +
            		expect(Mat33.fromCSSMatrix('scale(20e2%)')).objEq(Mat33.scaling2D(20));
         | 
| 72 | 
            +
            		expect(Mat33.fromCSSMatrix('scale(200%) scale(50%)')).objEq(Mat33.identity);
         | 
| 73 | 
            +
            	});
         | 
| 74 | 
            +
             | 
| 75 | 
            +
            	it('should convert scale()s with two arguments', () => {
         | 
| 76 | 
            +
            		expect(Mat33.fromCSSMatrix('scale(1\t 1)')).objEq(Mat33.identity);
         | 
| 77 | 
            +
            		expect(Mat33.fromCSSMatrix('scale(1\t 2)')).objEq(Mat33.scaling2D(Vec2.of(1, 2)));
         | 
| 78 | 
            +
            		expect(Mat33.fromCSSMatrix('scale(1\t 2) scale(1)')).objEq(Mat33.scaling2D(Vec2.of(1, 2)));
         | 
| 79 | 
            +
            	});
         | 
| 80 | 
            +
             | 
| 81 | 
            +
            	it('should convert translate()s', () => {
         | 
| 82 | 
            +
            		expect(Mat33.fromCSSMatrix('translate(0)')).objEq(Mat33.identity);
         | 
| 83 | 
            +
            		expect(Mat33.fromCSSMatrix('translate(1, 1)')).objEq(Mat33.translation(Vec2.of(1, 1)));
         | 
| 84 | 
            +
            		expect(Mat33.fromCSSMatrix('translate(1 200%)')).objEq(Mat33.translation(Vec2.of(1, 2)));
         | 
| 85 | 
            +
            	});
         | 
| 86 | 
            +
             | 
| 87 | 
            +
            	it('should support px following numbers', () => {
         | 
| 88 | 
            +
            		expect(Mat33.fromCSSMatrix('translate(1px, 2px)')).objEq(Mat33.translation(Vec2.of(1, 2)));
         | 
| 89 | 
            +
            	});
         | 
| 90 | 
            +
            });
         | 
    
        package/src/Mat33.test.ts
    CHANGED
    
    | @@ -198,47 +198,11 @@ describe('Mat33 tests', () => { | |
| 198 198 | 
             
            		));
         | 
| 199 199 | 
             
            	});
         | 
| 200 200 |  | 
| 201 | 
            -
            	it('should  | 
| 202 | 
            -
            		 | 
| 203 | 
            -
            		 | 
| 204 | 
            -
             | 
| 205 | 
            -
            		// | 
| 206 | 
            -
            		 | 
| 207 | 
            -
            		expect(identity).objEq(Mat33.identity);
         | 
| 208 | 
            -
            		expect(Mat33.fromCSSMatrix('matrix(1, 2, 3, 4, 5, 6)')).objEq(new Mat33(
         | 
| 209 | 
            -
            			1, 3, 5,
         | 
| 210 | 
            -
            			2, 4, 6,
         | 
| 211 | 
            -
            			0, 0, 1,
         | 
| 212 | 
            -
            		));
         | 
| 213 | 
            -
            		expect(Mat33.fromCSSMatrix('matrix(1e2, 2, 3, 4, 5, 6)')).objEq(new Mat33(
         | 
| 214 | 
            -
            			1e2, 3, 5,
         | 
| 215 | 
            -
            			2, 4, 6,
         | 
| 216 | 
            -
            			0, 0, 1,
         | 
| 217 | 
            -
            		));
         | 
| 218 | 
            -
            		expect(Mat33.fromCSSMatrix('matrix(1.6, 2, .3, 4, 5, 6)')).objEq(new Mat33(
         | 
| 219 | 
            -
            			1.6, .3, 5,
         | 
| 220 | 
            -
            			2, 4, 6,
         | 
| 221 | 
            -
            			0, 0, 1,
         | 
| 222 | 
            -
            		));
         | 
| 223 | 
            -
            		expect(Mat33.fromCSSMatrix('matrix(-1, 2, 3.E-2, 4, -5.123, -6.5)')).objEq(new Mat33(
         | 
| 224 | 
            -
            			-1, 0.03, -5.123,
         | 
| 225 | 
            -
            			2, 4, -6.5,
         | 
| 226 | 
            -
            			0, 0, 1,
         | 
| 227 | 
            -
            		));
         | 
| 228 | 
            -
            		expect(Mat33.fromCSSMatrix('matrix(1.6,\n\t2, .3, 4, 5, 6)')).objEq(new Mat33(
         | 
| 229 | 
            -
            			1.6, .3, 5,
         | 
| 230 | 
            -
            			2, 4, 6,
         | 
| 231 | 
            -
            			0, 0, 1,
         | 
| 232 | 
            -
            		));
         | 
| 233 | 
            -
            		expect(Mat33.fromCSSMatrix('matrix(1.6,2, .3E-2, 4, 5, 6)')).objEq(new Mat33(
         | 
| 234 | 
            -
            			1.6, 3e-3, 5,
         | 
| 235 | 
            -
            			2, 4, 6,
         | 
| 236 | 
            -
            			0, 0, 1,
         | 
| 237 | 
            -
            		));
         | 
| 238 | 
            -
            		expect(Mat33.fromCSSMatrix('matrix(-1, 2e6,	3E-2,-5.123, -6.5e-1, 0.01)')).objEq(new Mat33(
         | 
| 239 | 
            -
            			-1, 	3E-2,  	-6.5e-1,
         | 
| 240 | 
            -
            			2e6,	-5.123,	0.01,
         | 
| 241 | 
            -
            			0,  	0,     	1,
         | 
| 242 | 
            -
            		));
         | 
| 201 | 
            +
            	it('getColumn should return the given column index', () => {
         | 
| 202 | 
            +
            		expect(Mat33.identity.getColumn(0)).objEq(Vec3.unitX);
         | 
| 203 | 
            +
            		expect(Mat33.identity.getColumn(1)).objEq(Vec3.of(0, 1, 0));
         | 
| 204 | 
            +
             | 
| 205 | 
            +
            		// scaling2D only scales the x/y components of vectors it transforms
         | 
| 206 | 
            +
            		expect(Mat33.scaling2D(2).getColumn(2)).objEq(Vec3.of(0, 0, 1));
         | 
| 243 207 | 
             
            	});
         | 
| 244 208 | 
             
            });
         | 
    
        package/src/Mat33.ts
    CHANGED
    
    | @@ -335,9 +335,37 @@ export class Mat33 { | |
| 335 335 | 
             
            		return Math.hypot(this.a1, this.a2);
         | 
| 336 336 | 
             
            	}
         | 
| 337 337 |  | 
| 338 | 
            +
            	/** Returns the `idx`-th column (`idx` is 0-indexed). */
         | 
| 339 | 
            +
            	public getColumn(idx: number) {
         | 
| 340 | 
            +
            		return Vec3.of(
         | 
| 341 | 
            +
            			this.rows[0].at(idx),
         | 
| 342 | 
            +
            			this.rows[1].at(idx),
         | 
| 343 | 
            +
            			this.rows[2].at(idx),
         | 
| 344 | 
            +
            		);
         | 
| 345 | 
            +
            	}
         | 
| 346 | 
            +
             | 
| 347 | 
            +
            	/** Returns the magnitude of the entry with the largest entry */
         | 
| 348 | 
            +
            	public maximumEntryMagnitude() {
         | 
| 349 | 
            +
            		let greatestSoFar = Math.abs(this.a1);
         | 
| 350 | 
            +
            		for (const entry of this.toArray()) {
         | 
| 351 | 
            +
            			greatestSoFar = Math.max(greatestSoFar, Math.abs(entry));
         | 
| 352 | 
            +
            		}
         | 
| 353 | 
            +
             | 
| 354 | 
            +
            		return greatestSoFar;
         | 
| 355 | 
            +
            	}
         | 
| 356 | 
            +
             | 
| 338 357 | 
             
            	/**
         | 
| 339 358 | 
             
            	 * Constructs a 3x3 translation matrix (for translating `Vec2`s) using
         | 
| 340 359 | 
             
            	 * **transformVec2**.
         | 
| 360 | 
            +
            	 *
         | 
| 361 | 
            +
            	 * Creates a matrix in the form
         | 
| 362 | 
            +
            	 * $$
         | 
| 363 | 
            +
            	 * 	\begin{pmatrix}
         | 
| 364 | 
            +
            	 * 		1 & 0 & {\tt amount.x}\\
         | 
| 365 | 
            +
            	 * 		0 & 1 & {\tt amount.y}\\
         | 
| 366 | 
            +
            	 * 		0 & 0 & 1
         | 
| 367 | 
            +
            	 * 	\end{pmatrix}
         | 
| 368 | 
            +
            	 * $$
         | 
| 341 369 | 
             
            	 */
         | 
| 342 370 | 
             
            	public static translation(amount: Vec2): Mat33 {
         | 
| 343 371 | 
             
            		// When transforming Vec2s by a 3x3 matrix, we give the input
         | 
| @@ -392,7 +420,11 @@ export class Mat33 { | |
| 392 420 | 
             
            		return result.rightMul(Mat33.translation(center.times(-1)));
         | 
| 393 421 | 
             
            	}
         | 
| 394 422 |  | 
| 395 | 
            -
            	/** | 
| 423 | 
            +
            	/**
         | 
| 424 | 
            +
            	 * **Note**: Assumes `this.c1 = this.c2 = 0` and `this.c3 = 1`.
         | 
| 425 | 
            +
            	 *
         | 
| 426 | 
            +
            	 * @see {@link fromCSSMatrix} and {@link toSafeCSSTransformList}
         | 
| 427 | 
            +
            	 */
         | 
| 396 428 | 
             
            	public toCSSMatrix(): string {
         | 
| 397 429 | 
             
            		return `matrix(${this.a1},${this.b1},${this.a2},${this.b2},${this.a3},${this.b3})`;
         | 
| 398 430 | 
             
            	}
         | 
| @@ -412,39 +444,118 @@ export class Mat33 { | |
| 412 444 | 
             
            			return Mat33.identity;
         | 
| 413 445 | 
             
            		}
         | 
| 414 446 |  | 
| 415 | 
            -
            		const  | 
| 416 | 
            -
             | 
| 417 | 
            -
             | 
| 418 | 
            -
             | 
| 419 | 
            -
             | 
| 420 | 
            -
             | 
| 421 | 
            -
            				 | 
| 422 | 
            -
             | 
| 423 | 
            -
            				 | 
| 424 | 
            -
            				 | 
| 425 | 
            -
             | 
| 426 | 
            -
             | 
| 427 | 
            -
             | 
| 428 | 
            -
             | 
| 429 | 
            -
             | 
| 430 | 
            -
             | 
| 431 | 
            -
             | 
| 447 | 
            +
            		const parseArguments = (argumentString: string) => {
         | 
| 448 | 
            +
            			return argumentString.split(/[, \t\n]+/g).map(argString => {
         | 
| 449 | 
            +
            				let isPercentage = false;
         | 
| 450 | 
            +
            				if (argString.endsWith('%')) {
         | 
| 451 | 
            +
            					isPercentage = true;
         | 
| 452 | 
            +
            					argString = argString.substring(0, argString.length - 1);
         | 
| 453 | 
            +
            				}
         | 
| 454 | 
            +
             | 
| 455 | 
            +
            				// Remove trailing px units.
         | 
| 456 | 
            +
            				argString = argString.replace(/px$/ig, '');
         | 
| 457 | 
            +
             | 
| 458 | 
            +
            				const numberExp = /^[-]?\d*(?:\.\d*)?(?:[eE][-+]?\d+)?$/i;
         | 
| 459 | 
            +
             | 
| 460 | 
            +
            				if (!numberExp.exec(argString)) {
         | 
| 461 | 
            +
            					throw new Error(
         | 
| 462 | 
            +
            						`All arguments to transform functions must be numeric (state: ${
         | 
| 463 | 
            +
            							JSON.stringify({
         | 
| 464 | 
            +
            								currentArgument: argString,
         | 
| 465 | 
            +
            								allArguments: argumentString,
         | 
| 466 | 
            +
            							})
         | 
| 467 | 
            +
            						})`
         | 
| 468 | 
            +
            					);
         | 
| 469 | 
            +
            				}
         | 
| 470 | 
            +
             | 
| 471 | 
            +
            				let argNumber = parseFloat(argString);
         | 
| 472 | 
            +
             | 
| 473 | 
            +
            				if (isPercentage) {
         | 
| 474 | 
            +
            					argNumber /= 100;
         | 
| 475 | 
            +
            				}
         | 
| 476 | 
            +
             | 
| 477 | 
            +
            				return argNumber;
         | 
| 478 | 
            +
            			});
         | 
| 479 | 
            +
            		};
         | 
| 480 | 
            +
             | 
| 481 | 
            +
             | 
| 482 | 
            +
            		const keywordToAction = {
         | 
| 483 | 
            +
            			matrix: (matrixData: number[]) => {
         | 
| 484 | 
            +
            				if (matrixData.length !== 6) {
         | 
| 485 | 
            +
            					throw new Error(`Invalid matrix argument: ${matrixData}. Must have length 6`);
         | 
| 486 | 
            +
            				}
         | 
| 487 | 
            +
             | 
| 488 | 
            +
            				const a = matrixData[0];
         | 
| 489 | 
            +
            				const b = matrixData[1];
         | 
| 490 | 
            +
            				const c = matrixData[2];
         | 
| 491 | 
            +
            				const d = matrixData[3];
         | 
| 492 | 
            +
            				const e = matrixData[4];
         | 
| 493 | 
            +
            				const f = matrixData[5];
         | 
| 494 | 
            +
             | 
| 495 | 
            +
            				const transform = new Mat33(
         | 
| 496 | 
            +
            					a, c, e,
         | 
| 497 | 
            +
            					b, d, f,
         | 
| 498 | 
            +
            					0, 0, 1
         | 
| 499 | 
            +
            				);
         | 
| 500 | 
            +
            				return transform;
         | 
| 501 | 
            +
            			},
         | 
| 502 | 
            +
             | 
| 503 | 
            +
            			scale: (scaleArgs: number[]) => {
         | 
| 504 | 
            +
            				let scaleX, scaleY;
         | 
| 505 | 
            +
            				if (scaleArgs.length === 1) {
         | 
| 506 | 
            +
            					scaleX = scaleArgs[0];
         | 
| 507 | 
            +
            					scaleY = scaleArgs[0];
         | 
| 508 | 
            +
            				} else if (scaleArgs.length === 2) {
         | 
| 509 | 
            +
            					scaleX = scaleArgs[0];
         | 
| 510 | 
            +
            					scaleY = scaleArgs[1];
         | 
| 511 | 
            +
            				} else {
         | 
| 512 | 
            +
            					throw new Error(`The scale() function only supports two arguments. Given: ${scaleArgs}`);
         | 
| 513 | 
            +
            				}
         | 
| 514 | 
            +
             | 
| 515 | 
            +
            				return Mat33.scaling2D(Vec2.of(scaleX, scaleY));
         | 
| 516 | 
            +
            			},
         | 
| 517 | 
            +
             | 
| 518 | 
            +
            			translate: (translateArgs: number[]) => {
         | 
| 519 | 
            +
            				let translateX = 0;
         | 
| 520 | 
            +
            				let translateY = 0;
         | 
| 521 | 
            +
             | 
| 522 | 
            +
            				if (translateArgs.length === 1) {
         | 
| 523 | 
            +
            					// If no y translation is given, assume 0.
         | 
| 524 | 
            +
            					translateX = translateArgs[0];
         | 
| 525 | 
            +
            				} else if (translateArgs.length === 2) {
         | 
| 526 | 
            +
            					translateX = translateArgs[0];
         | 
| 527 | 
            +
            					translateY = translateArgs[1];
         | 
| 528 | 
            +
            				} else {
         | 
| 529 | 
            +
            					throw new Error(`The translate() function requires either 1 or 2 arguments. Given ${translateArgs}`);
         | 
| 530 | 
            +
            				}
         | 
| 531 | 
            +
             | 
| 532 | 
            +
            				return Mat33.translation(Vec2.of(translateX, translateY));
         | 
| 533 | 
            +
            			},
         | 
| 534 | 
            +
            		};
         | 
| 535 | 
            +
             | 
| 536 | 
            +
            		// A command (\w+)
         | 
| 537 | 
            +
            		// followed by a set of arguments ([ \t\n0-9eE.,\-%]+)
         | 
| 538 | 
            +
            		const partRegex = /\s*(\w+)\s*\(([^)]*)\)/ig;
         | 
| 539 | 
            +
            		let match;
         | 
| 540 | 
            +
            		let matrix: Mat33|null = null;
         | 
| 541 | 
            +
             | 
| 542 | 
            +
            		while ((match = partRegex.exec(cssString)) !== null) {
         | 
| 543 | 
            +
            			const action = match[1].toLowerCase();
         | 
| 544 | 
            +
            			if (!(action in keywordToAction)) {
         | 
| 545 | 
            +
            				throw new Error(`Unsupported CSS transform action: ${action}`);
         | 
| 546 | 
            +
            			}
         | 
| 547 | 
            +
             | 
| 548 | 
            +
            			const args = parseArguments(match[2]);
         | 
| 549 | 
            +
            			const currentMatrix = keywordToAction[action as keyof typeof keywordToAction](args);
         | 
| 550 | 
            +
             | 
| 551 | 
            +
            			if (!matrix) {
         | 
| 552 | 
            +
            				matrix = currentMatrix;
         | 
| 553 | 
            +
            			} else {
         | 
| 554 | 
            +
            				matrix = matrix.rightMul(currentMatrix);
         | 
| 555 | 
            +
            			}
         | 
| 432 556 | 
             
            		}
         | 
| 433 557 |  | 
| 434 | 
            -
            		 | 
| 435 | 
            -
            		const a = matrixData[0];
         | 
| 436 | 
            -
            		const b = matrixData[1];
         | 
| 437 | 
            -
            		const c = matrixData[2];
         | 
| 438 | 
            -
            		const d = matrixData[3];
         | 
| 439 | 
            -
            		const e = matrixData[4];
         | 
| 440 | 
            -
            		const f = matrixData[5];
         | 
| 441 | 
            -
             | 
| 442 | 
            -
            		const transform = new Mat33(
         | 
| 443 | 
            -
            			a, c, e,
         | 
| 444 | 
            -
            			b, d, f,
         | 
| 445 | 
            -
            			0, 0, 1
         | 
| 446 | 
            -
            		);
         | 
| 447 | 
            -
            		return transform;
         | 
| 558 | 
            +
            		return matrix ?? Mat33.identity;
         | 
| 448 559 | 
             
            	}
         | 
| 449 560 | 
             
            }
         | 
| 450 561 | 
             
            export default Mat33;
         | 
    
        package/src/Vec3.ts
    CHANGED
    
    | @@ -63,6 +63,16 @@ export class Vec3 { | |
| 63 63 | 
             
            		return this.dot(this);
         | 
| 64 64 | 
             
            	}
         | 
| 65 65 |  | 
| 66 | 
            +
            	/**
         | 
| 67 | 
            +
            	 * Returns the entry of this with the greatest magnitude.
         | 
| 68 | 
            +
            	 *
         | 
| 69 | 
            +
            	 * In other words, returns $\max \{ |x| : x \in {\bf v} \}$, where ${\bf v}$ is the set of
         | 
| 70 | 
            +
            	 * all entries of this vector.
         | 
| 71 | 
            +
            	 */
         | 
| 72 | 
            +
            	public maximumEntryMagnitude(): number {
         | 
| 73 | 
            +
            		return Math.max(Math.abs(this.x), Math.max(Math.abs(this.y), Math.abs(this.z)));
         | 
| 74 | 
            +
            	}
         | 
| 75 | 
            +
             | 
| 66 76 | 
             
            	/**
         | 
| 67 77 | 
             
            	 * Return this' angle in the XY plane (treats this as a Vec2).
         | 
| 68 78 | 
             
            	 *
         | 
    
        package/src/rounding.ts
    CHANGED