Skip to content

Commit

Permalink
feat(📉): addLine() and addArc() (#383)
Browse files Browse the repository at this point in the history
  • Loading branch information
wcandillon authored Oct 22, 2020
1 parent 5abc433 commit 6c96e99
Show file tree
Hide file tree
Showing 5 changed files with 347 additions and 0 deletions.
270 changes: 270 additions & 0 deletions src/Matrix4.ts
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);
35 changes: 35 additions & 0 deletions src/Paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,27 @@ export const createPath = (move: Vector): Path => {
};
};

/**
* @summary Add an arc command to a path
*/
export const addArc = (path: Path, corner: Vector, to: Vector) => {
"worklet";
const last = path.curves[path.curves.length - 1];
const from = last ? last.to : path.move;
const arc = 9 / 16;
path.curves.push({
c1: {
x: (corner.x - from.x) * arc + from.x,
y: (corner.y - from.y) * arc + from.y,
},
c2: {
x: (corner.x - to.x) * arc + to.x,
y: (corner.y - to.y) * arc + to.y,
},
to,
});
};

/**
* @summary Add a cubic Bèzier curve command to a path.
*/
Expand All @@ -179,6 +200,20 @@ export const addCurve = (path: Path, c: Curve) => {
});
};

/**
* @summary Add a line command to a path.
*/
export const addLine = (path: Path, to: Vector) => {
"worklet";
const last = path.curves[path.curves.length - 1];
const from = last ? last.to : path.move;
path.curves.push({
c1: from,
c2: to,
to,
});
};

/**
* @summary Add a quadratic Bèzier curve command to a path.
*/
Expand Down
40 changes: 40 additions & 0 deletions src/__tests__/Matrix4.test.ts
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]);
});
1 change: 1 addition & 0 deletions src/__tests__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ import "./Math.test";
import "./Physics.test";
import "./Paths.test";
import "./Array.test";
import "./Matrix4.test";
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export * from "./Colors";
export * from "./Paths";
export * from "./Physics";
export * from "./Array";
export * from "./Matrix4";
export { default as ReText } from "./ReText";

0 comments on commit 6c96e99

Please sign in to comment.