@nice2dev/ui-graphics 1.3.15 → 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.
- package/CHANGELOG.md +9 -0
- package/dist/cjs/animation/AnimatedPerson.js +470 -143
- package/dist/cjs/animation/AnimationEditor.js +289 -101
- package/dist/cjs/animation/Audience.js +83 -61
- package/dist/cjs/animation/BodyRenderer.js +69 -28
- package/dist/cjs/animation/animationHelper.js +281 -288
- package/dist/cjs/animation/characterTypes.js +200 -203
- package/dist/cjs/animation/choreoDSL.js +205 -219
- package/dist/cjs/animation/choreography.js +4 -32
- package/dist/cjs/animation/karaokeIntegration.js +68 -77
- package/dist/cjs/animation/lottieExport.js +108 -114
- package/dist/cjs/animation/motionPath.js +225 -248
- package/dist/cjs/animation/physics.js +227 -254
- package/dist/cjs/animation/rig/RigPlayer.js +53 -56
- package/dist/cjs/animation/rig/RiggedBody.js +71 -26
- package/dist/cjs/animation/rig/choreoRigBridge.js +72 -77
- package/dist/cjs/animation/rig/ik.js +13 -18
- package/dist/cjs/animation/rig/poseOps.js +34 -41
- package/dist/cjs/animation/rig/presets.js +64 -72
- package/dist/cjs/animation/rig/rigMath.js +25 -32
- package/dist/cjs/animation/rig/rigTypes.js +59 -63
- package/dist/cjs/animation/shapes/arms.js +31 -13
- package/dist/cjs/animation/shapes/eyes.js +129 -47
- package/dist/cjs/animation/shapes/hair.js +72 -45
- package/dist/cjs/animation/shapes/heads.js +131 -57
- package/dist/cjs/animation/shapes/headwear.js +47 -38
- package/dist/cjs/animation/shapes/legs.js +31 -13
- package/dist/cjs/animation/shapes/mouths.js +120 -44
- package/dist/cjs/animation/shapes/noses.js +80 -25
- package/dist/cjs/animation/shapes/outfits.js +101 -39
- package/dist/cjs/animation/shapes/torsos.js +15 -11
- package/dist/cjs/animation/spineExport.js +103 -121
- package/dist/cjs/core/LocalUI.js +108 -43
- package/dist/cjs/core/NiceBrushEngine.js +758 -552
- package/dist/cjs/core/collaboration.js +249 -251
- package/dist/cjs/core/colorBlindness.js +100 -115
- package/dist/cjs/core/dragDrop.js +155 -158
- package/dist/cjs/core/editorAsk.js +51 -41
- package/dist/cjs/core/fixes.js +140 -171
- package/dist/cjs/core/gridGuides.js +166 -201
- package/dist/cjs/core/historyVisual.js +156 -135
- package/dist/cjs/core/i18n.js +13 -14
- package/dist/cjs/core/integrations.js +238 -249
- package/dist/cjs/core/minimap.js +149 -151
- package/dist/cjs/core/plugins.js +98 -113
- package/dist/cjs/core/rtl.js +71 -83
- package/dist/cjs/core/shortcuts.js +196 -225
- package/dist/cjs/core/theme.js +142 -149
- package/dist/cjs/core/yjsCollaboration.js +232 -293
- package/dist/cjs/design/design-tools.js +12 -22
- package/dist/cjs/font/NiceFontEditor.js +877 -316
- package/dist/cjs/font/fontFormatUtils.js +388 -448
- package/dist/cjs/game/GameAsset2dEditor.js +1201 -392
- package/dist/cjs/game/GameAsset2dEditor.module.css.js +146 -5
- package/dist/cjs/game/gameAssetTypes.js +7 -16
- package/dist/cjs/game/gameAssetUtils.js +390 -413
- package/dist/cjs/game/useGameAssetEditor.js +820 -763
- package/dist/cjs/index.js +81 -84
- package/dist/cjs/nice2dev-ui-graphics.css +1 -1
- package/dist/cjs/node_modules/lib0/array.js +8 -70
- package/dist/cjs/node_modules/lib0/binary.js +3 -20
- package/dist/cjs/node_modules/lib0/broadcastchannel.js +24 -86
- package/dist/cjs/node_modules/lib0/buffer.js +15 -82
- package/dist/cjs/node_modules/lib0/conditions.js +3 -16
- package/dist/cjs/node_modules/lib0/decoding.js +108 -287
- package/dist/cjs/node_modules/lib0/dom.js +14 -44
- package/dist/cjs/node_modules/lib0/encoding.js +68 -418
- package/dist/cjs/node_modules/lib0/environment.js +26 -92
- package/dist/cjs/node_modules/lib0/error.js +5 -29
- package/dist/cjs/node_modules/lib0/function.js +27 -66
- package/dist/cjs/node_modules/lib0/iterator.js +8 -33
- package/dist/cjs/node_modules/lib0/logging.common.js +12 -22
- package/dist/cjs/node_modules/lib0/logging.js +32 -71
- package/dist/cjs/node_modules/lib0/map.js +16 -86
- package/dist/cjs/node_modules/lib0/math.js +3 -32
- package/dist/cjs/node_modules/lib0/number.js +4 -15
- package/dist/cjs/node_modules/lib0/object.js +14 -88
- package/dist/cjs/node_modules/lib0/observable.js +38 -78
- package/dist/cjs/node_modules/lib0/pair.js +3 -20
- package/dist/cjs/node_modules/lib0/prng.js +8 -77
- package/dist/cjs/node_modules/lib0/promise.js +6 -30
- package/dist/cjs/node_modules/lib0/random.js +8 -23
- package/dist/cjs/node_modules/lib0/schema.js +313 -687
- package/dist/cjs/node_modules/lib0/set.js +3 -10
- package/dist/cjs/node_modules/lib0/storage.js +20 -55
- package/dist/cjs/node_modules/lib0/string.js +17 -72
- package/dist/cjs/node_modules/lib0/symbol.js +2 -12
- package/dist/cjs/node_modules/lib0/time.js +2 -15
- package/dist/cjs/node_modules/lib0/trait/equality.js +3 -28
- package/dist/cjs/node_modules/lib0/url.js +4 -18
- package/dist/cjs/node_modules/lib0/webcrypto.js +2 -5
- package/dist/cjs/node_modules/y-protocols/auth.js +5 -19
- package/dist/cjs/node_modules/y-protocols/awareness.js +53 -116
- package/dist/cjs/node_modules/y-protocols/sync.js +16 -103
- package/dist/cjs/node_modules/y-websocket/src/y-websocket.js +77 -219
- package/dist/cjs/node_modules/yjs/dist/yjs.js +2128 -4868
- package/dist/cjs/photo/FilterThumb.js +37 -25
- package/dist/cjs/photo/NiceAITools.js +679 -467
- package/dist/cjs/photo/PhotoEditor.js +34 -20
- package/dist/cjs/photo/PhotoEditor.module.css.js +296 -5
- package/dist/cjs/photo/PhotoEditorCanvas.js +250 -91
- package/dist/cjs/photo/PhotoEditorLeftPanel.js +1086 -224
- package/dist/cjs/photo/PhotoEditorToolbar.js +135 -19
- package/dist/cjs/photo/photoEditorActions.js +89 -72
- package/dist/cjs/photo/photoEditorTypes.js +29 -32
- package/dist/cjs/photo/photoFilters.js +959 -985
- package/dist/cjs/photo/photoOverlays.js +899 -945
- package/dist/cjs/photo/photoSelection.js +427 -499
- package/dist/cjs/photo/usePhotoEditor.js +739 -741
- package/dist/cjs/pixel/HSVPicker.js +191 -175
- package/dist/cjs/pixel/PixelEditor.js +159 -48
- package/dist/cjs/pixel/PixelEditor.module.css.js +209 -5
- package/dist/cjs/pixel/PixelEditorCanvas.js +29 -16
- package/dist/cjs/pixel/PixelEditorMenuBar.js +160 -162
- package/dist/cjs/pixel/PixelEditorRightPanel.js +377 -45
- package/dist/cjs/pixel/PixelEditorStatusBar.js +47 -20
- package/dist/cjs/pixel/PixelEditorTimeline.js +106 -23
- package/dist/cjs/pixel/PixelEditorToolbar.js +99 -21
- package/dist/cjs/pixel/SpriteGeneratorPanel.js +483 -303
- package/dist/cjs/pixel/animalGeneratorPresets.js +776 -601
- package/dist/cjs/pixel/asepriteFormat.js +511 -560
- package/dist/cjs/pixel/buildingGeneratorPresets.js +693 -520
- package/dist/cjs/pixel/characterGeneratorPresets.js +644 -544
- package/dist/cjs/pixel/pixel-pro.js +52 -63
- package/dist/cjs/pixel/pixelEditorExports.js +388 -428
- package/dist/cjs/pixel/pixelEditorTypes.js +91 -108
- package/dist/cjs/pixel/pixelEditorUtils.js +519 -561
- package/dist/cjs/pixel/spriteGeneratorCore.js +206 -257
- package/dist/cjs/pixel/usePixelEditor.js +1321 -1343
- package/dist/cjs/texture/Nice3DTexturePainter.js +707 -440
- package/dist/cjs/ui/NiceUIDesigner.js +954 -527
- package/dist/cjs/vector/NiceSvgToCode.js +586 -446
- package/dist/cjs/vector/VectorEditor.js +290 -117
- package/dist/cjs/vector/VectorEditor.module.css.js +86 -5
- package/dist/cjs/vector/VectorEditorMenuBar.js +333 -275
- package/dist/cjs/vector/VectorEditorRightPanel.js +1094 -371
- package/dist/cjs/vector/VectorEditorShapeRenderer.js +198 -60
- package/dist/cjs/vector/VectorEditorStatusBar.js +39 -13
- package/dist/cjs/vector/useVectorEditor.js +848 -889
- package/dist/cjs/vector/vectorAnimation.js +110 -137
- package/dist/cjs/vector/vectorBooleanOps.js +398 -485
- package/dist/cjs/vector/vectorEditorExport.js +201 -203
- package/dist/cjs/vector/vectorEditorImport.js +173 -189
- package/dist/cjs/vector/vectorEditorTypes.js +42 -49
- package/dist/cjs/vector/vectorGradients.js +151 -174
- package/dist/cjs/vector/vectorIconExport.js +52 -59
- package/dist/cjs/vector/vectorIconImport.js +169 -219
- package/dist/esm/animation/AnimatedPerson.js +470 -141
- package/dist/esm/animation/AnimationEditor.js +290 -100
- package/dist/esm/animation/Audience.js +83 -59
- package/dist/esm/animation/BodyRenderer.js +71 -25
- package/dist/esm/animation/animationHelper.js +284 -287
- package/dist/esm/animation/characterTypes.js +204 -202
- package/dist/esm/animation/choreoDSL.js +212 -218
- package/dist/esm/animation/choreography.js +8 -31
- package/dist/esm/animation/karaokeIntegration.js +74 -76
- package/dist/esm/animation/lottieExport.js +112 -113
- package/dist/esm/animation/motionPath.js +235 -247
- package/dist/esm/animation/physics.js +235 -253
- package/dist/esm/animation/rig/RigPlayer.js +53 -54
- package/dist/esm/animation/rig/RiggedBody.js +71 -24
- package/dist/esm/animation/rig/choreoRigBridge.js +74 -76
- package/dist/esm/animation/rig/ik.js +14 -17
- package/dist/esm/animation/rig/poseOps.js +37 -40
- package/dist/esm/animation/rig/presets.js +70 -71
- package/dist/esm/animation/rig/rigMath.js +28 -31
- package/dist/esm/animation/rig/rigTypes.js +62 -62
- package/dist/esm/animation/shapes/arms.js +32 -12
- package/dist/esm/animation/shapes/eyes.js +135 -46
- package/dist/esm/animation/shapes/hair.js +80 -44
- package/dist/esm/animation/shapes/heads.js +140 -56
- package/dist/esm/animation/shapes/headwear.js +54 -37
- package/dist/esm/animation/shapes/legs.js +32 -12
- package/dist/esm/animation/shapes/mouths.js +127 -43
- package/dist/esm/animation/shapes/noses.js +87 -24
- package/dist/esm/animation/shapes/outfits.js +106 -38
- package/dist/esm/animation/shapes/torsos.js +16 -10
- package/dist/esm/animation/spineExport.js +106 -120
- package/dist/esm/core/LocalUI.js +111 -42
- package/dist/esm/core/NiceBrushEngine.js +761 -551
- package/dist/esm/core/collaboration.js +255 -250
- package/dist/esm/core/colorBlindness.js +106 -114
- package/dist/esm/core/dragDrop.js +157 -157
- package/dist/esm/core/editorAsk.js +53 -40
- package/dist/esm/core/fixes.js +145 -170
- package/dist/esm/core/gridGuides.js +178 -200
- package/dist/esm/core/historyVisual.js +158 -134
- package/dist/esm/core/i18n.js +15 -13
- package/dist/esm/core/integrations.js +244 -248
- package/dist/esm/core/minimap.js +151 -150
- package/dist/esm/core/plugins.js +102 -112
- package/dist/esm/core/rtl.js +77 -82
- package/dist/esm/core/shortcuts.js +200 -224
- package/dist/esm/core/theme.js +150 -148
- package/dist/esm/core/yjsCollaboration.js +240 -292
- package/dist/esm/design/design-tools.js +13 -21
- package/dist/esm/font/NiceFontEditor.js +880 -315
- package/dist/esm/font/fontFormatUtils.js +394 -447
- package/dist/esm/game/GameAsset2dEditor.js +1202 -391
- package/dist/esm/game/GameAsset2dEditor.module.css.js +146 -3
- package/dist/esm/game/gameAssetTypes.js +8 -15
- package/dist/esm/game/gameAssetUtils.js +408 -412
- package/dist/esm/game/useGameAssetEditor.js +821 -762
- package/dist/esm/index.js +332 -69
- package/dist/esm/nice2dev-ui-graphics.css +1 -1
- package/dist/esm/node_modules/lib0/array.js +15 -69
- package/dist/esm/node_modules/lib0/binary.js +14 -19
- package/dist/esm/node_modules/lib0/broadcastchannel.js +27 -85
- package/dist/esm/node_modules/lib0/buffer.js +20 -80
- package/dist/esm/node_modules/lib0/conditions.js +4 -15
- package/dist/esm/node_modules/lib0/decoding.js +128 -286
- package/dist/esm/node_modules/lib0/dom.js +20 -43
- package/dist/esm/node_modules/lib0/encoding.js +90 -415
- package/dist/esm/node_modules/lib0/environment.js +33 -91
- package/dist/esm/node_modules/lib0/error.js +8 -28
- package/dist/esm/node_modules/lib0/function.js +31 -65
- package/dist/esm/node_modules/lib0/iterator.js +11 -32
- package/dist/esm/node_modules/lib0/logging.common.js +22 -21
- package/dist/esm/node_modules/lib0/logging.js +44 -70
- package/dist/esm/node_modules/lib0/map.js +21 -85
- package/dist/esm/node_modules/lib0/math.js +9 -31
- package/dist/esm/node_modules/lib0/number.js +7 -14
- package/dist/esm/node_modules/lib0/object.js +26 -87
- package/dist/esm/node_modules/lib0/observable.js +40 -77
- package/dist/esm/node_modules/lib0/pair.js +5 -19
- package/dist/esm/node_modules/lib0/prng.js +15 -76
- package/dist/esm/node_modules/lib0/promise.js +7 -29
- package/dist/esm/node_modules/lib0/random.js +10 -22
- package/dist/esm/node_modules/lib0/schema.js +367 -686
- package/dist/esm/node_modules/lib0/set.js +4 -9
- package/dist/esm/node_modules/lib0/storage.js +23 -54
- package/dist/esm/node_modules/lib0/string.js +26 -71
- package/dist/esm/node_modules/lib0/symbol.js +3 -11
- package/dist/esm/node_modules/lib0/time.js +3 -14
- package/dist/esm/node_modules/lib0/trait/equality.js +5 -27
- package/dist/esm/node_modules/lib0/url.js +5 -17
- package/dist/esm/node_modules/lib0/webcrypto.js +3 -4
- package/dist/esm/node_modules/y-protocols/auth.js +7 -18
- package/dist/esm/node_modules/y-protocols/awareness.js +58 -115
- package/dist/esm/node_modules/y-protocols/sync.js +26 -102
- package/dist/esm/node_modules/y-websocket/src/y-websocket.js +82 -218
- package/dist/esm/node_modules/yjs/dist/yjs.js +2263 -4899
- package/dist/esm/photo/FilterThumb.js +37 -23
- package/dist/esm/photo/NiceAITools.js +685 -466
- package/dist/esm/photo/PhotoEditor.js +34 -18
- package/dist/esm/photo/PhotoEditor.module.css.js +296 -3
- package/dist/esm/photo/PhotoEditorCanvas.js +250 -89
- package/dist/esm/photo/PhotoEditorLeftPanel.js +1086 -222
- package/dist/esm/photo/PhotoEditorToolbar.js +135 -17
- package/dist/esm/photo/photoEditorActions.js +93 -71
- package/dist/esm/photo/photoEditorTypes.js +32 -31
- package/dist/esm/photo/photoFilters.js +967 -984
- package/dist/esm/photo/photoOverlays.js +913 -944
- package/dist/esm/photo/photoSelection.js +438 -498
- package/dist/esm/photo/usePhotoEditor.js +740 -740
- package/dist/esm/pixel/HSVPicker.js +192 -174
- package/dist/esm/pixel/PixelEditor.js +160 -47
- package/dist/esm/pixel/PixelEditor.module.css.js +209 -3
- package/dist/esm/pixel/PixelEditorCanvas.js +29 -14
- package/dist/esm/pixel/PixelEditorMenuBar.js +160 -160
- package/dist/esm/pixel/PixelEditorRightPanel.js +377 -43
- package/dist/esm/pixel/PixelEditorStatusBar.js +47 -18
- package/dist/esm/pixel/PixelEditorTimeline.js +106 -21
- package/dist/esm/pixel/PixelEditorToolbar.js +99 -19
- package/dist/esm/pixel/SpriteGeneratorPanel.js +483 -301
- package/dist/esm/pixel/animalGeneratorPresets.js +782 -600
- package/dist/esm/pixel/asepriteFormat.js +517 -559
- package/dist/esm/pixel/buildingGeneratorPresets.js +699 -519
- package/dist/esm/pixel/characterGeneratorPresets.js +650 -543
- package/dist/esm/pixel/pixel-pro.js +53 -62
- package/dist/esm/pixel/pixelEditorExports.js +397 -427
- package/dist/esm/pixel/pixelEditorTypes.js +103 -107
- package/dist/esm/pixel/pixelEditorUtils.js +551 -560
- package/dist/esm/pixel/spriteGeneratorCore.js +223 -256
- package/dist/esm/pixel/usePixelEditor.js +1322 -1342
- package/dist/esm/texture/Nice3DTexturePainter.js +709 -439
- package/dist/esm/ui/NiceUIDesigner.js +957 -526
- package/dist/esm/vector/NiceSvgToCode.js +589 -445
- package/dist/esm/vector/VectorEditor.js +291 -116
- package/dist/esm/vector/VectorEditor.module.css.js +86 -3
- package/dist/esm/vector/VectorEditorMenuBar.js +333 -273
- package/dist/esm/vector/VectorEditorRightPanel.js +1094 -369
- package/dist/esm/vector/VectorEditorShapeRenderer.js +199 -59
- package/dist/esm/vector/VectorEditorStatusBar.js +39 -11
- package/dist/esm/vector/useVectorEditor.js +849 -888
- package/dist/esm/vector/vectorAnimation.js +115 -136
- package/dist/esm/vector/vectorBooleanOps.js +401 -484
- package/dist/esm/vector/vectorEditorExport.js +207 -202
- package/dist/esm/vector/vectorEditorImport.js +174 -188
- package/dist/esm/vector/vectorEditorTypes.js +50 -48
- package/dist/esm/vector/vectorGradients.js +158 -173
- package/dist/esm/vector/vectorIconExport.js +53 -58
- package/dist/esm/vector/vectorIconImport.js +171 -218
- package/package.json +9 -15
|
@@ -1,529 +1,446 @@
|
|
|
1
|
-
import { sid } from
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const
|
|
220
|
-
if (
|
|
221
|
-
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
|
|
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
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
-
|
|
431
|
-
|
|
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
|
-
|
|
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
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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
|
-
|
|
442
|
+
export {
|
|
443
|
+
booleanOperation,
|
|
444
|
+
flattenToOutline,
|
|
445
|
+
shapesOverlap
|
|
446
|
+
};
|