@gitborlando/geo 1.0.1
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/CHANGELOG.md +7 -0
- package/__test__/aabb.test.ts +84 -0
- package/__test__/angle.test.ts +58 -0
- package/__test__/index.test.ts +85 -0
- package/__test__/math.test.ts +61 -0
- package/__test__/matrix.test.ts +73 -0
- package/__test__/obb.test.ts +95 -0
- package/__test__/points-of-bezier.test.ts +103 -0
- package/__test__/types.test.ts +31 -0
- package/__test__/xy.test.ts +143 -0
- package/dist/index.d.ts +245 -0
- package/dist/index.js +558 -0
- package/package.json +31 -0
- package/src/aabb.ts +89 -0
- package/src/angle.ts +55 -0
- package/src/index.ts +8 -0
- package/src/math.ts +22 -0
- package/src/matrix.ts +48 -0
- package/src/obb.ts +108 -0
- package/src/points-of-bezier.ts +156 -0
- package/src/types.ts +16 -0
- package/src/xy.ts +185 -0
- package/tsup.config.ts +10 -0
- package/vitest.config.ts +8 -0
package/src/angle.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export const { PI, cos, sin, tan, acos, asin, atan, atan2 } = Math
|
|
2
|
+
|
|
3
|
+
export class Angle {
|
|
4
|
+
static Cos(angle: number) {
|
|
5
|
+
return cos(Angle.RadianFy(angle))
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
static Sin(angle: number) {
|
|
9
|
+
return sin(Angle.RadianFy(angle))
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static Tan(angle: number) {
|
|
13
|
+
return tan(Angle.RadianFy(angle))
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
static ACos(angle: number) {
|
|
17
|
+
return Angle.AngleFy(acos(Angle.RadianFy(angle)))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static ASin(angle: number) {
|
|
21
|
+
return Angle.AngleFy(asin(Angle.RadianFy(angle)))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static ATan(angle: number) {
|
|
25
|
+
return Angle.AngleFy(atan(Angle.RadianFy(angle)))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static ATan2(y: number, x: number) {
|
|
29
|
+
return Angle.AngleFy(atan2(y, x))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static AngleFy(radians: number) {
|
|
33
|
+
return radians * (180 / Math.PI)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static RadianFy(angle: number) {
|
|
37
|
+
return angle * (Math.PI / 180)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static Normal(angle: number) {
|
|
41
|
+
return (angle + 360) % 360
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static Snap(angle: number, step = 90) {
|
|
45
|
+
return Angle.Normal(Math.round(angle / step) * step)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static RotatePoint(ax: number, ay: number, ox: number, oy: number, angle: number) {
|
|
49
|
+
const radian = Angle.RadianFy(angle)
|
|
50
|
+
return {
|
|
51
|
+
x: (ax - ox) * cos(radian) - (ay - oy) * sin(radian) + ox,
|
|
52
|
+
y: (ax - ox) * sin(radian) + (ay - oy) * cos(radian) + oy,
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
package/src/index.ts
ADDED
package/src/math.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const { sqrt, abs, min, max, round, floor, ceil, random } = Math
|
|
2
|
+
|
|
3
|
+
export function pow2(number: number) {
|
|
4
|
+
return Math.pow(number, 2)
|
|
5
|
+
}
|
|
6
|
+
export function pow3(number: number) {
|
|
7
|
+
return Math.pow(number, 3)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function multiply(...numbers: number[]) {
|
|
11
|
+
return numbers.reduce((i, all) => (all *= i), 1)
|
|
12
|
+
}
|
|
13
|
+
export function divide(a: number, b: number) {
|
|
14
|
+
return b === 0 ? 1 : a / b
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function numberHalfFix(number: number) {
|
|
18
|
+
const integerPart = ~~number
|
|
19
|
+
const floatPart = number - integerPart
|
|
20
|
+
const halfFixed = floatPart >= 0.75 ? 1 : floatPart >= 0.25 ? 0.5 : 0
|
|
21
|
+
return integerPart + halfFixed
|
|
22
|
+
}
|
package/src/matrix.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { AABB } from './aabb'
|
|
2
|
+
import { max, min } from './math'
|
|
3
|
+
import { IXY } from './types'
|
|
4
|
+
import { xy_ } from './xy'
|
|
5
|
+
|
|
6
|
+
export type IMatrix = [number, number, number, number, number, number]
|
|
7
|
+
|
|
8
|
+
export class Matrix {
|
|
9
|
+
static Create() {
|
|
10
|
+
return [1, 0, 0, 1, 0, 0] as IMatrix
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
static Invert(matrix: IMatrix) {
|
|
14
|
+
const [a, b, c, d, e, f] = matrix
|
|
15
|
+
const invDet = 1 / (a * d - b * c)
|
|
16
|
+
return [d, -b, -c, a, c * f - d * e, b * e - a * f].map(
|
|
17
|
+
(i) => i * invDet,
|
|
18
|
+
) as IMatrix
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static ApplyPoint(xy: IXY, matrix: IMatrix) {
|
|
22
|
+
const { x, y } = xy
|
|
23
|
+
const [a, b, c, d, e, f] = matrix
|
|
24
|
+
return xy_(a * x + c * y + e, b * x + d * y + f)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static ApplyAABB(aabb: AABB, matrix: IMatrix) {
|
|
28
|
+
const { minX, minY, maxX, maxY } = aabb
|
|
29
|
+
const xy1 = Matrix.ApplyPoint(xy_(minX, minY), matrix)
|
|
30
|
+
const xy2 = Matrix.ApplyPoint(xy_(maxX, minY), matrix)
|
|
31
|
+
const xy3 = Matrix.ApplyPoint(xy_(maxX, maxY), matrix)
|
|
32
|
+
const xy4 = Matrix.ApplyPoint(xy_(minX, maxY), matrix)
|
|
33
|
+
return {
|
|
34
|
+
minX: min(xy1.x, xy2.x, xy3.x, xy4.x),
|
|
35
|
+
minY: min(xy1.y, xy2.y, xy3.y, xy4.y),
|
|
36
|
+
maxX: max(xy1.x, xy2.x, xy3.x, xy4.x),
|
|
37
|
+
maxY: max(xy1.y, xy2.y, xy3.y, xy4.y),
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static InvertPoint(xy: IXY, matrix: IMatrix) {
|
|
42
|
+
return Matrix.ApplyPoint(xy, Matrix.Invert(matrix))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static InvertAABB(aabb: AABB, matrix: IMatrix) {
|
|
46
|
+
return Matrix.ApplyAABB(aabb, Matrix.Invert(matrix))
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/obb.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { AABB } from './aabb'
|
|
2
|
+
import { Angle } from './angle'
|
|
3
|
+
import { IRect, IXY } from './types'
|
|
4
|
+
import { xy_, xy_dot, xy_minus, xy_rotate } from './xy'
|
|
5
|
+
|
|
6
|
+
type IAxis = { widthAxis: IXY; heightAxis: IXY }
|
|
7
|
+
|
|
8
|
+
export class OBB {
|
|
9
|
+
center: IXY
|
|
10
|
+
axis: IAxis
|
|
11
|
+
aabb: AABB
|
|
12
|
+
vertexes: [IXY, IXY, IXY, IXY]
|
|
13
|
+
|
|
14
|
+
constructor(
|
|
15
|
+
public x: number,
|
|
16
|
+
public y: number,
|
|
17
|
+
public width: number,
|
|
18
|
+
public height: number,
|
|
19
|
+
public rotation: number,
|
|
20
|
+
) {
|
|
21
|
+
this.center = this.#calcCenter()
|
|
22
|
+
this.axis = this.#calcAxis()
|
|
23
|
+
this.vertexes = this.calcVertexXY()
|
|
24
|
+
this.aabb = AABB.FromOBB(this)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get xy() {
|
|
28
|
+
return xy_(this.x, this.y)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
#calcCenter = () => {
|
|
32
|
+
const center = xy_(this.x + this.width / 2, this.y + this.height / 2)
|
|
33
|
+
return xy_rotate(center, xy_(this.x, this.y), this.rotation)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
#calcAxis = () => {
|
|
37
|
+
const cos = Angle.Cos(this.rotation)
|
|
38
|
+
const sin = Angle.Sin(this.rotation)
|
|
39
|
+
const widthAxis = xy_(cos, -sin)
|
|
40
|
+
const heightAxis = xy_(sin, cos)
|
|
41
|
+
return (this.axis = { widthAxis, heightAxis })
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
calcVertexXY = () => {
|
|
45
|
+
const cos = Angle.Cos(this.rotation)
|
|
46
|
+
const sin = Angle.Sin(this.rotation)
|
|
47
|
+
const cosWidth = cos * this.width
|
|
48
|
+
const sinWidth = sin * this.width
|
|
49
|
+
const cosHeight = cos * this.height
|
|
50
|
+
const sinHeight = sin * this.height
|
|
51
|
+
const TL = xy_(this.x, this.y)
|
|
52
|
+
const TR = xy_(this.x + cosWidth, this.y + sinWidth)
|
|
53
|
+
const BR = xy_(this.x + cosWidth - sinHeight, this.y + sinWidth + cosHeight)
|
|
54
|
+
const BL = xy_(this.x - sinHeight, this.y + cosHeight)
|
|
55
|
+
return (this.vertexes = [TL, TR, BR, BL])
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
clone = () => {
|
|
59
|
+
return new OBB(this.x, this.y, this.width, this.height, this.rotation)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
projectionLengthAt = (anotherAxis: IXY) => {
|
|
63
|
+
const { widthAxis, heightAxis } = this.axis
|
|
64
|
+
return (
|
|
65
|
+
Math.abs(xy_dot(widthAxis, anotherAxis)) * this.width +
|
|
66
|
+
Math.abs(xy_dot(heightAxis, anotherAxis)) * this.height
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
collide = (another: OBB) => {
|
|
71
|
+
const centerVector = xy_minus(this.center, another.center)
|
|
72
|
+
if (
|
|
73
|
+
this.projectionLengthAt(another.axis.widthAxis) + another.width <=
|
|
74
|
+
2 * Math.abs(xy_dot(centerVector, another.axis.widthAxis))
|
|
75
|
+
)
|
|
76
|
+
return false
|
|
77
|
+
if (
|
|
78
|
+
this.projectionLengthAt(another.axis.heightAxis) + another.height <=
|
|
79
|
+
2 * Math.abs(xy_dot(centerVector, another.axis.heightAxis))
|
|
80
|
+
)
|
|
81
|
+
return false
|
|
82
|
+
if (
|
|
83
|
+
another.projectionLengthAt(this.axis.widthAxis) + this.width <=
|
|
84
|
+
2 * Math.abs(xy_dot(centerVector, this.axis.widthAxis))
|
|
85
|
+
)
|
|
86
|
+
return false
|
|
87
|
+
if (
|
|
88
|
+
another.projectionLengthAt(this.axis.heightAxis) + this.height <=
|
|
89
|
+
2 * Math.abs(xy_dot(centerVector, this.axis.heightAxis))
|
|
90
|
+
)
|
|
91
|
+
return false
|
|
92
|
+
return true
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
static IdentityOBB() {
|
|
96
|
+
return new OBB(0, 0, 0, 0, 0)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
static FromRect(rect: IRect, rotation = 0): OBB {
|
|
100
|
+
const { x, y, width, height } = rect
|
|
101
|
+
return new OBB(x, y, width, height, rotation)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
static FromAABB(aabb: AABB): OBB {
|
|
105
|
+
const { minX, minY, maxX, maxY } = aabb
|
|
106
|
+
return new OBB(minX, minY, maxX - minX, maxY - minY, 0)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
export type Point = {
|
|
2
|
+
x: number
|
|
3
|
+
y: number
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
// distance between 2 points
|
|
7
|
+
function distance(p1: Point, p2: Point): number {
|
|
8
|
+
return Math.sqrt(distanceSq(p1, p2))
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// distance between 2 points squared
|
|
12
|
+
function distanceSq(p1: Point, p2: Point): number {
|
|
13
|
+
return Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Sistance squared from a point p to the line segment vw
|
|
17
|
+
function distanceToSegmentSq(p: Point, v: Point, w: Point): number {
|
|
18
|
+
const l2 = distanceSq(v, w)
|
|
19
|
+
if (l2 === 0) {
|
|
20
|
+
return distanceSq(p, v)
|
|
21
|
+
}
|
|
22
|
+
let t = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2
|
|
23
|
+
t = Math.max(0, Math.min(1, t))
|
|
24
|
+
return distanceSq(p, lerp(v, w, t))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function lerp(a: Point, b: Point, t: number): Point {
|
|
28
|
+
return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Adapted from https://seant23.wordpress.com/2010/11/12/offset-bezier-curves/
|
|
32
|
+
function flatness(points: readonly Point[], offset: number): number {
|
|
33
|
+
const p1 = points[offset + 0]
|
|
34
|
+
const p2 = points[offset + 1]
|
|
35
|
+
const p3 = points[offset + 2]
|
|
36
|
+
const p4 = points[offset + 3]
|
|
37
|
+
|
|
38
|
+
let ux = 3 * p2.x - 2 * p1.x - p4.x
|
|
39
|
+
ux *= ux
|
|
40
|
+
let uy = 3 * p2.y - 2 * p1.y - p4.y
|
|
41
|
+
uy *= uy
|
|
42
|
+
let vx = 3 * p3.x - 2 * p4.x - p1.x
|
|
43
|
+
vx *= vx
|
|
44
|
+
let vy = 3 * p3.y - 2 * p4.y - p1.y
|
|
45
|
+
vy *= vy
|
|
46
|
+
|
|
47
|
+
if (ux < vx) {
|
|
48
|
+
ux = vx
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (uy < vy) {
|
|
52
|
+
uy = vy
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return ux + uy
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getPointsOnBezierCurveWithSplitting(
|
|
59
|
+
points: readonly Point[],
|
|
60
|
+
offset: number,
|
|
61
|
+
tolerance: number,
|
|
62
|
+
newPoints?: Point[],
|
|
63
|
+
): Point[] {
|
|
64
|
+
const outPoints = newPoints || []
|
|
65
|
+
if (flatness(points, offset) < tolerance) {
|
|
66
|
+
const p0 = points[offset + 0]
|
|
67
|
+
if (outPoints.length) {
|
|
68
|
+
const d = distance(outPoints[outPoints.length - 1], p0)
|
|
69
|
+
if (d > 1) {
|
|
70
|
+
outPoints.push(p0)
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
outPoints.push(p0)
|
|
74
|
+
}
|
|
75
|
+
outPoints.push(points[offset + 3])
|
|
76
|
+
} else {
|
|
77
|
+
// subdivide
|
|
78
|
+
const t = 0.5
|
|
79
|
+
const p1 = points[offset + 0]
|
|
80
|
+
const p2 = points[offset + 1]
|
|
81
|
+
const p3 = points[offset + 2]
|
|
82
|
+
const p4 = points[offset + 3]
|
|
83
|
+
|
|
84
|
+
const q1 = lerp(p1, p2, t)
|
|
85
|
+
const q2 = lerp(p2, p3, t)
|
|
86
|
+
const q3 = lerp(p3, p4, t)
|
|
87
|
+
|
|
88
|
+
const r1 = lerp(q1, q2, t)
|
|
89
|
+
const r2 = lerp(q2, q3, t)
|
|
90
|
+
|
|
91
|
+
const red = lerp(r1, r2, t)
|
|
92
|
+
|
|
93
|
+
getPointsOnBezierCurveWithSplitting([p1, q1, r1, red], 0, tolerance, outPoints)
|
|
94
|
+
getPointsOnBezierCurveWithSplitting([red, r2, q3, p4], 0, tolerance, outPoints)
|
|
95
|
+
}
|
|
96
|
+
return outPoints
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function simplify(points: readonly Point[], distance: number): Point[] {
|
|
100
|
+
return simplifyPoints(points, 0, points.length, distance)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Ramer–Douglas–Peucker algorithm
|
|
104
|
+
// https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm
|
|
105
|
+
export function simplifyPoints(
|
|
106
|
+
points: readonly Point[],
|
|
107
|
+
start: number,
|
|
108
|
+
end: number,
|
|
109
|
+
epsilon: number,
|
|
110
|
+
newPoints?: Point[],
|
|
111
|
+
): Point[] {
|
|
112
|
+
const outPoints = newPoints || []
|
|
113
|
+
|
|
114
|
+
// find the most distance point from the endpoints
|
|
115
|
+
const s = points[start]
|
|
116
|
+
const e = points[end - 1]
|
|
117
|
+
let maxDistSq = 0
|
|
118
|
+
let maxNdx = 1
|
|
119
|
+
for (let i = start + 1; i < end - 1; ++i) {
|
|
120
|
+
const distSq = distanceToSegmentSq(points[i], s, e)
|
|
121
|
+
if (distSq > maxDistSq) {
|
|
122
|
+
maxDistSq = distSq
|
|
123
|
+
maxNdx = i
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// if that point is too far, split
|
|
128
|
+
if (Math.sqrt(maxDistSq) > epsilon) {
|
|
129
|
+
simplifyPoints(points, start, maxNdx + 1, epsilon, outPoints)
|
|
130
|
+
simplifyPoints(points, maxNdx, end, epsilon, outPoints)
|
|
131
|
+
} else {
|
|
132
|
+
if (!outPoints.length) {
|
|
133
|
+
outPoints.push(s)
|
|
134
|
+
}
|
|
135
|
+
outPoints.push(e)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return outPoints
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function pointsOnBezierCurves(
|
|
142
|
+
points: readonly Point[],
|
|
143
|
+
tolerance: number = 0.15,
|
|
144
|
+
distance?: number,
|
|
145
|
+
): Point[] {
|
|
146
|
+
const newPoints: Point[] = []
|
|
147
|
+
const numSegments = (points.length - 1) / 3
|
|
148
|
+
for (let i = 0; i < numSegments; i++) {
|
|
149
|
+
const offset = i * 3
|
|
150
|
+
getPointsOnBezierCurveWithSplitting(points, offset, tolerance, newPoints)
|
|
151
|
+
}
|
|
152
|
+
if (distance && distance > 0) {
|
|
153
|
+
return simplifyPoints(newPoints, 0, newPoints.length, distance)
|
|
154
|
+
}
|
|
155
|
+
return newPoints
|
|
156
|
+
}
|
package/src/types.ts
ADDED
package/src/xy.ts
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { Angle } from './angle'
|
|
2
|
+
import { sqrt } from './math'
|
|
3
|
+
import { IXY } from './types'
|
|
4
|
+
|
|
5
|
+
export function xy_(x: number = 0, y: number = 0) {
|
|
6
|
+
return { x, y }
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function xy_client(e: any) {
|
|
10
|
+
return { x: e.clientX, y: e.clientY }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function xy_from(xy: IXY) {
|
|
14
|
+
return { x: xy.x, y: xy.y }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function xy_center(xy: { centerX: number; centerY: number }) {
|
|
18
|
+
return { x: xy.centerX, y: xy.centerY }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function xy_mutate(self: IXY, another: IXY) {
|
|
22
|
+
self.x = another.x
|
|
23
|
+
self.y = another.y
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function xy_plus(self: IXY, another: IXY) {
|
|
27
|
+
return { x: self.x + another.x, y: self.y + another.y }
|
|
28
|
+
}
|
|
29
|
+
export function xy_plus_mutate(self: IXY, another: IXY) {
|
|
30
|
+
self.x = self.x + another.x
|
|
31
|
+
self.y = self.y + another.y
|
|
32
|
+
}
|
|
33
|
+
export function xy_plus_all(...xys: IXY[]) {
|
|
34
|
+
return xys.reduce((a, b) => xy_plus(a, b))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function xy_minus(self: IXY, another: IXY) {
|
|
38
|
+
return { x: self.x - another.x, y: self.y - another.y }
|
|
39
|
+
}
|
|
40
|
+
export function xy_minus_mutate(self: IXY, another: IXY) {
|
|
41
|
+
self.x = self.x - another.x
|
|
42
|
+
self.y = self.y - another.y
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function xy_multiply(self: IXY, ...numbers: number[]) {
|
|
46
|
+
const n = numbers.reduce((a, b) => a * b, 1)
|
|
47
|
+
return { x: self.x * n, y: self.y * n }
|
|
48
|
+
}
|
|
49
|
+
export function xy_multiply_mutate(self: IXY, ...numbers: number[]) {
|
|
50
|
+
const n = numbers.reduce((a, b) => a * b, 1)
|
|
51
|
+
self.x = self.x * n
|
|
52
|
+
self.y = self.y * n
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function xy_divide(self: IXY, ...numbers: number[]) {
|
|
56
|
+
const n = numbers.reduce((a, b) => a * b, 1)
|
|
57
|
+
return { x: self.x / n, y: self.y / n }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function xy_distance(self: IXY, another: IXY = xy_(0, 0)) {
|
|
61
|
+
return Math.sqrt((self.x - another.x) ** 2 + (self.y - another.y) ** 2)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function xy_rotate(self: IXY, origin: IXY, rotation: number) {
|
|
65
|
+
if (rotation === 0) return self
|
|
66
|
+
return Angle.RotatePoint(self.x, self.y, origin.x, origin.y, rotation)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function xy_dot(self: IXY, another: IXY) {
|
|
70
|
+
return self.x * another.x + self.y * another.y
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function xy_symmetric(self: IXY, origin: IXY) {
|
|
74
|
+
return { x: 2 * origin.x - self.x, y: 2 * origin.y - self.y }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function xy_opposite(self: IXY) {
|
|
78
|
+
return { x: -self.x, y: -self.y }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function xy_getRotation(self: IXY, another: IXY, origin: IXY) {
|
|
82
|
+
return Angle.AngleFy(
|
|
83
|
+
Math.atan2(self.y - origin.y, self.x - origin.x) -
|
|
84
|
+
Math.atan2(another.y - origin.y, another.x - origin.x),
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function xy_toArray(self: IXY) {
|
|
89
|
+
return [self.x, self.y] as [number, number]
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function xy_xAxis(rotation: number) {
|
|
93
|
+
return { x: Angle.Cos(rotation), y: Angle.Sin(rotation) }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function xy_yAxis(rotation: number) {
|
|
97
|
+
return { x: -Angle.Sin(rotation), y: Angle.Cos(rotation) }
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export class XY {
|
|
101
|
+
constructor(
|
|
102
|
+
public x: number,
|
|
103
|
+
public y: number,
|
|
104
|
+
) {}
|
|
105
|
+
|
|
106
|
+
from(xy: IXY) {
|
|
107
|
+
this.x = xy.x
|
|
108
|
+
this.y = xy.y
|
|
109
|
+
return this
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
client(e: { clientX: number; clientY: number }) {
|
|
113
|
+
this.x = e.clientX
|
|
114
|
+
this.y = e.clientY
|
|
115
|
+
return this
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
center(xy: { centerX: number; centerY: number }) {
|
|
119
|
+
this.x = xy.centerX
|
|
120
|
+
this.y = xy.centerY
|
|
121
|
+
return this
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
plus(another: IXY) {
|
|
125
|
+
this.x = this.x + another.x
|
|
126
|
+
this.y = this.y + another.y
|
|
127
|
+
return this
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
minus(another: IXY) {
|
|
131
|
+
this.x = this.x - another.x
|
|
132
|
+
this.y = this.y - another.y
|
|
133
|
+
return this
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
multiply(...numbers: number[]) {
|
|
137
|
+
const n = numbers.reduce((a, b) => a * b, 1)
|
|
138
|
+
this.x = this.x * n
|
|
139
|
+
this.y = this.y * n
|
|
140
|
+
return this
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
divide(...numbers: number[]) {
|
|
144
|
+
const n = numbers.reduce((a, b) => a * b, 1)
|
|
145
|
+
this.x = this.x / n
|
|
146
|
+
this.y = this.y / n
|
|
147
|
+
return this
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
distance(another: IXY) {
|
|
151
|
+
return sqrt((this.x - another.x) ** 2 + (this.y - another.y) ** 2)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
rotate(origin: IXY, rotation: number) {
|
|
155
|
+
if (rotation === 0) return this
|
|
156
|
+
return Angle.RotatePoint(this.x, this.y, origin.x, origin.y, rotation)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
dot(another: IXY) {
|
|
160
|
+
return this.x * another.x + this.y * another.y
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
opposite() {
|
|
164
|
+
return { x: -this.x, y: -this.y }
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
symmetric(another: IXY, origin: IXY) {
|
|
168
|
+
return { x: 2 * origin.x - another.x, y: 2 * origin.y - another.y }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
angle(another: IXY, origin: IXY) {
|
|
172
|
+
return Angle.AngleFy(
|
|
173
|
+
Math.atan2(this.y - origin.y, this.x - origin.x) -
|
|
174
|
+
Math.atan2(another.y - origin.y, another.x - origin.x),
|
|
175
|
+
)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
static From(xy: IXY) {
|
|
179
|
+
return new XY(xy.x, xy.y)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
static FromArray(arr: [number, number]) {
|
|
183
|
+
return new XY(arr[0], arr[1])
|
|
184
|
+
}
|
|
185
|
+
}
|
package/tsup.config.ts
ADDED