UNPKG

@ue-too/board

Version:

<h1 align="center"> uē-tôo </h1> <p align="center"> pan, zoom, rotate, and more with your html canvas. </p>

318 lines (317 loc) 9.91 kB
/** * 2D affine transformation matrix in standard CSS/Canvas format. * * Represents a 3x3 matrix in homogeneous coordinates, stored in the compact 6-parameter form: * ``` * | a c e | * | b d f | * | 0 0 1 | * ``` * * @property a - Horizontal scaling / rotation component (m11) * @property b - Vertical skewing / rotation component (m12) * @property c - Horizontal skewing / rotation component (m21) * @property d - Vertical scaling / rotation component (m22) * @property e - Horizontal translation (tx) * @property f - Vertical translation (ty) * * @remarks * This format is compatible with: * - Canvas 2D context: `ctx.setTransform(a, b, c, d, e, f)` * - CSS transforms: `matrix(a, b, c, d, e, f)` * - SVG transforms: `matrix(a b c d e f)` * * Common transformation types: * - **Translation**: `{a: 1, b: 0, c: 0, d: 1, e: tx, f: ty}` * - **Scaling**: `{a: sx, b: 0, c: 0, d: sy, e: 0, f: 0}` * - **Rotation**: `{a: cos(θ), b: sin(θ), c: -sin(θ), d: cos(θ), e: 0, f: 0}` * * @example * ```typescript * // Identity matrix (no transformation) * const identity: TransformationMatrix = { * a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 * }; * * // Translation by (100, 50) * const translate: TransformationMatrix = { * a: 1, b: 0, c: 0, d: 1, e: 100, f: 50 * }; * * // 2x scale * const scale: TransformationMatrix = { * a: 2, b: 0, c: 0, d: 2, e: 0, f: 0 * }; * * // 45° rotation * const rotate: TransformationMatrix = { * a: 0.707, b: 0.707, c: -0.707, d: 0.707, e: 0, f: 0 * }; * ``` * * @category Camera */ export type TransformationMatrix = { a: number; b: number; c: number; d: number; e: number; f: number; }; /** * Decomposes a camera transformation matrix back to camera parameters. * Inverse operation of {@link createCameraMatrix}. * * @param transformMatrix - The combined transformation matrix to decompose * @param devicePixelRatio - Device pixel ratio used when creating the matrix * @param canvasWidth - Canvas width in CSS pixels * @param canvasHeight - Canvas height in CSS pixels * @returns Camera parameters: position, zoom, and rotation * * @remarks * This function reverses the transformation chain applied by {@link createCameraMatrix}: * 1. Scale by devicePixelRatio * 2. Translate to canvas center * 3. Rotate by -camera.rotation * 4. Scale by zoom level * 5. Translate by -camera.position * * Final matrix: M = Scale(DPR) * Translate(center) * Rotate * Scale(zoom) * Translate(-position) * * The decomposition extracts: * - **Rotation**: From the orientation of the transformation (atan2) * - **Zoom**: From the total scale after removing devicePixelRatio * - **Position**: By reversing the translation chain * * @example * ```typescript * // Create and then decompose a matrix * const matrix = createCameraMatrix( * { x: 100, y: 200 }, * 2.0, * Math.PI / 4, * window.devicePixelRatio, * 1920, 1080 * ); * * const params = decomposeCameraMatrix( * matrix, * window.devicePixelRatio, * 1920, 1080 * ); * // params ≈ { position: {x: 100, y: 200}, zoom: 2.0, rotation: π/4 } * ``` * * @category Camera * @see {@link createCameraMatrix} for the inverse operation */ export declare function decomposeCameraMatrix(transformMatrix: TransformationMatrix, devicePixelRatio: number, canvasWidth: number, canvasHeight: number): { position: { x: number; y: number; }; zoom: number; rotation: number; }; /** * Creates a camera transformation matrix from camera parameters. * This matrix transforms world coordinates to canvas pixel coordinates. * * @param cameraPos - Camera position in world coordinates * @param zoom - Zoom level (1.0 = 100%, 2.0 = 200%, etc.) * @param rotation - Camera rotation in radians * @param devicePixelRatio - Device pixel ratio (typically window.devicePixelRatio) * @param canvasWidth - Canvas width in CSS pixels (not canvas.width!) * @param canvasHeight - Canvas height in CSS pixels (not canvas.height!) * @returns Transformation matrix for world→canvas conversion * * @remarks * **Important**: canvasWidth and canvasHeight are CSS pixel dimensions, * not the internal canvas buffer size (canvas.width/canvas.height). * Use element.clientWidth/clientHeight or the CSS dimensions. * * Transformation order: * 1. Scale by devicePixelRatio (for high-DPI displays) * 2. Translate to canvas center * 3. Rotate by -camera.rotation (negated for correct direction) * 4. Scale by zoom * 5. Translate by -camera.position (world offset) * * The resulting matrix can be applied to a canvas context: * ```typescript * const {a, b, c, d, e, f} = createCameraMatrix(...); * ctx.setTransform(a, b, c, d, e, f); * // Now draw at world coordinates * ``` * * @example * ```typescript * const matrix = createCameraMatrix( * { x: 100, y: 200 }, // camera position * 2.0, // 2x zoom * Math.PI / 6, // 30° rotation * window.devicePixelRatio, * canvas.clientWidth, // CSS width, not canvas.width! * canvas.clientHeight * ); * * ctx.setTransform(matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f); * ctx.fillRect(100, 200, 50, 50); // Draws at world coordinates (100, 200) * ``` * * @category Camera * @see {@link decomposeCameraMatrix} for extracting camera parameters from a matrix */ export declare function createCameraMatrix(cameraPos: { x: number; y: number; }, zoom: number, rotation: number, devicePixelRatio: number, canvasWidth: number, canvasHeight: number): { a: number; b: number; c: number; d: number; e: number; f: number; }; /** * Multiplies two 2D transformation matrices. * Order matters: M = m1 × m2 applies m2 first, then m1. * * @param m1 - First transformation matrix (applied second) * @param m2 - Second transformation matrix (applied first) * @returns Combined transformation matrix * * @remarks * Matrix multiplication is not commutative: m1 × m2 ≠ m2 × m1 * * The result applies transformations in right-to-left order: * - Result = m1 × m2 * - Applying result to point P: (m1 × m2) × P = m1 × (m2 × P) * - m2 is applied first, then m1 * * Common use: Building composite transformations * ```typescript * // Translate then rotate (rotate happens first!) * const translate = { a: 1, b: 0, c: 0, d: 1, e: 100, f: 0 }; * const rotate = { a: 0, b: 1, c: -1, d: 0, e: 0, f: 0 }; // 90° ccw * const combined = multiplyMatrix(translate, rotate); * // Points are rotated, then translated * ``` * * @example * ```typescript * // Combine scale and translation * const scale2x: TransformationMatrix = { * a: 2, b: 0, c: 0, d: 2, e: 0, f: 0 * }; * const translate: TransformationMatrix = { * a: 1, b: 0, c: 0, d: 1, e: 100, f: 50 * }; * * // Scale then translate * const combined = multiplyMatrix(translate, scale2x); * // Points are scaled by 2, then translated by (100, 50) * * // Chain multiple transformations * const m = multiplyMatrix( * multiplyMatrix(translate, rotate), * scale * ); * // Equivalent to: scale → rotate → translate * ``` * * @category Matrix */ export declare function multiplyMatrix(m1: TransformationMatrix, m2: TransformationMatrix): { a: number; b: number; c: number; d: number; e: number; f: number; }; /** * Decomposes a 2D transformation matrix into Translation, Rotation, and Scale (TRS) * * @param matrix - The transformation matrix to decompose * @returns Object containing translation, rotation (in radians), and scale components * * @category Matrix */ export declare function decomposeTRS(matrix: TransformationMatrix): { translation: { x: number; y: number; }; rotation: number; scale: { x: number; y: number; }; }; /** * Creates a transformation matrix from Translation, Rotation, and Scale components. * Inverse of {@link decomposeTRS}. * * @param translation - Translation vector (tx, ty) * @param rotation - Rotation angle in radians (counter-clockwise) * @param scale - Scale vector (sx, sy) * @returns Transformation matrix combining TRS * * @remarks * Transformation order: Scale → Rotate → Translate * * The resulting matrix is in standard form compatible with Canvas/CSS/SVG. * Applying this matrix transforms points as: * 1. Scale by (sx, sy) * 2. Rotate by θ radians * 3. Translate by (tx, ty) * * @example * ```typescript * // Create a transform that scales 2x, rotates 45°, then moves to (100, 50) * const matrix = createTRSMatrix( * { x: 100, y: 50 }, // translation * Math.PI / 4, // 45° rotation * { x: 2, y: 2 } // 2x scale * ); * * ctx.setTransform(matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f); * // Now drawing happens with scale→rotate→translate applied * * // Round-trip test * const decomposed = decomposeTRS(matrix); * // decomposed ≈ { translation: {x:100, y:50}, rotation: π/4, scale: {x:2, y:2} } * ``` * * @category Matrix * @see {@link decomposeTRS} for extracting TRS from a matrix */ export declare function createTRSMatrix(translation: { x: number; y: number; }, rotation: number, scale: { x: number; y: number; }): TransformationMatrix; /** * Decomposes a matrix using SVD (Singular Value Decomposition) approach * This is an alternative method that can handle more complex transformations * * @param matrix - The transformation matrix to decompose * @returns Object containing translation, rotation, and scale components * * @category Matrix */ export declare function decomposeTRSSVD(matrix: TransformationMatrix): { translation: { x: number; y: number; }; rotation: number; scale: { x: number; y: number; }; };