@texel/color 1.0.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.
@@ -0,0 +1,50 @@
1
+ import { vec3 } from "../util.js";
2
+ import {
3
+ linear_A98RGB_to_XYZ_M,
4
+ XYZ_to_linear_A98RGB_M,
5
+ linear_A98RGB_to_LMS_M,
6
+ LMS_to_linear_A98RGB_M,
7
+ OKLab_to_linear_A98RGB_coefficients,
8
+ } from "../conversion_matrices.js";
9
+
10
+ const A98RGBToLinear = (val) => {
11
+ let sign = val < 0 ? -1 : 1;
12
+ let abs = Math.abs(val);
13
+ return sign * Math.pow(abs, 563 / 256);
14
+ };
15
+
16
+ const A98RGBToGamma = (val) => {
17
+ let sign = val < 0 ? -1 : 1;
18
+ let abs = Math.abs(val);
19
+ return sign * Math.pow(abs, 256 / 563);
20
+ };
21
+
22
+ export const A98RGBLinear = {
23
+ id: "a98-rgb-linear",
24
+ toXYZ_M: linear_A98RGB_to_XYZ_M,
25
+ fromXYZ_M: XYZ_to_linear_A98RGB_M,
26
+ toLMS_M: linear_A98RGB_to_LMS_M,
27
+ fromLMS_M: LMS_to_linear_A98RGB_M,
28
+ };
29
+
30
+ export const A98RGB = {
31
+ id: "a98-rgb",
32
+ base: A98RGBLinear,
33
+ toBase: (vec, out = vec3()) => {
34
+ out[0] = A98RGBToLinear(vec[0]);
35
+ out[1] = A98RGBToLinear(vec[1]);
36
+ out[2] = A98RGBToLinear(vec[2]);
37
+ return out;
38
+ },
39
+ fromBase: (vec, out = vec3()) => {
40
+ out[0] = A98RGBToGamma(vec[0]);
41
+ out[1] = A98RGBToGamma(vec[1]);
42
+ out[2] = A98RGBToGamma(vec[2]);
43
+ return out;
44
+ },
45
+ };
46
+
47
+ export const A98RGBGamut = {
48
+ space: A98RGB,
49
+ coefficients: OKLab_to_linear_A98RGB_coefficients,
50
+ };
@@ -0,0 +1,28 @@
1
+ import {
2
+ linear_DisplayP3_to_LMS_M,
3
+ linear_DisplayP3_to_XYZ_M,
4
+ LMS_to_linear_DisplayP3_M,
5
+ XYZ_to_linear_DisplayP3_M,
6
+ OKLab_to_linear_DisplayP3_coefficients,
7
+ } from "../conversion_matrices.js";
8
+ import { sRGBGammaToLinearVec3, sRGBLinearToGammaVec3 } from "./util.js";
9
+
10
+ export const DisplayP3Linear = {
11
+ id: "display-p3-linear",
12
+ toXYZ_M: linear_DisplayP3_to_XYZ_M,
13
+ fromXYZ_M: XYZ_to_linear_DisplayP3_M,
14
+ toLMS_M: linear_DisplayP3_to_LMS_M,
15
+ fromLMS_M: LMS_to_linear_DisplayP3_M,
16
+ };
17
+
18
+ export const DisplayP3 = {
19
+ id: "display-p3",
20
+ base: DisplayP3Linear,
21
+ toBase: sRGBGammaToLinearVec3,
22
+ fromBase: sRGBLinearToGammaVec3,
23
+ };
24
+
25
+ export const DisplayP3Gamut = {
26
+ space: DisplayP3,
27
+ coefficients: OKLab_to_linear_DisplayP3_coefficients,
28
+ };
@@ -0,0 +1,70 @@
1
+ // Oklab and related spaces: OKLCH, OKHSV(sRGB), OKHSL(sRGB)
2
+
3
+ import { constrainAngle, vec3 } from "../util.js";
4
+ import {
5
+ OKHSLToOKLab,
6
+ OKHSVToOKLab,
7
+ OKLabToOKHSL,
8
+ OKLabToOKHSV,
9
+ } from "../okhsl.js";
10
+
11
+ import { sRGBGamut } from "./srgb.js";
12
+
13
+ // based on colorjs.io, could perhaps use a more specific number than this
14
+ const ACHROMATIC_EPSILON = (0.4 - 0.0) / 100000;
15
+
16
+ export const OKLab = {
17
+ id: "oklab",
18
+ };
19
+
20
+ export const OKLCH = {
21
+ id: "oklch",
22
+ base: OKLab,
23
+ toBase: (oklch, out = vec3()) => {
24
+ // Note: newer version of Colorjs.io clamps oklch chroma
25
+ // However, this means that oklch(0.5, -0.36, 90) -> srgb will result in an in-gamut rgb
26
+ // which seems a bit odd; you'd expect it to be out of gamut. So we will leave
27
+ // chroma unclamped for this conversion.
28
+ // const C = Math.max(0, oklch[1]);
29
+
30
+ const C = oklch[1];
31
+ const H = oklch[2];
32
+ out[0] = oklch[0]; // L remains the same
33
+ out[1] = C * Math.cos((H * Math.PI) / 180); // a
34
+ out[2] = C * Math.sin((H * Math.PI) / 180); // b
35
+ return out;
36
+ },
37
+ fromBase: (oklab, out = vec3()) => {
38
+ // These methods are used for other polar forms as well, so we can't hardcode the ε
39
+ const a = oklab[1];
40
+ const b = oklab[2];
41
+ let isAchromatic =
42
+ Math.abs(a) < ACHROMATIC_EPSILON && Math.abs(b) < ACHROMATIC_EPSILON;
43
+ let hue = isAchromatic
44
+ ? 0
45
+ : constrainAngle((Math.atan2(b, a) * 180) / Math.PI);
46
+ let C = isAchromatic ? 0 : Math.sqrt(a * a + b * b);
47
+ out[0] = oklab[0]; // L remains the same
48
+ out[1] = C; // Chroma
49
+ out[2] = hue; // Hue, in degrees [0 to 360)
50
+ return out;
51
+ },
52
+ };
53
+
54
+ export const OKHSL = {
55
+ // Note: sRGB gamut only
56
+ // For other gamuts, use okhsl method directly
57
+ id: "okhsl",
58
+ base: OKLab,
59
+ toBase: (okhsl, out = vec3()) => OKHSLToOKLab(okhsl, sRGBGamut, out),
60
+ fromBase: (oklab, out = vec3()) => OKLabToOKHSL(oklab, sRGBGamut, out),
61
+ };
62
+
63
+ export const OKHSV = {
64
+ // Note: sRGB gamut only
65
+ // For other gamuts, use okhsv method directly
66
+ id: "okhsv",
67
+ base: OKLab,
68
+ toBase: (okhsl, out = vec3()) => OKHSVToOKLab(okhsl, sRGBGamut, out),
69
+ fromBase: (oklab, out = vec3()) => OKLabToOKHSV(oklab, sRGBGamut, out),
70
+ };
@@ -0,0 +1,72 @@
1
+ import { vec3 } from "../util.js";
2
+ import {
3
+ linear_ProPhotoRGB_to_XYZ_M,
4
+ XYZ_to_linear_ProPhotoRGB_M,
5
+ } from "../conversion_matrices.js";
6
+ import { D50_to_D65_M, D65_to_D50_M } from "./xyz.js";
7
+
8
+ const Et = 1 / 512;
9
+ const Et2 = 16 / 512;
10
+
11
+ // Transfer curve is gamma 1.8 with a small linear portion
12
+ const ProPhotoRGBToLinear = (v) => (v < Et2 ? v / 16 : v ** 1.8);
13
+ const ProPhotoRGBToGamma = (v) => (v >= Et ? v ** (1 / 1.8) : 16 * v);
14
+
15
+ // Note: below is a possibly improved transfer function proposed by CSS Module 4 spec
16
+ // It is currently not matching the outputs of Colorjs.io when dealing with particular
17
+ // negative values, such as convert([1,1,1], OKlab, ProPhotoRGB)
18
+
19
+ // const ProPhotoRGBToLinear = (val) => {
20
+ // // convert an array of prophoto-rgb values
21
+ // // where in-gamut colors are in the range [0.0 - 1.0]
22
+ // // to linear light (un-companded) form.
23
+ // // Transfer curve is gamma 1.8 with a small linear portion
24
+ // let sign = val < 0 ? -1 : 1;
25
+ // let abs = Math.abs(val);
26
+ // return abs <= Et2 ? val / 16 : sign * Math.pow(abs, 1.8);
27
+ // };
28
+
29
+ // const ProPhotoRGBToGamma = (val) => {
30
+ // // convert an array of linear-light prophoto-rgb in the range 0.0-1.0
31
+ // // to gamma corrected form
32
+ // // Transfer curve is gamma 1.8 with a small linear portion
33
+ // let sign = val < 0 ? -1 : 1;
34
+ // let abs = Math.abs(val);
35
+ // return abs >= Et ? sign * Math.pow(abs, 1 / 1.8) : 16 * val;
36
+ // };
37
+
38
+ export const ProPhotoRGBLinear = {
39
+ id: "prophoto-rgb-linear",
40
+ adapt: {
41
+ // chromatic adaptation to and from D65
42
+ to: D50_to_D65_M,
43
+ from: D65_to_D50_M,
44
+ },
45
+ // Note these are in D50
46
+ toXYZ_M: linear_ProPhotoRGB_to_XYZ_M,
47
+ fromXYZ_M: XYZ_to_linear_ProPhotoRGB_M,
48
+ };
49
+
50
+ export const ProPhotoRGB = {
51
+ id: "prophoto-rgb",
52
+ base: ProPhotoRGBLinear,
53
+ toBase: (vec, out = vec3()) => {
54
+ out[0] = ProPhotoRGBToLinear(vec[0]);
55
+ out[1] = ProPhotoRGBToLinear(vec[1]);
56
+ out[2] = ProPhotoRGBToLinear(vec[2]);
57
+ return out;
58
+ },
59
+ fromBase: (vec, out = vec3()) => {
60
+ out[0] = ProPhotoRGBToGamma(vec[0]);
61
+ out[1] = ProPhotoRGBToGamma(vec[1]);
62
+ out[2] = ProPhotoRGBToGamma(vec[2]);
63
+ return out;
64
+ },
65
+ };
66
+
67
+ // Note: this is currently not supported, some overflows are occurring
68
+ // in the python script. Please file an issue or PR if you think you can help.
69
+ // export const ProPhotoRGBGamut = {
70
+ // space: ProPhotoRGB,
71
+ // coefficients: OKLab_to_linear_ProPhotoRGB_coefficients,
72
+ // };
@@ -0,0 +1,47 @@
1
+ import { vec3 } from "../util.js";
2
+ import {
3
+ linear_Rec2020_to_LMS_M,
4
+ linear_Rec2020_to_XYZ_M,
5
+ LMS_to_linear_Rec2020_M,
6
+ XYZ_to_linear_Rec2020_M,
7
+ OKLab_to_linear_Rec2020_coefficients,
8
+ } from "../conversion_matrices.js";
9
+
10
+ const ALPHA = 1.09929682680944;
11
+ const BETA = 0.018053968510807;
12
+
13
+ const Rec2020ToLinear = (val) =>
14
+ val < BETA * 4.5 ? val / 4.5 : Math.pow((val + ALPHA - 1) / ALPHA, 1 / 0.45);
15
+
16
+ const Rec2020ToGamma = (val) =>
17
+ val >= BETA ? ALPHA * Math.pow(val, 0.45) - (ALPHA - 1) : 4.5 * val;
18
+
19
+ export const Rec2020Linear = {
20
+ id: "rec2020-linear",
21
+ toXYZ_M: linear_Rec2020_to_XYZ_M,
22
+ fromXYZ_M: XYZ_to_linear_Rec2020_M,
23
+ toLMS_M: linear_Rec2020_to_LMS_M,
24
+ fromLMS_M: LMS_to_linear_Rec2020_M,
25
+ };
26
+
27
+ export const Rec2020 = {
28
+ id: "rec2020",
29
+ base: Rec2020Linear,
30
+ toBase: (vec, out = vec3()) => {
31
+ out[0] = Rec2020ToLinear(vec[0]);
32
+ out[1] = Rec2020ToLinear(vec[1]);
33
+ out[2] = Rec2020ToLinear(vec[2]);
34
+ return out;
35
+ },
36
+ fromBase: (vec, out = vec3()) => {
37
+ out[0] = Rec2020ToGamma(vec[0]);
38
+ out[1] = Rec2020ToGamma(vec[1]);
39
+ out[2] = Rec2020ToGamma(vec[2]);
40
+ return out;
41
+ },
42
+ };
43
+
44
+ export const Rec2020Gamut = {
45
+ space: Rec2020,
46
+ coefficients: OKLab_to_linear_Rec2020_coefficients,
47
+ };
@@ -0,0 +1,29 @@
1
+ import {
2
+ linear_sRGB_to_LMS_M,
3
+ linear_sRGB_to_XYZ_M,
4
+ LMS_to_linear_sRGB_M,
5
+ XYZ_to_linear_sRGB_M,
6
+ OKLab_to_linear_sRGB_coefficients,
7
+ } from "../conversion_matrices.js";
8
+
9
+ import { sRGBGammaToLinearVec3, sRGBLinearToGammaVec3 } from "./util.js";
10
+
11
+ export const sRGBLinear = {
12
+ id: "srgb-linear",
13
+ toXYZ_M: linear_sRGB_to_XYZ_M,
14
+ fromXYZ_M: XYZ_to_linear_sRGB_M,
15
+ toLMS_M: linear_sRGB_to_LMS_M,
16
+ fromLMS_M: LMS_to_linear_sRGB_M,
17
+ };
18
+
19
+ export const sRGB = {
20
+ id: "srgb",
21
+ base: sRGBLinear,
22
+ toBase: sRGBGammaToLinearVec3,
23
+ fromBase: sRGBLinearToGammaVec3,
24
+ };
25
+
26
+ export const sRGBGamut = {
27
+ space: sRGB,
28
+ coefficients: OKLab_to_linear_sRGB_coefficients,
29
+ };
@@ -0,0 +1,44 @@
1
+ import { vec3 } from "../util.js";
2
+
3
+ export const sRGBGammaToLinear = (val) => {
4
+ // convert a single channel value
5
+ // where in-gamut values are in the range [0 - 1]
6
+ // to linear light (un-companded) form.
7
+ // https://en.wikipedia.org/wiki/SRGB
8
+ // Extended transfer function:
9
+ // for negative values, linear portion is extended on reflection of axis,
10
+ // then reflected power function is used.
11
+ let sign = val < 0 ? -1 : 1;
12
+ let abs = Math.abs(val);
13
+ return abs <= 0.04045
14
+ ? val / 12.92
15
+ : sign * Math.pow((abs + 0.055) / 1.055, 2.4);
16
+ };
17
+
18
+ export const sRGBLinearToGamma = (val) => {
19
+ // convert a single channel linear-light value in range 0-1
20
+ // to gamma corrected form
21
+ // https://en.wikipedia.org/wiki/SRGB
22
+ // Extended transfer function:
23
+ // For negative values, linear portion extends on reflection
24
+ // of axis, then uses reflected pow below that
25
+ let sign = val < 0 ? -1 : 1;
26
+ let abs = Math.abs(val);
27
+ return abs > 0.0031308
28
+ ? sign * (1.055 * Math.pow(abs, 1 / 2.4) - 0.055)
29
+ : 12.92 * val;
30
+ };
31
+
32
+ export const sRGBLinearToGammaVec3 = (vec, out = vec3()) => {
33
+ out[0] = sRGBLinearToGamma(vec[0]);
34
+ out[1] = sRGBLinearToGamma(vec[1]);
35
+ out[2] = sRGBLinearToGamma(vec[2]);
36
+ return out;
37
+ };
38
+
39
+ export const sRGBGammaToLinearVec3 = (vec, out = vec3()) => {
40
+ out[0] = sRGBGammaToLinear(vec[0]);
41
+ out[1] = sRGBGammaToLinear(vec[1]);
42
+ out[2] = sRGBGammaToLinear(vec[2]);
43
+ return out;
44
+ };
@@ -0,0 +1,44 @@
1
+ import { vec3 } from "../util.js";
2
+ import { transform } from "../core.js";
3
+ import { LMS_to_XYZ_M, XYZ_to_LMS_M } from "../conversion_matrices.js";
4
+
5
+ // Bradford chromatic adaptation from D65 to D50
6
+ // The matrix below is the result of three operations:
7
+ // - convert from XYZ to retinal cone domain
8
+ // - scale components from one reference white to another
9
+ // - convert back to XYZ
10
+ // see https://github.com/LeaVerou/color.js/pull/354/files
11
+ export const D65_to_D50_M = [
12
+ [1.0479297925449969, 0.022946870601609652, -0.05019226628920524],
13
+ [0.02962780877005599, 0.9904344267538799, -0.017073799063418826],
14
+ [-0.009243040646204504, 0.015055191490298152, 0.7518742814281371],
15
+ ];
16
+
17
+ // Bradford chromatic adaptation from D50 to D65
18
+ // See https://github.com/LeaVerou/color.js/pull/360/files
19
+ export const D50_to_D65_M = [
20
+ [0.955473421488075, -0.02309845494876471, 0.06325924320057072],
21
+ [-0.0283697093338637, 1.0099953980813041, 0.021041441191917323],
22
+ [0.012314014864481998, -0.020507649298898964, 1.330365926242124],
23
+ ];
24
+
25
+ export const XYZD65ToD50 = (XYZ, out = vec3()) =>
26
+ transform(XYZ, D65_to_D50_M, out);
27
+
28
+ export const XYZD50ToD65 = (XYZ, out = vec3()) =>
29
+ transform(XYZ, D50_to_D65_M, out);
30
+
31
+ // XYZ using D65 whitepoint
32
+ export const XYZ = {
33
+ id: "xyz", // xyz-d65
34
+ toLMS_M: XYZ_to_LMS_M,
35
+ fromLMS_M: LMS_to_XYZ_M,
36
+ };
37
+
38
+ // XYZ using D50 whitepoint
39
+ export const XYZD50 = {
40
+ id: "xyz-d50",
41
+ base: XYZ,
42
+ toBase: XYZD50ToD65,
43
+ fromBase: XYZD65ToD50,
44
+ };
package/src/spaces.js ADDED
@@ -0,0 +1,44 @@
1
+ import { XYZ, XYZD50 } from "./spaces/xyz.js";
2
+ import { OKLab, OKLCH, OKHSV, OKHSL } from "./spaces/oklab.js";
3
+ import { sRGB, sRGBLinear, sRGBGamut } from "./spaces/srgb.js";
4
+ import {
5
+ DisplayP3,
6
+ DisplayP3Linear,
7
+ DisplayP3Gamut,
8
+ } from "./spaces/display-p3.js";
9
+ import { Rec2020, Rec2020Linear, Rec2020Gamut } from "./spaces/rec2020.js";
10
+ import { A98RGB, A98RGBLinear, A98RGBGamut } from "./spaces/a98-rgb.js";
11
+ import { ProPhotoRGB, ProPhotoRGBLinear } from "./spaces/prophoto-rgb.js";
12
+
13
+ export * from "./spaces/xyz.js";
14
+ export * from "./spaces/oklab.js";
15
+ export * from "./spaces/srgb.js";
16
+ export * from "./spaces/display-p3.js";
17
+ export * from "./spaces/rec2020.js";
18
+ export * from "./spaces/a98-rgb.js";
19
+ export * from "./spaces/prophoto-rgb.js";
20
+
21
+ export const listColorSpaces = () => {
22
+ return [
23
+ XYZ, // D65
24
+ XYZD50,
25
+ OKLab,
26
+ OKLCH,
27
+ OKHSV,
28
+ OKHSL,
29
+ sRGB,
30
+ sRGBLinear,
31
+ DisplayP3,
32
+ DisplayP3Linear,
33
+ Rec2020,
34
+ Rec2020Linear,
35
+ A98RGB,
36
+ A98RGBLinear,
37
+ ProPhotoRGB,
38
+ ProPhotoRGBLinear,
39
+ ];
40
+ };
41
+
42
+ export const listColorGamuts = () => {
43
+ return [sRGBGamut, DisplayP3Gamut, Rec2020Gamut, A98RGBGamut];
44
+ };
package/src/util.js ADDED
@@ -0,0 +1,119 @@
1
+ const GAMUT_EPSILON = 0.000075;
2
+
3
+ export const clamp = (value, min, max) => Math.max(Math.min(value, max), min);
4
+
5
+ export const lerp = (min, max, t) => min * (1 - t) + max * t;
6
+
7
+ export const degToRad = (n) => (n * Math.PI) / 180;
8
+
9
+ export const radToDeg = (n) => (n * 180) / Math.PI;
10
+
11
+ export const constrainAngle = (angle) => ((angle % 360) + 360) % 360;
12
+
13
+ export const hexToRGB = (str, out = vec3()) => {
14
+ let hex = str.replace(/#/, "");
15
+ if (hex.length === 3) {
16
+ // expand shorthand
17
+ hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
18
+ } else if (hex.length > 6) {
19
+ // discard alpha
20
+ hex = hex.slice(0, 6);
21
+ }
22
+ const rgb = parseInt(hex, 16);
23
+ out[0] = ((rgb >> 16) & 0xff) / 0xff;
24
+ out[1] = ((rgb >> 8) & 0xff) / 0xff;
25
+ out[2] = (rgb & 0xff) / 0xff;
26
+ return out;
27
+ };
28
+
29
+ export const RGBtoHex = (rgb) =>
30
+ `#${rgb.map((n) => floatToByte(n).toString(16).padStart(2, "0")).join("")}`;
31
+
32
+ export const isRGBInGamut = (lrgb, ep = GAMUT_EPSILON) => {
33
+ const r = lrgb[0];
34
+ const g = lrgb[1];
35
+ const b = lrgb[2];
36
+ return (
37
+ r >= -ep &&
38
+ r <= 1 + ep &&
39
+ g >= -ep &&
40
+ g <= 1 + ep &&
41
+ b >= -ep &&
42
+ b <= 1 + ep
43
+ );
44
+ };
45
+
46
+ export const clampedRGB = (rgb, out = vec3()) => {
47
+ out[0] = clamp(rgb[0], 0, 1);
48
+ out[1] = clamp(rgb[1], 0, 1);
49
+ out[2] = clamp(rgb[2], 0, 1);
50
+ return out;
51
+ };
52
+
53
+ export const xyY_to_XYZ = (arg, out = vec3()) => {
54
+ let X, Y, Z, x, y;
55
+ x = arg[0];
56
+ y = arg[1];
57
+ Y = arg[2];
58
+ if (y === 0) {
59
+ out[0] = out[1] = out[2] = 0;
60
+ return out;
61
+ }
62
+ X = (x * Y) / y;
63
+ Z = ((1 - x - y) * Y) / y;
64
+ out[0] = X;
65
+ out[1] = Y;
66
+ out[2] = Z;
67
+ return out;
68
+ };
69
+
70
+ export const XYZ_to_xyY = (arg, out = vec3()) => {
71
+ let sum, X, Y, Z;
72
+ X = arg[0];
73
+ Y = arg[1];
74
+ Z = arg[2];
75
+ sum = X + Y + Z;
76
+ if (sum === 0) {
77
+ out[0] = out[1] = 0;
78
+ out[2] = Y;
79
+ return out;
80
+ }
81
+ out[0] = X / sum;
82
+ out[1] = Y / sum;
83
+ out[2] = Y;
84
+ return out;
85
+ };
86
+
87
+ export const floatToByte = (n) => clamp(Math.round(255 * n), 0, 255);
88
+
89
+ // Undocumented
90
+
91
+ export const vec3 = () => [0, 0, 0];
92
+
93
+ // export const normalizeHue = (hue) => ((hue = hue % 360) < 0 ? hue + 360 : hue);
94
+
95
+ // in degrees
96
+ // export const angle_delta = (angle1, angle2) => {
97
+ // const diff = ((angle2 - angle1 + 180) % 360) - 180;
98
+ // return diff < -180 ? diff + 360 : diff;
99
+ // };
100
+
101
+ // function shortAngleDistRad(a0, a1) {
102
+ // var max = Math.PI * 2;
103
+ // var da = (a1 - a0) % max;
104
+ // return ((2 * da) % max) - da;
105
+ // }
106
+
107
+ // function shortAngleDistDeg(a0, a1) {
108
+ // var max = 360;
109
+ // var da = (a1 - a0) % max;
110
+ // return ((2 * da) % max) - da;
111
+ // }
112
+
113
+ // function lerpAngleRad(a0, a1, t) {
114
+ // return a0 + shortAngleDistRad(a0, a1) * t;
115
+ // }
116
+
117
+ // function lerpAngleDeg(a0, a1, t) {
118
+ // return radToDeg(lerpAngleRad(degToRad(a0), degToRad(a1), t));
119
+ // }
@@ -0,0 +1,15 @@
1
+ const EPSILON = Math.pow(2, -33); // ~= 0.0000000001
2
+
3
+ export default function arrayAlmostEqual(a, b, tolerance = EPSILON) {
4
+ if (!a || !b) return false;
5
+ if (a.length !== b.length) return false;
6
+ for (let i = 0; i < a.length; i++) {
7
+ const p0 = a[i];
8
+ const p1 = b[i];
9
+ if (p0 !== p1) {
10
+ const diff = Math.abs(p1 - p0);
11
+ if (diff > tolerance) return false;
12
+ }
13
+ }
14
+ return true;
15
+ }
Binary file