@js-draw/math 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.
- package/README.md +3 -0
- package/build-config.json +4 -0
- package/dist/cjs/Color4.d.ts +83 -0
- package/dist/cjs/Color4.js +277 -0
- package/dist/cjs/Mat33.d.ts +131 -0
- package/dist/cjs/Mat33.js +345 -0
- package/dist/cjs/Vec2.d.ts +42 -0
- package/dist/cjs/Vec2.js +48 -0
- package/dist/cjs/Vec3.d.ts +126 -0
- package/dist/cjs/Vec3.js +203 -0
- package/dist/cjs/lib.d.ts +27 -0
- package/dist/cjs/lib.js +42 -0
- package/dist/cjs/polynomial/solveQuadratic.d.ts +9 -0
- package/dist/cjs/polynomial/solveQuadratic.js +39 -0
- package/dist/cjs/rounding.d.ts +15 -0
- package/dist/cjs/rounding.js +146 -0
- package/dist/cjs/shapes/Abstract2DShape.d.ts +49 -0
- package/dist/cjs/shapes/Abstract2DShape.js +38 -0
- package/dist/cjs/shapes/BezierJSWrapper.d.ts +36 -0
- package/dist/cjs/shapes/BezierJSWrapper.js +94 -0
- package/dist/cjs/shapes/CubicBezier.d.ts +17 -0
- package/dist/cjs/shapes/CubicBezier.js +35 -0
- package/dist/cjs/shapes/LineSegment2.d.ts +70 -0
- package/dist/cjs/shapes/LineSegment2.js +183 -0
- package/dist/cjs/shapes/Path.d.ts +96 -0
- package/dist/cjs/shapes/Path.js +766 -0
- package/dist/cjs/shapes/PointShape2D.d.ts +18 -0
- package/dist/cjs/shapes/PointShape2D.js +31 -0
- package/dist/cjs/shapes/QuadraticBezier.d.ts +35 -0
- package/dist/cjs/shapes/QuadraticBezier.js +120 -0
- package/dist/cjs/shapes/Rect2.d.ts +58 -0
- package/dist/cjs/shapes/Rect2.js +259 -0
- package/dist/cjs/shapes/Triangle.d.ts +46 -0
- package/dist/cjs/shapes/Triangle.js +126 -0
- package/dist/mjs/Color4.d.ts +83 -0
- package/dist/mjs/Color4.mjs +271 -0
- package/dist/mjs/Mat33.d.ts +131 -0
- package/dist/mjs/Mat33.mjs +338 -0
- package/dist/mjs/Vec2.d.ts +42 -0
- package/dist/mjs/Vec2.mjs +42 -0
- package/dist/mjs/Vec3.d.ts +126 -0
- package/dist/mjs/Vec3.mjs +199 -0
- package/dist/mjs/lib.d.ts +27 -0
- package/dist/mjs/lib.mjs +29 -0
- package/dist/mjs/polynomial/solveQuadratic.d.ts +9 -0
- package/dist/mjs/polynomial/solveQuadratic.mjs +37 -0
- package/dist/mjs/rounding.d.ts +15 -0
- package/dist/mjs/rounding.mjs +139 -0
- package/dist/mjs/shapes/Abstract2DShape.d.ts +49 -0
- package/dist/mjs/shapes/Abstract2DShape.mjs +36 -0
- package/dist/mjs/shapes/BezierJSWrapper.d.ts +36 -0
- package/dist/mjs/shapes/BezierJSWrapper.mjs +89 -0
- package/dist/mjs/shapes/CubicBezier.d.ts +17 -0
- package/dist/mjs/shapes/CubicBezier.mjs +30 -0
- package/dist/mjs/shapes/LineSegment2.d.ts +70 -0
- package/dist/mjs/shapes/LineSegment2.mjs +176 -0
- package/dist/mjs/shapes/Path.d.ts +96 -0
- package/dist/mjs/shapes/Path.mjs +759 -0
- package/dist/mjs/shapes/PointShape2D.d.ts +18 -0
- package/dist/mjs/shapes/PointShape2D.mjs +26 -0
- package/dist/mjs/shapes/QuadraticBezier.d.ts +35 -0
- package/dist/mjs/shapes/QuadraticBezier.mjs +113 -0
- package/dist/mjs/shapes/Rect2.d.ts +58 -0
- package/dist/mjs/shapes/Rect2.mjs +252 -0
- package/dist/mjs/shapes/Triangle.d.ts +46 -0
- package/dist/mjs/shapes/Triangle.mjs +121 -0
- package/package.json +48 -0
- package/tsconfig.json +7 -0
- package/typedoc.json +5 -0
@@ -0,0 +1,126 @@
|
|
1
|
+
"use strict";
|
2
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
4
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
5
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
6
|
+
};
|
7
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
8
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
9
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
10
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
11
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
12
|
+
};
|
13
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
14
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
15
|
+
};
|
16
|
+
var _Triangle_sides;
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
18
|
+
const Abstract2DShape_1 = __importDefault(require("./Abstract2DShape"));
|
19
|
+
const LineSegment2_1 = __importDefault(require("./LineSegment2"));
|
20
|
+
const Rect2_1 = __importDefault(require("./Rect2"));
|
21
|
+
class Triangle extends Abstract2DShape_1.default {
|
22
|
+
/**
|
23
|
+
* @see {@link fromVertices}
|
24
|
+
*/
|
25
|
+
constructor(vertex1, vertex2, vertex3) {
|
26
|
+
super();
|
27
|
+
this.vertex1 = vertex1;
|
28
|
+
this.vertex2 = vertex2;
|
29
|
+
this.vertex3 = vertex3;
|
30
|
+
_Triangle_sides.set(this, undefined);
|
31
|
+
}
|
32
|
+
/**
|
33
|
+
* Creates a triangle from its three corners. Corners may be stored in a different
|
34
|
+
* order than given.
|
35
|
+
*/
|
36
|
+
static fromVertices(vertex1, vertex2, vertex3) {
|
37
|
+
return new Triangle(vertex1, vertex2, vertex3);
|
38
|
+
}
|
39
|
+
get vertices() {
|
40
|
+
return [this.vertex1, this.vertex2, this.vertex3];
|
41
|
+
}
|
42
|
+
map(mapping) {
|
43
|
+
return new Triangle(mapping(this.vertex1), mapping(this.vertex2), mapping(this.vertex3));
|
44
|
+
}
|
45
|
+
// Transform, treating this as composed of 2D points.
|
46
|
+
transformed2DBy(affineTransform) {
|
47
|
+
return this.map(affineTransform.transformVec2);
|
48
|
+
}
|
49
|
+
// Transforms this by a linear transform --- verticies are treated as
|
50
|
+
// 3D points.
|
51
|
+
transformedBy(linearTransform) {
|
52
|
+
return this.map(linearTransform.transformVec3);
|
53
|
+
}
|
54
|
+
/**
|
55
|
+
* Returns the sides of this triangle, as an array of `LineSegment2`s.
|
56
|
+
*
|
57
|
+
* The first side is from `vertex1` to `vertex2`, the next from `vertex2` to `vertex3`,
|
58
|
+
* and the last from `vertex3` to `vertex1`.
|
59
|
+
*/
|
60
|
+
getEdges() {
|
61
|
+
if (__classPrivateFieldGet(this, _Triangle_sides, "f")) {
|
62
|
+
return __classPrivateFieldGet(this, _Triangle_sides, "f");
|
63
|
+
}
|
64
|
+
const side1 = new LineSegment2_1.default(this.vertex1, this.vertex2);
|
65
|
+
const side2 = new LineSegment2_1.default(this.vertex2, this.vertex3);
|
66
|
+
const side3 = new LineSegment2_1.default(this.vertex3, this.vertex1);
|
67
|
+
const sides = [side1, side2, side3];
|
68
|
+
__classPrivateFieldSet(this, _Triangle_sides, sides, "f");
|
69
|
+
return sides;
|
70
|
+
}
|
71
|
+
intersectsLineSegment(lineSegment) {
|
72
|
+
const result = [];
|
73
|
+
for (const edge of this.getEdges()) {
|
74
|
+
edge.intersectsLineSegment(lineSegment)
|
75
|
+
.forEach(point => result.push(point));
|
76
|
+
}
|
77
|
+
return result;
|
78
|
+
}
|
79
|
+
/** @inheritdoc */
|
80
|
+
containsPoint(point, epsilon = Abstract2DShape_1.default.smallValue) {
|
81
|
+
// Project `point` onto normals to each of this' sides.
|
82
|
+
// Uses the Separating Axis Theorem (https://en.wikipedia.org/wiki/Hyperplane_separation_theorem#Use_in_collision_detection)
|
83
|
+
const sides = this.getEdges();
|
84
|
+
for (const side of sides) {
|
85
|
+
const orthog = side.direction.orthog();
|
86
|
+
// Project all three vertices
|
87
|
+
// TODO: Performance can be improved here (two vertices will always have the same projection)
|
88
|
+
const projv1 = orthog.dot(this.vertex1);
|
89
|
+
const projv2 = orthog.dot(this.vertex2);
|
90
|
+
const projv3 = orthog.dot(this.vertex3);
|
91
|
+
const minProjVertex = Math.min(projv1, projv2, projv3);
|
92
|
+
const maxProjVertex = Math.max(projv1, projv2, projv3);
|
93
|
+
const projPoint = orthog.dot(point);
|
94
|
+
const inProjection = projPoint >= minProjVertex - epsilon && projPoint <= maxProjVertex + epsilon;
|
95
|
+
if (!inProjection) {
|
96
|
+
return false;
|
97
|
+
}
|
98
|
+
}
|
99
|
+
return true;
|
100
|
+
}
|
101
|
+
/**
|
102
|
+
* @returns the signed distance from `point` to the closest edge of this triangle.
|
103
|
+
*
|
104
|
+
* If `point` is inside `this`, the result is negative, otherwise, the result is
|
105
|
+
* positive.
|
106
|
+
*/
|
107
|
+
signedDistance(point) {
|
108
|
+
const sides = this.getEdges();
|
109
|
+
const distances = sides.map(side => side.distance(point));
|
110
|
+
const distance = Math.min(...distances);
|
111
|
+
// If the point is in this' interior, signedDistance must return a negative
|
112
|
+
// number.
|
113
|
+
if (this.containsPoint(point, 0)) {
|
114
|
+
return -distance;
|
115
|
+
}
|
116
|
+
else {
|
117
|
+
return distance;
|
118
|
+
}
|
119
|
+
}
|
120
|
+
/** @inheritdoc */
|
121
|
+
getTightBoundingBox() {
|
122
|
+
return Rect2_1.default.bboxOf(this.vertices);
|
123
|
+
}
|
124
|
+
}
|
125
|
+
_Triangle_sides = new WeakMap();
|
126
|
+
exports.default = Triangle;
|
@@ -0,0 +1,83 @@
|
|
1
|
+
import Vec3 from './Vec3';
|
2
|
+
/**
|
3
|
+
* Represents a color.
|
4
|
+
*
|
5
|
+
* @example
|
6
|
+
* ```ts,runnable,console
|
7
|
+
* import { Color4 } from '@js-draw/math';
|
8
|
+
*
|
9
|
+
* console.log('Red:', Color4.fromString('#f00'));
|
10
|
+
* console.log('Also red:', Color4.ofRGB(1, 0, 0), Color4.red);
|
11
|
+
* console.log('Mixing red and blue:', Color4.red.mix(Color4.blue, 0.5));
|
12
|
+
* console.log('To string:', Color4.orange.toHexString());
|
13
|
+
* ```
|
14
|
+
*/
|
15
|
+
export default class Color4 {
|
16
|
+
/** Red component. Should be in the range [0, 1]. */
|
17
|
+
readonly r: number;
|
18
|
+
/** Green component. ${\tt g} \in [0, 1]$ */
|
19
|
+
readonly g: number;
|
20
|
+
/** Blue component. ${\tt b} \in [0, 1]$ */
|
21
|
+
readonly b: number;
|
22
|
+
/** Alpha/transparent component. ${\tt a} \in [0, 1]$. 0 = transparent */
|
23
|
+
readonly a: number;
|
24
|
+
private constructor();
|
25
|
+
/**
|
26
|
+
* Create a color from red, green, blue components. The color is fully opaque (`a = 1.0`).
|
27
|
+
*
|
28
|
+
* Each component should be in the range [0, 1].
|
29
|
+
*/
|
30
|
+
static ofRGB(red: number, green: number, blue: number): Color4;
|
31
|
+
static ofRGBA(red: number, green: number, blue: number, alpha: number): Color4;
|
32
|
+
static fromHex(hexString: string): Color4;
|
33
|
+
/** Like fromHex, but can handle additional colors if an `HTMLCanvasElement` is available. */
|
34
|
+
static fromString(text: string): Color4;
|
35
|
+
/** @returns true if `this` and `other` are approximately equal. */
|
36
|
+
eq(other: Color4 | null | undefined): boolean;
|
37
|
+
/**
|
38
|
+
* If `fractionTo` is not in the range $[0, 1]$, it will be clamped to the nearest number
|
39
|
+
* in that range. For example, `a.mix(b, -1)` is equivalent to `a.mix(b, 0)`.
|
40
|
+
*
|
41
|
+
* @returns a color `fractionTo` of the way from this color to `other`.
|
42
|
+
*
|
43
|
+
* @example
|
44
|
+
* ```ts
|
45
|
+
* Color4.ofRGB(1, 0, 0).mix(Color4.ofRGB(0, 1, 0), 0.1) // -> Color4(0.9, 0.1, 0)
|
46
|
+
* ```
|
47
|
+
*/
|
48
|
+
mix(other: Color4, fractionTo: number): Color4;
|
49
|
+
/**
|
50
|
+
* @returns the component-wise average of `colors`, or `Color4.transparent` if `colors` is empty.
|
51
|
+
*/
|
52
|
+
static average(colors: Color4[]): Color4;
|
53
|
+
/**
|
54
|
+
* Converts to (hue, saturation, value).
|
55
|
+
* See also https://en.wikipedia.org/wiki/HSL_and_HSV#General_approach
|
56
|
+
*
|
57
|
+
* The resultant hue is represented in radians and is thus in $[0, 2\pi]$.
|
58
|
+
*/
|
59
|
+
asHSV(): Vec3;
|
60
|
+
private hexString;
|
61
|
+
/**
|
62
|
+
* @returns a hexadecimal color string representation of `this`, in the form `#rrggbbaa`.
|
63
|
+
*
|
64
|
+
* @example
|
65
|
+
* ```
|
66
|
+
* Color4.red.toHexString(); // -> #ff0000ff
|
67
|
+
* ```
|
68
|
+
*/
|
69
|
+
toHexString(): string;
|
70
|
+
toString(): string;
|
71
|
+
static transparent: Color4;
|
72
|
+
static red: Color4;
|
73
|
+
static orange: Color4;
|
74
|
+
static green: Color4;
|
75
|
+
static blue: Color4;
|
76
|
+
static purple: Color4;
|
77
|
+
static yellow: Color4;
|
78
|
+
static clay: Color4;
|
79
|
+
static black: Color4;
|
80
|
+
static gray: Color4;
|
81
|
+
static white: Color4;
|
82
|
+
}
|
83
|
+
export { Color4 };
|
@@ -0,0 +1,271 @@
|
|
1
|
+
import Vec3 from './Vec3.mjs';
|
2
|
+
/**
|
3
|
+
* Represents a color.
|
4
|
+
*
|
5
|
+
* @example
|
6
|
+
* ```ts,runnable,console
|
7
|
+
* import { Color4 } from '@js-draw/math';
|
8
|
+
*
|
9
|
+
* console.log('Red:', Color4.fromString('#f00'));
|
10
|
+
* console.log('Also red:', Color4.ofRGB(1, 0, 0), Color4.red);
|
11
|
+
* console.log('Mixing red and blue:', Color4.red.mix(Color4.blue, 0.5));
|
12
|
+
* console.log('To string:', Color4.orange.toHexString());
|
13
|
+
* ```
|
14
|
+
*/
|
15
|
+
class Color4 {
|
16
|
+
constructor(
|
17
|
+
/** Red component. Should be in the range [0, 1]. */
|
18
|
+
r,
|
19
|
+
/** Green component. ${\tt g} \in [0, 1]$ */
|
20
|
+
g,
|
21
|
+
/** Blue component. ${\tt b} \in [0, 1]$ */
|
22
|
+
b,
|
23
|
+
/** Alpha/transparent component. ${\tt a} \in [0, 1]$. 0 = transparent */
|
24
|
+
a) {
|
25
|
+
this.r = r;
|
26
|
+
this.g = g;
|
27
|
+
this.b = b;
|
28
|
+
this.a = a;
|
29
|
+
this.hexString = null;
|
30
|
+
}
|
31
|
+
/**
|
32
|
+
* Create a color from red, green, blue components. The color is fully opaque (`a = 1.0`).
|
33
|
+
*
|
34
|
+
* Each component should be in the range [0, 1].
|
35
|
+
*/
|
36
|
+
static ofRGB(red, green, blue) {
|
37
|
+
return Color4.ofRGBA(red, green, blue, 1.0);
|
38
|
+
}
|
39
|
+
static ofRGBA(red, green, blue, alpha) {
|
40
|
+
red = Math.max(0, Math.min(red, 1));
|
41
|
+
green = Math.max(0, Math.min(green, 1));
|
42
|
+
blue = Math.max(0, Math.min(blue, 1));
|
43
|
+
alpha = Math.max(0, Math.min(alpha, 1));
|
44
|
+
return new Color4(red, green, blue, alpha);
|
45
|
+
}
|
46
|
+
static fromHex(hexString) {
|
47
|
+
// Remove starting '#' (if present)
|
48
|
+
hexString = (hexString.match(/^[#]?(.*)$/) ?? [])[1];
|
49
|
+
hexString = hexString.toUpperCase();
|
50
|
+
if (!hexString.match(/^[0-9A-F]+$/)) {
|
51
|
+
throw new Error(`${hexString} is not in a valid format.`);
|
52
|
+
}
|
53
|
+
// RGBA or RGB
|
54
|
+
if (hexString.length === 3 || hexString.length === 4) {
|
55
|
+
// Each character is a component
|
56
|
+
const components = hexString.split('');
|
57
|
+
// Convert to RRGGBBAA or RRGGBB format
|
58
|
+
hexString = components.map(component => `${component}0`).join('');
|
59
|
+
}
|
60
|
+
if (hexString.length === 6) {
|
61
|
+
// Alpha component
|
62
|
+
hexString += 'FF';
|
63
|
+
}
|
64
|
+
const components = [];
|
65
|
+
for (let i = 2; i <= hexString.length; i += 2) {
|
66
|
+
const chunk = hexString.substring(i - 2, i);
|
67
|
+
components.push(parseInt(chunk, 16) / 255);
|
68
|
+
}
|
69
|
+
if (components.length !== 4) {
|
70
|
+
throw new Error(`Unable to parse ${hexString}: Wrong number of components.`);
|
71
|
+
}
|
72
|
+
return Color4.ofRGBA(components[0], components[1], components[2], components[3]);
|
73
|
+
}
|
74
|
+
/** Like fromHex, but can handle additional colors if an `HTMLCanvasElement` is available. */
|
75
|
+
static fromString(text) {
|
76
|
+
if (text.startsWith('#')) {
|
77
|
+
return Color4.fromHex(text);
|
78
|
+
}
|
79
|
+
if (text === 'none' || text === 'transparent') {
|
80
|
+
return Color4.transparent;
|
81
|
+
}
|
82
|
+
// rgba?: Match both rgb and rgba strings.
|
83
|
+
// ([,0-9.]+): Match any string of only numeric, '.' and ',' characters.
|
84
|
+
const rgbRegex = /^rgba?\(([,0-9.]+)\)$/i;
|
85
|
+
const rgbMatch = text.replace(/\s*/g, '').match(rgbRegex);
|
86
|
+
if (rgbMatch) {
|
87
|
+
const componentsListStr = rgbMatch[1];
|
88
|
+
const componentsList = JSON.parse(`[ ${componentsListStr} ]`);
|
89
|
+
if (componentsList.length === 3) {
|
90
|
+
return Color4.ofRGB(componentsList[0] / 255, componentsList[1] / 255, componentsList[2] / 255);
|
91
|
+
}
|
92
|
+
else if (componentsList.length === 4) {
|
93
|
+
return Color4.ofRGBA(componentsList[0] / 255, componentsList[1] / 255, componentsList[2] / 255, componentsList[3]);
|
94
|
+
}
|
95
|
+
else {
|
96
|
+
throw new Error(`RGB string, ${text}, has wrong number of components: ${componentsList.length}`);
|
97
|
+
}
|
98
|
+
}
|
99
|
+
// Otherwise, try to use an HTMLCanvasElement to determine the color.
|
100
|
+
// Note: We may be unable to create an HTMLCanvasElement if running as a unit test.
|
101
|
+
const canvas = document.createElement('canvas');
|
102
|
+
canvas.width = 1;
|
103
|
+
canvas.height = 1;
|
104
|
+
const ctx = canvas.getContext('2d');
|
105
|
+
ctx.fillStyle = text;
|
106
|
+
ctx.fillRect(0, 0, 1, 1);
|
107
|
+
const data = ctx.getImageData(0, 0, 1, 1);
|
108
|
+
const red = data.data[0] / 255;
|
109
|
+
const green = data.data[1] / 255;
|
110
|
+
const blue = data.data[2] / 255;
|
111
|
+
const alpha = data.data[3] / 255;
|
112
|
+
return Color4.ofRGBA(red, green, blue, alpha);
|
113
|
+
}
|
114
|
+
/** @returns true if `this` and `other` are approximately equal. */
|
115
|
+
eq(other) {
|
116
|
+
if (other == null) {
|
117
|
+
return false;
|
118
|
+
}
|
119
|
+
// If both completely transparent,
|
120
|
+
if (this.a === 0 && other.a === 0) {
|
121
|
+
return true;
|
122
|
+
}
|
123
|
+
return this.toHexString() === other.toHexString();
|
124
|
+
}
|
125
|
+
/**
|
126
|
+
* If `fractionTo` is not in the range $[0, 1]$, it will be clamped to the nearest number
|
127
|
+
* in that range. For example, `a.mix(b, -1)` is equivalent to `a.mix(b, 0)`.
|
128
|
+
*
|
129
|
+
* @returns a color `fractionTo` of the way from this color to `other`.
|
130
|
+
*
|
131
|
+
* @example
|
132
|
+
* ```ts
|
133
|
+
* Color4.ofRGB(1, 0, 0).mix(Color4.ofRGB(0, 1, 0), 0.1) // -> Color4(0.9, 0.1, 0)
|
134
|
+
* ```
|
135
|
+
*/
|
136
|
+
mix(other, fractionTo) {
|
137
|
+
fractionTo = Math.min(Math.max(fractionTo, 0), 1);
|
138
|
+
const fractionOfThis = 1 - fractionTo;
|
139
|
+
return new Color4(this.r * fractionOfThis + other.r * fractionTo, this.g * fractionOfThis + other.g * fractionTo, this.b * fractionOfThis + other.b * fractionTo, this.a * fractionOfThis + other.a * fractionTo);
|
140
|
+
}
|
141
|
+
/**
|
142
|
+
* @returns the component-wise average of `colors`, or `Color4.transparent` if `colors` is empty.
|
143
|
+
*/
|
144
|
+
static average(colors) {
|
145
|
+
let averageA = 0;
|
146
|
+
let averageR = 0;
|
147
|
+
let averageG = 0;
|
148
|
+
let averageB = 0;
|
149
|
+
for (const color of colors) {
|
150
|
+
averageA += color.a;
|
151
|
+
averageR += color.r;
|
152
|
+
averageG += color.g;
|
153
|
+
averageB += color.b;
|
154
|
+
}
|
155
|
+
if (colors.length > 0) {
|
156
|
+
averageA /= colors.length;
|
157
|
+
averageR /= colors.length;
|
158
|
+
averageG /= colors.length;
|
159
|
+
averageB /= colors.length;
|
160
|
+
}
|
161
|
+
return new Color4(averageR, averageG, averageB, averageA);
|
162
|
+
}
|
163
|
+
/**
|
164
|
+
* Converts to (hue, saturation, value).
|
165
|
+
* See also https://en.wikipedia.org/wiki/HSL_and_HSV#General_approach
|
166
|
+
*
|
167
|
+
* The resultant hue is represented in radians and is thus in $[0, 2\pi]$.
|
168
|
+
*/
|
169
|
+
asHSV() {
|
170
|
+
// Ref: https://en.wikipedia.org/wiki/HSL_and_HSV#General_approach
|
171
|
+
//
|
172
|
+
// HUE:
|
173
|
+
// First, consider the unit cube. Rotate it such that one vertex is at the origin
|
174
|
+
// of a plane and its three neighboring vertices are equidistant from that plane:
|
175
|
+
//
|
176
|
+
// /\
|
177
|
+
// / | \
|
178
|
+
// 2 / 3 \ 1
|
179
|
+
// \ | /
|
180
|
+
// \ | /
|
181
|
+
// . \/ .
|
182
|
+
//
|
183
|
+
// .
|
184
|
+
//
|
185
|
+
// Let z be up and (x, y, 0) be in the plane.
|
186
|
+
//
|
187
|
+
// Label vectors 1,2,3 with R, G, and B, respectively. Let R's projection into the plane
|
188
|
+
// lie along the x axis.
|
189
|
+
//
|
190
|
+
// Because R is a unit vector and R, G, B are equidistant from the plane, they must
|
191
|
+
// form 30-60-90 triangles, which have side lengths proportional to (1, √3, 2)
|
192
|
+
//
|
193
|
+
// /|
|
194
|
+
// 1/ | (√3)/2
|
195
|
+
// / |
|
196
|
+
// 1/2
|
197
|
+
//
|
198
|
+
const minComponent = Math.min(this.r, this.g, this.b);
|
199
|
+
const maxComponent = Math.max(this.r, this.g, this.b);
|
200
|
+
const chroma = maxComponent - minComponent;
|
201
|
+
let hue;
|
202
|
+
// See https://en.wikipedia.org/wiki/HSL_and_HSV#General_approach
|
203
|
+
if (chroma === 0) {
|
204
|
+
hue = 0;
|
205
|
+
}
|
206
|
+
else if (this.r >= this.g && this.r >= this.b) {
|
207
|
+
hue = ((this.g - this.b) / chroma) % 6;
|
208
|
+
}
|
209
|
+
else if (this.g >= this.r && this.g >= this.b) {
|
210
|
+
hue = (this.b - this.r) / chroma + 2;
|
211
|
+
}
|
212
|
+
else {
|
213
|
+
hue = (this.r - this.g) / chroma + 4;
|
214
|
+
}
|
215
|
+
// Convert to degree representation, then to radians.
|
216
|
+
hue *= 60;
|
217
|
+
hue *= Math.PI / 180;
|
218
|
+
// Ensure positivity.
|
219
|
+
if (hue < 0) {
|
220
|
+
hue += Math.PI * 2;
|
221
|
+
}
|
222
|
+
const value = maxComponent;
|
223
|
+
const saturation = value > 0 ? chroma / value : 0;
|
224
|
+
return Vec3.of(hue, saturation, value);
|
225
|
+
}
|
226
|
+
/**
|
227
|
+
* @returns a hexadecimal color string representation of `this`, in the form `#rrggbbaa`.
|
228
|
+
*
|
229
|
+
* @example
|
230
|
+
* ```
|
231
|
+
* Color4.red.toHexString(); // -> #ff0000ff
|
232
|
+
* ```
|
233
|
+
*/
|
234
|
+
toHexString() {
|
235
|
+
if (this.hexString) {
|
236
|
+
return this.hexString;
|
237
|
+
}
|
238
|
+
const componentToHex = (component) => {
|
239
|
+
const res = Math.round(255 * component).toString(16);
|
240
|
+
if (res.length === 1) {
|
241
|
+
return `0${res}`;
|
242
|
+
}
|
243
|
+
return res;
|
244
|
+
};
|
245
|
+
const alpha = componentToHex(this.a);
|
246
|
+
const red = componentToHex(this.r);
|
247
|
+
const green = componentToHex(this.g);
|
248
|
+
const blue = componentToHex(this.b);
|
249
|
+
if (alpha === 'ff') {
|
250
|
+
return `#${red}${green}${blue}`;
|
251
|
+
}
|
252
|
+
this.hexString = `#${red}${green}${blue}${alpha}`;
|
253
|
+
return this.hexString;
|
254
|
+
}
|
255
|
+
toString() {
|
256
|
+
return this.toHexString();
|
257
|
+
}
|
258
|
+
}
|
259
|
+
Color4.transparent = Color4.ofRGBA(0, 0, 0, 0);
|
260
|
+
Color4.red = Color4.ofRGB(1.0, 0.0, 0.0);
|
261
|
+
Color4.orange = Color4.ofRGB(1.0, 0.65, 0.0);
|
262
|
+
Color4.green = Color4.ofRGB(0.0, 1.0, 0.0);
|
263
|
+
Color4.blue = Color4.ofRGB(0.0, 0.0, 1.0);
|
264
|
+
Color4.purple = Color4.ofRGB(0.5, 0.2, 0.5);
|
265
|
+
Color4.yellow = Color4.ofRGB(1, 1, 0.1);
|
266
|
+
Color4.clay = Color4.ofRGB(0.8, 0.4, 0.2);
|
267
|
+
Color4.black = Color4.ofRGB(0, 0, 0);
|
268
|
+
Color4.gray = Color4.ofRGB(0.5, 0.5, 0.5);
|
269
|
+
Color4.white = Color4.ofRGB(1, 1, 1);
|
270
|
+
export default Color4;
|
271
|
+
export { Color4 };
|
@@ -0,0 +1,131 @@
|
|
1
|
+
import { Point2, Vec2 } from './Vec2';
|
2
|
+
import Vec3 from './Vec3';
|
3
|
+
export type Mat33Array = [
|
4
|
+
number,
|
5
|
+
number,
|
6
|
+
number,
|
7
|
+
number,
|
8
|
+
number,
|
9
|
+
number,
|
10
|
+
number,
|
11
|
+
number,
|
12
|
+
number
|
13
|
+
];
|
14
|
+
/**
|
15
|
+
* Represents a three dimensional linear transformation or
|
16
|
+
* a two-dimensional affine transformation. (An affine transformation scales/rotates/shears
|
17
|
+
* **and** translates while a linear transformation just scales/rotates/shears).
|
18
|
+
*/
|
19
|
+
export declare class Mat33 {
|
20
|
+
readonly a1: number;
|
21
|
+
readonly a2: number;
|
22
|
+
readonly a3: number;
|
23
|
+
readonly b1: number;
|
24
|
+
readonly b2: number;
|
25
|
+
readonly b3: number;
|
26
|
+
readonly c1: number;
|
27
|
+
readonly c2: number;
|
28
|
+
readonly c3: number;
|
29
|
+
private readonly rows;
|
30
|
+
/**
|
31
|
+
* Creates a matrix from inputs in the form,
|
32
|
+
* $$
|
33
|
+
* \begin{bmatrix}
|
34
|
+
* a1 & a2 & a3 \\
|
35
|
+
* b1 & b2 & b3 \\
|
36
|
+
* c1 & c2 & c3
|
37
|
+
* \end{bmatrix}
|
38
|
+
* $$
|
39
|
+
*/
|
40
|
+
constructor(a1: number, a2: number, a3: number, b1: number, b2: number, b3: number, c1: number, c2: number, c3: number);
|
41
|
+
/**
|
42
|
+
* Creates a matrix from the given rows:
|
43
|
+
* $$
|
44
|
+
* \begin{bmatrix}
|
45
|
+
* \texttt{r1.x} & \texttt{r1.y} & \texttt{r1.z}\\
|
46
|
+
* \texttt{r2.x} & \texttt{r2.y} & \texttt{r2.z}\\
|
47
|
+
* \texttt{r3.x} & \texttt{r3.y} & \texttt{r3.z}\\
|
48
|
+
* \end{bmatrix}
|
49
|
+
* $$
|
50
|
+
*/
|
51
|
+
static ofRows(r1: Vec3, r2: Vec3, r3: Vec3): Mat33;
|
52
|
+
static identity: Mat33;
|
53
|
+
/**
|
54
|
+
* Either returns the inverse of this, or, if this matrix is singular/uninvertable,
|
55
|
+
* returns Mat33.identity.
|
56
|
+
*
|
57
|
+
* This may cache the computed inverse and return the cached version instead of recomputing
|
58
|
+
* it.
|
59
|
+
*/
|
60
|
+
inverse(): Mat33;
|
61
|
+
invertable(): boolean;
|
62
|
+
private cachedInverse;
|
63
|
+
private computeInverse;
|
64
|
+
transposed(): Mat33;
|
65
|
+
rightMul(other: Mat33): Mat33;
|
66
|
+
/**
|
67
|
+
* Applies this as an **affine** transformation to the given vector.
|
68
|
+
* Returns a transformed version of `other`.
|
69
|
+
*
|
70
|
+
* Unlike {@link transformVec3}, this **does** translate the given vector.
|
71
|
+
*/
|
72
|
+
transformVec2(other: Vec2): Vec2;
|
73
|
+
/**
|
74
|
+
* Applies this as a linear transformation to the given vector (doesn't translate).
|
75
|
+
* This is the standard way of transforming vectors in ℝ³.
|
76
|
+
*/
|
77
|
+
transformVec3(other: Vec3): Vec3;
|
78
|
+
/** @returns true iff this is the identity matrix. */
|
79
|
+
isIdentity(): boolean;
|
80
|
+
/** Returns true iff this = other ± fuzz */
|
81
|
+
eq(other: Mat33, fuzz?: number): boolean;
|
82
|
+
toString(): string;
|
83
|
+
/**
|
84
|
+
* ```
|
85
|
+
* result[0] = top left element
|
86
|
+
* result[1] = element at row zero, column 1
|
87
|
+
* ...
|
88
|
+
* ```
|
89
|
+
*/
|
90
|
+
toArray(): Mat33Array;
|
91
|
+
/**
|
92
|
+
* Returns a new `Mat33` where each entry is the output of the function
|
93
|
+
* `mapping`.
|
94
|
+
*
|
95
|
+
* @example
|
96
|
+
* ```
|
97
|
+
* new Mat33(
|
98
|
+
* 1, 2, 3,
|
99
|
+
* 4, 5, 6,
|
100
|
+
* 7, 8, 9,
|
101
|
+
* ).mapEntries(component => component - 1);
|
102
|
+
* // → ⎡ 0, 1, 2 ⎤
|
103
|
+
* // ⎢ 3, 4, 5 ⎥
|
104
|
+
* // ⎣ 6, 7, 8 ⎦
|
105
|
+
* ```
|
106
|
+
*/
|
107
|
+
mapEntries(mapping: (component: number, rowcol: [number, number]) => number): Mat33;
|
108
|
+
/** Estimate the scale factor of this matrix (based on the first row). */
|
109
|
+
getScaleFactor(): number;
|
110
|
+
/**
|
111
|
+
* Constructs a 3x3 translation matrix (for translating `Vec2`s) using
|
112
|
+
* **transformVec2**.
|
113
|
+
*/
|
114
|
+
static translation(amount: Vec2): Mat33;
|
115
|
+
static zRotation(radians: number, center?: Point2): Mat33;
|
116
|
+
static scaling2D(amount: number | Vec2, center?: Point2): Mat33;
|
117
|
+
/** @see {@link fromCSSMatrix} */
|
118
|
+
toCSSMatrix(): string;
|
119
|
+
/**
|
120
|
+
* Converts a CSS-form `matrix(a, b, c, d, e, f)` to a Mat33.
|
121
|
+
*
|
122
|
+
* Note that such a matrix has the form,
|
123
|
+
* ```
|
124
|
+
* ⎡ a c e ⎤
|
125
|
+
* ⎢ b d f ⎥
|
126
|
+
* ⎣ 0 0 1 ⎦
|
127
|
+
* ```
|
128
|
+
*/
|
129
|
+
static fromCSSMatrix(cssString: string): Mat33;
|
130
|
+
}
|
131
|
+
export default Mat33;
|