-
-
Notifications
You must be signed in to change notification settings - Fork 117
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(📉): addLine() and addArc() (#383)
- Loading branch information
1 parent
5abc433
commit 6c96e99
Showing
5 changed files
with
347 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,270 @@ | ||
export type Vec4 = readonly [number, number, number, number]; | ||
|
||
export type Matrix4 = readonly [Vec4, Vec4, Vec4, Vec4]; | ||
|
||
type Transform3dName = | ||
| "translateX" | ||
| "translateY" | ||
| "translateZ" | ||
| "scale" | ||
| "scaleX" | ||
| "scaleY" | ||
| "skewX" | ||
| "skewY" | ||
| "rotateZ" | ||
| "rotate" | ||
| "perspective" | ||
| "rotateX" | ||
| "rotateY" | ||
| "rotateZ" | ||
| "matrix"; | ||
|
||
type Transformations = { | ||
[Name in Transform3dName]: Name extends "matrix" ? Matrix4 : number; | ||
}; | ||
export type Transforms3d = ( | ||
| Pick<Transformations, "translateX"> | ||
| Pick<Transformations, "translateY"> | ||
| Pick<Transformations, "translateZ"> | ||
| Pick<Transformations, "scale"> | ||
| Pick<Transformations, "scaleX"> | ||
| Pick<Transformations, "scaleY"> | ||
| Pick<Transformations, "skewX"> | ||
| Pick<Transformations, "skewY"> | ||
| Pick<Transformations, "perspective"> | ||
| Pick<Transformations, "rotateX"> | ||
| Pick<Transformations, "rotateY"> | ||
| Pick<Transformations, "rotateZ"> | ||
| Pick<Transformations, "rotate"> | ||
| Pick<Transformations, "matrix"> | ||
)[]; | ||
|
||
const exhaustiveCheck = (a: never): never => { | ||
throw new Error(`Unexhaustive handling for ${a}`); | ||
}; | ||
|
||
export const identityMatrix4: Matrix4 = [ | ||
[1, 0, 0, 0], | ||
[0, 1, 0, 0], | ||
[0, 0, 1, 0], | ||
[0, 0, 0, 1], | ||
]; | ||
|
||
const translateXMatrix = (x: number): Matrix4 => { | ||
"worklet"; | ||
return [ | ||
[1, 0, 0, x], | ||
[0, 1, 0, 0], | ||
[0, 0, 1, 0], | ||
[0, 0, 0, 1], | ||
]; | ||
}; | ||
|
||
const translateYMatrix = (y: number): Matrix4 => { | ||
"worklet"; | ||
return [ | ||
[1, 0, 0, 0], | ||
[0, 1, 0, y], | ||
[0, 0, 1, 0], | ||
[0, 0, 0, 1], | ||
]; | ||
}; | ||
|
||
const translateZMatrix = (z: number): Matrix4 => { | ||
"worklet"; | ||
return [ | ||
[1, 0, 0, 0], | ||
[0, 1, 0, 0], | ||
[0, 0, 1, z], | ||
[0, 0, 0, 1], | ||
]; | ||
}; | ||
|
||
const scaleMatrix = (s: number): Matrix4 => { | ||
"worklet"; | ||
return [ | ||
[s, 0, 0, 0], | ||
[0, s, 0, 0], | ||
[0, 0, 1, 0], | ||
[0, 0, 0, 1], | ||
]; | ||
}; | ||
|
||
const scaleXMatrix = (s: number): Matrix4 => { | ||
"worklet"; | ||
return [ | ||
[s, 0, 0, 0], | ||
[0, 1, 0, 0], | ||
[0, 0, 1, 0], | ||
[0, 0, 0, 1], | ||
]; | ||
}; | ||
|
||
const skewXMatrix = (s: number): Matrix4 => { | ||
"worklet"; | ||
return [ | ||
[1, Math.tan(s), 0, 0], | ||
[0, 1, 0, 0], | ||
[0, 0, 1, 0], | ||
[0, 0, 0, 1], | ||
]; | ||
}; | ||
|
||
const skewYMatrix = (s: number): Matrix4 => { | ||
"worklet"; | ||
return [ | ||
[1, 0, 0, 0], | ||
[Math.tan(s), 1, 0, 0], | ||
[0, 0, 1, 0], | ||
[0, 0, 0, 1], | ||
]; | ||
}; | ||
|
||
const scaleYMatrix = (s: number): Matrix4 => { | ||
"worklet"; | ||
return [ | ||
[1, 0, 0, 0], | ||
[0, s, 0, 0], | ||
[0, 0, 1, 0], | ||
[0, 0, 0, 1], | ||
]; | ||
}; | ||
|
||
const perspectiveMatrix = (p: number): Matrix4 => { | ||
"worklet"; | ||
return [ | ||
[1, 0, 0, 0], | ||
[0, 1, 0, 0], | ||
[0, 0, 1, 0], | ||
[0, 0, -1 / p, 1], | ||
]; | ||
}; | ||
|
||
const rotateXMatrix = (r: number): Matrix4 => { | ||
"worklet"; | ||
return [ | ||
[1, 0, 0, 0], | ||
[0, Math.cos(r), -Math.sin(r), 0], | ||
[0, Math.sin(r), Math.cos(r), 0], | ||
[0, 0, 0, 1], | ||
]; | ||
}; | ||
|
||
const rotateYMatrix = (r: number): Matrix4 => { | ||
"worklet"; | ||
return [ | ||
[Math.cos(r), 0, Math.sin(r), 0], | ||
[0, 1, 0, 0], | ||
[-Math.sin(r), 0, Math.cos(r), 0], | ||
[0, 0, 0, 1], | ||
]; | ||
}; | ||
|
||
const rotateZMatrix = (r: number): Matrix4 => { | ||
"worklet"; | ||
return [ | ||
[Math.cos(r), -Math.sin(r), 0, 0], | ||
[Math.sin(r), Math.cos(r), 0, 0], | ||
[0, 0, 1, 0], | ||
[0, 0, 0, 1], | ||
]; | ||
}; | ||
|
||
export const dot4 = (row: Vec4, col: Vec4) => { | ||
"worklet"; | ||
return row[0] * col[0] + row[1] * col[1] + row[2] * col[2] + row[3] * col[3]; | ||
}; | ||
|
||
export const matrixVecMul4 = (m: Matrix4, v: Vec4) => | ||
[dot4(m[0], v), dot4(m[1], v), dot4(m[2], v), dot4(m[3], v)] as const; | ||
|
||
export const multiply4 = (m1: Matrix4, m2: Matrix4) => { | ||
const col0 = [m2[0][0], m2[1][0], m2[2][0], m2[3][0]] as const; | ||
const col1 = [m2[0][1], m2[1][1], m2[2][1], m2[3][1]] as const; | ||
const col2 = [m2[0][2], m2[1][2], m2[2][2], m2[3][2]] as const; | ||
const col3 = [m2[0][3], m2[1][3], m2[2][3], m2[3][3]] as const; | ||
return [ | ||
[ | ||
dot4(m1[0], col0), | ||
dot4(m1[0], col1), | ||
dot4(m1[0], col2), | ||
dot4(m1[0], col3), | ||
], | ||
[ | ||
dot4(m1[1], col0), | ||
dot4(m1[1], col1), | ||
dot4(m1[1], col2), | ||
dot4(m1[1], col3), | ||
], | ||
[ | ||
dot4(m1[2], col0), | ||
dot4(m1[2], col1), | ||
dot4(m1[2], col2), | ||
dot4(m1[2], col3), | ||
], | ||
[ | ||
dot4(m1[3], col0), | ||
dot4(m1[3], col1), | ||
dot4(m1[3], col2), | ||
dot4(m1[3], col3), | ||
], | ||
] as const; | ||
}; | ||
|
||
export const processTransform3d = (transforms: Transforms3d) => | ||
transforms.reduce((acc, transform) => { | ||
const key = Object.keys(transform)[0] as Transform3dName; | ||
if (key === "translateX") { | ||
const value = (transform as Pick<Transformations, typeof key>)[key]; | ||
return multiply4(acc, translateXMatrix(value)); | ||
} | ||
if (key === "translateY") { | ||
const value = (transform as Pick<Transformations, typeof key>)[key]; | ||
return multiply4(acc, translateYMatrix(value)); | ||
} | ||
if (key === "translateZ") { | ||
const value = (transform as Pick<Transformations, typeof key>)[key]; | ||
return multiply4(acc, translateZMatrix(value)); | ||
} | ||
if (key === "scale") { | ||
const value = (transform as Pick<Transformations, typeof key>)[key]; | ||
return multiply4(acc, scaleMatrix(value)); | ||
} | ||
if (key === "scaleX") { | ||
const value = (transform as Pick<Transformations, typeof key>)[key]; | ||
return multiply4(acc, scaleXMatrix(value)); | ||
} | ||
if (key === "scaleY") { | ||
const value = (transform as Pick<Transformations, typeof key>)[key]; | ||
return multiply4(acc, scaleYMatrix(value)); | ||
} | ||
if (key === "skewX") { | ||
const value = (transform as Pick<Transformations, typeof key>)[key]; | ||
return multiply4(acc, skewXMatrix(value)); | ||
} | ||
if (key === "skewY") { | ||
const value = (transform as Pick<Transformations, typeof key>)[key]; | ||
return multiply4(acc, skewYMatrix(value)); | ||
} | ||
if (key === "rotateX") { | ||
const value = (transform as Pick<Transformations, typeof key>)[key]; | ||
return multiply4(acc, rotateXMatrix(value)); | ||
} | ||
if (key === "rotateY") { | ||
const value = (transform as Pick<Transformations, typeof key>)[key]; | ||
return multiply4(acc, rotateYMatrix(value)); | ||
} | ||
if (key === "perspective") { | ||
const value = (transform as Pick<Transformations, typeof key>)[key]; | ||
return multiply4(acc, perspectiveMatrix(value)); | ||
} | ||
if (key === "rotate" || key === "rotateZ") { | ||
const value = (transform as Pick<Transformations, typeof key>)[key]; | ||
return multiply4(acc, rotateZMatrix(value)); | ||
} | ||
if (key === "matrix") { | ||
const matrix = (transform as Pick<Transformations, typeof key>)[key]; | ||
return multiply4(acc, matrix); | ||
} | ||
return exhaustiveCheck(key); | ||
}, identityMatrix4); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { | ||
identityMatrix4, | ||
matrixVecMul4, | ||
multiply4, | ||
processTransform3d, | ||
} from "../Matrix4"; | ||
|
||
test("processTransform3d()", () => { | ||
expect( | ||
processTransform3d([{ rotateX: Math.PI }, { rotateY: Math.PI }]) | ||
).toStrictEqual([ | ||
[-1, 0, 1.2246467991473532e-16, 0], | ||
[1.4997597826618576e-32, -1, 1.2246467991473532e-16, 0], | ||
[1.2246467991473532e-16, 1.2246467991473532e-16, 1, 0], | ||
[0, 0, 0, 1], | ||
]); | ||
}); | ||
|
||
test("multiply4()", () => { | ||
expect( | ||
multiply4( | ||
identityMatrix4, | ||
processTransform3d([{ rotateX: Math.PI }, { rotateY: Math.PI }]) | ||
) | ||
).toStrictEqual([ | ||
[-1, 0, 1.2246467991473532e-16, 0], | ||
[1.4997597826618576e-32, -1, 1.2246467991473532e-16, 0], | ||
[1.2246467991473532e-16, 1.2246467991473532e-16, 1, 0], | ||
[0, 0, 0, 1], | ||
]); | ||
}); | ||
|
||
test("matrixVecMul4()", () => { | ||
expect( | ||
matrixVecMul4( | ||
processTransform3d([{ rotateX: Math.PI }, { rotateY: Math.PI }]), | ||
[0.5, 0.5, 0, 1] | ||
) | ||
).toStrictEqual([-0.5, -0.5, 1.2246467991473532e-16, 1]); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters