@crazyhappyone/auto-graph 0.2.9 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.cjs +311 -44
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +311 -44
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +311 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +311 -44
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4805,7 +4805,10 @@ function findObstacleFreePath(source, target, obstacles, options = {}, diagnosti
|
|
|
4805
4805
|
detail: {
|
|
4806
4806
|
xsCount: xs.length,
|
|
4807
4807
|
ysCount: ys.length,
|
|
4808
|
-
maxNodes
|
|
4808
|
+
maxNodes,
|
|
4809
|
+
obstacleCount: obstacles.length,
|
|
4810
|
+
stage: "corridor-filtered",
|
|
4811
|
+
...corridorMargin === void 0 ? {} : { corridorMargin }
|
|
4809
4812
|
}
|
|
4810
4813
|
});
|
|
4811
4814
|
return null;
|
|
@@ -4851,7 +4854,14 @@ function findObstacleFreePath(source, target, obstacles, options = {}, diagnosti
|
|
|
4851
4854
|
severity: "warning",
|
|
4852
4855
|
code: "routing.astar.grid_overflow",
|
|
4853
4856
|
message: `A* full-retry grid overflow: ${xsFull.length * ysFull.length} nodes > ${maxNodes} limit. Falling back to heuristic routing.`,
|
|
4854
|
-
detail: {
|
|
4857
|
+
detail: {
|
|
4858
|
+
xsCount: xsFull.length,
|
|
4859
|
+
ysCount: ysFull.length,
|
|
4860
|
+
maxNodes,
|
|
4861
|
+
obstacleCount: obstacles.length,
|
|
4862
|
+
stage: "full-retry",
|
|
4863
|
+
...corridorMargin === void 0 ? {} : { corridorMargin }
|
|
4864
|
+
}
|
|
4855
4865
|
});
|
|
4856
4866
|
return null;
|
|
4857
4867
|
}
|
|
@@ -5094,6 +5104,64 @@ function areCollinear(a, b, c) {
|
|
|
5094
5104
|
return a.x === b.x && b.x === c.x || a.y === b.y && b.y === c.y;
|
|
5095
5105
|
}
|
|
5096
5106
|
|
|
5107
|
+
// src/routing/budget.ts
|
|
5108
|
+
var MIN_CORNER_BUDGET = 600;
|
|
5109
|
+
var MAX_CORNER_BUDGET = 3e3;
|
|
5110
|
+
var MIN_NODE_BUDGET = 4e3;
|
|
5111
|
+
var MAX_NODE_BUDGET = 64e3;
|
|
5112
|
+
var CORNERS_PER_OBSTACLE = 12;
|
|
5113
|
+
var CORNER_HEADROOM = 2;
|
|
5114
|
+
var GRID_SAFETY_FACTOR = 3;
|
|
5115
|
+
var CORRIDOR_SCALING_K = 0.5;
|
|
5116
|
+
var CORRIDOR_SCALING_BASE = 200;
|
|
5117
|
+
function computeRoutingBudget(cornerObstacles, allObstacles, corridorMargin, overrides = {}) {
|
|
5118
|
+
const adaptiveMaxCorners = deriveMaxCorners(
|
|
5119
|
+
cornerObstacles.length,
|
|
5120
|
+
corridorMargin
|
|
5121
|
+
);
|
|
5122
|
+
const adaptiveMaxNodes = deriveMaxNodes(
|
|
5123
|
+
allObstacles.length,
|
|
5124
|
+
corridorMargin
|
|
5125
|
+
);
|
|
5126
|
+
return {
|
|
5127
|
+
maxCorners: resolveBudget(
|
|
5128
|
+
overrides.maxCorners,
|
|
5129
|
+
adaptiveMaxCorners,
|
|
5130
|
+
MIN_CORNER_BUDGET,
|
|
5131
|
+
MAX_CORNER_BUDGET
|
|
5132
|
+
),
|
|
5133
|
+
maxNodes: resolveBudget(
|
|
5134
|
+
overrides.maxNodes,
|
|
5135
|
+
adaptiveMaxNodes,
|
|
5136
|
+
MIN_NODE_BUDGET,
|
|
5137
|
+
MAX_NODE_BUDGET
|
|
5138
|
+
),
|
|
5139
|
+
cornerObstacleCount: cornerObstacles.length,
|
|
5140
|
+
gridObstacleCount: allObstacles.length,
|
|
5141
|
+
corridorMargin
|
|
5142
|
+
};
|
|
5143
|
+
}
|
|
5144
|
+
function deriveMaxCorners(obstacleCount, corridorMargin) {
|
|
5145
|
+
const base = 2 + obstacleCount * CORNERS_PER_OBSTACLE * CORNER_HEADROOM;
|
|
5146
|
+
const corridorFactor = corridorScalingFactor(corridorMargin);
|
|
5147
|
+
return Math.ceil(base * corridorFactor);
|
|
5148
|
+
}
|
|
5149
|
+
function deriveMaxNodes(obstacleCount, corridorMargin) {
|
|
5150
|
+
const base = 4 * obstacleCount * obstacleCount + 4 * obstacleCount + 100;
|
|
5151
|
+
const corridorFactor = corridorScalingFactor(corridorMargin);
|
|
5152
|
+
return Math.ceil(base * GRID_SAFETY_FACTOR * corridorFactor);
|
|
5153
|
+
}
|
|
5154
|
+
function corridorScalingFactor(corridorMargin) {
|
|
5155
|
+
return 1 + corridorMargin / CORRIDOR_SCALING_BASE * CORRIDOR_SCALING_K;
|
|
5156
|
+
}
|
|
5157
|
+
function resolveBudget(override, adaptive, min, max) {
|
|
5158
|
+
const chosen = override !== void 0 && Number.isFinite(override) && override >= 1 ? override : adaptive;
|
|
5159
|
+
return clamp(chosen, min, max);
|
|
5160
|
+
}
|
|
5161
|
+
function clamp(value, min, max) {
|
|
5162
|
+
return Math.max(min, Math.min(max, value));
|
|
5163
|
+
}
|
|
5164
|
+
|
|
5097
5165
|
// src/routing/visibility-router.ts
|
|
5098
5166
|
function findCornerGraphPath(source, target, obstacles, options = {}, diagnostics) {
|
|
5099
5167
|
const margin = options.margin ?? 0;
|
|
@@ -5107,7 +5175,12 @@ function findCornerGraphPath(source, target, obstacles, options = {}, diagnostic
|
|
|
5107
5175
|
severity: "warning",
|
|
5108
5176
|
code: "routing.visibility.corner_overflow",
|
|
5109
5177
|
message: `Corner graph overflow: ${vertices.length} vertices > ${maxCorners}. Falling back to grid A*.`,
|
|
5110
|
-
detail: {
|
|
5178
|
+
detail: {
|
|
5179
|
+
vertexCount: vertices.length,
|
|
5180
|
+
maxCorners,
|
|
5181
|
+
obstacleCount: obstacles.length,
|
|
5182
|
+
...options.corridorMargin === void 0 ? {} : { corridorMargin: options.corridorMargin }
|
|
5183
|
+
}
|
|
5111
5184
|
});
|
|
5112
5185
|
return null;
|
|
5113
5186
|
}
|
|
@@ -5381,7 +5454,7 @@ function segmentCrossesAnyObstacle(a, b, obstacles) {
|
|
|
5381
5454
|
}
|
|
5382
5455
|
|
|
5383
5456
|
// src/routing/routes.ts
|
|
5384
|
-
function checkBacktracking(points, source, target, diagnostics) {
|
|
5457
|
+
function checkBacktracking(points, source, target, diagnostics, maxRatio) {
|
|
5385
5458
|
if (points.length < 2) return;
|
|
5386
5459
|
const direct = Math.hypot(target.x - source.x, target.y - source.y);
|
|
5387
5460
|
if (direct <= 0) return;
|
|
@@ -5391,7 +5464,7 @@ function checkBacktracking(points, source, target, diagnostics) {
|
|
|
5391
5464
|
const b = points[i + 1];
|
|
5392
5465
|
routeLen += Math.hypot(b.x - a.x, b.y - a.y);
|
|
5393
5466
|
}
|
|
5394
|
-
const threshold =
|
|
5467
|
+
const threshold = maxRatio ?? 20;
|
|
5395
5468
|
if (routeLen > direct * threshold) {
|
|
5396
5469
|
diagnostics.push({
|
|
5397
5470
|
severity: "warning",
|
|
@@ -5409,8 +5482,20 @@ function routeEdge(input) {
|
|
|
5409
5482
|
const diagnostics = [];
|
|
5410
5483
|
const softObstacles = input.obstacles ?? [];
|
|
5411
5484
|
const hardObstacles = input.hardObstacles ?? [];
|
|
5485
|
+
let bestRejectedPath;
|
|
5486
|
+
let bestRejectedCrossings = Number.POSITIVE_INFINITY;
|
|
5412
5487
|
const softObstacleIndex = input.obstacleIndex ?? createBoxSpatialIndex(indexedBoxes(softObstacles));
|
|
5413
5488
|
const hardObstacleIndex = input.hardObstacleIndex ?? createBoxSpatialIndex(indexedBoxes(hardObstacles));
|
|
5489
|
+
const recordRejected = (candidate) => {
|
|
5490
|
+
if (routeIntersectsObstacles(candidate, hardObstacles, hardObstacleIndex)) {
|
|
5491
|
+
return;
|
|
5492
|
+
}
|
|
5493
|
+
const crossings = countObstacleCrossings(candidate, softObstacles);
|
|
5494
|
+
if (crossings < bestRejectedCrossings) {
|
|
5495
|
+
bestRejectedCrossings = crossings;
|
|
5496
|
+
bestRejectedPath = candidate;
|
|
5497
|
+
}
|
|
5498
|
+
};
|
|
5414
5499
|
const maxAttempts = input.maxRoutingAttempts ?? 5;
|
|
5415
5500
|
const defaultAnchors = defaultAnchorsForGeometry(
|
|
5416
5501
|
input.source.box,
|
|
@@ -5470,20 +5555,32 @@ function routeEdge(input) {
|
|
|
5470
5555
|
targetAnchor
|
|
5471
5556
|
);
|
|
5472
5557
|
const allObstacles = [...softObstacles, ...hardObstacles];
|
|
5558
|
+
const corridorMargin = input.corridorMargin ?? 32;
|
|
5473
5559
|
const corridorObstacles = filterObstaclesByCorridor(
|
|
5474
5560
|
source,
|
|
5475
5561
|
target,
|
|
5476
5562
|
allObstacles,
|
|
5477
5563
|
[],
|
|
5478
5564
|
// endpointObstacles passed separately via options
|
|
5479
|
-
|
|
5565
|
+
corridorMargin
|
|
5480
5566
|
);
|
|
5481
5567
|
const cornerObstacles = corridorObstacles.length === 0 && allObstacles.length > 0 ? allObstacles : corridorObstacles;
|
|
5568
|
+
const budget = computeRoutingBudget(
|
|
5569
|
+
cornerObstacles,
|
|
5570
|
+
allObstacles,
|
|
5571
|
+
corridorMargin,
|
|
5572
|
+
{ maxCorners: input.maxCorners, maxNodes: input.maxNodes }
|
|
5573
|
+
);
|
|
5482
5574
|
let cornerPath = findCornerGraphPath(
|
|
5483
5575
|
source,
|
|
5484
5576
|
target,
|
|
5485
5577
|
cornerObstacles,
|
|
5486
|
-
{
|
|
5578
|
+
{
|
|
5579
|
+
endpointObstacles,
|
|
5580
|
+
margin: 2,
|
|
5581
|
+
maxCorners: budget.maxCorners,
|
|
5582
|
+
corridorMargin
|
|
5583
|
+
},
|
|
5487
5584
|
diagnostics
|
|
5488
5585
|
);
|
|
5489
5586
|
if (cornerPath === null && cornerObstacles.length < allObstacles.length) {
|
|
@@ -5491,7 +5588,12 @@ function routeEdge(input) {
|
|
|
5491
5588
|
source,
|
|
5492
5589
|
target,
|
|
5493
5590
|
allObstacles,
|
|
5494
|
-
{
|
|
5591
|
+
{
|
|
5592
|
+
endpointObstacles,
|
|
5593
|
+
margin: 2,
|
|
5594
|
+
maxCorners: budget.maxCorners,
|
|
5595
|
+
corridorMargin
|
|
5596
|
+
},
|
|
5495
5597
|
diagnostics
|
|
5496
5598
|
);
|
|
5497
5599
|
}
|
|
@@ -5499,7 +5601,12 @@ function routeEdge(input) {
|
|
|
5499
5601
|
source,
|
|
5500
5602
|
target,
|
|
5501
5603
|
allObstacles,
|
|
5502
|
-
{
|
|
5604
|
+
{
|
|
5605
|
+
endpointObstacles,
|
|
5606
|
+
margin: 0,
|
|
5607
|
+
corridorMargin,
|
|
5608
|
+
maxNodes: budget.maxNodes
|
|
5609
|
+
},
|
|
5503
5610
|
diagnostics
|
|
5504
5611
|
);
|
|
5505
5612
|
if (path !== null && path.length >= 2) {
|
|
@@ -5516,15 +5623,27 @@ function routeEdge(input) {
|
|
|
5516
5623
|
softObstacles,
|
|
5517
5624
|
softObstacleIndex
|
|
5518
5625
|
) && !routeIntersectsObstacles(finalized, hardObstacles, hardObstacleIndex)) {
|
|
5519
|
-
checkBacktracking(
|
|
5626
|
+
checkBacktracking(
|
|
5627
|
+
finalized,
|
|
5628
|
+
source,
|
|
5629
|
+
target,
|
|
5630
|
+
diagnostics,
|
|
5631
|
+
input.maxBacktrackingRatio
|
|
5632
|
+
);
|
|
5520
5633
|
return { points: finalized, diagnostics };
|
|
5521
5634
|
}
|
|
5635
|
+
recordRejected(finalized);
|
|
5522
5636
|
if (cornerPath !== null) {
|
|
5523
5637
|
const fullCornerPath = cornerObstacles.length < allObstacles.length ? findCornerGraphPath(
|
|
5524
5638
|
source,
|
|
5525
5639
|
target,
|
|
5526
5640
|
allObstacles,
|
|
5527
|
-
{
|
|
5641
|
+
{
|
|
5642
|
+
endpointObstacles,
|
|
5643
|
+
margin: 2,
|
|
5644
|
+
maxCorners: budget.maxCorners,
|
|
5645
|
+
corridorMargin
|
|
5646
|
+
},
|
|
5528
5647
|
diagnostics
|
|
5529
5648
|
) : null;
|
|
5530
5649
|
if (fullCornerPath !== null && fullCornerPath.length >= 2) {
|
|
@@ -5545,15 +5664,27 @@ function routeEdge(input) {
|
|
|
5545
5664
|
hardObstacles,
|
|
5546
5665
|
hardObstacleIndex
|
|
5547
5666
|
)) {
|
|
5548
|
-
checkBacktracking(
|
|
5667
|
+
checkBacktracking(
|
|
5668
|
+
fullFinalized,
|
|
5669
|
+
source,
|
|
5670
|
+
target,
|
|
5671
|
+
diagnostics,
|
|
5672
|
+
input.maxBacktrackingRatio
|
|
5673
|
+
);
|
|
5549
5674
|
return { points: fullFinalized, diagnostics };
|
|
5550
5675
|
}
|
|
5676
|
+
recordRejected(fullFinalized);
|
|
5551
5677
|
}
|
|
5552
5678
|
const gridPath = findObstacleFreePath(
|
|
5553
5679
|
source,
|
|
5554
5680
|
target,
|
|
5555
5681
|
allObstacles,
|
|
5556
|
-
{
|
|
5682
|
+
{
|
|
5683
|
+
endpointObstacles,
|
|
5684
|
+
margin: 0,
|
|
5685
|
+
corridorMargin,
|
|
5686
|
+
maxNodes: budget.maxNodes
|
|
5687
|
+
},
|
|
5557
5688
|
diagnostics
|
|
5558
5689
|
);
|
|
5559
5690
|
if (gridPath !== null && gridPath.length >= 2) {
|
|
@@ -5574,9 +5705,16 @@ function routeEdge(input) {
|
|
|
5574
5705
|
hardObstacles,
|
|
5575
5706
|
hardObstacleIndex
|
|
5576
5707
|
)) {
|
|
5577
|
-
checkBacktracking(
|
|
5708
|
+
checkBacktracking(
|
|
5709
|
+
gridFinalized,
|
|
5710
|
+
source,
|
|
5711
|
+
target,
|
|
5712
|
+
diagnostics,
|
|
5713
|
+
input.maxBacktrackingRatio
|
|
5714
|
+
);
|
|
5578
5715
|
return { points: gridFinalized, diagnostics };
|
|
5579
5716
|
}
|
|
5717
|
+
recordRejected(gridFinalized);
|
|
5580
5718
|
}
|
|
5581
5719
|
}
|
|
5582
5720
|
}
|
|
@@ -5640,7 +5778,8 @@ function routeEdge(input) {
|
|
|
5640
5778
|
finalizedClean,
|
|
5641
5779
|
candidate.points[0],
|
|
5642
5780
|
candidate.points[candidate.points.length - 1],
|
|
5643
|
-
diagnostics
|
|
5781
|
+
diagnostics,
|
|
5782
|
+
input.maxBacktrackingRatio
|
|
5644
5783
|
);
|
|
5645
5784
|
return { points: finalizedClean, diagnostics };
|
|
5646
5785
|
}
|
|
@@ -5706,13 +5845,41 @@ function routeEdge(input) {
|
|
|
5706
5845
|
code: "routing.obstacle.unavoidable",
|
|
5707
5846
|
message: "No bounded orthogonal route candidate avoided all soft obstacles."
|
|
5708
5847
|
});
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5848
|
+
const finalizedSoftBest = finalizeRoute(
|
|
5849
|
+
bestPoints2,
|
|
5850
|
+
softObstacles,
|
|
5851
|
+
hardObstacles,
|
|
5852
|
+
diagnostics
|
|
5853
|
+
);
|
|
5854
|
+
let softFallback = finalizedSoftBest;
|
|
5855
|
+
if (bestRejectedPath !== void 0) {
|
|
5856
|
+
const finalizedRejected = finalizeRoute(
|
|
5857
|
+
bestRejectedPath,
|
|
5712
5858
|
softObstacles,
|
|
5713
5859
|
hardObstacles,
|
|
5714
5860
|
diagnostics
|
|
5715
|
-
)
|
|
5861
|
+
);
|
|
5862
|
+
const rejectedCrossings = countObstacleCrossings(
|
|
5863
|
+
finalizedRejected,
|
|
5864
|
+
softObstacles
|
|
5865
|
+
);
|
|
5866
|
+
const heuristicCrossings = countObstacleCrossings(
|
|
5867
|
+
finalizedSoftBest,
|
|
5868
|
+
softObstacles
|
|
5869
|
+
);
|
|
5870
|
+
if (rejectedCrossings < heuristicCrossings) {
|
|
5871
|
+
softFallback = finalizedRejected;
|
|
5872
|
+
}
|
|
5873
|
+
}
|
|
5874
|
+
checkBacktracking(
|
|
5875
|
+
softFallback,
|
|
5876
|
+
softFallback[0],
|
|
5877
|
+
softFallback[softFallback.length - 1],
|
|
5878
|
+
diagnostics,
|
|
5879
|
+
input.maxBacktrackingRatio
|
|
5880
|
+
);
|
|
5881
|
+
return {
|
|
5882
|
+
points: softFallback,
|
|
5716
5883
|
diagnostics
|
|
5717
5884
|
};
|
|
5718
5885
|
}
|
|
@@ -5744,6 +5911,22 @@ function routeEdge(input) {
|
|
|
5744
5911
|
maxAttempts
|
|
5745
5912
|
);
|
|
5746
5913
|
}
|
|
5914
|
+
if (bestRejectedPath !== void 0) {
|
|
5915
|
+
diagnostics.push({
|
|
5916
|
+
severity: "warning",
|
|
5917
|
+
code: "routing.obstacle.unavoidable",
|
|
5918
|
+
message: "Using A* route with minor soft-obstacle crossings to avoid hard evidence obstacles."
|
|
5919
|
+
});
|
|
5920
|
+
return {
|
|
5921
|
+
points: finalizeRoute(
|
|
5922
|
+
bestRejectedPath,
|
|
5923
|
+
softObstacles,
|
|
5924
|
+
hardObstacles,
|
|
5925
|
+
diagnostics
|
|
5926
|
+
),
|
|
5927
|
+
diagnostics
|
|
5928
|
+
};
|
|
5929
|
+
}
|
|
5747
5930
|
diagnostics.push({
|
|
5748
5931
|
severity: "error",
|
|
5749
5932
|
code: "routing.evidence.crossing_forbidden",
|
|
@@ -5791,13 +5974,41 @@ function routeEdge(input) {
|
|
|
5791
5974
|
code: "routing.obstacle.unavoidable",
|
|
5792
5975
|
message: "No bounded orthogonal route candidate avoided all obstacles."
|
|
5793
5976
|
});
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5977
|
+
const finalizedBestPoints = finalizeRoute(
|
|
5978
|
+
bestPoints,
|
|
5979
|
+
softObstacles,
|
|
5980
|
+
hardObstacles,
|
|
5981
|
+
diagnostics
|
|
5982
|
+
);
|
|
5983
|
+
let fallbackPoints = finalizedBestPoints;
|
|
5984
|
+
if (bestRejectedPath !== void 0) {
|
|
5985
|
+
const finalizedRejected = finalizeRoute(
|
|
5986
|
+
bestRejectedPath,
|
|
5797
5987
|
softObstacles,
|
|
5798
5988
|
hardObstacles,
|
|
5799
5989
|
diagnostics
|
|
5800
|
-
)
|
|
5990
|
+
);
|
|
5991
|
+
const rejectedCrossings = countObstacleCrossings(
|
|
5992
|
+
finalizedRejected,
|
|
5993
|
+
softObstacles
|
|
5994
|
+
);
|
|
5995
|
+
const heuristicCrossings = countObstacleCrossings(
|
|
5996
|
+
finalizedBestPoints,
|
|
5997
|
+
softObstacles
|
|
5998
|
+
);
|
|
5999
|
+
if (rejectedCrossings < heuristicCrossings) {
|
|
6000
|
+
fallbackPoints = finalizedRejected;
|
|
6001
|
+
}
|
|
6002
|
+
}
|
|
6003
|
+
checkBacktracking(
|
|
6004
|
+
fallbackPoints,
|
|
6005
|
+
fallbackPoints[0],
|
|
6006
|
+
fallbackPoints[fallbackPoints.length - 1],
|
|
6007
|
+
diagnostics,
|
|
6008
|
+
input.maxBacktrackingRatio
|
|
6009
|
+
);
|
|
6010
|
+
return {
|
|
6011
|
+
points: fallbackPoints,
|
|
5801
6012
|
diagnostics
|
|
5802
6013
|
};
|
|
5803
6014
|
}
|
|
@@ -6296,6 +6507,24 @@ function routeIntersectsObstacles(points, obstacles, spatialIndex) {
|
|
|
6296
6507
|
}
|
|
6297
6508
|
return false;
|
|
6298
6509
|
}
|
|
6510
|
+
function countObstacleCrossings(points, obstacles) {
|
|
6511
|
+
let count = 0;
|
|
6512
|
+
for (const obstacle of obstacles) {
|
|
6513
|
+
validateBox(obstacle);
|
|
6514
|
+
for (let pointIndex = 0; pointIndex < points.length - 1; pointIndex += 1) {
|
|
6515
|
+
const a = points[pointIndex];
|
|
6516
|
+
const b = points[pointIndex + 1];
|
|
6517
|
+
if (a === void 0 || b === void 0) {
|
|
6518
|
+
continue;
|
|
6519
|
+
}
|
|
6520
|
+
if (intersectsAabb(segmentBox2(a, b), obstacle)) {
|
|
6521
|
+
count += 1;
|
|
6522
|
+
break;
|
|
6523
|
+
}
|
|
6524
|
+
}
|
|
6525
|
+
}
|
|
6526
|
+
return count;
|
|
6527
|
+
}
|
|
6299
6528
|
function routeIntersectsEndpointInteriors(points, endpointInteriors) {
|
|
6300
6529
|
for (let index = 0; index < points.length - 1; index += 1) {
|
|
6301
6530
|
const a = points[index];
|
|
@@ -6741,7 +6970,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
6741
6970
|
constrained.boxes,
|
|
6742
6971
|
constrained.locks,
|
|
6743
6972
|
options?.overlapSpacing ?? 40,
|
|
6744
|
-
Math.max(0, options?.minLaneGutter ?? 0)
|
|
6973
|
+
Math.max(0, options?.minLaneGutter ?? 0),
|
|
6974
|
+
options.distributeContainedChildren ?? true
|
|
6745
6975
|
);
|
|
6746
6976
|
removeResolvedOverlapDiagnostics(diagnostics, constrained.boxes);
|
|
6747
6977
|
diagnostics.push(...swimlaneContracts.diagnostics);
|
|
@@ -6908,7 +7138,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
6908
7138
|
diagram.direction,
|
|
6909
7139
|
options,
|
|
6910
7140
|
diagnostics,
|
|
6911
|
-
coordinatedGroups
|
|
7141
|
+
coordinatedGroups,
|
|
7142
|
+
contentBounds
|
|
6912
7143
|
);
|
|
6913
7144
|
const edgeTextAnnotations = coordinateEdgeTextAnnotations(
|
|
6914
7145
|
coordinatedEdges,
|
|
@@ -7330,7 +7561,7 @@ function reportCjkTypographyDiagnostics(path, typography, previousStyle, diagnos
|
|
|
7330
7561
|
function containsCjk(value) {
|
|
7331
7562
|
return /[\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff]/u.test(value);
|
|
7332
7563
|
}
|
|
7333
|
-
function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottomFlow, nodeBoxes, locks, overlapSpacing, laneGutter) {
|
|
7564
|
+
function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottomFlow, nodeBoxes, locks, overlapSpacing, laneGutter, distributeContainedChildren) {
|
|
7334
7565
|
const layouts = /* @__PURE__ */ new Map();
|
|
7335
7566
|
const diagnostics = [];
|
|
7336
7567
|
const movedChildIds = /* @__PURE__ */ new Set();
|
|
@@ -7349,7 +7580,9 @@ function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottom
|
|
|
7349
7580
|
locks,
|
|
7350
7581
|
diagnostics,
|
|
7351
7582
|
movedChildIds,
|
|
7352
|
-
laneGutter
|
|
7583
|
+
laneGutter,
|
|
7584
|
+
constraints,
|
|
7585
|
+
distributeContainedChildren
|
|
7353
7586
|
);
|
|
7354
7587
|
if (layout2 !== void 0) {
|
|
7355
7588
|
layouts.set(swimlane.id, layout2);
|
|
@@ -7541,7 +7774,7 @@ function isStackRunaway(boxes, nodes, direction, options) {
|
|
|
7541
7774
|
const maxWidth = Math.max(...nodeBoxes.map((box) => box.width));
|
|
7542
7775
|
return xSpread <= Math.max(maxWidth, options.overlapSpacing ?? 40);
|
|
7543
7776
|
}
|
|
7544
|
-
function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, locks, diagnostics, movedChildIds, laneGutter) {
|
|
7777
|
+
function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, locks, diagnostics, movedChildIds, laneGutter, constraints, distributeContainedChildren) {
|
|
7545
7778
|
const headerHeight = swimlane.headerHeight ?? 28;
|
|
7546
7779
|
const padding = swimlane.padding ?? 16;
|
|
7547
7780
|
const laneBounds = swimlane.lanes.map((lane) => {
|
|
@@ -7566,7 +7799,9 @@ function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes
|
|
|
7566
7799
|
locks,
|
|
7567
7800
|
diagnostics,
|
|
7568
7801
|
movedChildIds,
|
|
7569
|
-
laneGutter
|
|
7802
|
+
laneGutter,
|
|
7803
|
+
constraints,
|
|
7804
|
+
distributeContainedChildren
|
|
7570
7805
|
);
|
|
7571
7806
|
}
|
|
7572
7807
|
return applyHorizontalSwimlaneContract(
|
|
@@ -7581,13 +7816,29 @@ function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes
|
|
|
7581
7816
|
laneGutter
|
|
7582
7817
|
);
|
|
7583
7818
|
}
|
|
7584
|
-
function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, laneBounds, headerHeight, padding, locks, diagnostics, movedChildIds, laneGutter) {
|
|
7819
|
+
function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, laneBounds, headerHeight, padding, locks, diagnostics, movedChildIds, laneGutter, constraints, distributeContainedChildren) {
|
|
7585
7820
|
const populatedBounds = laneBounds.filter(
|
|
7586
7821
|
(box) => box !== void 0
|
|
7587
7822
|
);
|
|
7588
7823
|
const top = Math.min(...populatedBounds.map((box) => box.y));
|
|
7589
7824
|
const left = Math.min(...populatedBounds.map((box) => box.x));
|
|
7590
7825
|
const maxChildHeight = Math.max(...populatedBounds.map((box) => box.height));
|
|
7826
|
+
const containedChildIds = /* @__PURE__ */ new Set();
|
|
7827
|
+
if (distributeContainedChildren) {
|
|
7828
|
+
for (const c of constraints) {
|
|
7829
|
+
if (c.kind !== "containment") continue;
|
|
7830
|
+
if (nodeBoxes.get(c.containerId) === void 0) continue;
|
|
7831
|
+
const distributable = c.childIds.filter((childId) => {
|
|
7832
|
+
if (nodeBoxes.get(childId) === void 0) return false;
|
|
7833
|
+
const lock = locks.get(childId);
|
|
7834
|
+
return lock === void 0 || lock.source === "fixed-position";
|
|
7835
|
+
});
|
|
7836
|
+
if (distributable.length < 2) continue;
|
|
7837
|
+
for (const childId of distributable) {
|
|
7838
|
+
containedChildIds.add(childId);
|
|
7839
|
+
}
|
|
7840
|
+
}
|
|
7841
|
+
}
|
|
7591
7842
|
const flowRanks = topToBottomFlow ? rankVerticalSwimlaneChildren(swimlane, edges) : /* @__PURE__ */ new Map();
|
|
7592
7843
|
const maxRank = flowRanks.size === 0 ? 0 : Math.max(...Array.from(flowRanks.values()));
|
|
7593
7844
|
const rankStackGap = Math.max(8, padding / 2);
|
|
@@ -7604,7 +7855,8 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
7604
7855
|
nodeBoxes,
|
|
7605
7856
|
flowRanks,
|
|
7606
7857
|
locks,
|
|
7607
|
-
rankStackGap
|
|
7858
|
+
rankStackGap,
|
|
7859
|
+
containedChildIds
|
|
7608
7860
|
);
|
|
7609
7861
|
const slotWidth = Math.max(
|
|
7610
7862
|
Math.max(...populatedBounds.map((box) => box.width)),
|
|
@@ -7626,7 +7878,10 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
7626
7878
|
const distributable = lane.children.filter(
|
|
7627
7879
|
(childId) => !locks.has(childId)
|
|
7628
7880
|
);
|
|
7629
|
-
|
|
7881
|
+
const coveredByContainment = lane.children.some(
|
|
7882
|
+
(childId) => containedChildIds.has(childId)
|
|
7883
|
+
);
|
|
7884
|
+
if (!coveredByContainment && distributable.length >= CROSS_AXIS_SPREAD_THRESHOLD) {
|
|
7630
7885
|
moveRankedVerticalLaneChildren(
|
|
7631
7886
|
lane.children,
|
|
7632
7887
|
nodeBoxes,
|
|
@@ -7654,6 +7909,9 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
7654
7909
|
);
|
|
7655
7910
|
continue;
|
|
7656
7911
|
}
|
|
7912
|
+
const rankedCoveredByContainment = lane.children.some(
|
|
7913
|
+
(childId) => containedChildIds.has(childId)
|
|
7914
|
+
);
|
|
7657
7915
|
moveRankedVerticalLaneChildren(
|
|
7658
7916
|
lane.children,
|
|
7659
7917
|
nodeBoxes,
|
|
@@ -7664,7 +7922,8 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
7664
7922
|
rankSpacing,
|
|
7665
7923
|
rankStackGap,
|
|
7666
7924
|
{ x: target.x, y: laneContentTop },
|
|
7667
|
-
slotWidth - padding * 2
|
|
7925
|
+
slotWidth - padding * 2,
|
|
7926
|
+
rankedCoveredByContainment
|
|
7668
7927
|
);
|
|
7669
7928
|
}
|
|
7670
7929
|
return {
|
|
@@ -7780,9 +8039,12 @@ function crossAxisSpreadWidth(items, gap) {
|
|
|
7780
8039
|
0
|
|
7781
8040
|
);
|
|
7782
8041
|
}
|
|
7783
|
-
function maxCrossAxisSpreadWidth(swimlane, nodeBoxes, flowRanks, locks, gap) {
|
|
8042
|
+
function maxCrossAxisSpreadWidth(swimlane, nodeBoxes, flowRanks, locks, gap, containedChildIds) {
|
|
7784
8043
|
let maxWidth = 0;
|
|
7785
8044
|
for (const lane of swimlane.lanes) {
|
|
8045
|
+
if (containedChildIds !== void 0 && lane.children.some((childId) => containedChildIds.has(childId))) {
|
|
8046
|
+
continue;
|
|
8047
|
+
}
|
|
7786
8048
|
for (const stack of rankStacks(
|
|
7787
8049
|
lane.children,
|
|
7788
8050
|
nodeBoxes,
|
|
@@ -7795,7 +8057,7 @@ function maxCrossAxisSpreadWidth(swimlane, nodeBoxes, flowRanks, locks, gap) {
|
|
|
7795
8057
|
}
|
|
7796
8058
|
return maxWidth;
|
|
7797
8059
|
}
|
|
7798
|
-
function moveRankedVerticalLaneChildren(childIds, nodeBoxes, locks, diagnostics, movedChildIds, flowRanks, rankSpacing, rankStackGap, target, contentWidth) {
|
|
8060
|
+
function moveRankedVerticalLaneChildren(childIds, nodeBoxes, locks, diagnostics, movedChildIds, flowRanks, rankSpacing, rankStackGap, target, contentWidth, suppressSpread) {
|
|
7799
8061
|
for (const [rank, stack] of rankStacks(childIds, nodeBoxes, flowRanks)) {
|
|
7800
8062
|
const unlocked = [];
|
|
7801
8063
|
for (const item of stack) {
|
|
@@ -7824,7 +8086,7 @@ function moveRankedVerticalLaneChildren(childIds, nodeBoxes, locks, diagnostics,
|
|
|
7824
8086
|
}
|
|
7825
8087
|
nodeBoxes.set(childId, next);
|
|
7826
8088
|
} else {
|
|
7827
|
-
const shouldSpread = unlocked.length >= CROSS_AXIS_SPREAD_THRESHOLD;
|
|
8089
|
+
const shouldSpread = !suppressSpread && unlocked.length >= CROSS_AXIS_SPREAD_THRESHOLD;
|
|
7828
8090
|
if (!shouldSpread) {
|
|
7829
8091
|
let yOffset = 0;
|
|
7830
8092
|
for (const { childId, box } of unlocked) {
|
|
@@ -8953,14 +9215,21 @@ function evidenceOverlapDiagnostic(block, conflict) {
|
|
|
8953
9215
|
}
|
|
8954
9216
|
};
|
|
8955
9217
|
}
|
|
8956
|
-
function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics, groups) {
|
|
9218
|
+
function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics, groups, contentBounds) {
|
|
8957
9219
|
const coordinated = [];
|
|
8958
9220
|
const coordinatedNodeById = new Map(
|
|
8959
9221
|
coordinatedNodes.map((node) => [node.id, node])
|
|
8960
9222
|
);
|
|
9223
|
+
const corridorMarginOption = options.corridorMargin ?? "auto";
|
|
9224
|
+
const corridorMargin = typeof corridorMarginOption === "number" ? corridorMarginOption : Math.max(
|
|
9225
|
+
200,
|
|
9226
|
+
Math.hypot(contentBounds.width, contentBounds.height) * 0.3
|
|
9227
|
+
);
|
|
9228
|
+
const routingGutter = options.routingGutter ?? 160;
|
|
9229
|
+
const queryGutter = (options.routeKind ?? "orthogonal") === "obstacle-avoiding" ? Math.max(routingGutter, corridorMargin) : routingGutter;
|
|
8961
9230
|
const nodeObstacleIndex = createBoxSpatialIndex(
|
|
8962
9231
|
obstacles.map((box, index) => ({ id: `node-obstacle:${index}`, box })),
|
|
8963
|
-
|
|
9232
|
+
queryGutter
|
|
8964
9233
|
);
|
|
8965
9234
|
for (const edge of edges) {
|
|
8966
9235
|
const source = nodes.get(edge.source.nodeId);
|
|
@@ -8982,11 +9251,7 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
8982
9251
|
const sourcePort = coordinatedNodeById.get(edge.source.nodeId)?.ports?.find((port) => port.id === edge.source.portId);
|
|
8983
9252
|
const targetPort = coordinatedNodeById.get(edge.target.nodeId)?.ports?.find((port) => port.id === edge.target.portId);
|
|
8984
9253
|
const routeTextObstacles = textObstacles.filter((annotation) => !isEdgeConnectedTextAnnotation(edge, annotation)).map((annotation) => annotation.box);
|
|
8985
|
-
const corridor = edgeCorridorBox(
|
|
8986
|
-
source.box,
|
|
8987
|
-
target.box,
|
|
8988
|
-
options.routingGutter ?? 160
|
|
8989
|
-
);
|
|
9254
|
+
const corridor = edgeCorridorBox(source.box, target.box, queryGutter);
|
|
8990
9255
|
const routeNodeObstacles = queryBoxSpatialIndex(nodeObstacleIndex, corridor).map((entry) => entry.box).filter(
|
|
8991
9256
|
(obstacle) => !sameBox(obstacle, source.obstacleBox) && !sameBox(obstacle, target.obstacleBox)
|
|
8992
9257
|
);
|
|
@@ -9004,7 +9269,9 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
9004
9269
|
...routeTextObstacles
|
|
9005
9270
|
],
|
|
9006
9271
|
hardObstacles,
|
|
9007
|
-
|
|
9272
|
+
corridorMargin,
|
|
9273
|
+
...options.maxRoutingAttempts === void 0 ? {} : { maxRoutingAttempts: options.maxRoutingAttempts },
|
|
9274
|
+
...options.maxBacktrackingRatio === void 0 ? {} : { maxBacktrackingRatio: options.maxBacktrackingRatio }
|
|
9008
9275
|
});
|
|
9009
9276
|
diagnostics.push(
|
|
9010
9277
|
...route.diagnostics.map((diagnostic) => ({
|