@tldraw/editor 3.14.0-canary.e0ab6f4c80f9 → 3.14.0-canary.f6a0206007b3
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 +64 -49
- package/dist-cjs/index.js +3 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +2 -2
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/derivations/notVisibleShapes.js +16 -20
- package/dist-cjs/lib/editor/derivations/notVisibleShapes.js.map +3 -3
- package/dist-cjs/lib/editor/managers/FocusManager.js +2 -0
- package/dist-cjs/lib/editor/managers/FocusManager.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js +8 -0
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js +6 -0
- package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js.map +2 -2
- package/dist-cjs/lib/primitives/Box.js +6 -0
- package/dist-cjs/lib/primitives/Box.js.map +2 -2
- package/dist-cjs/lib/primitives/Vec.js +18 -13
- package/dist-cjs/lib/primitives/Vec.js.map +3 -3
- 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 -21
- 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/Geometry2d.js +5 -0
- package/dist-cjs/lib/primitives/geometry/Geometry2d.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 +64 -49
- package/dist-esm/index.mjs +6 -2
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +2 -2
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs +16 -20
- package/dist-esm/lib/editor/derivations/notVisibleShapes.mjs.map +3 -3
- package/dist-esm/lib/editor/managers/FocusManager.mjs +2 -0
- package/dist-esm/lib/editor/managers/FocusManager.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs +8 -0
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs +6 -0
- package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs.map +2 -2
- package/dist-esm/lib/primitives/Box.mjs +6 -0
- package/dist-esm/lib/primitives/Box.mjs.map +2 -2
- package/dist-esm/lib/primitives/Vec.mjs +19 -14
- package/dist-esm/lib/primitives/Vec.mjs.map +3 -3
- 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 -21
- 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/Geometry2d.mjs +7 -1
- package/dist-esm/lib/primitives/geometry/Geometry2d.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 +5 -1
- package/src/lib/editor/Editor.ts +3 -2
- package/src/lib/editor/derivations/notVisibleShapes.ts +24 -25
- package/src/lib/editor/managers/FocusManager.ts +2 -0
- package/src/lib/editor/shapes/ShapeUtil.ts +9 -0
- package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +8 -0
- package/src/lib/editor/shapes/shared/getPerfectDashProps.ts +5 -2
- package/src/lib/primitives/Box.ts +8 -0
- package/src/lib/primitives/Vec.test.ts +2 -2
- package/src/lib/primitives/Vec.ts +15 -10
- 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 -25
- package/src/lib/primitives/geometry/Ellipse2d.ts +12 -13
- package/src/lib/primitives/geometry/Geometry2d.ts +6 -0
- 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
package/dist-esm/version.mjs.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/version.ts"],
|
|
4
|
-
"sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '3.14.0-canary.
|
|
4
|
+
"sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '3.14.0-canary.f6a0206007b3'\nexport const publishDates = {\n\tmajor: '2024-09-13T14:36:29.063Z',\n\tminor: '2025-06-03T09:18:36.909Z',\n\tpatch: '2025-06-03T09:18:36.909Z',\n}\n"],
|
|
5
5
|
"mappings": "AAGO,MAAM,UAAU;AAChB,MAAM,eAAe;AAAA,EAC3B,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACR;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tldraw/editor",
|
|
3
3
|
"description": "A tiny little drawing app (editor).",
|
|
4
|
-
"version": "3.14.0-canary.
|
|
4
|
+
"version": "3.14.0-canary.f6a0206007b3",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "tldraw Inc.",
|
|
7
7
|
"email": "hello@tldraw.com"
|
|
@@ -48,12 +48,12 @@
|
|
|
48
48
|
"@tiptap/core": "^2.9.1",
|
|
49
49
|
"@tiptap/pm": "^2.9.1",
|
|
50
50
|
"@tiptap/react": "^2.9.1",
|
|
51
|
-
"@tldraw/state": "3.14.0-canary.
|
|
52
|
-
"@tldraw/state-react": "3.14.0-canary.
|
|
53
|
-
"@tldraw/store": "3.14.0-canary.
|
|
54
|
-
"@tldraw/tlschema": "3.14.0-canary.
|
|
55
|
-
"@tldraw/utils": "3.14.0-canary.
|
|
56
|
-
"@tldraw/validate": "3.14.0-canary.
|
|
51
|
+
"@tldraw/state": "3.14.0-canary.f6a0206007b3",
|
|
52
|
+
"@tldraw/state-react": "3.14.0-canary.f6a0206007b3",
|
|
53
|
+
"@tldraw/store": "3.14.0-canary.f6a0206007b3",
|
|
54
|
+
"@tldraw/tlschema": "3.14.0-canary.f6a0206007b3",
|
|
55
|
+
"@tldraw/utils": "3.14.0-canary.f6a0206007b3",
|
|
56
|
+
"@tldraw/validate": "3.14.0-canary.f6a0206007b3",
|
|
57
57
|
"@types/core-js": "^2.5.8",
|
|
58
58
|
"@use-gesture/react": "^10.3.1",
|
|
59
59
|
"classnames": "^2.5.1",
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ import 'core-js/stable/array/flat-map.js'
|
|
|
4
4
|
import 'core-js/stable/array/flat.js'
|
|
5
5
|
import 'core-js/stable/string/at.js'
|
|
6
6
|
import 'core-js/stable/string/replace-all.js'
|
|
7
|
+
export { areShapesContentEqual } from './lib/utils/areShapesContentEqual'
|
|
7
8
|
|
|
8
9
|
// eslint-disable-next-line local/no-export-star
|
|
9
10
|
export * from '@tldraw/state'
|
|
@@ -185,7 +186,10 @@ export {
|
|
|
185
186
|
type TLShapeUtilConstructor,
|
|
186
187
|
} from './lib/editor/shapes/ShapeUtil'
|
|
187
188
|
export { GroupShapeUtil } from './lib/editor/shapes/group/GroupShapeUtil'
|
|
188
|
-
export {
|
|
189
|
+
export {
|
|
190
|
+
getPerfectDashProps,
|
|
191
|
+
type PerfectDashTerminal,
|
|
192
|
+
} from './lib/editor/shapes/shared/getPerfectDashProps'
|
|
189
193
|
export { resizeBox, type ResizeBoxOptions } from './lib/editor/shapes/shared/resizeBox'
|
|
190
194
|
export { resizeScaled } from './lib/editor/shapes/shared/resizeScaled'
|
|
191
195
|
export { BaseBoxShapeTool } from './lib/editor/tools/BaseBoxShapeTool/BaseBoxShapeTool'
|
package/src/lib/editor/Editor.ts
CHANGED
|
@@ -506,6 +506,8 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
506
506
|
shape: {
|
|
507
507
|
afterChange: (shapeBefore, shapeAfter) => {
|
|
508
508
|
for (const binding of this.getBindingsInvolvingShape(shapeAfter)) {
|
|
509
|
+
if (areShapesContentEqual(shapeBefore, shapeAfter)) continue
|
|
510
|
+
|
|
509
511
|
invalidBindingTypes.add(binding.type)
|
|
510
512
|
if (binding.fromId === shapeAfter.id) {
|
|
511
513
|
this.getBindingUtil(binding).onAfterChangeFromShape?.({
|
|
@@ -5796,8 +5798,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
5796
5798
|
parent: TLParentId | TLPage | TLShape,
|
|
5797
5799
|
visitor: (id: TLShapeId) => void | false
|
|
5798
5800
|
): this {
|
|
5799
|
-
const
|
|
5800
|
-
const children = this.getSortedChildIdsForParent(parentId)
|
|
5801
|
+
const children = this.getSortedChildIdsForParent(parent)
|
|
5801
5802
|
for (const id of children) {
|
|
5802
5803
|
if (visitor(id) === false) continue
|
|
5803
5804
|
this.visitDescendants(id, visitor)
|
|
@@ -1,49 +1,48 @@
|
|
|
1
1
|
import { computed, isUninitialized } from '@tldraw/state'
|
|
2
2
|
import { TLShapeId } from '@tldraw/tlschema'
|
|
3
|
-
import { Box } from '../../primitives/Box'
|
|
4
3
|
import { Editor } from '../Editor'
|
|
5
4
|
|
|
6
|
-
function
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
function fromScratch(editor: Editor): Set<TLShapeId> {
|
|
6
|
+
const shapesIds = editor.getCurrentPageShapeIds()
|
|
7
|
+
const viewportPageBounds = editor.getViewportPageBounds()
|
|
8
|
+
const notVisibleShapes = new Set<TLShapeId>()
|
|
9
|
+
shapesIds.forEach((id) => {
|
|
10
|
+
// If the shape is fully outside of the viewport page bounds, add it to the set.
|
|
11
|
+
// We'll ignore masks here, since they're more expensive to compute and the overhead is not worth it.
|
|
12
|
+
const pageBounds = editor.getShapePageBounds(id)
|
|
13
|
+
if (pageBounds === undefined || !viewportPageBounds.includes(pageBounds)) {
|
|
14
|
+
notVisibleShapes.add(id)
|
|
15
|
+
}
|
|
16
|
+
})
|
|
17
|
+
return notVisibleShapes
|
|
13
18
|
}
|
|
14
19
|
|
|
15
20
|
/**
|
|
16
21
|
* Incremental derivation of not visible shapes.
|
|
17
|
-
* Non visible shapes are shapes outside of the viewport page bounds
|
|
22
|
+
* Non visible shapes are shapes outside of the viewport page bounds.
|
|
18
23
|
*
|
|
19
24
|
* @param editor - Instance of the tldraw Editor.
|
|
20
25
|
* @returns Incremental derivation of non visible shapes.
|
|
21
26
|
*/
|
|
22
|
-
export
|
|
23
|
-
function
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
const notVisibleShapes = new Set<TLShapeId>()
|
|
27
|
-
shapes.forEach((id) => {
|
|
28
|
-
if (isShapeNotVisible(editor, id, viewportPageBounds)) {
|
|
29
|
-
notVisibleShapes.add(id)
|
|
30
|
-
}
|
|
31
|
-
})
|
|
32
|
-
return notVisibleShapes
|
|
33
|
-
}
|
|
34
|
-
return computed<Set<TLShapeId>>('notVisibleShapes', (prevValue) => {
|
|
27
|
+
export function notVisibleShapes(editor: Editor) {
|
|
28
|
+
return computed<Set<TLShapeId>>('notVisibleShapes', function updateNotVisibleShapes(prevValue) {
|
|
29
|
+
const nextValue = fromScratch(editor)
|
|
30
|
+
|
|
35
31
|
if (isUninitialized(prevValue)) {
|
|
36
|
-
return
|
|
32
|
+
return nextValue
|
|
37
33
|
}
|
|
38
34
|
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
// If there are more or less shapes, we know there's a change
|
|
41
36
|
if (prevValue.size !== nextValue.size) return nextValue
|
|
37
|
+
|
|
38
|
+
// If any of the old shapes are not in the new set, we know there's a change
|
|
42
39
|
for (const prev of prevValue) {
|
|
43
40
|
if (!nextValue.has(prev)) {
|
|
44
41
|
return nextValue
|
|
45
42
|
}
|
|
46
43
|
}
|
|
44
|
+
|
|
45
|
+
// If we've made it here, we know that the set is the same
|
|
47
46
|
return prevValue
|
|
48
47
|
})
|
|
49
48
|
}
|
|
@@ -58,6 +58,8 @@ export class FocusManager {
|
|
|
58
58
|
|
|
59
59
|
private handleKeyDown(keyEvent: KeyboardEvent) {
|
|
60
60
|
const container = this.editor.getContainer()
|
|
61
|
+
if (this.editor.isIn('select.editing_shape')) return
|
|
62
|
+
if (document.activeElement === container && this.editor.getSelectedShapeIds().length > 0) return
|
|
61
63
|
if (['Tab', 'ArrowUp', 'ArrowDown'].includes(keyEvent.key)) {
|
|
62
64
|
container.classList.remove('tl-container__no-focus-ring')
|
|
63
65
|
}
|
|
@@ -240,6 +240,15 @@ export abstract class ShapeUtil<Shape extends TLUnknownShape = TLUnknownShape> {
|
|
|
240
240
|
return true
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
+
/**
|
|
244
|
+
* When the shape is resized, whether the shape's children should also be resized.
|
|
245
|
+
*
|
|
246
|
+
* @public
|
|
247
|
+
*/
|
|
248
|
+
canResizeChildren(_shape: Shape): boolean {
|
|
249
|
+
return true
|
|
250
|
+
}
|
|
251
|
+
|
|
243
252
|
/**
|
|
244
253
|
* Whether the shape can be edited in read-only mode.
|
|
245
254
|
*
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { TLDefaultDashStyle } from '@tldraw/tlschema'
|
|
2
2
|
|
|
3
|
+
/** @public */
|
|
4
|
+
export type PerfectDashTerminal = 'skip' | 'outset' | 'none'
|
|
5
|
+
|
|
3
6
|
/** @public */
|
|
4
7
|
export function getPerfectDashProps(
|
|
5
8
|
totalLength: number,
|
|
@@ -7,8 +10,8 @@ export function getPerfectDashProps(
|
|
|
7
10
|
opts: {
|
|
8
11
|
style?: TLDefaultDashStyle
|
|
9
12
|
snap?: number
|
|
10
|
-
end?:
|
|
11
|
-
start?:
|
|
13
|
+
end?: PerfectDashTerminal
|
|
14
|
+
start?: PerfectDashTerminal
|
|
12
15
|
lengthRatio?: number
|
|
13
16
|
closed?: boolean
|
|
14
17
|
forceSolid?: boolean
|
|
@@ -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
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { VecModel } from '@tldraw/tlschema'
|
|
2
2
|
import { EASINGS } from './easings'
|
|
3
|
-
import { toFixed } from './utils'
|
|
3
|
+
import { clamp, toFixed } from './utils'
|
|
4
4
|
|
|
5
5
|
/** @public */
|
|
6
6
|
export type VecLike = Vec | VecModel
|
|
@@ -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 {
|
|
@@ -487,7 +492,7 @@ export class Vec {
|
|
|
487
492
|
(Math.pow(A.x, 2) + Math.pow(A.y, 2)) * (Math.pow(B.x, 2) + Math.pow(B.y, 2))
|
|
488
493
|
)
|
|
489
494
|
const sign = A.x * B.y - A.y * B.x < 0 ? -1 : 1
|
|
490
|
-
const angle = sign * Math.acos(p / n)
|
|
495
|
+
const angle = sign * Math.acos(clamp(p / n, -1, 1))
|
|
491
496
|
|
|
492
497
|
return angle
|
|
493
498
|
}
|
|
@@ -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
|
|
|
@@ -1,41 +1,36 @@
|
|
|
1
|
-
import { linesIntersect } from '../intersect'
|
|
2
1
|
import { Vec, VecLike } from '../Vec'
|
|
3
2
|
import { Geometry2d } from './Geometry2d'
|
|
4
3
|
|
|
5
4
|
/** @public */
|
|
6
5
|
export class Edge2d extends Geometry2d {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
private _start: Vec
|
|
7
|
+
private _end: Vec
|
|
8
|
+
private _d: Vec
|
|
9
|
+
private _u: Vec
|
|
10
|
+
private _ul: number
|
|
12
11
|
|
|
13
12
|
constructor(config: { start: Vec; end: Vec }) {
|
|
14
13
|
super({ ...config, isClosed: false, isFilled: false })
|
|
15
14
|
const { start, end } = config
|
|
16
15
|
|
|
17
|
-
this.
|
|
18
|
-
this.
|
|
16
|
+
this._start = start
|
|
17
|
+
this._end = end
|
|
19
18
|
|
|
20
|
-
this.
|
|
21
|
-
this.
|
|
22
|
-
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
|
|
23
22
|
}
|
|
24
23
|
|
|
25
24
|
override getLength() {
|
|
26
|
-
return this.
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
midPoint(): Vec {
|
|
30
|
-
return this.start.lrp(this.end, 0.5)
|
|
25
|
+
return this._d.len()
|
|
31
26
|
}
|
|
32
27
|
|
|
33
28
|
override getVertices(): Vec[] {
|
|
34
|
-
return [this.
|
|
29
|
+
return [this._start, this._end]
|
|
35
30
|
}
|
|
36
31
|
|
|
37
32
|
override nearestPoint(point: VecLike): Vec {
|
|
38
|
-
const { start, end, d, u,
|
|
33
|
+
const { _start: start, _end: end, _d: d, _u: u, _ul: l } = this
|
|
39
34
|
if (d.len() === 0) return start // start and end are the same
|
|
40
35
|
if (l === 0) return start // no length in the unit vector
|
|
41
36
|
const k = Vec.Sub(point, start).dpr(u) / l
|
|
@@ -48,14 +43,8 @@ export class Edge2d extends Geometry2d {
|
|
|
48
43
|
return new Vec(cx, cy)
|
|
49
44
|
}
|
|
50
45
|
|
|
51
|
-
override hitTestLineSegment(A: VecLike, B: VecLike, distance = 0): boolean {
|
|
52
|
-
return (
|
|
53
|
-
linesIntersect(A, B, this.start, this.end) || this.distanceToLineSegment(A, B) <= distance
|
|
54
|
-
)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
46
|
getSvgPathData(first = true) {
|
|
58
|
-
const { start, end } = this
|
|
47
|
+
const { _start: start, _end: end } = this
|
|
59
48
|
return `${first ? `M${start.toFixed()}` : ``} L${end.toFixed()}`
|
|
60
49
|
}
|
|
61
50
|
}
|