@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.
Files changed (48) hide show
  1. package/dist-cjs/index.d.ts +12 -78
  2. package/dist-cjs/index.js +1 -3
  3. package/dist-cjs/index.js.map +2 -2
  4. package/dist-cjs/lib/components/GeometryDebuggingView.js +2 -2
  5. package/dist-cjs/lib/components/GeometryDebuggingView.js.map +2 -2
  6. package/dist-cjs/lib/editor/Editor.js +8 -39
  7. package/dist-cjs/lib/editor/Editor.js.map +2 -2
  8. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js +13 -1
  9. package/dist-cjs/lib/editor/shapes/group/GroupShapeUtil.js.map +2 -2
  10. package/dist-cjs/lib/primitives/geometry/Geometry2d.js +16 -133
  11. package/dist-cjs/lib/primitives/geometry/Geometry2d.js.map +3 -3
  12. package/dist-cjs/lib/primitives/geometry/Group2d.js +11 -54
  13. package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
  14. package/dist-cjs/lib/primitives/intersect.js +0 -20
  15. package/dist-cjs/lib/primitives/intersect.js.map +2 -2
  16. package/dist-cjs/lib/utils/reorderShapes.js +8 -2
  17. package/dist-cjs/lib/utils/reorderShapes.js.map +2 -2
  18. package/dist-cjs/version.js +3 -3
  19. package/dist-cjs/version.js.map +1 -1
  20. package/dist-esm/index.d.mts +12 -78
  21. package/dist-esm/index.mjs +2 -8
  22. package/dist-esm/index.mjs.map +2 -2
  23. package/dist-esm/lib/components/GeometryDebuggingView.mjs +3 -3
  24. package/dist-esm/lib/components/GeometryDebuggingView.mjs.map +2 -2
  25. package/dist-esm/lib/editor/Editor.mjs +8 -39
  26. package/dist-esm/lib/editor/Editor.mjs.map +2 -2
  27. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs +13 -1
  28. package/dist-esm/lib/editor/shapes/group/GroupShapeUtil.mjs.map +2 -2
  29. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs +14 -137
  30. package/dist-esm/lib/primitives/geometry/Geometry2d.mjs.map +2 -2
  31. package/dist-esm/lib/primitives/geometry/Group2d.mjs +12 -55
  32. package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
  33. package/dist-esm/lib/primitives/intersect.mjs +0 -20
  34. package/dist-esm/lib/primitives/intersect.mjs.map +2 -2
  35. package/dist-esm/lib/utils/reorderShapes.mjs +8 -2
  36. package/dist-esm/lib/utils/reorderShapes.mjs.map +2 -2
  37. package/dist-esm/version.mjs +3 -3
  38. package/dist-esm/version.mjs.map +1 -1
  39. package/package.json +7 -7
  40. package/src/index.ts +1 -6
  41. package/src/lib/components/GeometryDebuggingView.tsx +3 -3
  42. package/src/lib/editor/Editor.ts +15 -44
  43. package/src/lib/editor/shapes/group/GroupShapeUtil.tsx +15 -3
  44. package/src/lib/primitives/geometry/Geometry2d.ts +16 -196
  45. package/src/lib/primitives/geometry/Group2d.ts +13 -76
  46. package/src/lib/primitives/intersect.ts +0 -41
  47. package/src/lib/utils/reorderShapes.ts +9 -2
  48. 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.getShapePageGeometry(shape).vertices\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 = editor.getShapePageGeometry(child).vertices\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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",
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": "AACA,SAAmB,SAAS,mBAAmB,mBAAmB;AAGlE,SAAS,yBAAyB;AAE3B,SAAS,2BACf,QACA,WACA,KACA,MACC;AACD,MAAI,IAAI,WAAW,EAAG,QAAO,CAAC;AAG9B,QAAM,UAAU,oBAAI,IAA+D;AAEnF,aAAW,SAAS,QAAQ,IAAI,IAAI,CAAC,OAAO,OAAO,SAAS,EAAE,CAAC,CAAC,GAAG;AAClE,UAAM,EAAE,SAAS,IAAI;AACrB,QAAI,CAAC,QAAQ,IAAI,QAAQ,GAAG;AAC3B,cAAQ,IAAI,UAAU;AAAA,QACrB,UAAU;AAAA,UACT,OAAO,2BAA2B,QAAQ,EAAE,IAAI,CAAC,OAAO,OAAO,SAAS,EAAE,CAAC;AAAA,QAC5E;AAAA,QACA,QAAQ,oBAAI,IAAI;AAAA,MACjB,CAAC;AAAA,IACF;AACA,YAAQ,IAAI,QAAQ,EAAG,OAAO,IAAI,KAAK;AAAA,EACxC;AAEA,QAAM,UAA4B,CAAC;AAEnC,UAAQ,WAAW;AAAA,IAClB,KAAK,UAAU;AACd,cAAQ,QAAQ,CAAC,EAAE,QAAQ,SAAS,MAAM,cAAc,QAAQ,UAAU,OAAO,CAAC;AAClF;AAAA,IACD;AAAA,IACA,KAAK,WAAW;AACf,cAAQ,QAAQ,CAAC,EAAE,QAAQ,SAAS,MAAM,eAAe,QAAQ,UAAU,OAAO,CAAC;AACnF;AAAA,IACD;AAAA,IACA,KAAK,WAAW;AACf,cAAQ;AAAA,QAAQ,CAAC,EAAE,QAAQ,SAAS,MACnC,eAAe,QAAQ,QAAQ,UAAU,SAAS,IAAI;AAAA,MACvD;AACA;AAAA,IACD;AAAA,IACA,KAAK,YAAY;AAChB,cAAQ;AAAA,QAAQ,CAAC,EAAE,QAAQ,SAAS,MACnC,gBAAgB,QAAQ,QAAQ,UAAU,SAAS,IAAI;AAAA,MACxD;AACA;AAAA,IACD;AAAA,EACD;AAEA,SAAO;AACR;AASA,SAAS,cAAc,QAAsB,UAAqB,SAA2B;AAC5F,QAAM,MAAM,SAAS;AAGrB,MAAI,OAAO,SAAS,IAAK;AAEzB,MAAI;AACJ,MAAI;AAGJ,WAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC7B,UAAM,QAAQ,SAAS,CAAC;AAExB,QAAI,OAAO,IAAI,KAAK,GAAG;AAItB,cAAQ,MAAM;AACd,aAAO,OAAO,KAAK;AAAA,IACpB,OAAO;AAGN,cAAQ,MAAM;AACd;AAAA,IACD;AAAA,EACD;AAEA,MAAI,OAAO,SAAS,GAAG;AAGtB;AAAA,EACD,OAAO;AAEN,UAAM,UAAU,kBAAkB,OAAO,OAAO,OAAO,IAAI;AAC3D,YAAQ;AAAA,MACP,GAAG,MAAM,KAAK,OAAO,OAAO,CAAC,EAC3B,KAAK,WAAW,EAChB,IAAI,CAAC,OAAO,OAAO,EAAE,GAAG,OAAO,OAAO,QAAQ,CAAC,EAAE,EAAE;AAAA,IACtD;AAAA,EACD;AACD;AASA,SAAS,eAAe,QAAsB,UAAqB,SAA2B;AAC7F,QAAM,MAAM,SAAS;AAGrB,MAAI,OAAO,SAAS,IAAK;AAEzB,MAAI;AACJ,MAAI;AAGJ,WAAS,IAAI,MAAM,GAAG,IAAI,IAAI,KAAK;AAClC,UAAM,QAAQ,SAAS,CAAC;AAExB,QAAI,OAAO,IAAI,KAAK,GAAG;AAItB,cAAQ,MAAM;AACd,aAAO,OAAO,KAAK;AAAA,IACpB,OAAO;AAGN,cAAQ,MAAM;AACd;AAAA,IACD;AAAA,EACD;AAEA,MAAI,OAAO,SAAS,GAAG;AAGtB;AAAA,EACD,OAAO;AAEN,UAAM,UAAU,kBAAkB,OAAO,OAAO,OAAO,IAAI;AAC3D,YAAQ;AAAA,MACP,GAAG,MAAM,KAAK,OAAO,OAAO,CAAC,EAC3B,KAAK,WAAW,EAChB,IAAI,CAAC,OAAO,OAAO,EAAE,GAAG,OAAO,OAAO,QAAQ,CAAC,EAAE,EAAE;AAAA,IACtD;AAAA,EACD;AACD;AAEA,SAAS,uBAAuB,QAAgB,OAAgB;AAC/D,QAAM,MAAM,OAAO,iBAAiB,KAAK;AACzC,QAAM,gBAAgB,OAAO,sBAAsB,KAAK;AACxD,MAAI,CAAC,OAAO,CAAC,cAAe,QAAO;AACnC,SAAO,cAAc,cAAc,IAAI,QAAQ;AAChD;AAEA,SAAS,kBAAkB,QAAgB,QAAsB;AAChE,QAAM,iBAAiB,MAAM,KAAK,MAAM,EACtC,IAAI,CAAC,UAAU;AACf,UAAM,WAAW,uBAAuB,QAAQ,KAAK;AACrD,QAAI,CAAC,SAAU,QAAO;AACtB,WAAO,EAAE,OAAO,SAAS;AAAA,EAC1B,CAAC,EACA,OAAO,OAAO;AAEhB,QAAM,gBAAgB,CAAC,UAAmB;AACzC,UAAM,WAAW,uBAAuB,QAAQ,KAAK;AACrD,QAAI,CAAC,SAAU,QAAO;AACtB,WAAO,eAAe,KAAK,CAAC,UAAU;AACrC,aAAO,kBAAkB,MAAM,UAAU,QAAQ;AAAA,IAClD,CAAC;AAAA,EACF;AAEA,SAAO;AACR;AAWA,SAAS,eACR,QACA,QACA,UACA,SACA,MACC;AACD,QAAM,gBAAgB,kBAAkB,QAAQ,MAAM;AAEtD,QAAM,MAAM,SAAS;AAGrB,MAAI,OAAO,SAAS,IAAK;AAEzB,MAAI,QAAQ,EAAE,MAAM,WAAW;AAK/B,WAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC7B,UAAM,WAAW,OAAO,IAAI,SAAS,CAAC,CAAC;AAEvC,YAAQ,MAAM,MAAM;AAAA,MACnB,KAAK,YAAY;AAChB,YAAI,CAAC,SAAU;AAEf,gBAAQ,EAAE,MAAM,aAAa,aAAa,EAAE;AAC5C;AAAA,MACD;AAAA,MACA,KAAK,aAAa;AACjB,YAAI,SAAU;AACd,YAAI,CAAC,MAAM,qBAAqB,CAAC,cAAc,SAAS,CAAC,CAAC,EAAG;AAG7D,cAAM,EAAE,YAAY,IAAI;AACxB,0BAAkB,SAAS,CAAC,EAAE,OAAO,SAAS,IAAI,CAAC,GAAG,OAAO,IAAI,WAAW,EAAE;AAAA,UAC7E,CAAC,OAAO,MAAM;AACb,kBAAM,QAAQ,SAAS,cAAc,CAAC;AAEtC,gBAAI,CAAC,OAAO,IAAI,KAAK,EAAG;AACxB,oBAAQ,KAAK,EAAE,GAAG,OAAO,MAAM,CAAC;AAAA,UACjC;AAAA,QACD;AACA,gBAAQ,EAAE,MAAM,WAAW;AAC3B;AAAA,MACD;AAAA,IACD;AAAA,EACD;AACD;AAWA,SAAS,gBACR,QACA,QACA,UACA,SACA,MACC;AACD,QAAM,gBAAgB,kBAAkB,QAAQ,MAAM;AAEtD,QAAM,MAAM,SAAS;AAErB,MAAI,OAAO,SAAS,IAAK;AAEzB,MAAI,QAAQ,EAAE,MAAM,WAAW;AAK/B,WAAS,IAAI,MAAM,GAAG,IAAI,IAAI,KAAK;AAClC,UAAM,WAAW,OAAO,IAAI,SAAS,CAAC,CAAC;AAEvC,YAAQ,MAAM,MAAM;AAAA,MACnB,KAAK,YAAY;AAChB,YAAI,CAAC,SAAU;AAEf,gBAAQ,EAAE,MAAM,aAAa,aAAa,EAAE;AAC5C;AAAA,MACD;AAAA,MACA,KAAK,aAAa;AACjB,YAAI,SAAU;AACd,YAAI,CAAC,MAAM,qBAAqB,CAAC,cAAc,SAAS,CAAC,CAAC,EAAG;AAG7D,0BAAkB,SAAS,IAAI,CAAC,GAAG,OAAO,SAAS,CAAC,EAAE,OAAO,MAAM,cAAc,CAAC,EAAE;AAAA,UACnF,CAAC,OAAO,MAAM;AACb,kBAAM,QAAQ,SAAS,IAAI,IAAI,CAAC;AAEhC,gBAAI,CAAC,OAAO,IAAI,KAAK,EAAG;AACxB,oBAAQ,KAAK,EAAE,GAAG,OAAO,MAAM,CAAC;AAAA,UACjC;AAAA,QACD;AACA,gBAAQ,EAAE,MAAM,WAAW;AAC3B;AAAA,MACD;AAAA,IACD;AAAA,EACD;AACD;",
6
6
  "names": []
7
7
  }
@@ -1,8 +1,8 @@
1
- const version = "3.12.0-canary.3e2ed74b5e86";
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:23:30.522Z",
5
- patch: "2025-04-03T13:23:30.522Z"
4
+ minor: "2025-04-03T13:02:18.145Z",
5
+ patch: "2025-04-03T13:02:18.145Z"
6
6
  };
7
7
  export {
8
8
  publishDates,
@@ -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.3e2ed74b5e86'\nexport const publishDates = {\n\tmajor: '2024-09-13T14:36:29.063Z',\n\tminor: '2025-04-03T13:23:30.522Z',\n\tpatch: '2025-04-03T13:23:30.522Z',\n}\n"],
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.3e2ed74b5e86",
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.3e2ed74b5e86",
52
- "@tldraw/state-react": "3.12.0-canary.3e2ed74b5e86",
53
- "@tldraw/store": "3.12.0-canary.3e2ed74b5e86",
54
- "@tldraw/tlschema": "3.12.0-canary.3e2ed74b5e86",
55
- "@tldraw/utils": "3.12.0-canary.3e2ed74b5e86",
56
- "@tldraw/validate": "3.12.0-canary.3e2ed74b5e86",
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
- <g stroke={geometry.debugColor}>
117
+ <>
118
118
  {[...geometry.children, ...geometry.ignoredChildren].map((child, i) => (
119
119
  <GeometryStroke geometry={child} key={i} />
120
120
  ))}
121
- </g>
121
+ </>
122
122
  )
123
123
  }
124
124
 
125
- return <path d={geometry.toSimpleSvgPath()} stroke={geometry.debugColor} />
125
+ return <path d={geometry.toSimpleSvgPath()} />
126
126
  }
@@ -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 in shape-space.
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
- return this.getShapePageGeometry(shape).bounds
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
- (s) =>
4584
- // Apply the frame transform to the frame outline to get the frame outline in the current page space
4585
- this.getShapePageGeometry(s.id).vertices
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
- return this.editor
37
- .getShapeGeometry(childId)
38
- .transform(this.editor.getShapeLocalTransform(shape)!)
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 { Mat, MatModel } from '../Mat'
4
- import { Vec, VecLike } from '../Vec'
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
- isExcludedByFilter(filters?: Geometry2dFilters) {
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 getVertices(filters: Geometry2dFilters): Vec[]
32
+ abstract nearestPoint(point: Vec): Vec
72
33
 
73
- abstract nearestPoint(point: Vec, filters?: Geometry2dFilters): Vec
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, filters?: Geometry2dFilters) {
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, filters?: Geometry2dFilters) {
48
+ distanceToPoint(point: Vec, hitInside = false) {
86
49
  return (
87
- point.dist(this.nearestPoint(point, filters)) *
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, filters?: Geometry2dFilters) {
95
- if (A.equals(B)) return this.distanceToPoint(A, false, filters)
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, filters?: Geometry2dFilters): boolean {
114
- return this.distanceToLineSegment(A, B, filters) <= distance
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(Geometry2dFilters.EXCLUDE_LABELS)
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
- }