@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/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
@@ -0,0 +1,8 @@
1
+ export * from './aabb'
2
+ export * from './angle'
3
+ export * from './math'
4
+ export * from './matrix'
5
+ export * from './obb'
6
+ export * from './points-of-bezier'
7
+ export * from './types'
8
+ export * from './xy'
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
@@ -0,0 +1,16 @@
1
+ export interface IXY {
2
+ x: number
3
+ y: number
4
+ }
5
+
6
+ export interface IRect {
7
+ x: number
8
+ y: number
9
+ width: number
10
+ height: number
11
+ }
12
+
13
+ export interface IRectWithCenter extends IRect {
14
+ centerX: number
15
+ centerY: number
16
+ }
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
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from 'tsup'
2
+
3
+ export default defineConfig({
4
+ entry: ['src/index.ts'],
5
+ outDir: 'dist',
6
+ format: ['esm'],
7
+ dts: true,
8
+ splitting: true,
9
+ clean: true,
10
+ })
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from 'vitest/config'
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ environment: 'node',
6
+ globals: true,
7
+ },
8
+ })