@fbltd/math 1.0.2
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 +1 -0
- package/bundler.config.json +6 -0
- package/index.ts +1 -0
- package/package.json +26 -0
- package/src/angle.ts +87 -0
- package/src/colors/color.ts +11 -0
- package/src/colors/conic.gradient.ts +105 -0
- package/src/colors/index.ts +2 -0
- package/src/figures/circle.ts +12 -0
- package/src/figures/index.ts +3 -0
- package/src/figures/point.ts +23 -0
- package/src/figures/straight-line.ts +15 -0
- package/src/index.ts +6 -0
- package/src/matrix.ts +32 -0
- package/src/operator.ts +23 -0
- package/src/utils.ts +17 -0
- package/tsconfig.json +10 -0
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# math
|
package/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './src'
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fbltd/math",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.ts",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "node_modules/.bin/tsc",
|
|
8
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/GlennMiller1991/math.git"
|
|
13
|
+
},
|
|
14
|
+
"author": "",
|
|
15
|
+
"license": "ISC",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/GlennMiller1991/math/issues"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://github.com/GlennMiller1991/math#readme",
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@fbltd/bundler": "^2.0.15"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/angle.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import {isCorrectNumber, toPositive} from "./utils";
|
|
2
|
+
|
|
3
|
+
export enum AngleUnits {
|
|
4
|
+
Deg = 0,
|
|
5
|
+
Rad = 1,
|
|
6
|
+
Turn = 2,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
export class Angle {
|
|
11
|
+
|
|
12
|
+
// region Converters
|
|
13
|
+
static toRad(angle: number, unit: AngleUnits = AngleUnits.Deg) {
|
|
14
|
+
switch (unit) {
|
|
15
|
+
case AngleUnits.Rad:
|
|
16
|
+
return angle
|
|
17
|
+
case AngleUnits.Deg:
|
|
18
|
+
return angle * Math.PI / 180
|
|
19
|
+
case AngleUnits.Turn:
|
|
20
|
+
return Math.PI * 2 * angle
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static toTurn(angle: number, unit: AngleUnits = AngleUnits.Deg) {
|
|
25
|
+
switch (unit) {
|
|
26
|
+
case AngleUnits.Turn:
|
|
27
|
+
return angle
|
|
28
|
+
case AngleUnits.Deg:
|
|
29
|
+
return angle / 360
|
|
30
|
+
case AngleUnits.Rad:
|
|
31
|
+
return angle / (Math.PI * 2)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static toDeg(angle: number, unit: AngleUnits = AngleUnits.Rad) {
|
|
36
|
+
switch (unit) {
|
|
37
|
+
case AngleUnits.Deg:
|
|
38
|
+
return angle
|
|
39
|
+
case AngleUnits.Turn:
|
|
40
|
+
return angle * 360
|
|
41
|
+
case AngleUnits.Rad:
|
|
42
|
+
return angle * 180 / Math.PI
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// endregion Converters
|
|
47
|
+
|
|
48
|
+
static toPositive(angle: number, unit: AngleUnits) {
|
|
49
|
+
switch (unit) {
|
|
50
|
+
case AngleUnits.Deg:
|
|
51
|
+
return toPositive(angle, 360)
|
|
52
|
+
case AngleUnits.Turn:
|
|
53
|
+
return toPositive(angle, 1)
|
|
54
|
+
case AngleUnits.Rad:
|
|
55
|
+
return toPositive(angle, Math.PI * 2)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
static normalize(angle: number, unit: AngleUnits) {
|
|
60
|
+
switch (unit) {
|
|
61
|
+
// 0 - 359.9999
|
|
62
|
+
case AngleUnits.Deg:
|
|
63
|
+
return Angle.toPositive(angle, unit) % 360
|
|
64
|
+
// 0 - Math.PI * 2
|
|
65
|
+
case AngleUnits.Rad:
|
|
66
|
+
return Angle.toPositive(angle, unit) % (Math.PI * 2)
|
|
67
|
+
// 0 - 1
|
|
68
|
+
case AngleUnits.Turn:
|
|
69
|
+
return Angle.toPositive(angle, unit) % 1
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// region representation
|
|
74
|
+
static toCSS(angle: number, unit: AngleUnits) {
|
|
75
|
+
if (!isCorrectNumber(angle)) return ''
|
|
76
|
+
return `rotate(${angle}${Angle.angleUnitCorrespondence[unit]})`
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
static angleUnitCorrespondence: Record<AngleUnits, string> = {
|
|
80
|
+
[AngleUnits.Rad]: 'rad',
|
|
81
|
+
[AngleUnits.Turn]: 'turn',
|
|
82
|
+
[AngleUnits.Deg]: 'deg',
|
|
83
|
+
}
|
|
84
|
+
// endregion representation
|
|
85
|
+
|
|
86
|
+
}
|
|
87
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export class Color {
|
|
2
|
+
constructor(public red: number, public green: number, public blue: number) {
|
|
3
|
+
this.red = Math.max(Math.min(red, 255), 0)
|
|
4
|
+
this.green = Math.max(Math.min(green, 255), 0)
|
|
5
|
+
this.blue = Math.max(Math.min(blue, 255), 0)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
toCSS() {
|
|
9
|
+
return `rgb(${this.red}, ${this.green}, ${this.blue})`
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import {Color} from "./color";
|
|
2
|
+
import {IPoint2} from "../figures";
|
|
3
|
+
|
|
4
|
+
export class ConicGradient {
|
|
5
|
+
colors: { angle: number, color: Color }[]
|
|
6
|
+
|
|
7
|
+
constructor(...colors: { angle: number, color: Color }[]) {
|
|
8
|
+
this.colors = [...colors].sort((a, b) => a.angle - b.angle)
|
|
9
|
+
this.colors.push({
|
|
10
|
+
angle: 1,
|
|
11
|
+
color: this.colors[0].color
|
|
12
|
+
})
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Получить цвет по углу от оси X по часовой стрелке
|
|
17
|
+
* @param angle единица измерения угла должна быть той же самой, что и углы всех передаваемых в констурктор цветов
|
|
18
|
+
*/
|
|
19
|
+
getColorAtAngle(angle: number) {
|
|
20
|
+
let prev = undefined
|
|
21
|
+
let next = undefined
|
|
22
|
+
for (let color of this.colors) {
|
|
23
|
+
if (color.angle === angle) return color.color
|
|
24
|
+
if (color.angle < angle) prev = color
|
|
25
|
+
if (color.angle > angle) {
|
|
26
|
+
next = color
|
|
27
|
+
break
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!prev || !next) return undefined
|
|
32
|
+
|
|
33
|
+
const coef = (angle - prev.angle) / (next.angle - prev.angle)
|
|
34
|
+
|
|
35
|
+
return new Color(
|
|
36
|
+
Math.floor((next.color.red - prev.color.red) * coef + prev.color.red),
|
|
37
|
+
Math.floor((next.color.green - prev.color.green) * coef + prev.color.green),
|
|
38
|
+
Math.floor((next.color.blue - prev.color.blue) * coef + prev.color.blue),
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
getAngleByColor(color: Color) {
|
|
43
|
+
let prev: typeof this.colors[number]
|
|
44
|
+
let next = this.colors[0]
|
|
45
|
+
if (color.red === next.color.red && color.green === next.color.green && color.blue === next.color.blue) return next.angle
|
|
46
|
+
for (let i = 1; i < this.colors.length; i++) {
|
|
47
|
+
next = this.colors[i]
|
|
48
|
+
prev = this.colors[i - 1]
|
|
49
|
+
if (color.red === next.color.red && color.green === next.color.green && color.blue === next.color.blue) return next.angle
|
|
50
|
+
|
|
51
|
+
let redDif = next.color.red - prev.color.red
|
|
52
|
+
let greenDif = next.color.green - prev.color.green
|
|
53
|
+
let blueDif = next.color.blue - prev.color.blue
|
|
54
|
+
let redDifColor = color.red - prev.color.red
|
|
55
|
+
let greenDifColor = color.green - prev.color.green
|
|
56
|
+
let blueDifColor = color.blue - prev.color.blue
|
|
57
|
+
|
|
58
|
+
if (
|
|
59
|
+
((redDifColor >= 0 && redDifColor <= redDif) || (redDifColor <= 0 && redDifColor >= redDif)) &&
|
|
60
|
+
((greenDifColor >= 0 && greenDifColor <= greenDif) || (greenDifColor <= 0 && greenDifColor >= greenDif)) &&
|
|
61
|
+
((blueDifColor >= 0 && blueDifColor <= blueDif) || (blueDifColor <= 0 && blueDifColor >= blueDif))
|
|
62
|
+
) {
|
|
63
|
+
|
|
64
|
+
const redCoef = ((color.red - prev.color.red) / (next.color.red - prev.color.red))
|
|
65
|
+
const greenCoef = ((color.green - prev.color.green) / (next.color.green - prev.color.green))
|
|
66
|
+
const blueCoef = ((color.blue - prev.color.blue) / (next.color.blue - prev.color.blue))
|
|
67
|
+
const coefs = [redCoef, greenCoef, blueCoef].filter(Boolean)
|
|
68
|
+
return (next.angle - prev.angle) * Math.min(...coefs) + prev.angle
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return undefined
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
isColorInRange(color: Color): boolean {
|
|
75
|
+
return Boolean(this.getAngleByColor(color))
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Для использования метода, углы должны быть в Turn единицах измерения
|
|
80
|
+
*/
|
|
81
|
+
toCanvas(ctx: CanvasRenderingContext2D, center: IPoint2) {
|
|
82
|
+
const gradient = ctx.createConicGradient(0, ...center as [number, number])
|
|
83
|
+
for (let color of this.colors) {
|
|
84
|
+
gradient.addColorStop(color.angle, color.color.toCSS())
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return gradient
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Для использования метода, углы должны быть в Turn единицах измерения
|
|
92
|
+
*/
|
|
93
|
+
toCSS() {
|
|
94
|
+
let s = ''
|
|
95
|
+
|
|
96
|
+
for (let color of this.colors) {
|
|
97
|
+
if (s) s += ','
|
|
98
|
+
s += `rgb(${color.color.red},${color.color.green},${color.color.blue})`
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
s = `conic-gradient(in srgb from 0.25turn at 50% 50%, ${s})`
|
|
102
|
+
return s
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {IPoint2} from "./point";
|
|
2
|
+
import {IMatrix2d, Matrix2d} from "../matrix";
|
|
3
|
+
|
|
4
|
+
export class Circle {
|
|
5
|
+
constructor(public center: IPoint2, public r: number) {
|
|
6
|
+
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
transform(matrix: IMatrix2d) {
|
|
10
|
+
this.center = Matrix2d.apply(matrix, this.center)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Точка/вектор
|
|
3
|
+
*/
|
|
4
|
+
export type IPoint2 = [number, number]
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Точка/вектор
|
|
8
|
+
*/
|
|
9
|
+
export class Point2 {
|
|
10
|
+
static sum(p1: IPoint2, p2: IPoint2): IPoint2 {
|
|
11
|
+
return [
|
|
12
|
+
p1[0] + p2[0],
|
|
13
|
+
p1[1] + p2[1],
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static scale(v: IPoint2, factor: number): IPoint2 {
|
|
18
|
+
return [
|
|
19
|
+
v[0] * factor,
|
|
20
|
+
v[1] * factor,
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {IPoint2} from "./point";
|
|
2
|
+
import {IMatrix2d, Matrix2d} from "../matrix";
|
|
3
|
+
|
|
4
|
+
export class StraightLine {
|
|
5
|
+
constructor(public p1: IPoint2, public p2: IPoint2) {
|
|
6
|
+
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
transform(matrix: IMatrix2d, transformThis = false) {
|
|
10
|
+
if (transformThis) {
|
|
11
|
+
return this
|
|
12
|
+
}
|
|
13
|
+
return new StraightLine(Matrix2d.apply(matrix, this.p1), Matrix2d.apply(matrix, this.p2))
|
|
14
|
+
}
|
|
15
|
+
}
|
package/src/index.ts
ADDED
package/src/matrix.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {IPoint2} from "./figures";
|
|
2
|
+
|
|
3
|
+
export type IMatrix2d = [
|
|
4
|
+
number, number, number,
|
|
5
|
+
number, number, number,
|
|
6
|
+
]
|
|
7
|
+
|
|
8
|
+
export const identityMatrix: IMatrix2d = [1, 0, 0, 1, 0, 0]
|
|
9
|
+
|
|
10
|
+
export class Matrix2d {
|
|
11
|
+
static multiply(matrix: IMatrix2d, ...matrices: IMatrix2d[]) {
|
|
12
|
+
for (let m of matrices) {
|
|
13
|
+
matrix = [
|
|
14
|
+
matrix[0] * m[0] + matrix[2] * m[1],
|
|
15
|
+
matrix[1] * m[0] + matrix[3] * m[1],
|
|
16
|
+
matrix[0] * m[2] + matrix[2] * m[3],
|
|
17
|
+
matrix[1] * m[2] + matrix[3] * m[3],
|
|
18
|
+
matrix[0] * m[4] + matrix[2] * m[5] + matrix[4],
|
|
19
|
+
matrix[1] * m[4] + matrix[3] * m[5] + matrix[5],
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return matrix
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static apply(matrix: IMatrix2d, point: IPoint2): IPoint2 {
|
|
27
|
+
return [
|
|
28
|
+
matrix[0] * point[0] + matrix[2] * point[1] + matrix[4],
|
|
29
|
+
matrix[1] * point[0] + matrix[3] * point[1] + matrix[5],
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/operator.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {IMatrix2d} from "./matrix";
|
|
2
|
+
import {Angle, AngleUnits} from "./angle";
|
|
3
|
+
|
|
4
|
+
export class Operator {
|
|
5
|
+
private static temp1: number
|
|
6
|
+
private static temp2: number
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @param angle поворот в градусах
|
|
10
|
+
*/
|
|
11
|
+
static rotateIdentity(angle: number, unit: AngleUnits = AngleUnits.Deg): IMatrix2d {
|
|
12
|
+
angle = Angle.toRad(angle, unit)
|
|
13
|
+
this.temp1 = Math.cos(angle)
|
|
14
|
+
this.temp2 = Math.sin(angle)
|
|
15
|
+
return [
|
|
16
|
+
this.temp1,
|
|
17
|
+
this.temp2,
|
|
18
|
+
-this.temp2,
|
|
19
|
+
this.temp1,
|
|
20
|
+
0, 0
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function approximately(theValue: number, is: number, withPrecision: number = 1e-8) {
|
|
2
|
+
return Math.abs(theValue - is) <= withPrecision
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function toPositive(value: number, range: number) {
|
|
6
|
+
if (value >= 0) return Math.abs(value)
|
|
7
|
+
return Math.abs((range + value % range) % range)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function isCorrectNumber(value: any) {
|
|
11
|
+
let v: number = +value
|
|
12
|
+
if (!isNaN(value) && isFinite(value)) {
|
|
13
|
+
v = parseFloat(value)
|
|
14
|
+
return !isNaN(v) && isFinite(v)
|
|
15
|
+
}
|
|
16
|
+
return false
|
|
17
|
+
}
|