@nice2dev/ui-graphics 1.3.14 → 1.3.16

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 (294) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/cjs/animation/AnimatedPerson.js +470 -143
  3. package/dist/cjs/animation/AnimationEditor.js +289 -101
  4. package/dist/cjs/animation/Audience.js +83 -61
  5. package/dist/cjs/animation/BodyRenderer.js +69 -28
  6. package/dist/cjs/animation/animationHelper.js +281 -288
  7. package/dist/cjs/animation/characterTypes.js +200 -203
  8. package/dist/cjs/animation/choreoDSL.js +205 -219
  9. package/dist/cjs/animation/choreography.js +4 -32
  10. package/dist/cjs/animation/karaokeIntegration.js +68 -77
  11. package/dist/cjs/animation/lottieExport.js +108 -114
  12. package/dist/cjs/animation/motionPath.js +225 -248
  13. package/dist/cjs/animation/physics.js +227 -254
  14. package/dist/cjs/animation/rig/RigPlayer.js +53 -56
  15. package/dist/cjs/animation/rig/RiggedBody.js +71 -26
  16. package/dist/cjs/animation/rig/choreoRigBridge.js +72 -77
  17. package/dist/cjs/animation/rig/ik.js +13 -18
  18. package/dist/cjs/animation/rig/poseOps.js +34 -41
  19. package/dist/cjs/animation/rig/presets.js +64 -72
  20. package/dist/cjs/animation/rig/rigMath.js +25 -32
  21. package/dist/cjs/animation/rig/rigTypes.js +59 -63
  22. package/dist/cjs/animation/shapes/arms.js +31 -13
  23. package/dist/cjs/animation/shapes/eyes.js +129 -47
  24. package/dist/cjs/animation/shapes/hair.js +72 -45
  25. package/dist/cjs/animation/shapes/heads.js +131 -57
  26. package/dist/cjs/animation/shapes/headwear.js +47 -38
  27. package/dist/cjs/animation/shapes/legs.js +31 -13
  28. package/dist/cjs/animation/shapes/mouths.js +120 -44
  29. package/dist/cjs/animation/shapes/noses.js +80 -25
  30. package/dist/cjs/animation/shapes/outfits.js +101 -39
  31. package/dist/cjs/animation/shapes/torsos.js +15 -11
  32. package/dist/cjs/animation/spineExport.js +103 -121
  33. package/dist/cjs/core/LocalUI.js +108 -43
  34. package/dist/cjs/core/NiceBrushEngine.js +758 -552
  35. package/dist/cjs/core/collaboration.js +249 -251
  36. package/dist/cjs/core/colorBlindness.js +100 -115
  37. package/dist/cjs/core/dragDrop.js +155 -158
  38. package/dist/cjs/core/editorAsk.js +51 -41
  39. package/dist/cjs/core/fixes.js +140 -171
  40. package/dist/cjs/core/gridGuides.js +166 -201
  41. package/dist/cjs/core/historyVisual.js +156 -135
  42. package/dist/cjs/core/i18n.js +13 -14
  43. package/dist/cjs/core/integrations.js +238 -249
  44. package/dist/cjs/core/minimap.js +149 -151
  45. package/dist/cjs/core/plugins.js +98 -113
  46. package/dist/cjs/core/rtl.js +71 -83
  47. package/dist/cjs/core/shortcuts.js +196 -225
  48. package/dist/cjs/core/theme.js +142 -149
  49. package/dist/cjs/core/yjsCollaboration.js +232 -293
  50. package/dist/cjs/design/design-tools.js +12 -22
  51. package/dist/cjs/font/NiceFontEditor.js +877 -316
  52. package/dist/cjs/font/fontFormatUtils.js +388 -448
  53. package/dist/cjs/game/GameAsset2dEditor.js +1201 -392
  54. package/dist/cjs/game/GameAsset2dEditor.module.css.js +146 -5
  55. package/dist/cjs/game/gameAssetTypes.js +7 -16
  56. package/dist/cjs/game/gameAssetUtils.js +390 -413
  57. package/dist/cjs/game/useGameAssetEditor.js +820 -763
  58. package/dist/cjs/index.js +81 -84
  59. package/dist/cjs/nice2dev-ui-graphics.css +1 -1
  60. package/dist/cjs/node_modules/lib0/array.js +8 -70
  61. package/dist/cjs/node_modules/lib0/binary.js +3 -20
  62. package/dist/cjs/node_modules/lib0/broadcastchannel.js +24 -86
  63. package/dist/cjs/node_modules/lib0/buffer.js +15 -82
  64. package/dist/cjs/node_modules/lib0/conditions.js +3 -16
  65. package/dist/cjs/node_modules/lib0/decoding.js +108 -287
  66. package/dist/cjs/node_modules/lib0/dom.js +14 -44
  67. package/dist/cjs/node_modules/lib0/encoding.js +68 -418
  68. package/dist/cjs/node_modules/lib0/environment.js +26 -92
  69. package/dist/cjs/node_modules/lib0/error.js +5 -29
  70. package/dist/cjs/node_modules/lib0/function.js +27 -66
  71. package/dist/cjs/node_modules/lib0/iterator.js +8 -33
  72. package/dist/cjs/node_modules/lib0/logging.common.js +12 -22
  73. package/dist/cjs/node_modules/lib0/logging.js +32 -71
  74. package/dist/cjs/node_modules/lib0/map.js +16 -86
  75. package/dist/cjs/node_modules/lib0/math.js +3 -32
  76. package/dist/cjs/node_modules/lib0/number.js +4 -15
  77. package/dist/cjs/node_modules/lib0/object.js +14 -88
  78. package/dist/cjs/node_modules/lib0/observable.js +38 -78
  79. package/dist/cjs/node_modules/lib0/pair.js +3 -20
  80. package/dist/cjs/node_modules/lib0/prng.js +8 -77
  81. package/dist/cjs/node_modules/lib0/promise.js +6 -30
  82. package/dist/cjs/node_modules/lib0/random.js +8 -23
  83. package/dist/cjs/node_modules/lib0/schema.js +313 -687
  84. package/dist/cjs/node_modules/lib0/set.js +3 -10
  85. package/dist/cjs/node_modules/lib0/storage.js +20 -55
  86. package/dist/cjs/node_modules/lib0/string.js +17 -72
  87. package/dist/cjs/node_modules/lib0/symbol.js +2 -12
  88. package/dist/cjs/node_modules/lib0/time.js +2 -15
  89. package/dist/cjs/node_modules/lib0/trait/equality.js +3 -28
  90. package/dist/cjs/node_modules/lib0/url.js +4 -18
  91. package/dist/cjs/node_modules/lib0/webcrypto.js +2 -5
  92. package/dist/cjs/node_modules/y-protocols/auth.js +5 -19
  93. package/dist/cjs/node_modules/y-protocols/awareness.js +53 -116
  94. package/dist/cjs/node_modules/y-protocols/sync.js +16 -103
  95. package/dist/cjs/node_modules/y-websocket/src/y-websocket.js +77 -219
  96. package/dist/cjs/node_modules/yjs/dist/yjs.js +2128 -4868
  97. package/dist/cjs/photo/FilterThumb.js +37 -25
  98. package/dist/cjs/photo/NiceAITools.js +679 -467
  99. package/dist/cjs/photo/PhotoEditor.js +34 -20
  100. package/dist/cjs/photo/PhotoEditor.module.css.js +296 -5
  101. package/dist/cjs/photo/PhotoEditorCanvas.js +250 -91
  102. package/dist/cjs/photo/PhotoEditorLeftPanel.js +1086 -224
  103. package/dist/cjs/photo/PhotoEditorToolbar.js +135 -19
  104. package/dist/cjs/photo/photoEditorActions.js +89 -72
  105. package/dist/cjs/photo/photoEditorTypes.js +29 -32
  106. package/dist/cjs/photo/photoFilters.js +959 -985
  107. package/dist/cjs/photo/photoOverlays.js +899 -945
  108. package/dist/cjs/photo/photoSelection.js +427 -499
  109. package/dist/cjs/photo/usePhotoEditor.js +739 -741
  110. package/dist/cjs/pixel/HSVPicker.js +191 -175
  111. package/dist/cjs/pixel/PixelEditor.js +159 -48
  112. package/dist/cjs/pixel/PixelEditor.module.css.js +209 -5
  113. package/dist/cjs/pixel/PixelEditorCanvas.js +29 -16
  114. package/dist/cjs/pixel/PixelEditorMenuBar.js +160 -162
  115. package/dist/cjs/pixel/PixelEditorRightPanel.js +377 -45
  116. package/dist/cjs/pixel/PixelEditorStatusBar.js +47 -20
  117. package/dist/cjs/pixel/PixelEditorTimeline.js +106 -23
  118. package/dist/cjs/pixel/PixelEditorToolbar.js +99 -21
  119. package/dist/cjs/pixel/SpriteGeneratorPanel.js +483 -303
  120. package/dist/cjs/pixel/animalGeneratorPresets.js +776 -601
  121. package/dist/cjs/pixel/asepriteFormat.js +511 -560
  122. package/dist/cjs/pixel/buildingGeneratorPresets.js +693 -520
  123. package/dist/cjs/pixel/characterGeneratorPresets.js +644 -544
  124. package/dist/cjs/pixel/pixel-pro.js +52 -63
  125. package/dist/cjs/pixel/pixelEditorExports.js +388 -428
  126. package/dist/cjs/pixel/pixelEditorTypes.js +91 -108
  127. package/dist/cjs/pixel/pixelEditorUtils.js +519 -561
  128. package/dist/cjs/pixel/spriteGeneratorCore.js +206 -257
  129. package/dist/cjs/pixel/usePixelEditor.js +1321 -1343
  130. package/dist/cjs/texture/Nice3DTexturePainter.js +707 -440
  131. package/dist/cjs/ui/NiceUIDesigner.js +954 -527
  132. package/dist/cjs/vector/NiceSvgToCode.js +586 -446
  133. package/dist/cjs/vector/VectorEditor.js +290 -117
  134. package/dist/cjs/vector/VectorEditor.module.css.js +86 -5
  135. package/dist/cjs/vector/VectorEditorMenuBar.js +333 -275
  136. package/dist/cjs/vector/VectorEditorRightPanel.js +1094 -371
  137. package/dist/cjs/vector/VectorEditorShapeRenderer.js +198 -60
  138. package/dist/cjs/vector/VectorEditorStatusBar.js +39 -13
  139. package/dist/cjs/vector/useVectorEditor.js +848 -889
  140. package/dist/cjs/vector/vectorAnimation.js +110 -137
  141. package/dist/cjs/vector/vectorBooleanOps.js +398 -485
  142. package/dist/cjs/vector/vectorEditorExport.js +201 -203
  143. package/dist/cjs/vector/vectorEditorImport.js +173 -189
  144. package/dist/cjs/vector/vectorEditorTypes.js +42 -49
  145. package/dist/cjs/vector/vectorGradients.js +151 -174
  146. package/dist/cjs/vector/vectorIconExport.js +52 -59
  147. package/dist/cjs/vector/vectorIconImport.js +169 -219
  148. package/dist/esm/animation/AnimatedPerson.js +470 -141
  149. package/dist/esm/animation/AnimationEditor.js +290 -100
  150. package/dist/esm/animation/Audience.js +83 -59
  151. package/dist/esm/animation/BodyRenderer.js +71 -25
  152. package/dist/esm/animation/animationHelper.js +284 -287
  153. package/dist/esm/animation/characterTypes.js +204 -202
  154. package/dist/esm/animation/choreoDSL.js +212 -218
  155. package/dist/esm/animation/choreography.js +8 -31
  156. package/dist/esm/animation/karaokeIntegration.js +74 -76
  157. package/dist/esm/animation/lottieExport.js +112 -113
  158. package/dist/esm/animation/motionPath.js +235 -247
  159. package/dist/esm/animation/physics.js +235 -253
  160. package/dist/esm/animation/rig/RigPlayer.js +53 -54
  161. package/dist/esm/animation/rig/RiggedBody.js +71 -24
  162. package/dist/esm/animation/rig/choreoRigBridge.js +74 -76
  163. package/dist/esm/animation/rig/ik.js +14 -17
  164. package/dist/esm/animation/rig/poseOps.js +37 -40
  165. package/dist/esm/animation/rig/presets.js +70 -71
  166. package/dist/esm/animation/rig/rigMath.js +28 -31
  167. package/dist/esm/animation/rig/rigTypes.js +62 -62
  168. package/dist/esm/animation/shapes/arms.js +32 -12
  169. package/dist/esm/animation/shapes/eyes.js +135 -46
  170. package/dist/esm/animation/shapes/hair.js +80 -44
  171. package/dist/esm/animation/shapes/heads.js +140 -56
  172. package/dist/esm/animation/shapes/headwear.js +54 -37
  173. package/dist/esm/animation/shapes/legs.js +32 -12
  174. package/dist/esm/animation/shapes/mouths.js +127 -43
  175. package/dist/esm/animation/shapes/noses.js +87 -24
  176. package/dist/esm/animation/shapes/outfits.js +106 -38
  177. package/dist/esm/animation/shapes/torsos.js +16 -10
  178. package/dist/esm/animation/spineExport.js +106 -120
  179. package/dist/esm/core/LocalUI.js +111 -42
  180. package/dist/esm/core/NiceBrushEngine.js +761 -551
  181. package/dist/esm/core/collaboration.js +255 -250
  182. package/dist/esm/core/colorBlindness.js +106 -114
  183. package/dist/esm/core/dragDrop.js +157 -157
  184. package/dist/esm/core/editorAsk.js +53 -40
  185. package/dist/esm/core/fixes.js +145 -170
  186. package/dist/esm/core/gridGuides.js +178 -200
  187. package/dist/esm/core/historyVisual.js +158 -134
  188. package/dist/esm/core/i18n.js +15 -13
  189. package/dist/esm/core/integrations.js +244 -248
  190. package/dist/esm/core/minimap.js +151 -150
  191. package/dist/esm/core/plugins.js +102 -112
  192. package/dist/esm/core/rtl.js +77 -82
  193. package/dist/esm/core/shortcuts.js +200 -224
  194. package/dist/esm/core/theme.js +150 -148
  195. package/dist/esm/core/yjsCollaboration.js +240 -292
  196. package/dist/esm/design/design-tools.js +13 -21
  197. package/dist/esm/font/NiceFontEditor.js +880 -315
  198. package/dist/esm/font/fontFormatUtils.js +394 -447
  199. package/dist/esm/game/GameAsset2dEditor.js +1202 -391
  200. package/dist/esm/game/GameAsset2dEditor.module.css.js +146 -3
  201. package/dist/esm/game/gameAssetTypes.js +8 -15
  202. package/dist/esm/game/gameAssetUtils.js +408 -412
  203. package/dist/esm/game/useGameAssetEditor.js +821 -762
  204. package/dist/esm/index.js +332 -69
  205. package/dist/esm/nice2dev-ui-graphics.css +1 -1
  206. package/dist/esm/node_modules/lib0/array.js +15 -69
  207. package/dist/esm/node_modules/lib0/binary.js +14 -19
  208. package/dist/esm/node_modules/lib0/broadcastchannel.js +27 -85
  209. package/dist/esm/node_modules/lib0/buffer.js +20 -80
  210. package/dist/esm/node_modules/lib0/conditions.js +4 -15
  211. package/dist/esm/node_modules/lib0/decoding.js +128 -286
  212. package/dist/esm/node_modules/lib0/dom.js +20 -43
  213. package/dist/esm/node_modules/lib0/encoding.js +90 -415
  214. package/dist/esm/node_modules/lib0/environment.js +33 -91
  215. package/dist/esm/node_modules/lib0/error.js +8 -28
  216. package/dist/esm/node_modules/lib0/function.js +31 -65
  217. package/dist/esm/node_modules/lib0/iterator.js +11 -32
  218. package/dist/esm/node_modules/lib0/logging.common.js +22 -21
  219. package/dist/esm/node_modules/lib0/logging.js +44 -70
  220. package/dist/esm/node_modules/lib0/map.js +21 -85
  221. package/dist/esm/node_modules/lib0/math.js +9 -31
  222. package/dist/esm/node_modules/lib0/number.js +7 -14
  223. package/dist/esm/node_modules/lib0/object.js +26 -87
  224. package/dist/esm/node_modules/lib0/observable.js +40 -77
  225. package/dist/esm/node_modules/lib0/pair.js +5 -19
  226. package/dist/esm/node_modules/lib0/prng.js +15 -76
  227. package/dist/esm/node_modules/lib0/promise.js +7 -29
  228. package/dist/esm/node_modules/lib0/random.js +10 -22
  229. package/dist/esm/node_modules/lib0/schema.js +367 -686
  230. package/dist/esm/node_modules/lib0/set.js +4 -9
  231. package/dist/esm/node_modules/lib0/storage.js +23 -54
  232. package/dist/esm/node_modules/lib0/string.js +26 -71
  233. package/dist/esm/node_modules/lib0/symbol.js +3 -11
  234. package/dist/esm/node_modules/lib0/time.js +3 -14
  235. package/dist/esm/node_modules/lib0/trait/equality.js +5 -27
  236. package/dist/esm/node_modules/lib0/url.js +5 -17
  237. package/dist/esm/node_modules/lib0/webcrypto.js +3 -4
  238. package/dist/esm/node_modules/y-protocols/auth.js +7 -18
  239. package/dist/esm/node_modules/y-protocols/awareness.js +58 -115
  240. package/dist/esm/node_modules/y-protocols/sync.js +26 -102
  241. package/dist/esm/node_modules/y-websocket/src/y-websocket.js +82 -218
  242. package/dist/esm/node_modules/yjs/dist/yjs.js +2263 -4899
  243. package/dist/esm/photo/FilterThumb.js +37 -23
  244. package/dist/esm/photo/NiceAITools.js +685 -466
  245. package/dist/esm/photo/PhotoEditor.js +34 -18
  246. package/dist/esm/photo/PhotoEditor.module.css.js +296 -3
  247. package/dist/esm/photo/PhotoEditorCanvas.js +250 -89
  248. package/dist/esm/photo/PhotoEditorLeftPanel.js +1086 -222
  249. package/dist/esm/photo/PhotoEditorToolbar.js +135 -17
  250. package/dist/esm/photo/photoEditorActions.js +93 -71
  251. package/dist/esm/photo/photoEditorTypes.js +32 -31
  252. package/dist/esm/photo/photoFilters.js +967 -984
  253. package/dist/esm/photo/photoOverlays.js +913 -944
  254. package/dist/esm/photo/photoSelection.js +438 -498
  255. package/dist/esm/photo/usePhotoEditor.js +740 -740
  256. package/dist/esm/pixel/HSVPicker.js +192 -174
  257. package/dist/esm/pixel/PixelEditor.js +160 -47
  258. package/dist/esm/pixel/PixelEditor.module.css.js +209 -3
  259. package/dist/esm/pixel/PixelEditorCanvas.js +29 -14
  260. package/dist/esm/pixel/PixelEditorMenuBar.js +160 -160
  261. package/dist/esm/pixel/PixelEditorRightPanel.js +377 -43
  262. package/dist/esm/pixel/PixelEditorStatusBar.js +47 -18
  263. package/dist/esm/pixel/PixelEditorTimeline.js +106 -21
  264. package/dist/esm/pixel/PixelEditorToolbar.js +99 -19
  265. package/dist/esm/pixel/SpriteGeneratorPanel.js +483 -301
  266. package/dist/esm/pixel/animalGeneratorPresets.js +782 -600
  267. package/dist/esm/pixel/asepriteFormat.js +517 -559
  268. package/dist/esm/pixel/buildingGeneratorPresets.js +699 -519
  269. package/dist/esm/pixel/characterGeneratorPresets.js +650 -543
  270. package/dist/esm/pixel/pixel-pro.js +53 -62
  271. package/dist/esm/pixel/pixelEditorExports.js +397 -427
  272. package/dist/esm/pixel/pixelEditorTypes.js +103 -107
  273. package/dist/esm/pixel/pixelEditorUtils.js +551 -560
  274. package/dist/esm/pixel/spriteGeneratorCore.js +223 -256
  275. package/dist/esm/pixel/usePixelEditor.js +1322 -1342
  276. package/dist/esm/texture/Nice3DTexturePainter.js +709 -439
  277. package/dist/esm/ui/NiceUIDesigner.js +957 -526
  278. package/dist/esm/vector/NiceSvgToCode.js +589 -445
  279. package/dist/esm/vector/VectorEditor.js +291 -116
  280. package/dist/esm/vector/VectorEditor.module.css.js +86 -3
  281. package/dist/esm/vector/VectorEditorMenuBar.js +333 -273
  282. package/dist/esm/vector/VectorEditorRightPanel.js +1094 -369
  283. package/dist/esm/vector/VectorEditorShapeRenderer.js +199 -59
  284. package/dist/esm/vector/VectorEditorStatusBar.js +39 -11
  285. package/dist/esm/vector/useVectorEditor.js +849 -888
  286. package/dist/esm/vector/vectorAnimation.js +115 -136
  287. package/dist/esm/vector/vectorBooleanOps.js +401 -484
  288. package/dist/esm/vector/vectorEditorExport.js +207 -202
  289. package/dist/esm/vector/vectorEditorImport.js +174 -188
  290. package/dist/esm/vector/vectorEditorTypes.js +50 -48
  291. package/dist/esm/vector/vectorGradients.js +158 -173
  292. package/dist/esm/vector/vectorIconExport.js +53 -58
  293. package/dist/esm/vector/vectorIconImport.js +171 -218
  294. package/package.json +9 -15
@@ -1,529 +1,446 @@
1
- import { sid } from './vectorEditorTypes.js';
2
-
3
- /**
4
- * Boolean operations for vector shapes
5
- * Implements union, difference, intersection, and exclusion operations
6
- */
7
- /* ────────────────────────────────────────────────────────────────
8
- SHAPE TO PATH CONVERSION
9
- ──────────────────────────────────────────────────────────────── */
10
- /** Convert VShape to array of points representing its outline */
1
+ import { sid } from "./vectorEditorTypes.js";
11
2
  function shapeToPolygon(shape, resolution = 32) {
12
- const cx = shape.x + shape.w / 2;
13
- const cy = shape.y + shape.h / 2;
14
- const rx = shape.w / 2;
15
- const ry = shape.h / 2;
16
- switch (shape.type) {
17
- case 'rect':
18
- case 'roundrect':
19
- // For roundrect, we approximate with more points at corners
20
- if (shape.type === 'roundrect' && shape.rx) {
21
- return roundRectToPolygon(shape.x, shape.y, shape.w, shape.h, shape.rx, resolution);
22
- }
23
- return [
24
- { x: shape.x, y: shape.y },
25
- { x: shape.x + shape.w, y: shape.y },
26
- { x: shape.x + shape.w, y: shape.y + shape.h },
27
- { x: shape.x, y: shape.y + shape.h },
28
- ];
29
- case 'ellipse': {
30
- const ellipsePts = [];
31
- for (let i = 0; i < resolution; i++) {
32
- const angle = (2 * Math.PI * i) / resolution;
33
- ellipsePts.push({
34
- x: cx + rx * Math.cos(angle),
35
- y: cy + ry * Math.sin(angle),
36
- });
37
- }
38
- return ellipsePts;
39
- }
40
- case 'polygon': {
41
- const sides = shape.sides || 6;
42
- const polygonPts = [];
43
- const r = Math.min(rx, ry);
44
- for (let i = 0; i < sides; i++) {
45
- const angle = (2 * Math.PI * i) / sides - Math.PI / 2 + (shape.rotation * Math.PI) / 180;
46
- polygonPts.push({
47
- x: cx + r * Math.cos(angle),
48
- y: cy + r * Math.sin(angle),
49
- });
50
- }
51
- return polygonPts;
52
- }
53
- case 'star': {
54
- const points = shape.sides || 5;
55
- const innerR = (shape.innerRadius || 0.4) * Math.min(rx, ry);
56
- const outerR = Math.min(rx, ry);
57
- const starPts = [];
58
- for (let i = 0; i < points * 2; i++) {
59
- const angle = (Math.PI * i) / points - Math.PI / 2 + (shape.rotation * Math.PI) / 180;
60
- const rad = i % 2 === 0 ? outerR : innerR;
61
- starPts.push({
62
- x: cx + rad * Math.cos(angle),
63
- y: cy + rad * Math.sin(angle),
64
- });
65
- }
66
- return starPts;
67
- }
68
- case 'polyline':
69
- return shape.points || [];
70
- default:
71
- // For line, arrow, text - return bounding box
72
- return [
73
- { x: shape.x, y: shape.y },
74
- { x: shape.x + shape.w, y: shape.y },
75
- { x: shape.x + shape.w, y: shape.y + shape.h },
76
- { x: shape.x, y: shape.y + shape.h },
77
- ];
78
- }
79
- }
80
- /** Generate rounded rectangle polygon */
81
- function roundRectToPolygon(x, y, w, h, radius, resolution) {
82
- const r = Math.min(radius, w / 2, h / 2);
83
- const pts = [];
84
- const cornerRes = Math.max(4, Math.floor(resolution / 4));
85
- // Top-right corner
86
- for (let i = 0; i <= cornerRes; i++) {
87
- const angle = -Math.PI / 2 + (Math.PI / 2) * (i / cornerRes);
88
- pts.push({
89
- x: x + w - r + r * Math.cos(angle),
90
- y: y + r + r * Math.sin(angle),
3
+ const cx = shape.x + shape.w / 2;
4
+ const cy = shape.y + shape.h / 2;
5
+ const rx = shape.w / 2;
6
+ const ry = shape.h / 2;
7
+ switch (shape.type) {
8
+ case "rect":
9
+ case "roundrect":
10
+ if (shape.type === "roundrect" && shape.rx) {
11
+ return roundRectToPolygon(shape.x, shape.y, shape.w, shape.h, shape.rx, resolution);
12
+ }
13
+ return [
14
+ { x: shape.x, y: shape.y },
15
+ { x: shape.x + shape.w, y: shape.y },
16
+ { x: shape.x + shape.w, y: shape.y + shape.h },
17
+ { x: shape.x, y: shape.y + shape.h }
18
+ ];
19
+ case "ellipse": {
20
+ const ellipsePts = [];
21
+ for (let i = 0; i < resolution; i++) {
22
+ const angle = 2 * Math.PI * i / resolution;
23
+ ellipsePts.push({
24
+ x: cx + rx * Math.cos(angle),
25
+ y: cy + ry * Math.sin(angle)
91
26
  });
92
- }
93
- // Bottom-right corner
94
- for (let i = 0; i <= cornerRes; i++) {
95
- const angle = 0 + (Math.PI / 2) * (i / cornerRes);
96
- pts.push({
97
- x: x + w - r + r * Math.cos(angle),
98
- y: y + h - r + r * Math.sin(angle),
27
+ }
28
+ return ellipsePts;
29
+ }
30
+ case "polygon": {
31
+ const sides = shape.sides || 6;
32
+ const polygonPts = [];
33
+ const r = Math.min(rx, ry);
34
+ for (let i = 0; i < sides; i++) {
35
+ const angle = 2 * Math.PI * i / sides - Math.PI / 2 + shape.rotation * Math.PI / 180;
36
+ polygonPts.push({
37
+ x: cx + r * Math.cos(angle),
38
+ y: cy + r * Math.sin(angle)
99
39
  });
100
- }
101
- // Bottom-left corner
102
- for (let i = 0; i <= cornerRes; i++) {
103
- const angle = Math.PI / 2 + (Math.PI / 2) * (i / cornerRes);
104
- pts.push({
105
- x: x + r + r * Math.cos(angle),
106
- y: y + h - r + r * Math.sin(angle),
40
+ }
41
+ return polygonPts;
42
+ }
43
+ case "star": {
44
+ const points = shape.sides || 5;
45
+ const innerR = (shape.innerRadius || 0.4) * Math.min(rx, ry);
46
+ const outerR = Math.min(rx, ry);
47
+ const starPts = [];
48
+ for (let i = 0; i < points * 2; i++) {
49
+ const angle = Math.PI * i / points - Math.PI / 2 + shape.rotation * Math.PI / 180;
50
+ const rad = i % 2 === 0 ? outerR : innerR;
51
+ starPts.push({
52
+ x: cx + rad * Math.cos(angle),
53
+ y: cy + rad * Math.sin(angle)
107
54
  });
108
- }
109
- // Top-left corner
110
- for (let i = 0; i <= cornerRes; i++) {
111
- const angle = Math.PI + (Math.PI / 2) * (i / cornerRes);
112
- pts.push({
113
- x: x + r + r * Math.cos(angle),
114
- y: y + r + r * Math.sin(angle),
115
- });
116
- }
117
- return pts;
55
+ }
56
+ return starPts;
57
+ }
58
+ case "polyline":
59
+ return shape.points || [];
60
+ default:
61
+ return [
62
+ { x: shape.x, y: shape.y },
63
+ { x: shape.x + shape.w, y: shape.y },
64
+ { x: shape.x + shape.w, y: shape.y + shape.h },
65
+ { x: shape.x, y: shape.y + shape.h }
66
+ ];
67
+ }
68
+ }
69
+ function roundRectToPolygon(x, y, w, h, radius, resolution) {
70
+ const r = Math.min(radius, w / 2, h / 2);
71
+ const pts = [];
72
+ const cornerRes = Math.max(4, Math.floor(resolution / 4));
73
+ for (let i = 0; i <= cornerRes; i++) {
74
+ const angle = -Math.PI / 2 + Math.PI / 2 * (i / cornerRes);
75
+ pts.push({
76
+ x: x + w - r + r * Math.cos(angle),
77
+ y: y + r + r * Math.sin(angle)
78
+ });
79
+ }
80
+ for (let i = 0; i <= cornerRes; i++) {
81
+ const angle = 0 + Math.PI / 2 * (i / cornerRes);
82
+ pts.push({
83
+ x: x + w - r + r * Math.cos(angle),
84
+ y: y + h - r + r * Math.sin(angle)
85
+ });
86
+ }
87
+ for (let i = 0; i <= cornerRes; i++) {
88
+ const angle = Math.PI / 2 + Math.PI / 2 * (i / cornerRes);
89
+ pts.push({
90
+ x: x + r + r * Math.cos(angle),
91
+ y: y + h - r + r * Math.sin(angle)
92
+ });
93
+ }
94
+ for (let i = 0; i <= cornerRes; i++) {
95
+ const angle = Math.PI + Math.PI / 2 * (i / cornerRes);
96
+ pts.push({
97
+ x: x + r + r * Math.cos(angle),
98
+ y: y + r + r * Math.sin(angle)
99
+ });
100
+ }
101
+ return pts;
118
102
  }
119
- /* ────────────────────────────────────────────────────────────────
120
- POLYGON UTILITIES
121
- ──────────────────────────────────────────────────────────────── */
122
- /** Check if point is inside polygon using ray casting */
123
103
  function pointInPolygon(pt, polygon) {
124
- let inside = false;
125
- const n = polygon.length;
126
- for (let i = 0, j = n - 1; i < n; j = i++) {
127
- const xi = polygon[i].x, yi = polygon[i].y;
128
- const xj = polygon[j].x, yj = polygon[j].y;
129
- if (yi > pt.y !== yj > pt.y && pt.x < ((xj - xi) * (pt.y - yi)) / (yj - yi) + xi) {
130
- inside = !inside;
131
- }
132
- }
133
- return inside;
104
+ let inside = false;
105
+ const n = polygon.length;
106
+ for (let i = 0, j = n - 1; i < n; j = i++) {
107
+ const xi = polygon[i].x, yi = polygon[i].y;
108
+ const xj = polygon[j].x, yj = polygon[j].y;
109
+ if (yi > pt.y !== yj > pt.y && pt.x < (xj - xi) * (pt.y - yi) / (yj - yi) + xi) {
110
+ inside = !inside;
111
+ }
112
+ }
113
+ return inside;
134
114
  }
135
- /** Get polygon centroid */
136
115
  function polygonCentroid(polygon) {
137
- let cx = 0, cy = 0;
138
- for (const pt of polygon) {
139
- cx += pt.x;
140
- cy += pt.y;
141
- }
142
- return { x: cx / polygon.length, y: cy / polygon.length };
116
+ let cx = 0, cy = 0;
117
+ for (const pt of polygon) {
118
+ cx += pt.x;
119
+ cy += pt.y;
120
+ }
121
+ return { x: cx / polygon.length, y: cy / polygon.length };
143
122
  }
144
- /** Get polygon bounding box */
145
123
  function polygonBounds(polygon) {
146
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
147
- for (const pt of polygon) {
148
- minX = Math.min(minX, pt.x);
149
- minY = Math.min(minY, pt.y);
150
- maxX = Math.max(maxX, pt.x);
151
- maxY = Math.max(maxY, pt.y);
152
- }
153
- return { minX, minY, maxX, maxY };
124
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
125
+ for (const pt of polygon) {
126
+ minX = Math.min(minX, pt.x);
127
+ minY = Math.min(minY, pt.y);
128
+ maxX = Math.max(maxX, pt.x);
129
+ maxY = Math.max(maxY, pt.y);
130
+ }
131
+ return { minX, minY, maxX, maxY };
154
132
  }
155
- /** Calculate polygon area (signed) */
156
133
  function polygonArea(polygon) {
157
- let area = 0;
158
- const n = polygon.length;
159
- for (let i = 0; i < n; i++) {
160
- const j = (i + 1) % n;
161
- area += polygon[i].x * polygon[j].y;
162
- area -= polygon[j].x * polygon[i].y;
163
- }
164
- return area / 2;
134
+ let area = 0;
135
+ const n = polygon.length;
136
+ for (let i = 0; i < n; i++) {
137
+ const j = (i + 1) % n;
138
+ area += polygon[i].x * polygon[j].y;
139
+ area -= polygon[j].x * polygon[i].y;
140
+ }
141
+ return area / 2;
165
142
  }
166
- /** Check if two line segments intersect and return intersection point */
167
143
  function lineIntersection(p1, p2, p3, p4) {
168
- const d = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);
169
- if (Math.abs(d) < 1e-10) {
170
- return null;
171
- }
172
- const ua = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / d;
173
- const ub = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / d;
174
- if (ua < 0 || ua > 1 || ub < 0 || ub > 1) {
175
- return null;
176
- }
177
- return {
178
- x: p1.x + ua * (p2.x - p1.x),
179
- y: p1.y + ua * (p2.y - p1.y),
180
- };
144
+ const d = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);
145
+ if (Math.abs(d) < 1e-10) {
146
+ return null;
147
+ }
148
+ const ua = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / d;
149
+ const ub = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / d;
150
+ if (ua < 0 || ua > 1 || ub < 0 || ub > 1) {
151
+ return null;
152
+ }
153
+ return {
154
+ x: p1.x + ua * (p2.x - p1.x),
155
+ y: p1.y + ua * (p2.y - p1.y)
156
+ };
181
157
  }
182
- /** Find all intersection points between two polygons */
183
158
  function findIntersections(polyA, polyB) {
184
- const intersections = [];
185
- for (let i = 0; i < polyA.length; i++) {
186
- const a1 = polyA[i];
187
- const a2 = polyA[(i + 1) % polyA.length];
188
- for (let j = 0; j < polyB.length; j++) {
189
- const b1 = polyB[j];
190
- const b2 = polyB[(j + 1) % polyB.length];
191
- const pt = lineIntersection(a1, a2, b1, b2);
192
- if (pt) {
193
- intersections.push(pt);
194
- }
195
- }
196
- }
197
- return intersections;
159
+ const intersections = [];
160
+ for (let i = 0; i < polyA.length; i++) {
161
+ const a1 = polyA[i];
162
+ const a2 = polyA[(i + 1) % polyA.length];
163
+ for (let j = 0; j < polyB.length; j++) {
164
+ const b1 = polyB[j];
165
+ const b2 = polyB[(j + 1) % polyB.length];
166
+ const pt = lineIntersection(a1, a2, b1, b2);
167
+ if (pt) {
168
+ intersections.push(pt);
169
+ }
170
+ }
171
+ }
172
+ return intersections;
198
173
  }
199
- /* ────────────────────────────────────────────────────────────────
200
- SUTHERLAND-HODGMAN CLIPPING (for intersection)
201
- ──────────────────────────────────────────────────────────────── */
202
- /** Clip polygon against a single edge using Sutherland-Hodgman */
203
174
  function clipPolygonEdge(polygon, edgeStart, edgeEnd) {
204
- if (polygon.length === 0) {
205
- return [];
206
- }
207
- const output = [];
208
- const n = polygon.length;
209
- // Check which side of edge a point is on
210
- const inside = (p) => {
211
- return ((edgeEnd.x - edgeStart.x) * (p.y - edgeStart.y) -
212
- (edgeEnd.y - edgeStart.y) * (p.x - edgeStart.x) >=
213
- 0);
214
- };
215
- for (let i = 0; i < n; i++) {
216
- const curr = polygon[i];
217
- const prev = polygon[(i + n - 1) % n];
218
- const currInside = inside(curr);
219
- const prevInside = inside(prev);
220
- if (currInside) {
221
- if (!prevInside) {
222
- // Entering - add intersection
223
- const pt = lineIntersection(prev, curr, edgeStart, edgeEnd);
224
- if (pt) {
225
- output.push(pt);
226
- }
227
- }
228
- output.push(curr);
175
+ if (polygon.length === 0) {
176
+ return [];
177
+ }
178
+ const output = [];
179
+ const n = polygon.length;
180
+ const inside = (p) => {
181
+ return (edgeEnd.x - edgeStart.x) * (p.y - edgeStart.y) - (edgeEnd.y - edgeStart.y) * (p.x - edgeStart.x) >= 0;
182
+ };
183
+ for (let i = 0; i < n; i++) {
184
+ const curr = polygon[i];
185
+ const prev = polygon[(i + n - 1) % n];
186
+ const currInside = inside(curr);
187
+ const prevInside = inside(prev);
188
+ if (currInside) {
189
+ if (!prevInside) {
190
+ const pt = lineIntersection(prev, curr, edgeStart, edgeEnd);
191
+ if (pt) {
192
+ output.push(pt);
229
193
  }
230
- else if (prevInside) {
231
- // Leaving - add intersection
232
- const pt = lineIntersection(prev, curr, edgeStart, edgeEnd);
233
- if (pt) {
234
- output.push(pt);
235
- }
236
- }
237
- }
238
- return output;
194
+ }
195
+ output.push(curr);
196
+ } else if (prevInside) {
197
+ const pt = lineIntersection(prev, curr, edgeStart, edgeEnd);
198
+ if (pt) {
199
+ output.push(pt);
200
+ }
201
+ }
202
+ }
203
+ return output;
239
204
  }
240
- /** Clip subject polygon by clip polygon using Sutherland-Hodgman */
241
205
  function clipPolygon(subject, clip) {
242
- let output = [...subject];
243
- for (let i = 0; i < clip.length; i++) {
244
- if (output.length === 0) {
245
- break;
246
- }
247
- const edgeStart = clip[i];
248
- const edgeEnd = clip[(i + 1) % clip.length];
249
- output = clipPolygonEdge(output, edgeStart, edgeEnd);
250
- }
251
- return output;
206
+ let output = [...subject];
207
+ for (let i = 0; i < clip.length; i++) {
208
+ if (output.length === 0) {
209
+ break;
210
+ }
211
+ const edgeStart = clip[i];
212
+ const edgeEnd = clip[(i + 1) % clip.length];
213
+ output = clipPolygonEdge(output, edgeStart, edgeEnd);
214
+ }
215
+ return output;
252
216
  }
253
- /* ────────────────────────────────────────────────────────────────
254
- BOOLEAN OPERATIONS
255
- ──────────────────────────────────────────────────────────────── */
256
- /** Union of two polygons (approximation using convex hull on combined points + edge walking) */
257
217
  function unionPolygons(polyA, polyB) {
258
- // Simple approach: collect vertices from both polygons that are outside the other
259
- // plus intersection points, then create convex hull or walk the outline
260
- const result = [];
261
- // Add points from A that are outside B or on the boundary
262
- for (const pt of polyA) {
263
- if (!pointInPolygon(pt, polyB)) {
264
- result.push(pt);
265
- }
266
- }
267
- // Add points from B that are outside A
268
- for (const pt of polyB) {
269
- if (!pointInPolygon(pt, polyA)) {
270
- result.push(pt);
271
- }
272
- }
273
- // Add intersection points
274
- result.push(...findIntersections(polyA, polyB));
275
- if (result.length < 3) {
276
- // One polygon is inside the other - return the larger one
277
- const areaA = Math.abs(polygonArea(polyA));
278
- const areaB = Math.abs(polygonArea(polyB));
279
- return areaA > areaB ? [...polyA] : [...polyB];
280
- }
281
- // Create convex hull of all points
282
- return convexHull(result);
218
+ const result = [];
219
+ for (const pt of polyA) {
220
+ if (!pointInPolygon(pt, polyB)) {
221
+ result.push(pt);
222
+ }
223
+ }
224
+ for (const pt of polyB) {
225
+ if (!pointInPolygon(pt, polyA)) {
226
+ result.push(pt);
227
+ }
228
+ }
229
+ result.push(...findIntersections(polyA, polyB));
230
+ if (result.length < 3) {
231
+ const areaA = Math.abs(polygonArea(polyA));
232
+ const areaB = Math.abs(polygonArea(polyB));
233
+ return areaA > areaB ? [...polyA] : [...polyB];
234
+ }
235
+ return convexHull(result);
283
236
  }
284
- /** Intersection of two polygons */
285
237
  function intersectPolygons(polyA, polyB) {
286
- // Use Sutherland-Hodgman clipping
287
- // Ensure polygons are counterclockwise
288
- const areaA = polygonArea(polyA);
289
- const areaB = polygonArea(polyB);
290
- const normA = areaA < 0 ? [...polyA].reverse() : polyA;
291
- const normB = areaB < 0 ? [...polyB].reverse() : polyB;
292
- return clipPolygon(normA, normB);
238
+ const areaA = polygonArea(polyA);
239
+ const areaB = polygonArea(polyB);
240
+ const normA = areaA < 0 ? [...polyA].reverse() : polyA;
241
+ const normB = areaB < 0 ? [...polyB].reverse() : polyB;
242
+ return clipPolygon(normA, normB);
293
243
  }
294
- /** Difference: A - B */
295
244
  function differencePolygons(polyA, polyB) {
296
- // Simple approach: A minus B
297
- // Get points from A that are outside B
298
- const result = [];
299
- for (const pt of polyA) {
300
- if (!pointInPolygon(pt, polyB)) {
301
- result.push(pt);
302
- }
303
- }
304
- // Add intersection points
305
- const intersections = findIntersections(polyA, polyB);
306
- result.push(...intersections);
307
- if (result.length < 3) {
308
- // B completely covers A
309
- return [];
310
- }
311
- // Sort points to form proper polygon
312
- return sortPolygonPoints(result);
245
+ const result = [];
246
+ for (const pt of polyA) {
247
+ if (!pointInPolygon(pt, polyB)) {
248
+ result.push(pt);
249
+ }
250
+ }
251
+ const intersections = findIntersections(polyA, polyB);
252
+ result.push(...intersections);
253
+ if (result.length < 3) {
254
+ return [];
255
+ }
256
+ return sortPolygonPoints(result);
313
257
  }
314
- /** XOR / Exclusion: (A ∪ B) - (A ∩ B) */
315
258
  function exclusionPolygons(polyA, polyB) {
316
- // Returns potentially multiple polygons
317
- const intersection = intersectPolygons(polyA, polyB);
318
- if (intersection.length < 3) {
319
- // No overlap, return both
320
- return [polyA, polyB];
321
- }
322
- // Return difference of each with intersection
323
- const diffA = differencePolygons(polyA, polyB);
324
- const diffB = differencePolygons(polyB, polyA);
325
- const results = [];
326
- if (diffA.length >= 3) {
327
- results.push(diffA);
328
- }
329
- if (diffB.length >= 3) {
330
- results.push(diffB);
331
- }
332
- return results;
259
+ const intersection = intersectPolygons(polyA, polyB);
260
+ if (intersection.length < 3) {
261
+ return [polyA, polyB];
262
+ }
263
+ const diffA = differencePolygons(polyA, polyB);
264
+ const diffB = differencePolygons(polyB, polyA);
265
+ const results = [];
266
+ if (diffA.length >= 3) {
267
+ results.push(diffA);
268
+ }
269
+ if (diffB.length >= 3) {
270
+ results.push(diffB);
271
+ }
272
+ return results;
333
273
  }
334
- /** Sort polygon points in CCW order around centroid */
335
274
  function sortPolygonPoints(points) {
336
- if (points.length < 3) {
337
- return points;
338
- }
339
- const center = polygonCentroid(points);
340
- return [...points].sort((a, b) => {
341
- const angleA = Math.atan2(a.y - center.y, a.x - center.x);
342
- const angleB = Math.atan2(b.y - center.y, b.x - center.x);
343
- return angleA - angleB;
344
- });
275
+ if (points.length < 3) {
276
+ return points;
277
+ }
278
+ const center = polygonCentroid(points);
279
+ return [...points].sort((a, b) => {
280
+ const angleA = Math.atan2(a.y - center.y, a.x - center.x);
281
+ const angleB = Math.atan2(b.y - center.y, b.x - center.x);
282
+ return angleA - angleB;
283
+ });
345
284
  }
346
- /** Compute convex hull using Graham scan */
347
285
  function convexHull(points) {
348
- if (points.length < 3) {
349
- return points;
350
- }
351
- // Find bottom-most point
352
- let start = 0;
353
- for (let i = 1; i < points.length; i++) {
354
- if (points[i].y < points[start].y ||
355
- (points[i].y === points[start].y && points[i].x < points[start].x)) {
356
- start = i;
357
- }
358
- }
359
- const pivot = points[start];
360
- // Sort by polar angle
361
- const sorted = points
362
- .filter((_, i) => i !== start)
363
- .map((p) => ({
364
- point: p,
365
- angle: Math.atan2(p.y - pivot.y, p.x - pivot.x),
366
- dist: Math.hypot(p.x - pivot.x, p.y - pivot.y),
367
- }))
368
- .sort((a, b) => a.angle - b.angle || a.dist - b.dist)
369
- .map((p) => p.point);
370
- // Graham scan
371
- const hull = [pivot];
372
- for (const pt of sorted) {
373
- while (hull.length > 1) {
374
- const a = hull[hull.length - 2];
375
- const b = hull[hull.length - 1];
376
- const cross = (b.x - a.x) * (pt.y - a.y) - (b.y - a.y) * (pt.x - a.x);
377
- if (cross > 0) {
378
- break;
379
- }
380
- hull.pop();
381
- }
382
- hull.push(pt);
383
- }
384
- return hull;
286
+ if (points.length < 3) {
287
+ return points;
288
+ }
289
+ let start = 0;
290
+ for (let i = 1; i < points.length; i++) {
291
+ if (points[i].y < points[start].y || points[i].y === points[start].y && points[i].x < points[start].x) {
292
+ start = i;
293
+ }
294
+ }
295
+ const pivot = points[start];
296
+ const sorted = points.filter((_, i) => i !== start).map((p) => ({
297
+ point: p,
298
+ angle: Math.atan2(p.y - pivot.y, p.x - pivot.x),
299
+ dist: Math.hypot(p.x - pivot.x, p.y - pivot.y)
300
+ })).sort((a, b) => a.angle - b.angle || a.dist - b.dist).map((p) => p.point);
301
+ const hull = [pivot];
302
+ for (const pt of sorted) {
303
+ while (hull.length > 1) {
304
+ const a = hull[hull.length - 2];
305
+ const b = hull[hull.length - 1];
306
+ const cross = (b.x - a.x) * (pt.y - a.y) - (b.y - a.y) * (pt.x - a.x);
307
+ if (cross > 0) {
308
+ break;
309
+ }
310
+ hull.pop();
311
+ }
312
+ hull.push(pt);
313
+ }
314
+ return hull;
385
315
  }
386
- /* ────────────────────────────────────────────────────────────────
387
- PUBLIC API
388
- ──────────────────────────────────────────────────────────────── */
389
- /** Perform boolean operation on two shapes */
390
316
  function booleanOperation(shapeA, shapeB, operation) {
391
- const polyA = shapeToPolygon(shapeA);
392
- const polyB = shapeToPolygon(shapeB);
393
- let resultPolygons;
394
- switch (operation) {
395
- case 'union':
396
- resultPolygons = [unionPolygons(polyA, polyB)];
397
- break;
398
- case 'intersection': {
399
- const intersection = intersectPolygons(polyA, polyB);
400
- if (intersection.length < 3) {
401
- return null;
402
- }
403
- resultPolygons = [intersection];
404
- break;
405
- }
406
- case 'difference': {
407
- const difference = differencePolygons(polyA, polyB);
408
- if (difference.length < 3) {
409
- return null;
410
- }
411
- resultPolygons = [difference];
412
- break;
413
- }
414
- case 'exclusion':
415
- resultPolygons = exclusionPolygons(polyA, polyB);
416
- if (resultPolygons.length === 0) {
417
- return null;
418
- }
419
- break;
420
- default:
421
- return null;
422
- }
423
- // Convert resulting polygons back to VShapes
424
- const shapes = resultPolygons
425
- .filter((poly) => poly.length >= 3)
426
- .map((poly) => polygonToShape(poly, shapeA));
427
- if (shapes.length === 0) {
317
+ const polyA = shapeToPolygon(shapeA);
318
+ const polyB = shapeToPolygon(shapeB);
319
+ let resultPolygons;
320
+ switch (operation) {
321
+ case "union":
322
+ resultPolygons = [unionPolygons(polyA, polyB)];
323
+ break;
324
+ case "intersection": {
325
+ const intersection = intersectPolygons(polyA, polyB);
326
+ if (intersection.length < 3) {
428
327
  return null;
328
+ }
329
+ resultPolygons = [intersection];
330
+ break;
429
331
  }
430
- if (shapes.length === 1) {
431
- return shapes[0];
332
+ case "difference": {
333
+ const difference = differencePolygons(polyA, polyB);
334
+ if (difference.length < 3) {
335
+ return null;
336
+ }
337
+ resultPolygons = [difference];
338
+ break;
432
339
  }
433
- return shapes;
340
+ case "exclusion":
341
+ resultPolygons = exclusionPolygons(polyA, polyB);
342
+ if (resultPolygons.length === 0) {
343
+ return null;
344
+ }
345
+ break;
346
+ default:
347
+ return null;
348
+ }
349
+ const shapes = resultPolygons.filter((poly) => poly.length >= 3).map((poly) => polygonToShape(poly, shapeA));
350
+ if (shapes.length === 0) {
351
+ return null;
352
+ }
353
+ if (shapes.length === 1) {
354
+ return shapes[0];
355
+ }
356
+ return shapes;
434
357
  }
435
- /** Convert polygon points back to a VShape */
436
358
  function polygonToShape(polygon, template) {
437
- const bounds = polygonBounds(polygon);
438
- return {
439
- id: sid(),
440
- type: 'polyline',
441
- x: bounds.minX,
442
- y: bounds.minY,
443
- w: bounds.maxX - bounds.minX,
444
- h: bounds.maxY - bounds.minY,
445
- points: polygon.map((pt) => ({
446
- x: pt.x - bounds.minX,
447
- y: pt.y - bounds.minY,
448
- })),
449
- fill: template.fill,
450
- stroke: template.stroke,
451
- strokeWidth: template.strokeWidth,
452
- opacity: template.opacity,
453
- rotation: 0,
454
- };
359
+ const bounds = polygonBounds(polygon);
360
+ return {
361
+ id: sid(),
362
+ type: "polyline",
363
+ x: bounds.minX,
364
+ y: bounds.minY,
365
+ w: bounds.maxX - bounds.minX,
366
+ h: bounds.maxY - bounds.minY,
367
+ points: polygon.map((pt) => ({
368
+ x: pt.x - bounds.minX,
369
+ y: pt.y - bounds.minY
370
+ })),
371
+ fill: template.fill,
372
+ stroke: template.stroke,
373
+ strokeWidth: template.strokeWidth,
374
+ opacity: template.opacity,
375
+ rotation: 0
376
+ };
455
377
  }
456
- /** Check if two shapes overlap */
457
378
  function shapesOverlap(shapeA, shapeB) {
458
- const polyA = shapeToPolygon(shapeA);
459
- const polyB = shapeToPolygon(shapeB);
460
- // Check if any point of A is inside B
461
- for (const pt of polyA) {
462
- if (pointInPolygon(pt, polyB)) {
463
- return true;
464
- }
465
- }
466
- // Check if any point of B is inside A
467
- for (const pt of polyB) {
468
- if (pointInPolygon(pt, polyA)) {
469
- return true;
470
- }
471
- }
472
- // Check for edge intersections
473
- return findIntersections(polyA, polyB).length > 0;
379
+ const polyA = shapeToPolygon(shapeA);
380
+ const polyB = shapeToPolygon(shapeB);
381
+ for (const pt of polyA) {
382
+ if (pointInPolygon(pt, polyB)) {
383
+ return true;
384
+ }
385
+ }
386
+ for (const pt of polyB) {
387
+ if (pointInPolygon(pt, polyA)) {
388
+ return true;
389
+ }
390
+ }
391
+ return findIntersections(polyA, polyB).length > 0;
474
392
  }
475
- /** Flatten a polyline to individual segment shapes (for outline stroke) */
476
393
  function flattenToOutline(shape, strokeWidth) {
477
- const sw = strokeWidth !== null && strokeWidth !== void 0 ? strokeWidth : shape.strokeWidth;
478
- const points = shape.points || shapeToPolygon(shape);
479
- // Create offset outline
480
- const halfW = sw / 2;
481
- const outer = [];
482
- const inner = [];
483
- for (let i = 0; i < points.length; i++) {
484
- const curr = points[i];
485
- const prev = points[(i + points.length - 1) % points.length];
486
- const next = points[(i + 1) % points.length];
487
- // Calculate perpendicular direction
488
- const dx1 = curr.x - prev.x;
489
- const dy1 = curr.y - prev.y;
490
- const dx2 = next.x - curr.x;
491
- const dy2 = next.y - curr.y;
492
- // Average normal
493
- const len1 = Math.hypot(dx1, dy1) || 1;
494
- const len2 = Math.hypot(dx2, dy2) || 1;
495
- const nx = (-dy1 / len1 - dy2 / len2) / 2;
496
- const ny = (dx1 / len1 + dx2 / len2) / 2;
497
- const nlen = Math.hypot(nx, ny) || 1;
498
- outer.push({
499
- x: curr.x + (nx / nlen) * halfW,
500
- y: curr.y + (ny / nlen) * halfW,
501
- });
502
- inner.push({
503
- x: curr.x - (nx / nlen) * halfW,
504
- y: curr.y - (ny / nlen) * halfW,
505
- });
506
- }
507
- // Combine outer + reversed inner
508
- const outline = [...outer, ...inner.reverse()];
509
- const bounds = polygonBounds(outline);
510
- return {
511
- id: sid(),
512
- type: 'polyline',
513
- x: bounds.minX,
514
- y: bounds.minY,
515
- w: bounds.maxX - bounds.minX,
516
- h: bounds.maxY - bounds.minY,
517
- points: outline.map((pt) => ({
518
- x: pt.x - bounds.minX,
519
- y: pt.y - bounds.minY,
520
- })),
521
- fill: shape.stroke, // Fill with original stroke color
522
- stroke: 'transparent',
523
- strokeWidth: 0,
524
- opacity: shape.opacity,
525
- rotation: 0,
526
- };
394
+ const sw = strokeWidth ?? shape.strokeWidth;
395
+ const points = shape.points || shapeToPolygon(shape);
396
+ const halfW = sw / 2;
397
+ const outer = [];
398
+ const inner = [];
399
+ for (let i = 0; i < points.length; i++) {
400
+ const curr = points[i];
401
+ const prev = points[(i + points.length - 1) % points.length];
402
+ const next = points[(i + 1) % points.length];
403
+ const dx1 = curr.x - prev.x;
404
+ const dy1 = curr.y - prev.y;
405
+ const dx2 = next.x - curr.x;
406
+ const dy2 = next.y - curr.y;
407
+ const len1 = Math.hypot(dx1, dy1) || 1;
408
+ const len2 = Math.hypot(dx2, dy2) || 1;
409
+ const nx = (-dy1 / len1 - dy2 / len2) / 2;
410
+ const ny = (dx1 / len1 + dx2 / len2) / 2;
411
+ const nlen = Math.hypot(nx, ny) || 1;
412
+ outer.push({
413
+ x: curr.x + nx / nlen * halfW,
414
+ y: curr.y + ny / nlen * halfW
415
+ });
416
+ inner.push({
417
+ x: curr.x - nx / nlen * halfW,
418
+ y: curr.y - ny / nlen * halfW
419
+ });
420
+ }
421
+ const outline = [...outer, ...inner.reverse()];
422
+ const bounds = polygonBounds(outline);
423
+ return {
424
+ id: sid(),
425
+ type: "polyline",
426
+ x: bounds.minX,
427
+ y: bounds.minY,
428
+ w: bounds.maxX - bounds.minX,
429
+ h: bounds.maxY - bounds.minY,
430
+ points: outline.map((pt) => ({
431
+ x: pt.x - bounds.minX,
432
+ y: pt.y - bounds.minY
433
+ })),
434
+ fill: shape.stroke,
435
+ // Fill with original stroke color
436
+ stroke: "transparent",
437
+ strokeWidth: 0,
438
+ opacity: shape.opacity,
439
+ rotation: 0
440
+ };
527
441
  }
528
-
529
- export { booleanOperation, flattenToOutline, shapesOverlap };
442
+ export {
443
+ booleanOperation,
444
+ flattenToOutline,
445
+ shapesOverlap
446
+ };