@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.
- package/CHANGELOG.md +13 -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,533 +1,446 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Boolean operations for vector shapes
|
|
7
|
-
* Implements union, difference, intersection, and exclusion operations
|
|
8
|
-
*/
|
|
9
|
-
/* ────────────────────────────────────────────────────────────────
|
|
10
|
-
SHAPE TO PATH CONVERSION
|
|
11
|
-
──────────────────────────────────────────────────────────────── */
|
|
12
|
-
/** Convert VShape to array of points representing its outline */
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const vectorEditorTypes = require("./vectorEditorTypes.js");
|
|
13
4
|
function shapeToPolygon(shape, resolution = 32) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
y: cy + ry * Math.sin(angle),
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
return ellipsePts;
|
|
41
|
-
}
|
|
42
|
-
case 'polygon': {
|
|
43
|
-
const sides = shape.sides || 6;
|
|
44
|
-
const polygonPts = [];
|
|
45
|
-
const r = Math.min(rx, ry);
|
|
46
|
-
for (let i = 0; i < sides; i++) {
|
|
47
|
-
const angle = (2 * Math.PI * i) / sides - Math.PI / 2 + (shape.rotation * Math.PI) / 180;
|
|
48
|
-
polygonPts.push({
|
|
49
|
-
x: cx + r * Math.cos(angle),
|
|
50
|
-
y: cy + r * Math.sin(angle),
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
return polygonPts;
|
|
54
|
-
}
|
|
55
|
-
case 'star': {
|
|
56
|
-
const points = shape.sides || 5;
|
|
57
|
-
const innerR = (shape.innerRadius || 0.4) * Math.min(rx, ry);
|
|
58
|
-
const outerR = Math.min(rx, ry);
|
|
59
|
-
const starPts = [];
|
|
60
|
-
for (let i = 0; i < points * 2; i++) {
|
|
61
|
-
const angle = (Math.PI * i) / points - Math.PI / 2 + (shape.rotation * Math.PI) / 180;
|
|
62
|
-
const rad = i % 2 === 0 ? outerR : innerR;
|
|
63
|
-
starPts.push({
|
|
64
|
-
x: cx + rad * Math.cos(angle),
|
|
65
|
-
y: cy + rad * Math.sin(angle),
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
return starPts;
|
|
69
|
-
}
|
|
70
|
-
case 'polyline':
|
|
71
|
-
return shape.points || [];
|
|
72
|
-
default:
|
|
73
|
-
// For line, arrow, text - return bounding box
|
|
74
|
-
return [
|
|
75
|
-
{ x: shape.x, y: shape.y },
|
|
76
|
-
{ x: shape.x + shape.w, y: shape.y },
|
|
77
|
-
{ x: shape.x + shape.w, y: shape.y + shape.h },
|
|
78
|
-
{ x: shape.x, y: shape.y + shape.h },
|
|
79
|
-
];
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
/** Generate rounded rectangle polygon */
|
|
83
|
-
function roundRectToPolygon(x, y, w, h, radius, resolution) {
|
|
84
|
-
const r = Math.min(radius, w / 2, h / 2);
|
|
85
|
-
const pts = [];
|
|
86
|
-
const cornerRes = Math.max(4, Math.floor(resolution / 4));
|
|
87
|
-
// Top-right corner
|
|
88
|
-
for (let i = 0; i <= cornerRes; i++) {
|
|
89
|
-
const angle = -Math.PI / 2 + (Math.PI / 2) * (i / cornerRes);
|
|
90
|
-
pts.push({
|
|
91
|
-
x: x + w - r + r * Math.cos(angle),
|
|
92
|
-
y: y + r + r * Math.sin(angle),
|
|
5
|
+
const cx = shape.x + shape.w / 2;
|
|
6
|
+
const cy = shape.y + shape.h / 2;
|
|
7
|
+
const rx = shape.w / 2;
|
|
8
|
+
const ry = shape.h / 2;
|
|
9
|
+
switch (shape.type) {
|
|
10
|
+
case "rect":
|
|
11
|
+
case "roundrect":
|
|
12
|
+
if (shape.type === "roundrect" && shape.rx) {
|
|
13
|
+
return roundRectToPolygon(shape.x, shape.y, shape.w, shape.h, shape.rx, resolution);
|
|
14
|
+
}
|
|
15
|
+
return [
|
|
16
|
+
{ x: shape.x, y: shape.y },
|
|
17
|
+
{ x: shape.x + shape.w, y: shape.y },
|
|
18
|
+
{ x: shape.x + shape.w, y: shape.y + shape.h },
|
|
19
|
+
{ x: shape.x, y: shape.y + shape.h }
|
|
20
|
+
];
|
|
21
|
+
case "ellipse": {
|
|
22
|
+
const ellipsePts = [];
|
|
23
|
+
for (let i = 0; i < resolution; i++) {
|
|
24
|
+
const angle = 2 * Math.PI * i / resolution;
|
|
25
|
+
ellipsePts.push({
|
|
26
|
+
x: cx + rx * Math.cos(angle),
|
|
27
|
+
y: cy + ry * Math.sin(angle)
|
|
93
28
|
});
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
29
|
+
}
|
|
30
|
+
return ellipsePts;
|
|
31
|
+
}
|
|
32
|
+
case "polygon": {
|
|
33
|
+
const sides = shape.sides || 6;
|
|
34
|
+
const polygonPts = [];
|
|
35
|
+
const r = Math.min(rx, ry);
|
|
36
|
+
for (let i = 0; i < sides; i++) {
|
|
37
|
+
const angle = 2 * Math.PI * i / sides - Math.PI / 2 + shape.rotation * Math.PI / 180;
|
|
38
|
+
polygonPts.push({
|
|
39
|
+
x: cx + r * Math.cos(angle),
|
|
40
|
+
y: cy + r * Math.sin(angle)
|
|
101
41
|
});
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
42
|
+
}
|
|
43
|
+
return polygonPts;
|
|
44
|
+
}
|
|
45
|
+
case "star": {
|
|
46
|
+
const points = shape.sides || 5;
|
|
47
|
+
const innerR = (shape.innerRadius || 0.4) * Math.min(rx, ry);
|
|
48
|
+
const outerR = Math.min(rx, ry);
|
|
49
|
+
const starPts = [];
|
|
50
|
+
for (let i = 0; i < points * 2; i++) {
|
|
51
|
+
const angle = Math.PI * i / points - Math.PI / 2 + shape.rotation * Math.PI / 180;
|
|
52
|
+
const rad = i % 2 === 0 ? outerR : innerR;
|
|
53
|
+
starPts.push({
|
|
54
|
+
x: cx + rad * Math.cos(angle),
|
|
55
|
+
y: cy + rad * Math.sin(angle)
|
|
109
56
|
});
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
|
|
57
|
+
}
|
|
58
|
+
return starPts;
|
|
59
|
+
}
|
|
60
|
+
case "polyline":
|
|
61
|
+
return shape.points || [];
|
|
62
|
+
default:
|
|
63
|
+
return [
|
|
64
|
+
{ x: shape.x, y: shape.y },
|
|
65
|
+
{ x: shape.x + shape.w, y: shape.y },
|
|
66
|
+
{ x: shape.x + shape.w, y: shape.y + shape.h },
|
|
67
|
+
{ x: shape.x, y: shape.y + shape.h }
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function roundRectToPolygon(x, y, w, h, radius, resolution) {
|
|
72
|
+
const r = Math.min(radius, w / 2, h / 2);
|
|
73
|
+
const pts = [];
|
|
74
|
+
const cornerRes = Math.max(4, Math.floor(resolution / 4));
|
|
75
|
+
for (let i = 0; i <= cornerRes; i++) {
|
|
76
|
+
const angle = -Math.PI / 2 + Math.PI / 2 * (i / cornerRes);
|
|
77
|
+
pts.push({
|
|
78
|
+
x: x + w - r + r * Math.cos(angle),
|
|
79
|
+
y: y + r + r * Math.sin(angle)
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
for (let i = 0; i <= cornerRes; i++) {
|
|
83
|
+
const angle = 0 + Math.PI / 2 * (i / cornerRes);
|
|
84
|
+
pts.push({
|
|
85
|
+
x: x + w - r + r * Math.cos(angle),
|
|
86
|
+
y: y + h - r + r * Math.sin(angle)
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
for (let i = 0; i <= cornerRes; i++) {
|
|
90
|
+
const angle = Math.PI / 2 + Math.PI / 2 * (i / cornerRes);
|
|
91
|
+
pts.push({
|
|
92
|
+
x: x + r + r * Math.cos(angle),
|
|
93
|
+
y: y + h - r + r * Math.sin(angle)
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
for (let i = 0; i <= cornerRes; i++) {
|
|
97
|
+
const angle = Math.PI + Math.PI / 2 * (i / cornerRes);
|
|
98
|
+
pts.push({
|
|
99
|
+
x: x + r + r * Math.cos(angle),
|
|
100
|
+
y: y + r + r * Math.sin(angle)
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
return pts;
|
|
120
104
|
}
|
|
121
|
-
/* ────────────────────────────────────────────────────────────────
|
|
122
|
-
POLYGON UTILITIES
|
|
123
|
-
──────────────────────────────────────────────────────────────── */
|
|
124
|
-
/** Check if point is inside polygon using ray casting */
|
|
125
105
|
function pointInPolygon(pt, polygon) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
106
|
+
let inside = false;
|
|
107
|
+
const n = polygon.length;
|
|
108
|
+
for (let i = 0, j = n - 1; i < n; j = i++) {
|
|
109
|
+
const xi = polygon[i].x, yi = polygon[i].y;
|
|
110
|
+
const xj = polygon[j].x, yj = polygon[j].y;
|
|
111
|
+
if (yi > pt.y !== yj > pt.y && pt.x < (xj - xi) * (pt.y - yi) / (yj - yi) + xi) {
|
|
112
|
+
inside = !inside;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return inside;
|
|
136
116
|
}
|
|
137
|
-
/** Get polygon centroid */
|
|
138
117
|
function polygonCentroid(polygon) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
118
|
+
let cx = 0, cy = 0;
|
|
119
|
+
for (const pt of polygon) {
|
|
120
|
+
cx += pt.x;
|
|
121
|
+
cy += pt.y;
|
|
122
|
+
}
|
|
123
|
+
return { x: cx / polygon.length, y: cy / polygon.length };
|
|
145
124
|
}
|
|
146
|
-
/** Get polygon bounding box */
|
|
147
125
|
function polygonBounds(polygon) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
126
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
127
|
+
for (const pt of polygon) {
|
|
128
|
+
minX = Math.min(minX, pt.x);
|
|
129
|
+
minY = Math.min(minY, pt.y);
|
|
130
|
+
maxX = Math.max(maxX, pt.x);
|
|
131
|
+
maxY = Math.max(maxY, pt.y);
|
|
132
|
+
}
|
|
133
|
+
return { minX, minY, maxX, maxY };
|
|
156
134
|
}
|
|
157
|
-
/** Calculate polygon area (signed) */
|
|
158
135
|
function polygonArea(polygon) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
136
|
+
let area = 0;
|
|
137
|
+
const n = polygon.length;
|
|
138
|
+
for (let i = 0; i < n; i++) {
|
|
139
|
+
const j = (i + 1) % n;
|
|
140
|
+
area += polygon[i].x * polygon[j].y;
|
|
141
|
+
area -= polygon[j].x * polygon[i].y;
|
|
142
|
+
}
|
|
143
|
+
return area / 2;
|
|
167
144
|
}
|
|
168
|
-
/** Check if two line segments intersect and return intersection point */
|
|
169
145
|
function lineIntersection(p1, p2, p3, p4) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
146
|
+
const d = (p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y);
|
|
147
|
+
if (Math.abs(d) < 1e-10) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
const ua = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / d;
|
|
151
|
+
const ub = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / d;
|
|
152
|
+
if (ua < 0 || ua > 1 || ub < 0 || ub > 1) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
x: p1.x + ua * (p2.x - p1.x),
|
|
157
|
+
y: p1.y + ua * (p2.y - p1.y)
|
|
158
|
+
};
|
|
183
159
|
}
|
|
184
|
-
/** Find all intersection points between two polygons */
|
|
185
160
|
function findIntersections(polyA, polyB) {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
161
|
+
const intersections = [];
|
|
162
|
+
for (let i = 0; i < polyA.length; i++) {
|
|
163
|
+
const a1 = polyA[i];
|
|
164
|
+
const a2 = polyA[(i + 1) % polyA.length];
|
|
165
|
+
for (let j = 0; j < polyB.length; j++) {
|
|
166
|
+
const b1 = polyB[j];
|
|
167
|
+
const b2 = polyB[(j + 1) % polyB.length];
|
|
168
|
+
const pt = lineIntersection(a1, a2, b1, b2);
|
|
169
|
+
if (pt) {
|
|
170
|
+
intersections.push(pt);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return intersections;
|
|
200
175
|
}
|
|
201
|
-
/* ────────────────────────────────────────────────────────────────
|
|
202
|
-
SUTHERLAND-HODGMAN CLIPPING (for intersection)
|
|
203
|
-
──────────────────────────────────────────────────────────────── */
|
|
204
|
-
/** Clip polygon against a single edge using Sutherland-Hodgman */
|
|
205
176
|
function clipPolygonEdge(polygon, edgeStart, edgeEnd) {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const
|
|
222
|
-
if (
|
|
223
|
-
|
|
224
|
-
// Entering - add intersection
|
|
225
|
-
const pt = lineIntersection(prev, curr, edgeStart, edgeEnd);
|
|
226
|
-
if (pt) {
|
|
227
|
-
output.push(pt);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
output.push(curr);
|
|
177
|
+
if (polygon.length === 0) {
|
|
178
|
+
return [];
|
|
179
|
+
}
|
|
180
|
+
const output = [];
|
|
181
|
+
const n = polygon.length;
|
|
182
|
+
const inside = (p) => {
|
|
183
|
+
return (edgeEnd.x - edgeStart.x) * (p.y - edgeStart.y) - (edgeEnd.y - edgeStart.y) * (p.x - edgeStart.x) >= 0;
|
|
184
|
+
};
|
|
185
|
+
for (let i = 0; i < n; i++) {
|
|
186
|
+
const curr = polygon[i];
|
|
187
|
+
const prev = polygon[(i + n - 1) % n];
|
|
188
|
+
const currInside = inside(curr);
|
|
189
|
+
const prevInside = inside(prev);
|
|
190
|
+
if (currInside) {
|
|
191
|
+
if (!prevInside) {
|
|
192
|
+
const pt = lineIntersection(prev, curr, edgeStart, edgeEnd);
|
|
193
|
+
if (pt) {
|
|
194
|
+
output.push(pt);
|
|
231
195
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
240
|
-
|
|
196
|
+
}
|
|
197
|
+
output.push(curr);
|
|
198
|
+
} else if (prevInside) {
|
|
199
|
+
const pt = lineIntersection(prev, curr, edgeStart, edgeEnd);
|
|
200
|
+
if (pt) {
|
|
201
|
+
output.push(pt);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return output;
|
|
241
206
|
}
|
|
242
|
-
/** Clip subject polygon by clip polygon using Sutherland-Hodgman */
|
|
243
207
|
function clipPolygon(subject, clip) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
208
|
+
let output = [...subject];
|
|
209
|
+
for (let i = 0; i < clip.length; i++) {
|
|
210
|
+
if (output.length === 0) {
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
const edgeStart = clip[i];
|
|
214
|
+
const edgeEnd = clip[(i + 1) % clip.length];
|
|
215
|
+
output = clipPolygonEdge(output, edgeStart, edgeEnd);
|
|
216
|
+
}
|
|
217
|
+
return output;
|
|
254
218
|
}
|
|
255
|
-
/* ────────────────────────────────────────────────────────────────
|
|
256
|
-
BOOLEAN OPERATIONS
|
|
257
|
-
──────────────────────────────────────────────────────────────── */
|
|
258
|
-
/** Union of two polygons (approximation using convex hull on combined points + edge walking) */
|
|
259
219
|
function unionPolygons(polyA, polyB) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
// One polygon is inside the other - return the larger one
|
|
279
|
-
const areaA = Math.abs(polygonArea(polyA));
|
|
280
|
-
const areaB = Math.abs(polygonArea(polyB));
|
|
281
|
-
return areaA > areaB ? [...polyA] : [...polyB];
|
|
282
|
-
}
|
|
283
|
-
// Create convex hull of all points
|
|
284
|
-
return convexHull(result);
|
|
220
|
+
const result = [];
|
|
221
|
+
for (const pt of polyA) {
|
|
222
|
+
if (!pointInPolygon(pt, polyB)) {
|
|
223
|
+
result.push(pt);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
for (const pt of polyB) {
|
|
227
|
+
if (!pointInPolygon(pt, polyA)) {
|
|
228
|
+
result.push(pt);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
result.push(...findIntersections(polyA, polyB));
|
|
232
|
+
if (result.length < 3) {
|
|
233
|
+
const areaA = Math.abs(polygonArea(polyA));
|
|
234
|
+
const areaB = Math.abs(polygonArea(polyB));
|
|
235
|
+
return areaA > areaB ? [...polyA] : [...polyB];
|
|
236
|
+
}
|
|
237
|
+
return convexHull(result);
|
|
285
238
|
}
|
|
286
|
-
/** Intersection of two polygons */
|
|
287
239
|
function intersectPolygons(polyA, polyB) {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
const normB = areaB < 0 ? [...polyB].reverse() : polyB;
|
|
294
|
-
return clipPolygon(normA, normB);
|
|
240
|
+
const areaA = polygonArea(polyA);
|
|
241
|
+
const areaB = polygonArea(polyB);
|
|
242
|
+
const normA = areaA < 0 ? [...polyA].reverse() : polyA;
|
|
243
|
+
const normB = areaB < 0 ? [...polyB].reverse() : polyB;
|
|
244
|
+
return clipPolygon(normA, normB);
|
|
295
245
|
}
|
|
296
|
-
/** Difference: A - B */
|
|
297
246
|
function differencePolygons(polyA, polyB) {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
// B completely covers A
|
|
311
|
-
return [];
|
|
312
|
-
}
|
|
313
|
-
// Sort points to form proper polygon
|
|
314
|
-
return sortPolygonPoints(result);
|
|
247
|
+
const result = [];
|
|
248
|
+
for (const pt of polyA) {
|
|
249
|
+
if (!pointInPolygon(pt, polyB)) {
|
|
250
|
+
result.push(pt);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
const intersections = findIntersections(polyA, polyB);
|
|
254
|
+
result.push(...intersections);
|
|
255
|
+
if (result.length < 3) {
|
|
256
|
+
return [];
|
|
257
|
+
}
|
|
258
|
+
return sortPolygonPoints(result);
|
|
315
259
|
}
|
|
316
|
-
/** XOR / Exclusion: (A ∪ B) - (A ∩ B) */
|
|
317
260
|
function exclusionPolygons(polyA, polyB) {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
results.push(diffB);
|
|
333
|
-
}
|
|
334
|
-
return results;
|
|
261
|
+
const intersection = intersectPolygons(polyA, polyB);
|
|
262
|
+
if (intersection.length < 3) {
|
|
263
|
+
return [polyA, polyB];
|
|
264
|
+
}
|
|
265
|
+
const diffA = differencePolygons(polyA, polyB);
|
|
266
|
+
const diffB = differencePolygons(polyB, polyA);
|
|
267
|
+
const results = [];
|
|
268
|
+
if (diffA.length >= 3) {
|
|
269
|
+
results.push(diffA);
|
|
270
|
+
}
|
|
271
|
+
if (diffB.length >= 3) {
|
|
272
|
+
results.push(diffB);
|
|
273
|
+
}
|
|
274
|
+
return results;
|
|
335
275
|
}
|
|
336
|
-
/** Sort polygon points in CCW order around centroid */
|
|
337
276
|
function sortPolygonPoints(points) {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
277
|
+
if (points.length < 3) {
|
|
278
|
+
return points;
|
|
279
|
+
}
|
|
280
|
+
const center = polygonCentroid(points);
|
|
281
|
+
return [...points].sort((a, b) => {
|
|
282
|
+
const angleA = Math.atan2(a.y - center.y, a.x - center.x);
|
|
283
|
+
const angleB = Math.atan2(b.y - center.y, b.x - center.x);
|
|
284
|
+
return angleA - angleB;
|
|
285
|
+
});
|
|
347
286
|
}
|
|
348
|
-
/** Compute convex hull using Graham scan */
|
|
349
287
|
function convexHull(points) {
|
|
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
|
-
|
|
378
|
-
|
|
379
|
-
if (cross > 0) {
|
|
380
|
-
break;
|
|
381
|
-
}
|
|
382
|
-
hull.pop();
|
|
383
|
-
}
|
|
384
|
-
hull.push(pt);
|
|
385
|
-
}
|
|
386
|
-
return hull;
|
|
288
|
+
if (points.length < 3) {
|
|
289
|
+
return points;
|
|
290
|
+
}
|
|
291
|
+
let start = 0;
|
|
292
|
+
for (let i = 1; i < points.length; i++) {
|
|
293
|
+
if (points[i].y < points[start].y || points[i].y === points[start].y && points[i].x < points[start].x) {
|
|
294
|
+
start = i;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
const pivot = points[start];
|
|
298
|
+
const sorted = points.filter((_, i) => i !== start).map((p) => ({
|
|
299
|
+
point: p,
|
|
300
|
+
angle: Math.atan2(p.y - pivot.y, p.x - pivot.x),
|
|
301
|
+
dist: Math.hypot(p.x - pivot.x, p.y - pivot.y)
|
|
302
|
+
})).sort((a, b) => a.angle - b.angle || a.dist - b.dist).map((p) => p.point);
|
|
303
|
+
const hull = [pivot];
|
|
304
|
+
for (const pt of sorted) {
|
|
305
|
+
while (hull.length > 1) {
|
|
306
|
+
const a = hull[hull.length - 2];
|
|
307
|
+
const b = hull[hull.length - 1];
|
|
308
|
+
const cross = (b.x - a.x) * (pt.y - a.y) - (b.y - a.y) * (pt.x - a.x);
|
|
309
|
+
if (cross > 0) {
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
hull.pop();
|
|
313
|
+
}
|
|
314
|
+
hull.push(pt);
|
|
315
|
+
}
|
|
316
|
+
return hull;
|
|
387
317
|
}
|
|
388
|
-
/* ────────────────────────────────────────────────────────────────
|
|
389
|
-
PUBLIC API
|
|
390
|
-
──────────────────────────────────────────────────────────────── */
|
|
391
|
-
/** Perform boolean operation on two shapes */
|
|
392
318
|
function booleanOperation(shapeA, shapeB, operation) {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
return null;
|
|
404
|
-
}
|
|
405
|
-
resultPolygons = [intersection];
|
|
406
|
-
break;
|
|
407
|
-
}
|
|
408
|
-
case 'difference': {
|
|
409
|
-
const difference = differencePolygons(polyA, polyB);
|
|
410
|
-
if (difference.length < 3) {
|
|
411
|
-
return null;
|
|
412
|
-
}
|
|
413
|
-
resultPolygons = [difference];
|
|
414
|
-
break;
|
|
415
|
-
}
|
|
416
|
-
case 'exclusion':
|
|
417
|
-
resultPolygons = exclusionPolygons(polyA, polyB);
|
|
418
|
-
if (resultPolygons.length === 0) {
|
|
419
|
-
return null;
|
|
420
|
-
}
|
|
421
|
-
break;
|
|
422
|
-
default:
|
|
423
|
-
return null;
|
|
424
|
-
}
|
|
425
|
-
// Convert resulting polygons back to VShapes
|
|
426
|
-
const shapes = resultPolygons
|
|
427
|
-
.filter((poly) => poly.length >= 3)
|
|
428
|
-
.map((poly) => polygonToShape(poly, shapeA));
|
|
429
|
-
if (shapes.length === 0) {
|
|
319
|
+
const polyA = shapeToPolygon(shapeA);
|
|
320
|
+
const polyB = shapeToPolygon(shapeB);
|
|
321
|
+
let resultPolygons;
|
|
322
|
+
switch (operation) {
|
|
323
|
+
case "union":
|
|
324
|
+
resultPolygons = [unionPolygons(polyA, polyB)];
|
|
325
|
+
break;
|
|
326
|
+
case "intersection": {
|
|
327
|
+
const intersection = intersectPolygons(polyA, polyB);
|
|
328
|
+
if (intersection.length < 3) {
|
|
430
329
|
return null;
|
|
330
|
+
}
|
|
331
|
+
resultPolygons = [intersection];
|
|
332
|
+
break;
|
|
431
333
|
}
|
|
432
|
-
|
|
433
|
-
|
|
334
|
+
case "difference": {
|
|
335
|
+
const difference = differencePolygons(polyA, polyB);
|
|
336
|
+
if (difference.length < 3) {
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
resultPolygons = [difference];
|
|
340
|
+
break;
|
|
434
341
|
}
|
|
435
|
-
|
|
342
|
+
case "exclusion":
|
|
343
|
+
resultPolygons = exclusionPolygons(polyA, polyB);
|
|
344
|
+
if (resultPolygons.length === 0) {
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
break;
|
|
348
|
+
default:
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
const shapes = resultPolygons.filter((poly) => poly.length >= 3).map((poly) => polygonToShape(poly, shapeA));
|
|
352
|
+
if (shapes.length === 0) {
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
if (shapes.length === 1) {
|
|
356
|
+
return shapes[0];
|
|
357
|
+
}
|
|
358
|
+
return shapes;
|
|
436
359
|
}
|
|
437
|
-
/** Convert polygon points back to a VShape */
|
|
438
360
|
function polygonToShape(polygon, template) {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
361
|
+
const bounds = polygonBounds(polygon);
|
|
362
|
+
return {
|
|
363
|
+
id: vectorEditorTypes.sid(),
|
|
364
|
+
type: "polyline",
|
|
365
|
+
x: bounds.minX,
|
|
366
|
+
y: bounds.minY,
|
|
367
|
+
w: bounds.maxX - bounds.minX,
|
|
368
|
+
h: bounds.maxY - bounds.minY,
|
|
369
|
+
points: polygon.map((pt) => ({
|
|
370
|
+
x: pt.x - bounds.minX,
|
|
371
|
+
y: pt.y - bounds.minY
|
|
372
|
+
})),
|
|
373
|
+
fill: template.fill,
|
|
374
|
+
stroke: template.stroke,
|
|
375
|
+
strokeWidth: template.strokeWidth,
|
|
376
|
+
opacity: template.opacity,
|
|
377
|
+
rotation: 0
|
|
378
|
+
};
|
|
457
379
|
}
|
|
458
|
-
/** Check if two shapes overlap */
|
|
459
380
|
function shapesOverlap(shapeA, shapeB) {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
}
|
|
474
|
-
// Check for edge intersections
|
|
475
|
-
return findIntersections(polyA, polyB).length > 0;
|
|
381
|
+
const polyA = shapeToPolygon(shapeA);
|
|
382
|
+
const polyB = shapeToPolygon(shapeB);
|
|
383
|
+
for (const pt of polyA) {
|
|
384
|
+
if (pointInPolygon(pt, polyB)) {
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
for (const pt of polyB) {
|
|
389
|
+
if (pointInPolygon(pt, polyA)) {
|
|
390
|
+
return true;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return findIntersections(polyA, polyB).length > 0;
|
|
476
394
|
}
|
|
477
|
-
/** Flatten a polyline to individual segment shapes (for outline stroke) */
|
|
478
395
|
function flattenToOutline(shape, strokeWidth) {
|
|
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
|
-
|
|
525
|
-
|
|
526
|
-
opacity: shape.opacity,
|
|
527
|
-
rotation: 0,
|
|
528
|
-
};
|
|
396
|
+
const sw = strokeWidth ?? shape.strokeWidth;
|
|
397
|
+
const points = shape.points || shapeToPolygon(shape);
|
|
398
|
+
const halfW = sw / 2;
|
|
399
|
+
const outer = [];
|
|
400
|
+
const inner = [];
|
|
401
|
+
for (let i = 0; i < points.length; i++) {
|
|
402
|
+
const curr = points[i];
|
|
403
|
+
const prev = points[(i + points.length - 1) % points.length];
|
|
404
|
+
const next = points[(i + 1) % points.length];
|
|
405
|
+
const dx1 = curr.x - prev.x;
|
|
406
|
+
const dy1 = curr.y - prev.y;
|
|
407
|
+
const dx2 = next.x - curr.x;
|
|
408
|
+
const dy2 = next.y - curr.y;
|
|
409
|
+
const len1 = Math.hypot(dx1, dy1) || 1;
|
|
410
|
+
const len2 = Math.hypot(dx2, dy2) || 1;
|
|
411
|
+
const nx = (-dy1 / len1 - dy2 / len2) / 2;
|
|
412
|
+
const ny = (dx1 / len1 + dx2 / len2) / 2;
|
|
413
|
+
const nlen = Math.hypot(nx, ny) || 1;
|
|
414
|
+
outer.push({
|
|
415
|
+
x: curr.x + nx / nlen * halfW,
|
|
416
|
+
y: curr.y + ny / nlen * halfW
|
|
417
|
+
});
|
|
418
|
+
inner.push({
|
|
419
|
+
x: curr.x - nx / nlen * halfW,
|
|
420
|
+
y: curr.y - ny / nlen * halfW
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
const outline = [...outer, ...inner.reverse()];
|
|
424
|
+
const bounds = polygonBounds(outline);
|
|
425
|
+
return {
|
|
426
|
+
id: vectorEditorTypes.sid(),
|
|
427
|
+
type: "polyline",
|
|
428
|
+
x: bounds.minX,
|
|
429
|
+
y: bounds.minY,
|
|
430
|
+
w: bounds.maxX - bounds.minX,
|
|
431
|
+
h: bounds.maxY - bounds.minY,
|
|
432
|
+
points: outline.map((pt) => ({
|
|
433
|
+
x: pt.x - bounds.minX,
|
|
434
|
+
y: pt.y - bounds.minY
|
|
435
|
+
})),
|
|
436
|
+
fill: shape.stroke,
|
|
437
|
+
// Fill with original stroke color
|
|
438
|
+
stroke: "transparent",
|
|
439
|
+
strokeWidth: 0,
|
|
440
|
+
opacity: shape.opacity,
|
|
441
|
+
rotation: 0
|
|
442
|
+
};
|
|
529
443
|
}
|
|
530
|
-
|
|
531
444
|
exports.booleanOperation = booleanOperation;
|
|
532
445
|
exports.flattenToOutline = flattenToOutline;
|
|
533
446
|
exports.shapesOverlap = shapesOverlap;
|