@tldraw/editor 3.14.0-canary.d926f92ca8d6 → 3.14.0-canary.e099968e8b09
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/dist-cjs/index.d.ts +51 -46
- package/dist-cjs/index.js +3 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +2 -4
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/managers/FontManager.js +5 -1
- package/dist-cjs/lib/editor/managers/FontManager.js.map +2 -2
- package/dist-cjs/lib/primitives/Box.js +39 -33
- package/dist-cjs/lib/primitives/Box.js.map +2 -2
- package/dist-cjs/lib/primitives/Vec.js +13 -8
- package/dist-cjs/lib/primitives/Vec.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Arc2d.js +41 -21
- package/dist-cjs/lib/primitives/geometry/Arc2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Circle2d.js +11 -11
- package/dist-cjs/lib/primitives/geometry/Circle2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js +13 -16
- package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/CubicSpline2d.js +4 -4
- package/dist-cjs/lib/primitives/geometry/CubicSpline2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Edge2d.js +14 -17
- package/dist-cjs/lib/primitives/geometry/Edge2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Ellipse2d.js +10 -10
- package/dist-cjs/lib/primitives/geometry/Ellipse2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Point2d.js +6 -6
- package/dist-cjs/lib/primitives/geometry/Point2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Polygon2d.js +3 -0
- package/dist-cjs/lib/primitives/geometry/Polygon2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Polyline2d.js +8 -5
- package/dist-cjs/lib/primitives/geometry/Polyline2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Rectangle2d.js +22 -11
- package/dist-cjs/lib/primitives/geometry/Rectangle2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Stadium2d.js +22 -22
- package/dist-cjs/lib/primitives/geometry/Stadium2d.js.map +2 -2
- package/dist-cjs/lib/utils/areShapesContentEqual.js +1 -1
- package/dist-cjs/lib/utils/areShapesContentEqual.js.map +2 -2
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +51 -46
- package/dist-esm/index.mjs +3 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +2 -4
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/FontManager.mjs +5 -1
- package/dist-esm/lib/editor/managers/FontManager.mjs.map +2 -2
- package/dist-esm/lib/primitives/Box.mjs +39 -33
- package/dist-esm/lib/primitives/Box.mjs.map +2 -2
- package/dist-esm/lib/primitives/Vec.mjs +13 -8
- package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Arc2d.mjs +41 -21
- package/dist-esm/lib/primitives/geometry/Arc2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Circle2d.mjs +11 -11
- package/dist-esm/lib/primitives/geometry/Circle2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs +13 -16
- package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/CubicSpline2d.mjs +4 -4
- package/dist-esm/lib/primitives/geometry/CubicSpline2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Edge2d.mjs +14 -17
- package/dist-esm/lib/primitives/geometry/Edge2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs +11 -11
- package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Point2d.mjs +6 -6
- package/dist-esm/lib/primitives/geometry/Point2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Polygon2d.mjs +3 -0
- package/dist-esm/lib/primitives/geometry/Polygon2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Polyline2d.mjs +8 -5
- package/dist-esm/lib/primitives/geometry/Polyline2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Rectangle2d.mjs +22 -11
- package/dist-esm/lib/primitives/geometry/Rectangle2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Stadium2d.mjs +22 -22
- package/dist-esm/lib/primitives/geometry/Stadium2d.mjs.map +2 -2
- package/dist-esm/lib/utils/areShapesContentEqual.mjs +1 -1
- package/dist-esm/lib/utils/areShapesContentEqual.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/package.json +7 -7
- package/src/index.ts +1 -0
- package/src/lib/editor/Editor.ts +3 -4
- package/src/lib/editor/managers/FontManager.ts +5 -1
- package/src/lib/primitives/Box.test.ts +588 -7
- package/src/lib/primitives/Box.ts +41 -33
- package/src/lib/primitives/Vec.test.ts +2 -2
- package/src/lib/primitives/Vec.ts +13 -8
- package/src/lib/primitives/geometry/Arc2d.ts +42 -23
- package/src/lib/primitives/geometry/Circle2d.ts +12 -12
- package/src/lib/primitives/geometry/CubicBezier2d.test.ts +5 -0
- package/src/lib/primitives/geometry/CubicBezier2d.ts +13 -17
- package/src/lib/primitives/geometry/CubicSpline2d.ts +5 -5
- package/src/lib/primitives/geometry/Edge2d.ts +14 -18
- package/src/lib/primitives/geometry/Ellipse2d.ts +12 -13
- package/src/lib/primitives/geometry/Point2d.ts +6 -6
- package/src/lib/primitives/geometry/Polygon2d.ts +4 -0
- package/src/lib/primitives/geometry/Polyline2d.ts +10 -7
- package/src/lib/primitives/geometry/Rectangle2d.ts +24 -11
- package/src/lib/primitives/geometry/Stadium2d.ts +22 -23
- package/src/lib/utils/areShapesContentEqual.ts +2 -1
- package/src/version.ts +3 -3
|
@@ -134,33 +134,33 @@ export class Box {
|
|
|
134
134
|
|
|
135
135
|
// eslint-disable-next-line no-restricted-syntax
|
|
136
136
|
get center() {
|
|
137
|
-
return new Vec(this.
|
|
137
|
+
return new Vec(this.x + this.w / 2, this.y + this.h / 2)
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
// eslint-disable-next-line no-restricted-syntax
|
|
141
141
|
set center(v: Vec) {
|
|
142
|
-
this.
|
|
143
|
-
this.
|
|
142
|
+
this.x = v.x - this.w / 2
|
|
143
|
+
this.y = v.y - this.h / 2
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
// eslint-disable-next-line no-restricted-syntax
|
|
147
147
|
get corners() {
|
|
148
148
|
return [
|
|
149
|
-
new Vec(this.
|
|
150
|
-
new Vec(this.
|
|
151
|
-
new Vec(this.
|
|
152
|
-
new Vec(this.
|
|
149
|
+
new Vec(this.x, this.y),
|
|
150
|
+
new Vec(this.x + this.w, this.y),
|
|
151
|
+
new Vec(this.x + this.w, this.y + this.h),
|
|
152
|
+
new Vec(this.x, this.y + this.h),
|
|
153
153
|
]
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
// eslint-disable-next-line no-restricted-syntax
|
|
157
157
|
get cornersAndCenter() {
|
|
158
158
|
return [
|
|
159
|
-
new Vec(this.
|
|
160
|
-
new Vec(this.
|
|
161
|
-
new Vec(this.
|
|
162
|
-
new Vec(this.
|
|
163
|
-
this.
|
|
159
|
+
new Vec(this.x, this.y),
|
|
160
|
+
new Vec(this.x + this.w, this.y),
|
|
161
|
+
new Vec(this.x + this.w, this.y + this.h),
|
|
162
|
+
new Vec(this.x, this.y + this.h),
|
|
163
|
+
new Vec(this.x + this.w / 2, this.y + this.h / 2),
|
|
164
164
|
]
|
|
165
165
|
}
|
|
166
166
|
|
|
@@ -205,10 +205,10 @@ export class Box {
|
|
|
205
205
|
}
|
|
206
206
|
|
|
207
207
|
expand(A: Box) {
|
|
208
|
-
const minX = Math.min(this.
|
|
209
|
-
const minY = Math.min(this.
|
|
210
|
-
const maxX = Math.max(this.
|
|
211
|
-
const maxY = Math.max(this.
|
|
208
|
+
const minX = Math.min(this.x, A.x)
|
|
209
|
+
const minY = Math.min(this.y, A.y)
|
|
210
|
+
const maxX = Math.max(this.x + this.w, A.x + A.w)
|
|
211
|
+
const maxY = Math.max(this.y + this.h, A.y + A.h)
|
|
212
212
|
|
|
213
213
|
this.x = minX
|
|
214
214
|
this.y = minY
|
|
@@ -245,10 +245,10 @@ export class Box {
|
|
|
245
245
|
}
|
|
246
246
|
|
|
247
247
|
snapToGrid(size: number) {
|
|
248
|
-
const minX = Math.round(this.
|
|
249
|
-
const minY = Math.round(this.
|
|
250
|
-
const maxX = Math.round(this.
|
|
251
|
-
const maxY = Math.round(this.
|
|
248
|
+
const minX = Math.round(this.x / size) * size
|
|
249
|
+
const minY = Math.round(this.y / size) * size
|
|
250
|
+
const maxX = Math.round((this.x + this.w) / size) * size
|
|
251
|
+
const maxY = Math.round((this.y + this.h) / size) * size
|
|
252
252
|
this.minX = minX
|
|
253
253
|
this.minY = minY
|
|
254
254
|
this.width = Math.max(1, maxX - minX)
|
|
@@ -274,26 +274,26 @@ export class Box {
|
|
|
274
274
|
getHandlePoint(handle: SelectionCorner | SelectionEdge) {
|
|
275
275
|
switch (handle) {
|
|
276
276
|
case 'top_left':
|
|
277
|
-
return new Vec(this.
|
|
277
|
+
return new Vec(this.x, this.y)
|
|
278
278
|
case 'top_right':
|
|
279
|
-
return new Vec(this.
|
|
279
|
+
return new Vec(this.x + this.w, this.y)
|
|
280
280
|
case 'bottom_left':
|
|
281
|
-
return new Vec(this.
|
|
281
|
+
return new Vec(this.x, this.y + this.h)
|
|
282
282
|
case 'bottom_right':
|
|
283
|
-
return new Vec(this.
|
|
283
|
+
return new Vec(this.x + this.w, this.y + this.h)
|
|
284
284
|
case 'top':
|
|
285
|
-
return new Vec(this.
|
|
285
|
+
return new Vec(this.x + this.w / 2, this.y)
|
|
286
286
|
case 'right':
|
|
287
|
-
return new Vec(this.
|
|
287
|
+
return new Vec(this.x + this.w, this.y + this.h / 2)
|
|
288
288
|
case 'bottom':
|
|
289
|
-
return new Vec(this.
|
|
289
|
+
return new Vec(this.x + this.w / 2, this.y + this.h)
|
|
290
290
|
case 'left':
|
|
291
|
-
return new Vec(this.
|
|
291
|
+
return new Vec(this.x, this.y + this.h / 2)
|
|
292
292
|
}
|
|
293
293
|
}
|
|
294
294
|
|
|
295
295
|
toJson(): BoxModel {
|
|
296
|
-
return { x: this.
|
|
296
|
+
return { x: this.x, y: this.y, w: this.w, h: this.h }
|
|
297
297
|
}
|
|
298
298
|
|
|
299
299
|
resize(handle: SelectionCorner | SelectionEdge | string, dx: number, dy: number) {
|
|
@@ -357,10 +357,10 @@ export class Box {
|
|
|
357
357
|
}
|
|
358
358
|
|
|
359
359
|
union(box: BoxModel) {
|
|
360
|
-
const minX = Math.min(this.
|
|
361
|
-
const minY = Math.min(this.
|
|
362
|
-
const maxX = Math.max(this.
|
|
363
|
-
const maxY = Math.max(this.
|
|
360
|
+
const minX = Math.min(this.x, box.x)
|
|
361
|
+
const minY = Math.min(this.y, box.y)
|
|
362
|
+
const maxX = Math.max(this.x + this.w, box.x + box.w)
|
|
363
|
+
const maxY = Math.max(this.y + this.h, box.y + box.h)
|
|
364
364
|
|
|
365
365
|
this.x = minX
|
|
366
366
|
this.y = minY
|
|
@@ -591,6 +591,14 @@ export class Box {
|
|
|
591
591
|
return b.x === a.x && b.y === a.y && b.w === a.w && b.h === a.h
|
|
592
592
|
}
|
|
593
593
|
|
|
594
|
+
prettyMuchEquals(other: Box | BoxModel) {
|
|
595
|
+
return this.clone().toFixed().equals(Box.From(other).toFixed())
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
static PrettyMuchEquals(a: Box | BoxModel, b: Box | BoxModel) {
|
|
599
|
+
return b.x === a.x && b.y === a.y && b.w === a.w && b.h === a.h
|
|
600
|
+
}
|
|
601
|
+
|
|
594
602
|
zeroFix() {
|
|
595
603
|
this.w = Math.max(1, this.w)
|
|
596
604
|
this.h = Math.max(1, this.h)
|
|
@@ -144,8 +144,8 @@ describe('Vec.Uni', () => {
|
|
|
144
144
|
expect(Vec.Uni(new Vec(10, 10))).toMatchObject(new Vec(0.7071067811865475, 0.7071067811865475))
|
|
145
145
|
})
|
|
146
146
|
|
|
147
|
-
it('Divide-by-zero spits out
|
|
148
|
-
expect(Vec.Uni(new Vec(0, 0))).toMatchObject(new Vec(
|
|
147
|
+
it('Divide-by-zero spits out 0', () => {
|
|
148
|
+
expect(Vec.Uni(new Vec(0, 0))).toMatchObject(new Vec(0, 0))
|
|
149
149
|
})
|
|
150
150
|
})
|
|
151
151
|
|
|
@@ -189,11 +189,15 @@ export class Vec {
|
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
uni() {
|
|
192
|
-
|
|
192
|
+
const l = this.len()
|
|
193
|
+
if (l === 0) return this
|
|
194
|
+
this.x /= l
|
|
195
|
+
this.y /= l
|
|
196
|
+
return this
|
|
193
197
|
}
|
|
194
198
|
|
|
195
199
|
tan(V: VecLike): Vec {
|
|
196
|
-
return
|
|
200
|
+
return this.sub(V).uni()
|
|
197
201
|
}
|
|
198
202
|
|
|
199
203
|
dist(V: VecLike): number {
|
|
@@ -236,15 +240,15 @@ export class Vec {
|
|
|
236
240
|
return Vec.EqualsXY(this, x, y)
|
|
237
241
|
}
|
|
238
242
|
|
|
243
|
+
/** @deprecated use `uni` instead */
|
|
239
244
|
norm() {
|
|
240
|
-
|
|
241
|
-
this.x = l === 0 ? 0 : this.x / l
|
|
242
|
-
this.y = l === 0 ? 0 : this.y / l
|
|
243
|
-
return this
|
|
245
|
+
return this.uni()
|
|
244
246
|
}
|
|
245
247
|
|
|
246
248
|
toFixed() {
|
|
247
|
-
|
|
249
|
+
this.x = toFixed(this.x)
|
|
250
|
+
this.y = toFixed(this.y)
|
|
251
|
+
return this
|
|
248
252
|
}
|
|
249
253
|
|
|
250
254
|
toString() {
|
|
@@ -375,7 +379,8 @@ export class Vec {
|
|
|
375
379
|
* Get the unit vector of A.
|
|
376
380
|
*/
|
|
377
381
|
static Uni(A: VecLike) {
|
|
378
|
-
|
|
382
|
+
const l = Vec.Len(A)
|
|
383
|
+
return new Vec(l === 0 ? 0 : A.x / l, l === 0 ? 0 : A.y / l)
|
|
379
384
|
}
|
|
380
385
|
|
|
381
386
|
static Tan(A: VecLike, B: VecLike): Vec {
|
|
@@ -6,16 +6,15 @@ import { getVerticesCountForLength } from './geometry-constants'
|
|
|
6
6
|
|
|
7
7
|
/** @public */
|
|
8
8
|
export class Arc2d extends Geometry2d {
|
|
9
|
-
_center: Vec
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
angleEnd: number
|
|
9
|
+
private _center: Vec
|
|
10
|
+
private _radius: number
|
|
11
|
+
private _start: Vec
|
|
12
|
+
private _end: Vec
|
|
13
|
+
private _largeArcFlag: number
|
|
14
|
+
private _sweepFlag: number
|
|
15
|
+
private _measure: number
|
|
16
|
+
private _angleStart: number
|
|
17
|
+
private _angleEnd: number
|
|
19
18
|
|
|
20
19
|
constructor(
|
|
21
20
|
config: Omit<Geometry2dOptions, 'isFilled' | 'isClosed'> & {
|
|
@@ -31,21 +30,29 @@ export class Arc2d extends Geometry2d {
|
|
|
31
30
|
if (start.equals(end)) throw Error(`Arc must have different start and end points.`)
|
|
32
31
|
|
|
33
32
|
// ensure that the start and end are clockwise
|
|
34
|
-
this.
|
|
35
|
-
this.
|
|
36
|
-
this.
|
|
37
|
-
this.
|
|
33
|
+
this._angleStart = Vec.Angle(center, start)
|
|
34
|
+
this._angleEnd = Vec.Angle(center, end)
|
|
35
|
+
this._radius = Vec.Dist(center, start)
|
|
36
|
+
this._measure = getArcMeasure(this._angleStart, this._angleEnd, sweepFlag, largeArcFlag)
|
|
38
37
|
|
|
39
|
-
this.
|
|
40
|
-
this.
|
|
38
|
+
this._start = start
|
|
39
|
+
this._end = end
|
|
41
40
|
|
|
42
|
-
this.
|
|
43
|
-
this.
|
|
41
|
+
this._sweepFlag = sweepFlag
|
|
42
|
+
this._largeArcFlag = largeArcFlag
|
|
44
43
|
this._center = center
|
|
45
44
|
}
|
|
46
45
|
|
|
47
46
|
nearestPoint(point: VecLike): Vec {
|
|
48
|
-
const {
|
|
47
|
+
const {
|
|
48
|
+
_center,
|
|
49
|
+
_measure: measure,
|
|
50
|
+
_radius: radius,
|
|
51
|
+
_angleEnd: angleEnd,
|
|
52
|
+
_angleStart: angleStart,
|
|
53
|
+
_start: A,
|
|
54
|
+
_end: B,
|
|
55
|
+
} = this
|
|
49
56
|
const t = getPointInArcT(measure, angleStart, angleEnd, _center.angle(point))
|
|
50
57
|
if (t <= 0) return A
|
|
51
58
|
if (t >= 1) return B
|
|
@@ -68,7 +75,13 @@ export class Arc2d extends Geometry2d {
|
|
|
68
75
|
}
|
|
69
76
|
|
|
70
77
|
hitTestLineSegment(A: VecLike, B: VecLike): boolean {
|
|
71
|
-
const {
|
|
78
|
+
const {
|
|
79
|
+
_center,
|
|
80
|
+
_radius: radius,
|
|
81
|
+
_measure: measure,
|
|
82
|
+
_angleStart: angleStart,
|
|
83
|
+
_angleEnd: angleEnd,
|
|
84
|
+
} = this
|
|
72
85
|
const intersection = intersectLineSegmentCircle(A, B, _center, radius)
|
|
73
86
|
if (intersection === null) return false
|
|
74
87
|
|
|
@@ -79,7 +92,7 @@ export class Arc2d extends Geometry2d {
|
|
|
79
92
|
}
|
|
80
93
|
|
|
81
94
|
getVertices(): Vec[] {
|
|
82
|
-
const { _center, measure, length, radius, angleStart } = this
|
|
95
|
+
const { _center, _measure: measure, length, _radius: radius, _angleStart: angleStart } = this
|
|
83
96
|
const vertices: Vec[] = []
|
|
84
97
|
for (let i = 0, n = getVerticesCountForLength(Math.abs(length)); i < n + 1; i++) {
|
|
85
98
|
const t = (i / n) * measure
|
|
@@ -90,11 +103,17 @@ export class Arc2d extends Geometry2d {
|
|
|
90
103
|
}
|
|
91
104
|
|
|
92
105
|
getSvgPathData(first = true) {
|
|
93
|
-
const {
|
|
106
|
+
const {
|
|
107
|
+
_start: start,
|
|
108
|
+
_end: end,
|
|
109
|
+
_radius: radius,
|
|
110
|
+
_largeArcFlag: largeArcFlag,
|
|
111
|
+
_sweepFlag: sweepFlag,
|
|
112
|
+
} = this
|
|
94
113
|
return `${first ? `M${start.toFixed()}` : ``} A${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${end.toFixed()}`
|
|
95
114
|
}
|
|
96
115
|
|
|
97
116
|
override getLength() {
|
|
98
|
-
return Math.abs(this.
|
|
117
|
+
return Math.abs(this._measure * this._radius)
|
|
99
118
|
}
|
|
100
119
|
}
|
|
@@ -7,10 +7,10 @@ import { getVerticesCountForLength } from './geometry-constants'
|
|
|
7
7
|
|
|
8
8
|
/** @public */
|
|
9
9
|
export class Circle2d extends Geometry2d {
|
|
10
|
-
_center: Vec
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
private _center: Vec
|
|
11
|
+
private _radius: number
|
|
12
|
+
private _x: number
|
|
13
|
+
private _y: number
|
|
14
14
|
|
|
15
15
|
constructor(
|
|
16
16
|
public config: Omit<Geometry2dOptions, 'isClosed'> & {
|
|
@@ -22,18 +22,18 @@ export class Circle2d extends Geometry2d {
|
|
|
22
22
|
) {
|
|
23
23
|
super({ isClosed: true, ...config })
|
|
24
24
|
const { x = 0, y = 0, radius } = config
|
|
25
|
-
this.
|
|
26
|
-
this.
|
|
25
|
+
this._x = x
|
|
26
|
+
this._y = y
|
|
27
27
|
this._center = new Vec(radius + x, radius + y)
|
|
28
|
-
this.
|
|
28
|
+
this._radius = radius
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
getBounds() {
|
|
32
|
-
return new Box(this.
|
|
32
|
+
return new Box(this._x, this._y, this._radius * 2, this._radius * 2)
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
getVertices(): Vec[] {
|
|
36
|
-
const { _center, radius } = this
|
|
36
|
+
const { _center, _radius: radius } = this
|
|
37
37
|
const perimeter = PI2 * radius
|
|
38
38
|
const vertices: Vec[] = []
|
|
39
39
|
for (let i = 0, n = getVerticesCountForLength(perimeter); i < n; i++) {
|
|
@@ -44,18 +44,18 @@ export class Circle2d extends Geometry2d {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
nearestPoint(point: VecLike): Vec {
|
|
47
|
-
const { _center, radius } = this
|
|
47
|
+
const { _center, _radius: radius } = this
|
|
48
48
|
if (_center.equals(point)) return Vec.AddXY(_center, radius, 0)
|
|
49
49
|
return Vec.Sub(point, _center).uni().mul(radius).add(_center)
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
hitTestLineSegment(A: VecLike, B: VecLike, distance = 0): boolean {
|
|
53
|
-
const { _center, radius } = this
|
|
53
|
+
const { _center, _radius: radius } = this
|
|
54
54
|
return intersectLineSegmentCircle(A, B, _center, radius + distance) !== null
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
getSvgPathData(): string {
|
|
58
|
-
const { _center, radius } = this
|
|
58
|
+
const { _center, _radius: radius } = this
|
|
59
59
|
return `M${_center.x + radius},${_center.y} a${radius},${radius} 0 1,0 ${radius * 2},0a${radius},${radius} 0 1,0 -${radius * 2},0`
|
|
60
60
|
}
|
|
61
61
|
}
|
|
@@ -4,10 +4,10 @@ import { Polyline2d } from './Polyline2d'
|
|
|
4
4
|
|
|
5
5
|
/** @public */
|
|
6
6
|
export class CubicBezier2d extends Polyline2d {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
private _a: Vec
|
|
8
|
+
private _b: Vec
|
|
9
|
+
private _c: Vec
|
|
10
|
+
private _d: Vec
|
|
11
11
|
|
|
12
12
|
constructor(
|
|
13
13
|
config: Omit<Geometry2dOptions, 'isFilled' | 'isClosed'> & {
|
|
@@ -20,15 +20,15 @@ export class CubicBezier2d extends Polyline2d {
|
|
|
20
20
|
const { start: a, cp1: b, cp2: c, end: d } = config
|
|
21
21
|
super({ ...config, points: [a, d] })
|
|
22
22
|
|
|
23
|
-
this.
|
|
24
|
-
this.
|
|
25
|
-
this.
|
|
26
|
-
this.
|
|
23
|
+
this._a = a
|
|
24
|
+
this._b = b
|
|
25
|
+
this._c = c
|
|
26
|
+
this._d = d
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
override getVertices() {
|
|
30
30
|
const vertices = [] as Vec[]
|
|
31
|
-
const { a, b, c, d } = this
|
|
31
|
+
const { _a: a, _b: b, _c: c, _d: d } = this
|
|
32
32
|
// we'll always use ten vertices for each bezier curve
|
|
33
33
|
for (let i = 0, n = 10; i <= n; i++) {
|
|
34
34
|
const t = i / n
|
|
@@ -48,10 +48,6 @@ export class CubicBezier2d extends Polyline2d {
|
|
|
48
48
|
return vertices
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
midPoint() {
|
|
52
|
-
return CubicBezier2d.GetAtT(this, 0.5)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
51
|
nearestPoint(A: VecLike): Vec {
|
|
56
52
|
let nearest: Vec | undefined
|
|
57
53
|
let dist = Infinity
|
|
@@ -71,12 +67,12 @@ export class CubicBezier2d extends Polyline2d {
|
|
|
71
67
|
}
|
|
72
68
|
|
|
73
69
|
getSvgPathData(first = true) {
|
|
74
|
-
const { a, b, c, d } = this
|
|
70
|
+
const { _a: a, _b: b, _c: c, _d: d } = this
|
|
75
71
|
return `${first ? `M ${a.toFixed()} ` : ``} C${b.toFixed()} ${c.toFixed()} ${d.toFixed()}`
|
|
76
72
|
}
|
|
77
73
|
|
|
78
74
|
static GetAtT(segment: CubicBezier2d, t: number) {
|
|
79
|
-
const { a, b, c, d } = segment
|
|
75
|
+
const { _a: a, _b: b, _c: c, _d: d } = segment
|
|
80
76
|
return new Vec(
|
|
81
77
|
(1 - t) * (1 - t) * (1 - t) * a.x +
|
|
82
78
|
3 * ((1 - t) * (1 - t)) * t * b.x +
|
|
@@ -89,9 +85,9 @@ export class CubicBezier2d extends Polyline2d {
|
|
|
89
85
|
)
|
|
90
86
|
}
|
|
91
87
|
|
|
92
|
-
override getLength(
|
|
88
|
+
override getLength(_filters?: Geometry2dFilters, precision = 32) {
|
|
93
89
|
let n1: Vec,
|
|
94
|
-
p1 = this.
|
|
90
|
+
p1 = this._a,
|
|
95
91
|
length = 0
|
|
96
92
|
for (let i = 1; i <= precision; i++) {
|
|
97
93
|
n1 = CubicBezier2d.GetAtT(this, i / precision)
|
|
@@ -4,22 +4,22 @@ import { Geometry2d, Geometry2dOptions } from './Geometry2d'
|
|
|
4
4
|
|
|
5
5
|
/** @public */
|
|
6
6
|
export class CubicSpline2d extends Geometry2d {
|
|
7
|
-
|
|
7
|
+
private _points: Vec[]
|
|
8
8
|
|
|
9
9
|
constructor(config: Omit<Geometry2dOptions, 'isClosed' | 'isFilled'> & { points: Vec[] }) {
|
|
10
10
|
super({ ...config, isClosed: false, isFilled: false })
|
|
11
11
|
const { points } = config
|
|
12
12
|
|
|
13
|
-
this.
|
|
13
|
+
this._points = points
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
_segments?: CubicBezier2d[]
|
|
16
|
+
private _segments?: CubicBezier2d[]
|
|
17
17
|
|
|
18
18
|
// eslint-disable-next-line no-restricted-syntax
|
|
19
19
|
get segments() {
|
|
20
20
|
if (!this._segments) {
|
|
21
21
|
this._segments = []
|
|
22
|
-
const { points } = this
|
|
22
|
+
const { _points: points } = this
|
|
23
23
|
|
|
24
24
|
const len = points.length
|
|
25
25
|
const last = len - 2
|
|
@@ -54,7 +54,7 @@ export class CubicSpline2d extends Geometry2d {
|
|
|
54
54
|
const vertices = this.segments.reduce((acc, segment) => {
|
|
55
55
|
return acc.concat(segment.vertices)
|
|
56
56
|
}, [] as Vec[])
|
|
57
|
-
vertices.push(this.
|
|
57
|
+
vertices.push(this._points[this._points.length - 1])
|
|
58
58
|
return vertices
|
|
59
59
|
}
|
|
60
60
|
|
|
@@ -3,38 +3,34 @@ import { Geometry2d } from './Geometry2d'
|
|
|
3
3
|
|
|
4
4
|
/** @public */
|
|
5
5
|
export class Edge2d extends Geometry2d {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
private _start: Vec
|
|
7
|
+
private _end: Vec
|
|
8
|
+
private _d: Vec
|
|
9
|
+
private _u: Vec
|
|
10
|
+
private _ul: number
|
|
11
11
|
|
|
12
12
|
constructor(config: { start: Vec; end: Vec }) {
|
|
13
13
|
super({ ...config, isClosed: false, isFilled: false })
|
|
14
14
|
const { start, end } = config
|
|
15
15
|
|
|
16
|
-
this.
|
|
17
|
-
this.
|
|
16
|
+
this._start = start
|
|
17
|
+
this._end = end
|
|
18
18
|
|
|
19
|
-
this.
|
|
20
|
-
this.
|
|
21
|
-
this.
|
|
19
|
+
this._d = start.clone().sub(end) // the delta from start to end
|
|
20
|
+
this._u = this._d.clone().uni() // the unit vector of the edge
|
|
21
|
+
this._ul = this._u.len() // the length of the unit vector
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
override getLength() {
|
|
25
|
-
return this.
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
midPoint(): Vec {
|
|
29
|
-
return this.start.lrp(this.end, 0.5)
|
|
25
|
+
return this._d.len()
|
|
30
26
|
}
|
|
31
27
|
|
|
32
28
|
override getVertices(): Vec[] {
|
|
33
|
-
return [this.
|
|
29
|
+
return [this._start, this._end]
|
|
34
30
|
}
|
|
35
31
|
|
|
36
32
|
override nearestPoint(point: VecLike): Vec {
|
|
37
|
-
const { start, end, d, u,
|
|
33
|
+
const { _start: start, _end: end, _d: d, _u: u, _ul: l } = this
|
|
38
34
|
if (d.len() === 0) return start // start and end are the same
|
|
39
35
|
if (l === 0) return start // no length in the unit vector
|
|
40
36
|
const k = Vec.Sub(point, start).dpr(u) / l
|
|
@@ -48,7 +44,7 @@ export class Edge2d extends Geometry2d {
|
|
|
48
44
|
}
|
|
49
45
|
|
|
50
46
|
getSvgPathData(first = true) {
|
|
51
|
-
const { start, end } = this
|
|
47
|
+
const { _start: start, _end: end } = this
|
|
52
48
|
return `${first ? `M${start.toFixed()}` : ``} L${end.toFixed()}`
|
|
53
49
|
}
|
|
54
50
|
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { Box } from '../Box'
|
|
2
2
|
import { Vec, VecLike } from '../Vec'
|
|
3
|
-
import { PI, PI2, perimeterOfEllipse } from '../utils'
|
|
3
|
+
import { PI, PI2, clamp, perimeterOfEllipse } from '../utils'
|
|
4
4
|
import { Edge2d } from './Edge2d'
|
|
5
5
|
import { Geometry2d, Geometry2dOptions } from './Geometry2d'
|
|
6
6
|
import { getVerticesCountForLength } from './geometry-constants'
|
|
7
7
|
|
|
8
8
|
/** @public */
|
|
9
9
|
export class Ellipse2d extends Geometry2d {
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
private _w: number
|
|
11
|
+
private _h: number
|
|
12
|
+
private _edges?: Edge2d[]
|
|
12
13
|
|
|
13
14
|
constructor(
|
|
14
15
|
public config: Omit<Geometry2dOptions, 'isClosed'> & {
|
|
@@ -18,12 +19,10 @@ export class Ellipse2d extends Geometry2d {
|
|
|
18
19
|
) {
|
|
19
20
|
super({ ...config, isClosed: true })
|
|
20
21
|
const { width, height } = config
|
|
21
|
-
this.
|
|
22
|
-
this.
|
|
22
|
+
this._w = width
|
|
23
|
+
this._h = height
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
_edges?: Edge2d[]
|
|
26
|
-
|
|
27
26
|
// eslint-disable-next-line no-restricted-syntax
|
|
28
27
|
get edges() {
|
|
29
28
|
if (!this._edges) {
|
|
@@ -41,8 +40,8 @@ export class Ellipse2d extends Geometry2d {
|
|
|
41
40
|
|
|
42
41
|
getVertices() {
|
|
43
42
|
// Perimeter of the ellipse
|
|
44
|
-
const w = Math.max(1, this.
|
|
45
|
-
const h = Math.max(1, this.
|
|
43
|
+
const w = Math.max(1, this._w)
|
|
44
|
+
const h = Math.max(1, this._h)
|
|
46
45
|
const cx = w / 2
|
|
47
46
|
const cy = h / 2
|
|
48
47
|
const q = Math.pow(cx - cy, 2) / Math.pow(cx + cy, 2)
|
|
@@ -63,7 +62,7 @@ export class Ellipse2d extends Geometry2d {
|
|
|
63
62
|
const vertices = Array(len)
|
|
64
63
|
|
|
65
64
|
for (let i = 0; i < len; i++) {
|
|
66
|
-
vertices[i] = new Vec(cx + cx * cos, cy + cy * sin)
|
|
65
|
+
vertices[i] = new Vec(clamp(cx + cx * cos, 0, w), clamp(cy + cy * sin, 0, h))
|
|
67
66
|
ts = b * cos + a * sin
|
|
68
67
|
tc = a * cos - b * sin
|
|
69
68
|
sin = ts
|
|
@@ -95,11 +94,11 @@ export class Ellipse2d extends Geometry2d {
|
|
|
95
94
|
}
|
|
96
95
|
|
|
97
96
|
getBounds() {
|
|
98
|
-
return new Box(0, 0, this.
|
|
97
|
+
return new Box(0, 0, this._w, this._h)
|
|
99
98
|
}
|
|
100
99
|
|
|
101
100
|
getLength(): number {
|
|
102
|
-
const { w, h } = this
|
|
101
|
+
const { _w: w, _h: h } = this
|
|
103
102
|
const cx = w / 2
|
|
104
103
|
const cy = h / 2
|
|
105
104
|
const rx = Math.max(0, cx)
|
|
@@ -108,7 +107,7 @@ export class Ellipse2d extends Geometry2d {
|
|
|
108
107
|
}
|
|
109
108
|
|
|
110
109
|
getSvgPathData(first = false) {
|
|
111
|
-
const { w, h } = this
|
|
110
|
+
const { _w: w, _h: h } = this
|
|
112
111
|
const cx = w / 2
|
|
113
112
|
const cy = h / 2
|
|
114
113
|
const rx = Math.max(0, cx)
|
|
@@ -3,7 +3,7 @@ import { Geometry2d, Geometry2dOptions } from './Geometry2d'
|
|
|
3
3
|
|
|
4
4
|
/** @public */
|
|
5
5
|
export class Point2d extends Geometry2d {
|
|
6
|
-
|
|
6
|
+
private _point: Vec
|
|
7
7
|
|
|
8
8
|
constructor(
|
|
9
9
|
config: Omit<Geometry2dOptions, 'isClosed' | 'isFilled'> & { margin: number; point: Vec }
|
|
@@ -11,23 +11,23 @@ export class Point2d extends Geometry2d {
|
|
|
11
11
|
super({ ...config, isClosed: true, isFilled: true })
|
|
12
12
|
const { point } = config
|
|
13
13
|
|
|
14
|
-
this.
|
|
14
|
+
this._point = point
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
getVertices() {
|
|
18
|
-
return [this.
|
|
18
|
+
return [this._point]
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
nearestPoint(): Vec {
|
|
22
|
-
return this.
|
|
22
|
+
return this._point
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
hitTestLineSegment(A: VecLike, B: VecLike, margin: number): boolean {
|
|
26
|
-
return Vec.DistanceToLineSegment(A, B, this.
|
|
26
|
+
return Vec.DistanceToLineSegment(A, B, this._point) < margin
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
getSvgPathData() {
|
|
30
|
-
const { point } = this
|
|
30
|
+
const { _point: point } = this
|
|
31
31
|
return `M${point.toFixed()}`
|
|
32
32
|
}
|
|
33
33
|
}
|
|
@@ -7,5 +7,9 @@ export class Polygon2d extends Polyline2d {
|
|
|
7
7
|
constructor(config: Omit<Geometry2dOptions, 'isClosed'> & { points: Vec[] }) {
|
|
8
8
|
super({ ...config })
|
|
9
9
|
this.isClosed = true
|
|
10
|
+
|
|
11
|
+
if (config.points.length < 3) {
|
|
12
|
+
throw new Error('Polygon2d: points must be an array of at least 3 points')
|
|
13
|
+
}
|
|
10
14
|
}
|
|
11
15
|
}
|