@tldraw/editor 3.12.0-canary.3e2ed74b5e86 → 3.12.0-canary.423f9b4f2a86
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 +12 -78
- package/dist-cjs/index.js +1 -3
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/components/GeometryDebuggingView.js +2 -2
- package/dist-cjs/lib/components/GeometryDebuggingView.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +8 -39
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js +13 -1
- package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Geometry2d.js +16 -133
- package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +3 -3
- package/dist-cjs/lib/primitives/geometry/Group2d.js +11 -54
- package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
- package/dist-cjs/lib/primitives/intersect.js +0 -20
- package/dist-cjs/lib/primitives/intersect.js.map +2 -2
- package/dist-cjs/lib/utils/reorderShapes.js +8 -2
- package/dist-cjs/lib/utils/reorderShapes.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 +12 -78
- package/dist-esm/index.mjs +2 -8
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/components/GeometryDebuggingView.mjs +3 -3
- package/dist-esm/lib/components/GeometryDebuggingView.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +8 -39
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs +13 -1
- package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +14 -137
- package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Group2d.mjs +12 -55
- package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/intersect.mjs +0 -20
- package/dist-esm/lib/primitives/intersect.mjs.map +2 -2
- package/dist-esm/lib/utils/reorderShapes.mjs +8 -2
- package/dist-esm/lib/utils/reorderShapes.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 -6
- package/src/lib/components/GeometryDebuggingView.tsx +3 -3
- package/src/lib/editor/Editor.ts +15 -44
- package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +15 -3
- package/src/lib/primitives/geometry/Geometry2d.ts +16 -196
- package/src/lib/primitives/geometry/Group2d.ts +13 -76
- package/src/lib/primitives/intersect.ts +0 -41
- package/src/lib/utils/reorderShapes.ts +9 -2
- package/src/version.ts +3 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/utils/reorderShapes.ts"],
|
|
4
|
-
"sourcesContent": ["import { TLParentId, TLShape, TLShapeId, TLShapePartial } from '@tldraw/tlschema'\nimport { IndexKey, compact, getIndicesBetween, sortByIndex } from '@tldraw/utils'\nimport { Editor } from '../editor/Editor'\nimport { Vec } from '../primitives/Vec'\nimport { polygonsIntersect } from '../primitives/intersect'\n\nexport function getReorderingShapesChanges(\n\teditor: Editor,\n\toperation: 'toBack' | 'toFront' | 'forward' | 'backward',\n\tids: TLShapeId[],\n\topts?: { considerAllShapes?: boolean }\n) {\n\tif (ids.length === 0) return []\n\n\t// From the ids that are moving, collect the parents, their children, and which of those children are moving\n\tconst parents = new Map<TLParentId, { moving: Set<TLShape>; children: TLShape[] }>()\n\n\tfor (const shape of compact(ids.map((id) => editor.getShape(id)))) {\n\t\tconst { parentId } = shape\n\t\tif (!parents.has(parentId)) {\n\t\t\tparents.set(parentId, {\n\t\t\t\tchildren: compact(\n\t\t\t\t\teditor.getSortedChildIdsForParent(parentId).map((id) => editor.getShape(id))\n\t\t\t\t),\n\t\t\t\tmoving: new Set(),\n\t\t\t})\n\t\t}\n\t\tparents.get(parentId)!.moving.add(shape)\n\t}\n\n\tconst changes: TLShapePartial[] = []\n\n\tswitch (operation) {\n\t\tcase 'toBack': {\n\t\t\tparents.forEach(({ moving, children }) => reorderToBack(moving, children, changes))\n\t\t\tbreak\n\t\t}\n\t\tcase 'toFront': {\n\t\t\tparents.forEach(({ moving, children }) => reorderToFront(moving, children, changes))\n\t\t\tbreak\n\t\t}\n\t\tcase 'forward': {\n\t\t\tparents.forEach(({ moving, children }) =>\n\t\t\t\treorderForward(editor, moving, children, changes, opts)\n\t\t\t)\n\t\t\tbreak\n\t\t}\n\t\tcase 'backward': {\n\t\t\tparents.forEach(({ moving, children }) =>\n\t\t\t\treorderBackward(editor, moving, children, changes, opts)\n\t\t\t)\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn changes\n}\n\n/**\n * Reorders the moving shapes to the back of the parent's children.\n *\n * @param moving The set of shapes that are moving\n * @param children The parent's children\n * @param changes The changes array to push changes to\n */\nfunction reorderToBack(moving: Set<TLShape>, children: TLShape[], changes: TLShapePartial[]) {\n\tconst len = children.length\n\n\t// If all of the children are moving, there's nothing to do\n\tif (moving.size === len) return\n\n\tlet below: IndexKey | undefined\n\tlet above: IndexKey | undefined\n\n\t// Starting at the bottom of this parent's children...\n\tfor (let i = 0; i < len; i++) {\n\t\tconst shape = children[i]\n\n\t\tif (moving.has(shape)) {\n\t\t\t// If we've found a moving shape before we've found a non-moving shape,\n\t\t\t// then that shape is already at the back; we can remove it from the\n\t\t\t// moving set and mark it as the shape that will be below the moved shapes.\n\t\t\tbelow = shape.index\n\t\t\tmoving.delete(shape)\n\t\t} else {\n\t\t\t// The first non-moving shape we find will be above our moved shapes; we'll\n\t\t\t// put our moving shapes between it and the shape marked as below (if any).\n\t\t\tabove = shape.index\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif (moving.size === 0) {\n\t\t// If our moving set is empty, there's nothing to do; all of our shapes were\n\t\t// already at the back of the parent's children.\n\t\treturn\n\t} else {\n\t\t// Sort the moving shapes by their current index, then apply the new indices\n\t\tconst indices = getIndicesBetween(below, above, moving.size)\n\t\tchanges.push(\n\t\t\t...Array.from(moving.values())\n\t\t\t\t.sort(sortByIndex)\n\t\t\t\t.map((shape, i) => ({ ...shape, index: indices[i] }))\n\t\t)\n\t}\n}\n\n/**\n * Reorders the moving shapes to the front of the parent's children.\n *\n * @param moving The set of shapes that are moving\n * @param children The parent's children\n * @param changes The changes array to push changes to\n */\nfunction reorderToFront(moving: Set<TLShape>, children: TLShape[], changes: TLShapePartial[]) {\n\tconst len = children.length\n\n\t// If all of the children are moving, there's nothing to do\n\tif (moving.size === len) return\n\n\tlet below: IndexKey | undefined\n\tlet above: IndexKey | undefined\n\n\t// Starting at the top of this parent's children...\n\tfor (let i = len - 1; i > -1; i--) {\n\t\tconst shape = children[i]\n\n\t\tif (moving.has(shape)) {\n\t\t\t// If we've found a moving shape before we've found a non-moving shape,\n\t\t\t// then that shape is already at the front; we can remove it from the\n\t\t\t// moving set and mark it as the shape that will be above the moved shapes.\n\t\t\tabove = shape.index\n\t\t\tmoving.delete(shape)\n\t\t} else {\n\t\t\t// The first non-moving shape we find will be below our moved shapes; we'll\n\t\t\t// put our moving shapes between it and the shape marked as above (if any).\n\t\t\tbelow = shape.index\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif (moving.size === 0) {\n\t\t// If our moving set is empty, there's nothing to do; all of our shapes were\n\t\t// already at the front of the parent's children.\n\t\treturn\n\t} else {\n\t\t// Sort the moving shapes by their current index, then apply the new indices\n\t\tconst indices = getIndicesBetween(below, above, moving.size)\n\t\tchanges.push(\n\t\t\t...Array.from(moving.values())\n\t\t\t\t.sort(sortByIndex)\n\t\t\t\t.map((shape, i) => ({ ...shape, index: indices[i] }))\n\t\t)\n\t}\n}\n\nfunction getOverlapChecker(editor: Editor, moving: Set<TLShape>) {\n\tconst movingVertices = Array.from(moving)\n\t\t.map((shape) => {\n\t\t\tconst vertices = editor
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import { TLParentId, TLShape, TLShapeId, TLShapePartial } from '@tldraw/tlschema'\nimport { IndexKey, compact, getIndicesBetween, sortByIndex } from '@tldraw/utils'\nimport { Editor } from '../editor/Editor'\nimport { Vec } from '../primitives/Vec'\nimport { polygonsIntersect } from '../primitives/intersect'\n\nexport function getReorderingShapesChanges(\n\teditor: Editor,\n\toperation: 'toBack' | 'toFront' | 'forward' | 'backward',\n\tids: TLShapeId[],\n\topts?: { considerAllShapes?: boolean }\n) {\n\tif (ids.length === 0) return []\n\n\t// From the ids that are moving, collect the parents, their children, and which of those children are moving\n\tconst parents = new Map<TLParentId, { moving: Set<TLShape>; children: TLShape[] }>()\n\n\tfor (const shape of compact(ids.map((id) => editor.getShape(id)))) {\n\t\tconst { parentId } = shape\n\t\tif (!parents.has(parentId)) {\n\t\t\tparents.set(parentId, {\n\t\t\t\tchildren: compact(\n\t\t\t\t\teditor.getSortedChildIdsForParent(parentId).map((id) => editor.getShape(id))\n\t\t\t\t),\n\t\t\t\tmoving: new Set(),\n\t\t\t})\n\t\t}\n\t\tparents.get(parentId)!.moving.add(shape)\n\t}\n\n\tconst changes: TLShapePartial[] = []\n\n\tswitch (operation) {\n\t\tcase 'toBack': {\n\t\t\tparents.forEach(({ moving, children }) => reorderToBack(moving, children, changes))\n\t\t\tbreak\n\t\t}\n\t\tcase 'toFront': {\n\t\t\tparents.forEach(({ moving, children }) => reorderToFront(moving, children, changes))\n\t\t\tbreak\n\t\t}\n\t\tcase 'forward': {\n\t\t\tparents.forEach(({ moving, children }) =>\n\t\t\t\treorderForward(editor, moving, children, changes, opts)\n\t\t\t)\n\t\t\tbreak\n\t\t}\n\t\tcase 'backward': {\n\t\t\tparents.forEach(({ moving, children }) =>\n\t\t\t\treorderBackward(editor, moving, children, changes, opts)\n\t\t\t)\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn changes\n}\n\n/**\n * Reorders the moving shapes to the back of the parent's children.\n *\n * @param moving The set of shapes that are moving\n * @param children The parent's children\n * @param changes The changes array to push changes to\n */\nfunction reorderToBack(moving: Set<TLShape>, children: TLShape[], changes: TLShapePartial[]) {\n\tconst len = children.length\n\n\t// If all of the children are moving, there's nothing to do\n\tif (moving.size === len) return\n\n\tlet below: IndexKey | undefined\n\tlet above: IndexKey | undefined\n\n\t// Starting at the bottom of this parent's children...\n\tfor (let i = 0; i < len; i++) {\n\t\tconst shape = children[i]\n\n\t\tif (moving.has(shape)) {\n\t\t\t// If we've found a moving shape before we've found a non-moving shape,\n\t\t\t// then that shape is already at the back; we can remove it from the\n\t\t\t// moving set and mark it as the shape that will be below the moved shapes.\n\t\t\tbelow = shape.index\n\t\t\tmoving.delete(shape)\n\t\t} else {\n\t\t\t// The first non-moving shape we find will be above our moved shapes; we'll\n\t\t\t// put our moving shapes between it and the shape marked as below (if any).\n\t\t\tabove = shape.index\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif (moving.size === 0) {\n\t\t// If our moving set is empty, there's nothing to do; all of our shapes were\n\t\t// already at the back of the parent's children.\n\t\treturn\n\t} else {\n\t\t// Sort the moving shapes by their current index, then apply the new indices\n\t\tconst indices = getIndicesBetween(below, above, moving.size)\n\t\tchanges.push(\n\t\t\t...Array.from(moving.values())\n\t\t\t\t.sort(sortByIndex)\n\t\t\t\t.map((shape, i) => ({ ...shape, index: indices[i] }))\n\t\t)\n\t}\n}\n\n/**\n * Reorders the moving shapes to the front of the parent's children.\n *\n * @param moving The set of shapes that are moving\n * @param children The parent's children\n * @param changes The changes array to push changes to\n */\nfunction reorderToFront(moving: Set<TLShape>, children: TLShape[], changes: TLShapePartial[]) {\n\tconst len = children.length\n\n\t// If all of the children are moving, there's nothing to do\n\tif (moving.size === len) return\n\n\tlet below: IndexKey | undefined\n\tlet above: IndexKey | undefined\n\n\t// Starting at the top of this parent's children...\n\tfor (let i = len - 1; i > -1; i--) {\n\t\tconst shape = children[i]\n\n\t\tif (moving.has(shape)) {\n\t\t\t// If we've found a moving shape before we've found a non-moving shape,\n\t\t\t// then that shape is already at the front; we can remove it from the\n\t\t\t// moving set and mark it as the shape that will be above the moved shapes.\n\t\t\tabove = shape.index\n\t\t\tmoving.delete(shape)\n\t\t} else {\n\t\t\t// The first non-moving shape we find will be below our moved shapes; we'll\n\t\t\t// put our moving shapes between it and the shape marked as above (if any).\n\t\t\tbelow = shape.index\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif (moving.size === 0) {\n\t\t// If our moving set is empty, there's nothing to do; all of our shapes were\n\t\t// already at the front of the parent's children.\n\t\treturn\n\t} else {\n\t\t// Sort the moving shapes by their current index, then apply the new indices\n\t\tconst indices = getIndicesBetween(below, above, moving.size)\n\t\tchanges.push(\n\t\t\t...Array.from(moving.values())\n\t\t\t\t.sort(sortByIndex)\n\t\t\t\t.map((shape, i) => ({ ...shape, index: indices[i] }))\n\t\t)\n\t}\n}\n\nfunction getVerticesInPageSpace(editor: Editor, shape: TLShape) {\n\tconst geo = editor.getShapeGeometry(shape)\n\tconst pageTransform = editor.getShapePageTransform(shape)\n\tif (!geo || !pageTransform) return null\n\treturn pageTransform.applyToPoints(geo.vertices)\n}\n\nfunction getOverlapChecker(editor: Editor, moving: Set<TLShape>) {\n\tconst movingVertices = Array.from(moving)\n\t\t.map((shape) => {\n\t\t\tconst vertices = getVerticesInPageSpace(editor, shape)\n\t\t\tif (!vertices) return null\n\t\t\treturn { shape, vertices }\n\t\t})\n\t\t.filter(Boolean) as { shape: TLShape; vertices: Vec[] }[]\n\n\tconst isOverlapping = (child: TLShape) => {\n\t\tconst vertices = getVerticesInPageSpace(editor, child)\n\t\tif (!vertices) return false\n\t\treturn movingVertices.some((other) => {\n\t\t\treturn polygonsIntersect(other.vertices, vertices)\n\t\t})\n\t}\n\n\treturn isOverlapping\n}\n\n/**\n * Reorders the moving shapes forward in the parent's children.\n *\n * @param editor The editor\n * @param moving The set of shapes that are moving\n * @param children The parent's children\n * @param changes The changes array to push changes to\n * @param opts The options\n */\nfunction reorderForward(\n\teditor: Editor,\n\tmoving: Set<TLShape>,\n\tchildren: TLShape[],\n\tchanges: TLShapePartial[],\n\topts?: { considerAllShapes?: boolean }\n) {\n\tconst isOverlapping = getOverlapChecker(editor, moving)\n\n\tconst len = children.length\n\n\t// If all of the children are moving, there's nothing to do\n\tif (moving.size === len) return\n\n\tlet state = { name: 'skipping' } as\n\t\t| { name: 'skipping' }\n\t\t| { name: 'selecting'; selectIndex: number }\n\n\t// Starting at the bottom of this parent's children...\n\tfor (let i = 0; i < len; i++) {\n\t\tconst isMoving = moving.has(children[i])\n\n\t\tswitch (state.name) {\n\t\t\tcase 'skipping': {\n\t\t\t\tif (!isMoving) continue\n\t\t\t\t// If we find a moving shape while skipping, start selecting\n\t\t\t\tstate = { name: 'selecting', selectIndex: i }\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'selecting': {\n\t\t\t\tif (isMoving) continue\n\t\t\t\tif (!opts?.considerAllShapes && !isOverlapping(children[i])) continue\n\t\t\t\t// if we find a non-moving and overlapping shape while selecting, move all selected\n\t\t\t\t// shapes in front of the not moving shape; and start skipping\n\t\t\t\tconst { selectIndex } = state\n\t\t\t\tgetIndicesBetween(children[i].index, children[i + 1]?.index, i - selectIndex).forEach(\n\t\t\t\t\t(index, k) => {\n\t\t\t\t\t\tconst child = children[selectIndex + k]\n\t\t\t\t\t\t// If the shape is not moving (therefore also not overlapping), skip it\n\t\t\t\t\t\tif (!moving.has(child)) return\n\t\t\t\t\t\tchanges.push({ ...child, index })\n\t\t\t\t\t}\n\t\t\t\t)\n\t\t\t\tstate = { name: 'skipping' }\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Reorders the moving shapes backward in the parent's children.\n *\n * @param editor The editor\n * @param moving The set of shapes that are moving\n * @param children The parent's children\n * @param changes The changes array to push changes to\n * @param opts The options\n */\nfunction reorderBackward(\n\teditor: Editor,\n\tmoving: Set<TLShape>,\n\tchildren: TLShape[],\n\tchanges: TLShapePartial[],\n\topts?: { considerAllShapes?: boolean }\n) {\n\tconst isOverlapping = getOverlapChecker(editor, moving)\n\n\tconst len = children.length\n\n\tif (moving.size === len) return\n\n\tlet state = { name: 'skipping' } as\n\t\t| { name: 'skipping' }\n\t\t| { name: 'selecting'; selectIndex: number }\n\n\t// Starting at the top of this parent's children...\n\tfor (let i = len - 1; i > -1; i--) {\n\t\tconst isMoving = moving.has(children[i])\n\n\t\tswitch (state.name) {\n\t\t\tcase 'skipping': {\n\t\t\t\tif (!isMoving) continue\n\t\t\t\t// If we find a moving shape while skipping, start selecting\n\t\t\t\tstate = { name: 'selecting', selectIndex: i }\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'selecting': {\n\t\t\t\tif (isMoving) continue\n\t\t\t\tif (!opts?.considerAllShapes && !isOverlapping(children[i])) continue\n\t\t\t\t// if we find a non-moving and overlapping shape while selecting, move all selected\n\t\t\t\t// shapes in behind of the not moving shape; and start skipping\n\t\t\t\tgetIndicesBetween(children[i - 1]?.index, children[i].index, state.selectIndex - i).forEach(\n\t\t\t\t\t(index, k) => {\n\t\t\t\t\t\tconst child = children[i + k + 1]\n\t\t\t\t\t\t// If the shape is not moving (therefore also not overlapping), skip it\n\t\t\t\t\t\tif (!moving.has(child)) return\n\t\t\t\t\t\tchanges.push({ ...child, index })\n\t\t\t\t\t}\n\t\t\t\t)\n\t\t\t\tstate = { name: 'skipping' }\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist-esm/version.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const version = "3.12.0-canary.
|
|
1
|
+
const version = "3.12.0-canary.423f9b4f2a86";
|
|
2
2
|
const publishDates = {
|
|
3
3
|
major: "2024-09-13T14:36:29.063Z",
|
|
4
|
-
minor: "2025-04-03T13:
|
|
5
|
-
patch: "2025-04-03T13:
|
|
4
|
+
minor: "2025-04-03T13:02:18.145Z",
|
|
5
|
+
patch: "2025-04-03T13:02:18.145Z"
|
|
6
6
|
};
|
|
7
7
|
export {
|
|
8
8
|
publishDates,
|
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.12.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.12.0-canary.423f9b4f2a86'\nexport const publishDates = {\n\tmajor: '2024-09-13T14:36:29.063Z',\n\tminor: '2025-04-03T13:02:18.145Z',\n\tpatch: '2025-04-03T13:02:18.145Z',\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.12.0-canary.
|
|
4
|
+
"version": "3.12.0-canary.423f9b4f2a86",
|
|
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.12.0-canary.
|
|
52
|
-
"@tldraw/state-react": "3.12.0-canary.
|
|
53
|
-
"@tldraw/store": "3.12.0-canary.
|
|
54
|
-
"@tldraw/tlschema": "3.12.0-canary.
|
|
55
|
-
"@tldraw/utils": "3.12.0-canary.
|
|
56
|
-
"@tldraw/validate": "3.12.0-canary.
|
|
51
|
+
"@tldraw/state": "3.12.0-canary.423f9b4f2a86",
|
|
52
|
+
"@tldraw/state-react": "3.12.0-canary.423f9b4f2a86",
|
|
53
|
+
"@tldraw/store": "3.12.0-canary.423f9b4f2a86",
|
|
54
|
+
"@tldraw/tlschema": "3.12.0-canary.423f9b4f2a86",
|
|
55
|
+
"@tldraw/utils": "3.12.0-canary.423f9b4f2a86",
|
|
56
|
+
"@tldraw/validate": "3.12.0-canary.423f9b4f2a86",
|
|
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
|
@@ -360,12 +360,7 @@ export { CubicBezier2d } from './lib/primitives/geometry/CubicBezier2d'
|
|
|
360
360
|
export { CubicSpline2d } from './lib/primitives/geometry/CubicSpline2d'
|
|
361
361
|
export { Edge2d } from './lib/primitives/geometry/Edge2d'
|
|
362
362
|
export { Ellipse2d } from './lib/primitives/geometry/Ellipse2d'
|
|
363
|
-
export {
|
|
364
|
-
Geometry2d,
|
|
365
|
-
Geometry2dFilters,
|
|
366
|
-
TransformedGeometry2d,
|
|
367
|
-
type Geometry2dOptions,
|
|
368
|
-
} from './lib/primitives/geometry/Geometry2d'
|
|
363
|
+
export { Geometry2d, type Geometry2dOptions } from './lib/primitives/geometry/Geometry2d'
|
|
369
364
|
export { Group2d } from './lib/primitives/geometry/Group2d'
|
|
370
365
|
export { Point2d } from './lib/primitives/geometry/Point2d'
|
|
371
366
|
export { Polygon2d } from './lib/primitives/geometry/Polygon2d'
|
|
@@ -114,13 +114,13 @@ export const GeometryDebuggingView = track(function GeometryDebuggingView({
|
|
|
114
114
|
function GeometryStroke({ geometry }: { geometry: Geometry2d }) {
|
|
115
115
|
if (geometry instanceof Group2d) {
|
|
116
116
|
return (
|
|
117
|
-
|
|
117
|
+
<>
|
|
118
118
|
{[...geometry.children, ...geometry.ignoredChildren].map((child, i) => (
|
|
119
119
|
<GeometryStroke geometry={child} key={i} />
|
|
120
120
|
))}
|
|
121
|
-
|
|
121
|
+
</>
|
|
122
122
|
)
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
return <path d={geometry.toSimpleSvgPath()}
|
|
125
|
+
return <path d={geometry.toSimpleSvgPath()} />
|
|
126
126
|
}
|
package/src/lib/editor/Editor.ts
CHANGED
|
@@ -4330,7 +4330,7 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4330
4330
|
private _shapeGeometryCaches: Record<string, ComputedCache<Geometry2d, TLShape>> = {}
|
|
4331
4331
|
|
|
4332
4332
|
/**
|
|
4333
|
-
* Get the geometry of a shape
|
|
4333
|
+
* Get the geometry of a shape.
|
|
4334
4334
|
*
|
|
4335
4335
|
* @example
|
|
4336
4336
|
* ```ts
|
|
@@ -4361,44 +4361,6 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4361
4361
|
)! as T
|
|
4362
4362
|
}
|
|
4363
4363
|
|
|
4364
|
-
private _shapePageGeometryCaches: Record<string, ComputedCache<Geometry2d, TLShape>> = {}
|
|
4365
|
-
|
|
4366
|
-
/**
|
|
4367
|
-
* Get the geometry of a shape in page-space.
|
|
4368
|
-
*
|
|
4369
|
-
* @example
|
|
4370
|
-
* ```ts
|
|
4371
|
-
* editor.getShapePageGeometry(myShape)
|
|
4372
|
-
* editor.getShapePageGeometry(myShapeId)
|
|
4373
|
-
* editor.getShapePageGeometry(myShapeId, { context: "arrow" })
|
|
4374
|
-
* ```
|
|
4375
|
-
*
|
|
4376
|
-
* @param shape - The shape (or shape id) to get the geometry for.
|
|
4377
|
-
* @param opts - Additional options about the request for geometry. Passed to {@link ShapeUtil.getGeometry}.
|
|
4378
|
-
*
|
|
4379
|
-
* @public
|
|
4380
|
-
*/
|
|
4381
|
-
getShapePageGeometry<T extends Geometry2d>(shape: TLShape | TLShapeId, opts?: TLGeometryOpts): T {
|
|
4382
|
-
const context = opts?.context ?? 'none'
|
|
4383
|
-
if (!this._shapePageGeometryCaches[context]) {
|
|
4384
|
-
this._shapePageGeometryCaches[context] = this.store.createComputedCache(
|
|
4385
|
-
'bounds',
|
|
4386
|
-
(shape) => {
|
|
4387
|
-
const geometry = this.getShapeGeometry(shape.id, opts)
|
|
4388
|
-
const pageTransform = this.getShapePageTransform(shape.id)
|
|
4389
|
-
return geometry.transform(pageTransform)
|
|
4390
|
-
},
|
|
4391
|
-
{
|
|
4392
|
-
// we only depend directly on the shape id, and changing geometry/transform will update us anyway
|
|
4393
|
-
areRecordsEqual: () => true,
|
|
4394
|
-
}
|
|
4395
|
-
)
|
|
4396
|
-
}
|
|
4397
|
-
return this._shapePageGeometryCaches[context].get(
|
|
4398
|
-
typeof shape === 'string' ? shape : shape.id
|
|
4399
|
-
)! as T
|
|
4400
|
-
}
|
|
4401
|
-
|
|
4402
4364
|
/** @internal */
|
|
4403
4365
|
@computed private _getShapeHandlesCache(): ComputedCache<TLHandle[] | undefined, TLShape> {
|
|
4404
4366
|
return this.store.createComputedCache('handles', (shape) => {
|
|
@@ -4505,7 +4467,15 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4505
4467
|
/** @internal */
|
|
4506
4468
|
@computed private _getShapePageBoundsCache(): ComputedCache<Box, TLShape> {
|
|
4507
4469
|
return this.store.createComputedCache<Box, TLShape>('pageBoundsCache', (shape) => {
|
|
4508
|
-
|
|
4470
|
+
const pageTransform = this._getShapePageTransformCache().get(shape.id)
|
|
4471
|
+
|
|
4472
|
+
if (!pageTransform) return new Box()
|
|
4473
|
+
|
|
4474
|
+
const result = Box.FromPoints(
|
|
4475
|
+
Mat.applyToPoints(pageTransform, this.getShapeGeometry(shape).vertices)
|
|
4476
|
+
)
|
|
4477
|
+
|
|
4478
|
+
return result
|
|
4509
4479
|
})
|
|
4510
4480
|
}
|
|
4511
4481
|
|
|
@@ -4579,10 +4549,11 @@ export class Editor extends EventEmitter<TLEventMap> {
|
|
|
4579
4549
|
if (frameAncestors.length === 0) return undefined
|
|
4580
4550
|
|
|
4581
4551
|
const pageMask = frameAncestors
|
|
4582
|
-
.map<Vec[] | undefined>(
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4552
|
+
.map<Vec[] | undefined>((s) =>
|
|
4553
|
+
// Apply the frame transform to the frame outline to get the frame outline in the current page space
|
|
4554
|
+
this._getShapePageTransformCache()
|
|
4555
|
+
.get(s.id)!
|
|
4556
|
+
.applyToPoints(this.getShapeGeometry(s).vertices)
|
|
4586
4557
|
)
|
|
4587
4558
|
.reduce((acc, b) => {
|
|
4588
4559
|
if (!(b && acc)) return undefined
|
|
@@ -2,6 +2,8 @@ import { TLGroupShape, groupShapeMigrations, groupShapeProps } from '@tldraw/tls
|
|
|
2
2
|
import { SVGContainer } from '../../../components/SVGContainer'
|
|
3
3
|
import { Geometry2d } from '../../../primitives/geometry/Geometry2d'
|
|
4
4
|
import { Group2d } from '../../../primitives/geometry/Group2d'
|
|
5
|
+
import { Polygon2d } from '../../../primitives/geometry/Polygon2d'
|
|
6
|
+
import { Polyline2d } from '../../../primitives/geometry/Polyline2d'
|
|
5
7
|
import { Rectangle2d } from '../../../primitives/geometry/Rectangle2d'
|
|
6
8
|
import { ShapeUtil } from '../ShapeUtil'
|
|
7
9
|
import { DashedOutlineBox } from './DashedOutlineBox'
|
|
@@ -33,9 +35,19 @@ export class GroupShapeUtil extends ShapeUtil<TLGroupShape> {
|
|
|
33
35
|
return new Group2d({
|
|
34
36
|
children: children.map((childId) => {
|
|
35
37
|
const shape = this.editor.getShape(childId)!
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
const geometry = this.editor.getShapeGeometry(childId)
|
|
39
|
+
const points = this.editor.getShapeLocalTransform(shape)!.applyToPoints(geometry.vertices)
|
|
40
|
+
|
|
41
|
+
if (geometry.isClosed) {
|
|
42
|
+
return new Polygon2d({
|
|
43
|
+
points,
|
|
44
|
+
isFilled: true,
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return new Polyline2d({
|
|
49
|
+
points,
|
|
50
|
+
})
|
|
39
51
|
}),
|
|
40
52
|
})
|
|
41
53
|
}
|
|
@@ -1,44 +1,12 @@
|
|
|
1
|
-
import { assert } from '@tldraw/utils'
|
|
2
1
|
import { Box } from '../Box'
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
intersectCirclePolygon,
|
|
7
|
-
intersectCirclePolyline,
|
|
8
|
-
intersectLineSegmentPolygon,
|
|
9
|
-
intersectLineSegmentPolyline,
|
|
10
|
-
intersectPolys,
|
|
11
|
-
} from '../intersect'
|
|
12
|
-
import { approximately, pointInPolygon } from '../utils'
|
|
13
|
-
|
|
14
|
-
/** @public */
|
|
15
|
-
export interface Geometry2dFilters {
|
|
16
|
-
readonly includeLabels?: boolean
|
|
17
|
-
readonly includeInternal?: boolean
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** @public */
|
|
21
|
-
export const Geometry2dFilters: {
|
|
22
|
-
EXCLUDE_NON_STANDARD: Geometry2dFilters
|
|
23
|
-
INCLUDE_ALL: Geometry2dFilters
|
|
24
|
-
EXCLUDE_LABELS: Geometry2dFilters
|
|
25
|
-
EXCLUDE_INTERNAL: Geometry2dFilters
|
|
26
|
-
} = {
|
|
27
|
-
EXCLUDE_NON_STANDARD: {
|
|
28
|
-
includeLabels: false,
|
|
29
|
-
includeInternal: false,
|
|
30
|
-
},
|
|
31
|
-
INCLUDE_ALL: { includeLabels: true, includeInternal: true },
|
|
32
|
-
EXCLUDE_LABELS: { includeLabels: false, includeInternal: true },
|
|
33
|
-
EXCLUDE_INTERNAL: { includeLabels: true, includeInternal: false },
|
|
34
|
-
}
|
|
2
|
+
import { Vec } from '../Vec'
|
|
3
|
+
import { pointInPolygon } from '../utils'
|
|
35
4
|
|
|
36
5
|
/** @public */
|
|
37
6
|
export interface Geometry2dOptions {
|
|
38
7
|
isFilled: boolean
|
|
39
8
|
isClosed: boolean
|
|
40
9
|
isLabel?: boolean
|
|
41
|
-
isInternal?: boolean
|
|
42
10
|
debugColor?: string
|
|
43
11
|
ignore?: boolean
|
|
44
12
|
}
|
|
@@ -48,7 +16,6 @@ export abstract class Geometry2d {
|
|
|
48
16
|
isFilled = false
|
|
49
17
|
isClosed = true
|
|
50
18
|
isLabel = false
|
|
51
|
-
isInternal = false
|
|
52
19
|
debugColor?: string
|
|
53
20
|
ignore?: boolean
|
|
54
21
|
|
|
@@ -56,24 +23,20 @@ export abstract class Geometry2d {
|
|
|
56
23
|
this.isFilled = opts.isFilled
|
|
57
24
|
this.isClosed = opts.isClosed
|
|
58
25
|
this.isLabel = opts.isLabel ?? false
|
|
59
|
-
this.isInternal = opts.isInternal ?? false
|
|
60
26
|
this.debugColor = opts.debugColor
|
|
61
27
|
this.ignore = opts.ignore
|
|
62
28
|
}
|
|
63
29
|
|
|
64
|
-
|
|
65
|
-
if (!filters) return false
|
|
66
|
-
if (this.isLabel && !filters.includeLabels) return true
|
|
67
|
-
if (this.isInternal && !filters.includeInternal) return true
|
|
68
|
-
return false
|
|
69
|
-
}
|
|
30
|
+
abstract getVertices(): Vec[]
|
|
70
31
|
|
|
71
|
-
abstract
|
|
32
|
+
abstract nearestPoint(point: Vec): Vec
|
|
72
33
|
|
|
73
|
-
|
|
34
|
+
// hitTestPoint(point: Vec, margin = 0, hitInside = false) {
|
|
35
|
+
// // We've removed the broad phase here; that should be done outside of the call
|
|
36
|
+
// return this.distanceToPoint(point, hitInside) <= margin
|
|
37
|
+
// }
|
|
74
38
|
|
|
75
|
-
hitTestPoint(point: Vec, margin = 0, hitInside = false
|
|
76
|
-
if (this.isExcludedByFilter(filters)) return false
|
|
39
|
+
hitTestPoint(point: Vec, margin = 0, hitInside = false) {
|
|
77
40
|
// First check whether the point is inside
|
|
78
41
|
if (this.isClosed && (this.isFilled || hitInside) && pointInPolygon(point, this.vertices)) {
|
|
79
42
|
return true
|
|
@@ -82,17 +45,17 @@ export abstract class Geometry2d {
|
|
|
82
45
|
return Vec.Dist2(point, this.nearestPoint(point)) <= margin * margin
|
|
83
46
|
}
|
|
84
47
|
|
|
85
|
-
distanceToPoint(point: Vec, hitInside = false
|
|
48
|
+
distanceToPoint(point: Vec, hitInside = false) {
|
|
86
49
|
return (
|
|
87
|
-
point.dist(this.nearestPoint(point
|
|
50
|
+
point.dist(this.nearestPoint(point)) *
|
|
88
51
|
(this.isClosed && (this.isFilled || hitInside) && pointInPolygon(point, this.vertices)
|
|
89
52
|
? -1
|
|
90
53
|
: 1)
|
|
91
54
|
)
|
|
92
55
|
}
|
|
93
56
|
|
|
94
|
-
distanceToLineSegment(A: Vec, B: Vec
|
|
95
|
-
if (A.equals(B)) return this.distanceToPoint(A
|
|
57
|
+
distanceToLineSegment(A: Vec, B: Vec) {
|
|
58
|
+
if (A.equals(B)) return this.distanceToPoint(A)
|
|
96
59
|
const { vertices } = this
|
|
97
60
|
let nearest: Vec | undefined
|
|
98
61
|
let dist = Infinity
|
|
@@ -110,41 +73,10 @@ export abstract class Geometry2d {
|
|
|
110
73
|
return this.isClosed && this.isFilled && pointInPolygon(nearest, this.vertices) ? -dist : dist
|
|
111
74
|
}
|
|
112
75
|
|
|
113
|
-
hitTestLineSegment(A: Vec, B: Vec, distance = 0
|
|
114
|
-
return this.distanceToLineSegment(A, B
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
intersectLineSegment(A: VecLike, B: VecLike, filters?: Geometry2dFilters): VecLike[] {
|
|
118
|
-
if (this.isExcludedByFilter(filters)) return []
|
|
119
|
-
|
|
120
|
-
const intersections = this.isClosed
|
|
121
|
-
? intersectLineSegmentPolygon(A, B, this.vertices)
|
|
122
|
-
: intersectLineSegmentPolyline(A, B, this.vertices)
|
|
123
|
-
|
|
124
|
-
return intersections ?? []
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
intersectCircle(center: VecLike, radius: number, filters?: Geometry2dFilters): VecLike[] {
|
|
128
|
-
if (this.isExcludedByFilter(filters)) return []
|
|
129
|
-
const intersections = this.isClosed
|
|
130
|
-
? intersectCirclePolygon(center, radius, this.vertices)
|
|
131
|
-
: intersectCirclePolyline(center, radius, this.vertices)
|
|
132
|
-
|
|
133
|
-
return intersections ?? []
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
intersectPolygon(polygon: VecLike[], filters?: Geometry2dFilters): VecLike[] {
|
|
137
|
-
if (this.isExcludedByFilter(filters)) return []
|
|
138
|
-
|
|
139
|
-
return intersectPolys(polygon, this.vertices, true, this.isClosed)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
intersectPolyline(polyline: VecLike[], filters?: Geometry2dFilters): VecLike[] {
|
|
143
|
-
if (this.isExcludedByFilter(filters)) return []
|
|
144
|
-
return intersectPolys(polyline, this.vertices, false, this.isClosed)
|
|
76
|
+
hitTestLineSegment(A: Vec, B: Vec, distance = 0): boolean {
|
|
77
|
+
return this.distanceToLineSegment(A, B) <= distance
|
|
145
78
|
}
|
|
146
79
|
|
|
147
|
-
/** @deprecated Iterate the vertices instead. */
|
|
148
80
|
nearestPointOnLineSegment(A: Vec, B: Vec): Vec {
|
|
149
81
|
const { vertices } = this
|
|
150
82
|
let nearest: Vec | undefined
|
|
@@ -173,16 +105,12 @@ export abstract class Geometry2d {
|
|
|
173
105
|
)
|
|
174
106
|
}
|
|
175
107
|
|
|
176
|
-
transform(transform: MatModel): Geometry2d {
|
|
177
|
-
return new TransformedGeometry2d(this, transform)
|
|
178
|
-
}
|
|
179
|
-
|
|
180
108
|
private _vertices: Vec[] | undefined
|
|
181
109
|
|
|
182
110
|
// eslint-disable-next-line no-restricted-syntax
|
|
183
111
|
get vertices(): Vec[] {
|
|
184
112
|
if (!this._vertices) {
|
|
185
|
-
this._vertices = this.getVertices(
|
|
113
|
+
this._vertices = this.getVertices()
|
|
186
114
|
}
|
|
187
115
|
|
|
188
116
|
return this._vertices
|
|
@@ -276,111 +204,3 @@ export abstract class Geometry2d {
|
|
|
276
204
|
|
|
277
205
|
abstract getSvgPathData(first: boolean): string
|
|
278
206
|
}
|
|
279
|
-
|
|
280
|
-
// =================================================================================================
|
|
281
|
-
// Because Geometry2d.transform depends on TransformedGeometry2d, we need to define it here instead
|
|
282
|
-
// of in its own files. This prevents a circular import error.
|
|
283
|
-
// =================================================================================================
|
|
284
|
-
|
|
285
|
-
/** @public */
|
|
286
|
-
export class TransformedGeometry2d extends Geometry2d {
|
|
287
|
-
private readonly inverse: MatModel
|
|
288
|
-
private readonly decomposed
|
|
289
|
-
|
|
290
|
-
constructor(
|
|
291
|
-
private readonly geometry: Geometry2d,
|
|
292
|
-
private readonly matrix: MatModel
|
|
293
|
-
) {
|
|
294
|
-
super(geometry)
|
|
295
|
-
this.inverse = Mat.Inverse(matrix)
|
|
296
|
-
this.decomposed = Mat.Decompose(matrix)
|
|
297
|
-
|
|
298
|
-
assert(
|
|
299
|
-
approximately(this.decomposed.scaleX, this.decomposed.scaleY),
|
|
300
|
-
'non-uniform scaling is not yet supported'
|
|
301
|
-
)
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
getVertices(filters: Geometry2dFilters): Vec[] {
|
|
305
|
-
return this.geometry.getVertices(filters).map((v) => Mat.applyToPoint(this.matrix, v))
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
nearestPoint(point: Vec, filters?: Geometry2dFilters): Vec {
|
|
309
|
-
return Mat.applyToPoint(
|
|
310
|
-
this.matrix,
|
|
311
|
-
this.geometry.nearestPoint(Mat.applyToPoint(this.inverse, point), filters)
|
|
312
|
-
)
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
override hitTestPoint(
|
|
316
|
-
point: Vec,
|
|
317
|
-
margin = 0,
|
|
318
|
-
hitInside?: boolean,
|
|
319
|
-
filters?: Geometry2dFilters
|
|
320
|
-
): boolean {
|
|
321
|
-
return this.geometry.hitTestPoint(
|
|
322
|
-
Mat.applyToPoint(this.inverse, point),
|
|
323
|
-
margin / this.decomposed.scaleX,
|
|
324
|
-
hitInside,
|
|
325
|
-
filters
|
|
326
|
-
)
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
override distanceToPoint(point: Vec, hitInside = false, filters?: Geometry2dFilters) {
|
|
330
|
-
return (
|
|
331
|
-
this.geometry.distanceToPoint(Mat.applyToPoint(this.inverse, point), hitInside, filters) *
|
|
332
|
-
this.decomposed.scaleX
|
|
333
|
-
)
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
override distanceToLineSegment(A: Vec, B: Vec, filters?: Geometry2dFilters) {
|
|
337
|
-
return (
|
|
338
|
-
this.geometry.distanceToLineSegment(
|
|
339
|
-
Mat.applyToPoint(this.inverse, A),
|
|
340
|
-
Mat.applyToPoint(this.inverse, B),
|
|
341
|
-
filters
|
|
342
|
-
) * this.decomposed.scaleX
|
|
343
|
-
)
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
override hitTestLineSegment(A: Vec, B: Vec, distance = 0, filters?: Geometry2dFilters): boolean {
|
|
347
|
-
return this.geometry.hitTestLineSegment(
|
|
348
|
-
Mat.applyToPoint(this.inverse, A),
|
|
349
|
-
Mat.applyToPoint(this.inverse, B),
|
|
350
|
-
distance / this.decomposed.scaleX,
|
|
351
|
-
filters
|
|
352
|
-
)
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
override intersectLineSegment(A: VecLike, B: VecLike, filters?: Geometry2dFilters) {
|
|
356
|
-
return this.geometry.intersectLineSegment(
|
|
357
|
-
Mat.applyToPoint(this.inverse, A),
|
|
358
|
-
Mat.applyToPoint(this.inverse, B),
|
|
359
|
-
filters
|
|
360
|
-
)
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
override intersectCircle(center: VecLike, radius: number, filters?: Geometry2dFilters) {
|
|
364
|
-
return this.geometry.intersectCircle(
|
|
365
|
-
Mat.applyToPoint(this.inverse, center),
|
|
366
|
-
radius / this.decomposed.scaleX,
|
|
367
|
-
filters
|
|
368
|
-
)
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
override intersectPolygon(polygon: VecLike[], filters?: Geometry2dFilters): VecLike[] {
|
|
372
|
-
return this.geometry.intersectPolygon(Mat.applyToPoints(this.inverse, polygon), filters)
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
override intersectPolyline(polyline: VecLike[], filters?: Geometry2dFilters): VecLike[] {
|
|
376
|
-
return this.geometry.intersectPolyline(Mat.applyToPoints(this.inverse, polyline), filters)
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
override transform(transform: MatModel): Geometry2d {
|
|
380
|
-
return new TransformedGeometry2d(this.geometry, Mat.Multiply(transform, this.matrix))
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
getSvgPathData(): string {
|
|
384
|
-
throw new Error('Cannot get SVG path data for transformed geometry.')
|
|
385
|
-
}
|
|
386
|
-
}
|