openpencil/packages/scene-graph/src/matrix.ts
2026-06-30 10:54:32 +03:00

117 lines
2.8 KiB
TypeScript

import type { Vector } from './primitives'
export type Mat3 = number[]
const identity = (): Mat3 => [1, 0, 0, 0, 1, 0, 0, 0, 1]
const multiply2 = (m1: Mat3, m2: Mat3): Mat3 => {
// row-major general 3x3 multiply: out = m1 * m2
return [
m1[0] * m2[0] + m1[1] * m2[3] + m1[2] * m2[6],
m1[0] * m2[1] + m1[1] * m2[4] + m1[2] * m2[7],
m1[0] * m2[2] + m1[1] * m2[5] + m1[2] * m2[8],
m1[3] * m2[0] + m1[4] * m2[3] + m1[5] * m2[6],
m1[3] * m2[1] + m1[4] * m2[4] + m1[5] * m2[7],
m1[3] * m2[2] + m1[4] * m2[5] + m1[5] * m2[8],
m1[6] * m2[0] + m1[7] * m2[3] + m1[8] * m2[6],
m1[6] * m2[1] + m1[7] * m2[4] + m1[8] * m2[7],
m1[6] * m2[2] + m1[7] * m2[5] + m1[8] * m2[8]
]
}
const multiply = (...ms: Mat3[]): Mat3 => {
if (ms.length === 0) return identity()
let out = ms[0].slice() as Mat3
for (let i = 1; i < ms.length; i++) out = multiply2(out, ms[i])
return out
}
const translated = (dx: number, dy: number): Mat3 => [1, 0, dx, 0, 1, dy, 0, 0, 1]
const rotated = (radians: number, px = 0, py = 0): Mat3 => {
const s = Math.sin(radians)
const c = Math.cos(radians)
// exactly like ck.Matrix.rotated
return [
c,
-s,
s * py + (1 - c) * px,
s,
c,
-s * px + (1 - c) * py,
0,
0,
1
]
}
const scaled = (sx: number, sy: number, px = 0, py = 0): Mat3 => {
// match ck.Matrix.scaled
return [
sx,
0,
px - sx * px,
0,
sy,
py - sy * py,
0,
0,
1
]
}
const invert = (m: Mat3): Mat3 | null => {
// match ck.Matrix.invert (Sarrus)
const det =
m[0] * m[4] * m[8] +
m[1] * m[5] * m[6] +
m[2] * m[3] * m[7] -
m[2] * m[4] * m[6] -
m[1] * m[3] * m[8] -
m[0] * m[5] * m[7]
if (!det) return null
return [
(m[4] * m[8] - m[5] * m[7]) / det,
(m[2] * m[7] - m[1] * m[8]) / det,
(m[1] * m[5] - m[2] * m[4]) / det,
(m[5] * m[6] - m[3] * m[8]) / det,
(m[0] * m[8] - m[2] * m[6]) / det,
(m[2] * m[3] - m[0] * m[5]) / det,
(m[3] * m[7] - m[4] * m[6]) / det,
(m[1] * m[6] - m[0] * m[7]) / det,
(m[0] * m[4] - m[1] * m[3]) / det
]
}
const mapPoints = (matrix: Mat3, ptArr: number[]): number[] => {
if (ptArr.length % 2) throw new Error('mapPoints requires even length [x,y,...].')
const out = ptArr.slice()
for (let i = 0; i < out.length; i += 2) {
const x = out[i]
const y = out[i + 1]
const denom = matrix[6] * x + matrix[7] * y + matrix[8]
const xTrans = matrix[0] * x + matrix[1] * y + matrix[2]
const yTrans = matrix[3] * x + matrix[4] * y + matrix[5]
out[i] = xTrans / denom
out[i + 1] = yTrans / denom
}
return out
}
const mapPoint = (m: Mat3, p: Vector): Vector => {
const arr = mapPoints(m, [p.x, p.y])
return { x: arr[0], y: arr[1] }
}
const Matrix = { identity, multiply, translated, rotated, scaled, invert, mapPoints, mapPoint }
export default Matrix