@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/cli/index.cjs
CHANGED
|
@@ -4164,7 +4164,10 @@ function findObstacleFreePath(source, target, obstacles, options = {}, diagnosti
|
|
|
4164
4164
|
detail: {
|
|
4165
4165
|
xsCount: xs.length,
|
|
4166
4166
|
ysCount: ys.length,
|
|
4167
|
-
maxNodes
|
|
4167
|
+
maxNodes,
|
|
4168
|
+
obstacleCount: obstacles.length,
|
|
4169
|
+
stage: "corridor-filtered",
|
|
4170
|
+
...corridorMargin === void 0 ? {} : { corridorMargin }
|
|
4168
4171
|
}
|
|
4169
4172
|
});
|
|
4170
4173
|
return null;
|
|
@@ -4210,7 +4213,14 @@ function findObstacleFreePath(source, target, obstacles, options = {}, diagnosti
|
|
|
4210
4213
|
severity: "warning",
|
|
4211
4214
|
code: "routing.astar.grid_overflow",
|
|
4212
4215
|
message: `A* full-retry grid overflow: ${xsFull.length * ysFull.length} nodes > ${maxNodes} limit. Falling back to heuristic routing.`,
|
|
4213
|
-
detail: {
|
|
4216
|
+
detail: {
|
|
4217
|
+
xsCount: xsFull.length,
|
|
4218
|
+
ysCount: ysFull.length,
|
|
4219
|
+
maxNodes,
|
|
4220
|
+
obstacleCount: obstacles.length,
|
|
4221
|
+
stage: "full-retry",
|
|
4222
|
+
...corridorMargin === void 0 ? {} : { corridorMargin }
|
|
4223
|
+
}
|
|
4214
4224
|
});
|
|
4215
4225
|
return null;
|
|
4216
4226
|
}
|
|
@@ -4453,6 +4463,64 @@ function areCollinear(a, b, c) {
|
|
|
4453
4463
|
return a.x === b.x && b.x === c.x || a.y === b.y && b.y === c.y;
|
|
4454
4464
|
}
|
|
4455
4465
|
|
|
4466
|
+
// src/routing/budget.ts
|
|
4467
|
+
var MIN_CORNER_BUDGET = 600;
|
|
4468
|
+
var MAX_CORNER_BUDGET = 3e3;
|
|
4469
|
+
var MIN_NODE_BUDGET = 4e3;
|
|
4470
|
+
var MAX_NODE_BUDGET = 64e3;
|
|
4471
|
+
var CORNERS_PER_OBSTACLE = 12;
|
|
4472
|
+
var CORNER_HEADROOM = 2;
|
|
4473
|
+
var GRID_SAFETY_FACTOR = 3;
|
|
4474
|
+
var CORRIDOR_SCALING_K = 0.5;
|
|
4475
|
+
var CORRIDOR_SCALING_BASE = 200;
|
|
4476
|
+
function computeRoutingBudget(cornerObstacles, allObstacles, corridorMargin, overrides = {}) {
|
|
4477
|
+
const adaptiveMaxCorners = deriveMaxCorners(
|
|
4478
|
+
cornerObstacles.length,
|
|
4479
|
+
corridorMargin
|
|
4480
|
+
);
|
|
4481
|
+
const adaptiveMaxNodes = deriveMaxNodes(
|
|
4482
|
+
allObstacles.length,
|
|
4483
|
+
corridorMargin
|
|
4484
|
+
);
|
|
4485
|
+
return {
|
|
4486
|
+
maxCorners: resolveBudget(
|
|
4487
|
+
overrides.maxCorners,
|
|
4488
|
+
adaptiveMaxCorners,
|
|
4489
|
+
MIN_CORNER_BUDGET,
|
|
4490
|
+
MAX_CORNER_BUDGET
|
|
4491
|
+
),
|
|
4492
|
+
maxNodes: resolveBudget(
|
|
4493
|
+
overrides.maxNodes,
|
|
4494
|
+
adaptiveMaxNodes,
|
|
4495
|
+
MIN_NODE_BUDGET,
|
|
4496
|
+
MAX_NODE_BUDGET
|
|
4497
|
+
),
|
|
4498
|
+
cornerObstacleCount: cornerObstacles.length,
|
|
4499
|
+
gridObstacleCount: allObstacles.length,
|
|
4500
|
+
corridorMargin
|
|
4501
|
+
};
|
|
4502
|
+
}
|
|
4503
|
+
function deriveMaxCorners(obstacleCount, corridorMargin) {
|
|
4504
|
+
const base = 2 + obstacleCount * CORNERS_PER_OBSTACLE * CORNER_HEADROOM;
|
|
4505
|
+
const corridorFactor = corridorScalingFactor(corridorMargin);
|
|
4506
|
+
return Math.ceil(base * corridorFactor);
|
|
4507
|
+
}
|
|
4508
|
+
function deriveMaxNodes(obstacleCount, corridorMargin) {
|
|
4509
|
+
const base = 4 * obstacleCount * obstacleCount + 4 * obstacleCount + 100;
|
|
4510
|
+
const corridorFactor = corridorScalingFactor(corridorMargin);
|
|
4511
|
+
return Math.ceil(base * GRID_SAFETY_FACTOR * corridorFactor);
|
|
4512
|
+
}
|
|
4513
|
+
function corridorScalingFactor(corridorMargin) {
|
|
4514
|
+
return 1 + corridorMargin / CORRIDOR_SCALING_BASE * CORRIDOR_SCALING_K;
|
|
4515
|
+
}
|
|
4516
|
+
function resolveBudget(override, adaptive, min, max) {
|
|
4517
|
+
const chosen = override !== void 0 && Number.isFinite(override) && override >= 1 ? override : adaptive;
|
|
4518
|
+
return clamp(chosen, min, max);
|
|
4519
|
+
}
|
|
4520
|
+
function clamp(value, min, max) {
|
|
4521
|
+
return Math.max(min, Math.min(max, value));
|
|
4522
|
+
}
|
|
4523
|
+
|
|
4456
4524
|
// src/routing/visibility-router.ts
|
|
4457
4525
|
function findCornerGraphPath(source, target, obstacles, options = {}, diagnostics) {
|
|
4458
4526
|
const margin = options.margin ?? 0;
|
|
@@ -4466,7 +4534,12 @@ function findCornerGraphPath(source, target, obstacles, options = {}, diagnostic
|
|
|
4466
4534
|
severity: "warning",
|
|
4467
4535
|
code: "routing.visibility.corner_overflow",
|
|
4468
4536
|
message: `Corner graph overflow: ${vertices.length} vertices > ${maxCorners}. Falling back to grid A*.`,
|
|
4469
|
-
detail: {
|
|
4537
|
+
detail: {
|
|
4538
|
+
vertexCount: vertices.length,
|
|
4539
|
+
maxCorners,
|
|
4540
|
+
obstacleCount: obstacles.length,
|
|
4541
|
+
...options.corridorMargin === void 0 ? {} : { corridorMargin: options.corridorMargin }
|
|
4542
|
+
}
|
|
4470
4543
|
});
|
|
4471
4544
|
return null;
|
|
4472
4545
|
}
|
|
@@ -4740,7 +4813,7 @@ function segmentCrossesAnyObstacle(a, b, obstacles) {
|
|
|
4740
4813
|
}
|
|
4741
4814
|
|
|
4742
4815
|
// src/routing/routes.ts
|
|
4743
|
-
function checkBacktracking(points, source, target, diagnostics) {
|
|
4816
|
+
function checkBacktracking(points, source, target, diagnostics, maxRatio) {
|
|
4744
4817
|
if (points.length < 2) return;
|
|
4745
4818
|
const direct = Math.hypot(target.x - source.x, target.y - source.y);
|
|
4746
4819
|
if (direct <= 0) return;
|
|
@@ -4750,7 +4823,7 @@ function checkBacktracking(points, source, target, diagnostics) {
|
|
|
4750
4823
|
const b = points[i + 1];
|
|
4751
4824
|
routeLen += Math.hypot(b.x - a.x, b.y - a.y);
|
|
4752
4825
|
}
|
|
4753
|
-
const threshold =
|
|
4826
|
+
const threshold = maxRatio ?? 20;
|
|
4754
4827
|
if (routeLen > direct * threshold) {
|
|
4755
4828
|
diagnostics.push({
|
|
4756
4829
|
severity: "warning",
|
|
@@ -4768,8 +4841,20 @@ function routeEdge(input) {
|
|
|
4768
4841
|
const diagnostics = [];
|
|
4769
4842
|
const softObstacles = input.obstacles ?? [];
|
|
4770
4843
|
const hardObstacles = input.hardObstacles ?? [];
|
|
4844
|
+
let bestRejectedPath;
|
|
4845
|
+
let bestRejectedCrossings = Number.POSITIVE_INFINITY;
|
|
4771
4846
|
const softObstacleIndex = input.obstacleIndex ?? createBoxSpatialIndex(indexedBoxes(softObstacles));
|
|
4772
4847
|
const hardObstacleIndex = input.hardObstacleIndex ?? createBoxSpatialIndex(indexedBoxes(hardObstacles));
|
|
4848
|
+
const recordRejected = (candidate) => {
|
|
4849
|
+
if (routeIntersectsObstacles(candidate, hardObstacles, hardObstacleIndex)) {
|
|
4850
|
+
return;
|
|
4851
|
+
}
|
|
4852
|
+
const crossings = countObstacleCrossings(candidate, softObstacles);
|
|
4853
|
+
if (crossings < bestRejectedCrossings) {
|
|
4854
|
+
bestRejectedCrossings = crossings;
|
|
4855
|
+
bestRejectedPath = candidate;
|
|
4856
|
+
}
|
|
4857
|
+
};
|
|
4773
4858
|
const maxAttempts = input.maxRoutingAttempts ?? 5;
|
|
4774
4859
|
const defaultAnchors = defaultAnchorsForGeometry(
|
|
4775
4860
|
input.source.box,
|
|
@@ -4829,20 +4914,32 @@ function routeEdge(input) {
|
|
|
4829
4914
|
targetAnchor
|
|
4830
4915
|
);
|
|
4831
4916
|
const allObstacles = [...softObstacles, ...hardObstacles];
|
|
4917
|
+
const corridorMargin = input.corridorMargin ?? 32;
|
|
4832
4918
|
const corridorObstacles = filterObstaclesByCorridor(
|
|
4833
4919
|
source,
|
|
4834
4920
|
target,
|
|
4835
4921
|
allObstacles,
|
|
4836
4922
|
[],
|
|
4837
4923
|
// endpointObstacles passed separately via options
|
|
4838
|
-
|
|
4924
|
+
corridorMargin
|
|
4839
4925
|
);
|
|
4840
4926
|
const cornerObstacles = corridorObstacles.length === 0 && allObstacles.length > 0 ? allObstacles : corridorObstacles;
|
|
4927
|
+
const budget = computeRoutingBudget(
|
|
4928
|
+
cornerObstacles,
|
|
4929
|
+
allObstacles,
|
|
4930
|
+
corridorMargin,
|
|
4931
|
+
{ maxCorners: input.maxCorners, maxNodes: input.maxNodes }
|
|
4932
|
+
);
|
|
4841
4933
|
let cornerPath = findCornerGraphPath(
|
|
4842
4934
|
source,
|
|
4843
4935
|
target,
|
|
4844
4936
|
cornerObstacles,
|
|
4845
|
-
{
|
|
4937
|
+
{
|
|
4938
|
+
endpointObstacles,
|
|
4939
|
+
margin: 2,
|
|
4940
|
+
maxCorners: budget.maxCorners,
|
|
4941
|
+
corridorMargin
|
|
4942
|
+
},
|
|
4846
4943
|
diagnostics
|
|
4847
4944
|
);
|
|
4848
4945
|
if (cornerPath === null && cornerObstacles.length < allObstacles.length) {
|
|
@@ -4850,7 +4947,12 @@ function routeEdge(input) {
|
|
|
4850
4947
|
source,
|
|
4851
4948
|
target,
|
|
4852
4949
|
allObstacles,
|
|
4853
|
-
{
|
|
4950
|
+
{
|
|
4951
|
+
endpointObstacles,
|
|
4952
|
+
margin: 2,
|
|
4953
|
+
maxCorners: budget.maxCorners,
|
|
4954
|
+
corridorMargin
|
|
4955
|
+
},
|
|
4854
4956
|
diagnostics
|
|
4855
4957
|
);
|
|
4856
4958
|
}
|
|
@@ -4858,7 +4960,12 @@ function routeEdge(input) {
|
|
|
4858
4960
|
source,
|
|
4859
4961
|
target,
|
|
4860
4962
|
allObstacles,
|
|
4861
|
-
{
|
|
4963
|
+
{
|
|
4964
|
+
endpointObstacles,
|
|
4965
|
+
margin: 0,
|
|
4966
|
+
corridorMargin,
|
|
4967
|
+
maxNodes: budget.maxNodes
|
|
4968
|
+
},
|
|
4862
4969
|
diagnostics
|
|
4863
4970
|
);
|
|
4864
4971
|
if (path !== null && path.length >= 2) {
|
|
@@ -4875,15 +4982,27 @@ function routeEdge(input) {
|
|
|
4875
4982
|
softObstacles,
|
|
4876
4983
|
softObstacleIndex
|
|
4877
4984
|
) && !routeIntersectsObstacles(finalized, hardObstacles, hardObstacleIndex)) {
|
|
4878
|
-
checkBacktracking(
|
|
4985
|
+
checkBacktracking(
|
|
4986
|
+
finalized,
|
|
4987
|
+
source,
|
|
4988
|
+
target,
|
|
4989
|
+
diagnostics,
|
|
4990
|
+
input.maxBacktrackingRatio
|
|
4991
|
+
);
|
|
4879
4992
|
return { points: finalized, diagnostics };
|
|
4880
4993
|
}
|
|
4994
|
+
recordRejected(finalized);
|
|
4881
4995
|
if (cornerPath !== null) {
|
|
4882
4996
|
const fullCornerPath = cornerObstacles.length < allObstacles.length ? findCornerGraphPath(
|
|
4883
4997
|
source,
|
|
4884
4998
|
target,
|
|
4885
4999
|
allObstacles,
|
|
4886
|
-
{
|
|
5000
|
+
{
|
|
5001
|
+
endpointObstacles,
|
|
5002
|
+
margin: 2,
|
|
5003
|
+
maxCorners: budget.maxCorners,
|
|
5004
|
+
corridorMargin
|
|
5005
|
+
},
|
|
4887
5006
|
diagnostics
|
|
4888
5007
|
) : null;
|
|
4889
5008
|
if (fullCornerPath !== null && fullCornerPath.length >= 2) {
|
|
@@ -4904,15 +5023,27 @@ function routeEdge(input) {
|
|
|
4904
5023
|
hardObstacles,
|
|
4905
5024
|
hardObstacleIndex
|
|
4906
5025
|
)) {
|
|
4907
|
-
checkBacktracking(
|
|
5026
|
+
checkBacktracking(
|
|
5027
|
+
fullFinalized,
|
|
5028
|
+
source,
|
|
5029
|
+
target,
|
|
5030
|
+
diagnostics,
|
|
5031
|
+
input.maxBacktrackingRatio
|
|
5032
|
+
);
|
|
4908
5033
|
return { points: fullFinalized, diagnostics };
|
|
4909
5034
|
}
|
|
5035
|
+
recordRejected(fullFinalized);
|
|
4910
5036
|
}
|
|
4911
5037
|
const gridPath = findObstacleFreePath(
|
|
4912
5038
|
source,
|
|
4913
5039
|
target,
|
|
4914
5040
|
allObstacles,
|
|
4915
|
-
{
|
|
5041
|
+
{
|
|
5042
|
+
endpointObstacles,
|
|
5043
|
+
margin: 0,
|
|
5044
|
+
corridorMargin,
|
|
5045
|
+
maxNodes: budget.maxNodes
|
|
5046
|
+
},
|
|
4916
5047
|
diagnostics
|
|
4917
5048
|
);
|
|
4918
5049
|
if (gridPath !== null && gridPath.length >= 2) {
|
|
@@ -4933,9 +5064,16 @@ function routeEdge(input) {
|
|
|
4933
5064
|
hardObstacles,
|
|
4934
5065
|
hardObstacleIndex
|
|
4935
5066
|
)) {
|
|
4936
|
-
checkBacktracking(
|
|
5067
|
+
checkBacktracking(
|
|
5068
|
+
gridFinalized,
|
|
5069
|
+
source,
|
|
5070
|
+
target,
|
|
5071
|
+
diagnostics,
|
|
5072
|
+
input.maxBacktrackingRatio
|
|
5073
|
+
);
|
|
4937
5074
|
return { points: gridFinalized, diagnostics };
|
|
4938
5075
|
}
|
|
5076
|
+
recordRejected(gridFinalized);
|
|
4939
5077
|
}
|
|
4940
5078
|
}
|
|
4941
5079
|
}
|
|
@@ -4999,7 +5137,8 @@ function routeEdge(input) {
|
|
|
4999
5137
|
finalizedClean,
|
|
5000
5138
|
candidate.points[0],
|
|
5001
5139
|
candidate.points[candidate.points.length - 1],
|
|
5002
|
-
diagnostics
|
|
5140
|
+
diagnostics,
|
|
5141
|
+
input.maxBacktrackingRatio
|
|
5003
5142
|
);
|
|
5004
5143
|
return { points: finalizedClean, diagnostics };
|
|
5005
5144
|
}
|
|
@@ -5065,13 +5204,41 @@ function routeEdge(input) {
|
|
|
5065
5204
|
code: "routing.obstacle.unavoidable",
|
|
5066
5205
|
message: "No bounded orthogonal route candidate avoided all soft obstacles."
|
|
5067
5206
|
});
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5207
|
+
const finalizedSoftBest = finalizeRoute(
|
|
5208
|
+
bestPoints2,
|
|
5209
|
+
softObstacles,
|
|
5210
|
+
hardObstacles,
|
|
5211
|
+
diagnostics
|
|
5212
|
+
);
|
|
5213
|
+
let softFallback = finalizedSoftBest;
|
|
5214
|
+
if (bestRejectedPath !== void 0) {
|
|
5215
|
+
const finalizedRejected = finalizeRoute(
|
|
5216
|
+
bestRejectedPath,
|
|
5071
5217
|
softObstacles,
|
|
5072
5218
|
hardObstacles,
|
|
5073
5219
|
diagnostics
|
|
5074
|
-
)
|
|
5220
|
+
);
|
|
5221
|
+
const rejectedCrossings = countObstacleCrossings(
|
|
5222
|
+
finalizedRejected,
|
|
5223
|
+
softObstacles
|
|
5224
|
+
);
|
|
5225
|
+
const heuristicCrossings = countObstacleCrossings(
|
|
5226
|
+
finalizedSoftBest,
|
|
5227
|
+
softObstacles
|
|
5228
|
+
);
|
|
5229
|
+
if (rejectedCrossings < heuristicCrossings) {
|
|
5230
|
+
softFallback = finalizedRejected;
|
|
5231
|
+
}
|
|
5232
|
+
}
|
|
5233
|
+
checkBacktracking(
|
|
5234
|
+
softFallback,
|
|
5235
|
+
softFallback[0],
|
|
5236
|
+
softFallback[softFallback.length - 1],
|
|
5237
|
+
diagnostics,
|
|
5238
|
+
input.maxBacktrackingRatio
|
|
5239
|
+
);
|
|
5240
|
+
return {
|
|
5241
|
+
points: softFallback,
|
|
5075
5242
|
diagnostics
|
|
5076
5243
|
};
|
|
5077
5244
|
}
|
|
@@ -5103,6 +5270,22 @@ function routeEdge(input) {
|
|
|
5103
5270
|
maxAttempts
|
|
5104
5271
|
);
|
|
5105
5272
|
}
|
|
5273
|
+
if (bestRejectedPath !== void 0) {
|
|
5274
|
+
diagnostics.push({
|
|
5275
|
+
severity: "warning",
|
|
5276
|
+
code: "routing.obstacle.unavoidable",
|
|
5277
|
+
message: "Using A* route with minor soft-obstacle crossings to avoid hard evidence obstacles."
|
|
5278
|
+
});
|
|
5279
|
+
return {
|
|
5280
|
+
points: finalizeRoute(
|
|
5281
|
+
bestRejectedPath,
|
|
5282
|
+
softObstacles,
|
|
5283
|
+
hardObstacles,
|
|
5284
|
+
diagnostics
|
|
5285
|
+
),
|
|
5286
|
+
diagnostics
|
|
5287
|
+
};
|
|
5288
|
+
}
|
|
5106
5289
|
diagnostics.push({
|
|
5107
5290
|
severity: "error",
|
|
5108
5291
|
code: "routing.evidence.crossing_forbidden",
|
|
@@ -5150,13 +5333,41 @@ function routeEdge(input) {
|
|
|
5150
5333
|
code: "routing.obstacle.unavoidable",
|
|
5151
5334
|
message: "No bounded orthogonal route candidate avoided all obstacles."
|
|
5152
5335
|
});
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5336
|
+
const finalizedBestPoints = finalizeRoute(
|
|
5337
|
+
bestPoints,
|
|
5338
|
+
softObstacles,
|
|
5339
|
+
hardObstacles,
|
|
5340
|
+
diagnostics
|
|
5341
|
+
);
|
|
5342
|
+
let fallbackPoints = finalizedBestPoints;
|
|
5343
|
+
if (bestRejectedPath !== void 0) {
|
|
5344
|
+
const finalizedRejected = finalizeRoute(
|
|
5345
|
+
bestRejectedPath,
|
|
5156
5346
|
softObstacles,
|
|
5157
5347
|
hardObstacles,
|
|
5158
5348
|
diagnostics
|
|
5159
|
-
)
|
|
5349
|
+
);
|
|
5350
|
+
const rejectedCrossings = countObstacleCrossings(
|
|
5351
|
+
finalizedRejected,
|
|
5352
|
+
softObstacles
|
|
5353
|
+
);
|
|
5354
|
+
const heuristicCrossings = countObstacleCrossings(
|
|
5355
|
+
finalizedBestPoints,
|
|
5356
|
+
softObstacles
|
|
5357
|
+
);
|
|
5358
|
+
if (rejectedCrossings < heuristicCrossings) {
|
|
5359
|
+
fallbackPoints = finalizedRejected;
|
|
5360
|
+
}
|
|
5361
|
+
}
|
|
5362
|
+
checkBacktracking(
|
|
5363
|
+
fallbackPoints,
|
|
5364
|
+
fallbackPoints[0],
|
|
5365
|
+
fallbackPoints[fallbackPoints.length - 1],
|
|
5366
|
+
diagnostics,
|
|
5367
|
+
input.maxBacktrackingRatio
|
|
5368
|
+
);
|
|
5369
|
+
return {
|
|
5370
|
+
points: fallbackPoints,
|
|
5160
5371
|
diagnostics
|
|
5161
5372
|
};
|
|
5162
5373
|
}
|
|
@@ -5655,6 +5866,24 @@ function routeIntersectsObstacles(points, obstacles, spatialIndex) {
|
|
|
5655
5866
|
}
|
|
5656
5867
|
return false;
|
|
5657
5868
|
}
|
|
5869
|
+
function countObstacleCrossings(points, obstacles) {
|
|
5870
|
+
let count = 0;
|
|
5871
|
+
for (const obstacle of obstacles) {
|
|
5872
|
+
validateBox(obstacle);
|
|
5873
|
+
for (let pointIndex = 0; pointIndex < points.length - 1; pointIndex += 1) {
|
|
5874
|
+
const a = points[pointIndex];
|
|
5875
|
+
const b = points[pointIndex + 1];
|
|
5876
|
+
if (a === void 0 || b === void 0) {
|
|
5877
|
+
continue;
|
|
5878
|
+
}
|
|
5879
|
+
if (intersectsAabb(segmentBox2(a, b), obstacle)) {
|
|
5880
|
+
count += 1;
|
|
5881
|
+
break;
|
|
5882
|
+
}
|
|
5883
|
+
}
|
|
5884
|
+
}
|
|
5885
|
+
return count;
|
|
5886
|
+
}
|
|
5658
5887
|
function routeIntersectsEndpointInteriors(points, endpointInteriors) {
|
|
5659
5888
|
for (let index = 0; index < points.length - 1; index += 1) {
|
|
5660
5889
|
const a = points[index];
|
|
@@ -5889,7 +6118,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5889
6118
|
constrained.boxes,
|
|
5890
6119
|
constrained.locks,
|
|
5891
6120
|
options?.overlapSpacing ?? 40,
|
|
5892
|
-
Math.max(0, options?.minLaneGutter ?? 0)
|
|
6121
|
+
Math.max(0, options?.minLaneGutter ?? 0),
|
|
6122
|
+
options.distributeContainedChildren ?? true
|
|
5893
6123
|
);
|
|
5894
6124
|
removeResolvedOverlapDiagnostics(diagnostics, constrained.boxes);
|
|
5895
6125
|
diagnostics.push(...swimlaneContracts.diagnostics);
|
|
@@ -6056,7 +6286,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
6056
6286
|
diagram.direction,
|
|
6057
6287
|
options,
|
|
6058
6288
|
diagnostics,
|
|
6059
|
-
coordinatedGroups
|
|
6289
|
+
coordinatedGroups,
|
|
6290
|
+
contentBounds
|
|
6060
6291
|
);
|
|
6061
6292
|
const edgeTextAnnotations = coordinateEdgeTextAnnotations(
|
|
6062
6293
|
coordinatedEdges,
|
|
@@ -6475,7 +6706,7 @@ function reportCjkTypographyDiagnostics(path, typography, previousStyle, diagnos
|
|
|
6475
6706
|
function containsCjk(value) {
|
|
6476
6707
|
return /[\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff]/u.test(value);
|
|
6477
6708
|
}
|
|
6478
|
-
function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottomFlow, nodeBoxes, locks, overlapSpacing, laneGutter) {
|
|
6709
|
+
function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottomFlow, nodeBoxes, locks, overlapSpacing, laneGutter, distributeContainedChildren) {
|
|
6479
6710
|
const layouts = /* @__PURE__ */ new Map();
|
|
6480
6711
|
const diagnostics = [];
|
|
6481
6712
|
const movedChildIds = /* @__PURE__ */ new Set();
|
|
@@ -6494,7 +6725,9 @@ function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottom
|
|
|
6494
6725
|
locks,
|
|
6495
6726
|
diagnostics,
|
|
6496
6727
|
movedChildIds,
|
|
6497
|
-
laneGutter
|
|
6728
|
+
laneGutter,
|
|
6729
|
+
constraints,
|
|
6730
|
+
distributeContainedChildren
|
|
6498
6731
|
);
|
|
6499
6732
|
if (layout2 !== void 0) {
|
|
6500
6733
|
layouts.set(swimlane.id, layout2);
|
|
@@ -6686,7 +6919,7 @@ function isStackRunaway(boxes, nodes, direction, options) {
|
|
|
6686
6919
|
const maxWidth = Math.max(...nodeBoxes.map((box) => box.width));
|
|
6687
6920
|
return xSpread <= Math.max(maxWidth, options.overlapSpacing ?? 40);
|
|
6688
6921
|
}
|
|
6689
|
-
function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, locks, diagnostics, movedChildIds, laneGutter) {
|
|
6922
|
+
function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, locks, diagnostics, movedChildIds, laneGutter, constraints, distributeContainedChildren) {
|
|
6690
6923
|
const headerHeight = swimlane.headerHeight ?? 28;
|
|
6691
6924
|
const padding = swimlane.padding ?? 16;
|
|
6692
6925
|
const laneBounds = swimlane.lanes.map((lane) => {
|
|
@@ -6711,7 +6944,9 @@ function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes
|
|
|
6711
6944
|
locks,
|
|
6712
6945
|
diagnostics,
|
|
6713
6946
|
movedChildIds,
|
|
6714
|
-
laneGutter
|
|
6947
|
+
laneGutter,
|
|
6948
|
+
constraints,
|
|
6949
|
+
distributeContainedChildren
|
|
6715
6950
|
);
|
|
6716
6951
|
}
|
|
6717
6952
|
return applyHorizontalSwimlaneContract(
|
|
@@ -6726,13 +6961,29 @@ function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes
|
|
|
6726
6961
|
laneGutter
|
|
6727
6962
|
);
|
|
6728
6963
|
}
|
|
6729
|
-
function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, laneBounds, headerHeight, padding, locks, diagnostics, movedChildIds, laneGutter) {
|
|
6964
|
+
function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, laneBounds, headerHeight, padding, locks, diagnostics, movedChildIds, laneGutter, constraints, distributeContainedChildren) {
|
|
6730
6965
|
const populatedBounds = laneBounds.filter(
|
|
6731
6966
|
(box) => box !== void 0
|
|
6732
6967
|
);
|
|
6733
6968
|
const top = Math.min(...populatedBounds.map((box) => box.y));
|
|
6734
6969
|
const left = Math.min(...populatedBounds.map((box) => box.x));
|
|
6735
6970
|
const maxChildHeight = Math.max(...populatedBounds.map((box) => box.height));
|
|
6971
|
+
const containedChildIds = /* @__PURE__ */ new Set();
|
|
6972
|
+
if (distributeContainedChildren) {
|
|
6973
|
+
for (const c of constraints) {
|
|
6974
|
+
if (c.kind !== "containment") continue;
|
|
6975
|
+
if (nodeBoxes.get(c.containerId) === void 0) continue;
|
|
6976
|
+
const distributable = c.childIds.filter((childId) => {
|
|
6977
|
+
if (nodeBoxes.get(childId) === void 0) return false;
|
|
6978
|
+
const lock = locks.get(childId);
|
|
6979
|
+
return lock === void 0 || lock.source === "fixed-position";
|
|
6980
|
+
});
|
|
6981
|
+
if (distributable.length < 2) continue;
|
|
6982
|
+
for (const childId of distributable) {
|
|
6983
|
+
containedChildIds.add(childId);
|
|
6984
|
+
}
|
|
6985
|
+
}
|
|
6986
|
+
}
|
|
6736
6987
|
const flowRanks = topToBottomFlow ? rankVerticalSwimlaneChildren(swimlane, edges) : /* @__PURE__ */ new Map();
|
|
6737
6988
|
const maxRank = flowRanks.size === 0 ? 0 : Math.max(...Array.from(flowRanks.values()));
|
|
6738
6989
|
const rankStackGap = Math.max(8, padding / 2);
|
|
@@ -6749,7 +7000,8 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
6749
7000
|
nodeBoxes,
|
|
6750
7001
|
flowRanks,
|
|
6751
7002
|
locks,
|
|
6752
|
-
rankStackGap
|
|
7003
|
+
rankStackGap,
|
|
7004
|
+
containedChildIds
|
|
6753
7005
|
);
|
|
6754
7006
|
const slotWidth = Math.max(
|
|
6755
7007
|
Math.max(...populatedBounds.map((box) => box.width)),
|
|
@@ -6771,7 +7023,10 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
6771
7023
|
const distributable = lane.children.filter(
|
|
6772
7024
|
(childId) => !locks.has(childId)
|
|
6773
7025
|
);
|
|
6774
|
-
|
|
7026
|
+
const coveredByContainment = lane.children.some(
|
|
7027
|
+
(childId) => containedChildIds.has(childId)
|
|
7028
|
+
);
|
|
7029
|
+
if (!coveredByContainment && distributable.length >= CROSS_AXIS_SPREAD_THRESHOLD) {
|
|
6775
7030
|
moveRankedVerticalLaneChildren(
|
|
6776
7031
|
lane.children,
|
|
6777
7032
|
nodeBoxes,
|
|
@@ -6799,6 +7054,9 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
6799
7054
|
);
|
|
6800
7055
|
continue;
|
|
6801
7056
|
}
|
|
7057
|
+
const rankedCoveredByContainment = lane.children.some(
|
|
7058
|
+
(childId) => containedChildIds.has(childId)
|
|
7059
|
+
);
|
|
6802
7060
|
moveRankedVerticalLaneChildren(
|
|
6803
7061
|
lane.children,
|
|
6804
7062
|
nodeBoxes,
|
|
@@ -6809,7 +7067,8 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
6809
7067
|
rankSpacing,
|
|
6810
7068
|
rankStackGap,
|
|
6811
7069
|
{ x: target.x, y: laneContentTop },
|
|
6812
|
-
slotWidth - padding * 2
|
|
7070
|
+
slotWidth - padding * 2,
|
|
7071
|
+
rankedCoveredByContainment
|
|
6813
7072
|
);
|
|
6814
7073
|
}
|
|
6815
7074
|
return {
|
|
@@ -6925,9 +7184,12 @@ function crossAxisSpreadWidth(items, gap) {
|
|
|
6925
7184
|
0
|
|
6926
7185
|
);
|
|
6927
7186
|
}
|
|
6928
|
-
function maxCrossAxisSpreadWidth(swimlane, nodeBoxes, flowRanks, locks, gap) {
|
|
7187
|
+
function maxCrossAxisSpreadWidth(swimlane, nodeBoxes, flowRanks, locks, gap, containedChildIds) {
|
|
6929
7188
|
let maxWidth = 0;
|
|
6930
7189
|
for (const lane of swimlane.lanes) {
|
|
7190
|
+
if (containedChildIds !== void 0 && lane.children.some((childId) => containedChildIds.has(childId))) {
|
|
7191
|
+
continue;
|
|
7192
|
+
}
|
|
6931
7193
|
for (const stack of rankStacks(
|
|
6932
7194
|
lane.children,
|
|
6933
7195
|
nodeBoxes,
|
|
@@ -6940,7 +7202,7 @@ function maxCrossAxisSpreadWidth(swimlane, nodeBoxes, flowRanks, locks, gap) {
|
|
|
6940
7202
|
}
|
|
6941
7203
|
return maxWidth;
|
|
6942
7204
|
}
|
|
6943
|
-
function moveRankedVerticalLaneChildren(childIds, nodeBoxes, locks, diagnostics, movedChildIds, flowRanks, rankSpacing, rankStackGap, target, contentWidth) {
|
|
7205
|
+
function moveRankedVerticalLaneChildren(childIds, nodeBoxes, locks, diagnostics, movedChildIds, flowRanks, rankSpacing, rankStackGap, target, contentWidth, suppressSpread) {
|
|
6944
7206
|
for (const [rank, stack] of rankStacks(childIds, nodeBoxes, flowRanks)) {
|
|
6945
7207
|
const unlocked = [];
|
|
6946
7208
|
for (const item of stack) {
|
|
@@ -6969,7 +7231,7 @@ function moveRankedVerticalLaneChildren(childIds, nodeBoxes, locks, diagnostics,
|
|
|
6969
7231
|
}
|
|
6970
7232
|
nodeBoxes.set(childId, next);
|
|
6971
7233
|
} else {
|
|
6972
|
-
const shouldSpread = unlocked.length >= CROSS_AXIS_SPREAD_THRESHOLD;
|
|
7234
|
+
const shouldSpread = !suppressSpread && unlocked.length >= CROSS_AXIS_SPREAD_THRESHOLD;
|
|
6973
7235
|
if (!shouldSpread) {
|
|
6974
7236
|
let yOffset = 0;
|
|
6975
7237
|
for (const { childId, box } of unlocked) {
|
|
@@ -8098,14 +8360,21 @@ function evidenceOverlapDiagnostic(block, conflict) {
|
|
|
8098
8360
|
}
|
|
8099
8361
|
};
|
|
8100
8362
|
}
|
|
8101
|
-
function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics, groups) {
|
|
8363
|
+
function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics, groups, contentBounds) {
|
|
8102
8364
|
const coordinated = [];
|
|
8103
8365
|
const coordinatedNodeById = new Map(
|
|
8104
8366
|
coordinatedNodes.map((node) => [node.id, node])
|
|
8105
8367
|
);
|
|
8368
|
+
const corridorMarginOption = options.corridorMargin ?? "auto";
|
|
8369
|
+
const corridorMargin = typeof corridorMarginOption === "number" ? corridorMarginOption : Math.max(
|
|
8370
|
+
200,
|
|
8371
|
+
Math.hypot(contentBounds.width, contentBounds.height) * 0.3
|
|
8372
|
+
);
|
|
8373
|
+
const routingGutter = options.routingGutter ?? 160;
|
|
8374
|
+
const queryGutter = (options.routeKind ?? "orthogonal") === "obstacle-avoiding" ? Math.max(routingGutter, corridorMargin) : routingGutter;
|
|
8106
8375
|
const nodeObstacleIndex = createBoxSpatialIndex(
|
|
8107
8376
|
obstacles.map((box, index) => ({ id: `node-obstacle:${index}`, box })),
|
|
8108
|
-
|
|
8377
|
+
queryGutter
|
|
8109
8378
|
);
|
|
8110
8379
|
for (const edge of edges) {
|
|
8111
8380
|
const source = nodes.get(edge.source.nodeId);
|
|
@@ -8127,11 +8396,7 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
8127
8396
|
const sourcePort = coordinatedNodeById.get(edge.source.nodeId)?.ports?.find((port) => port.id === edge.source.portId);
|
|
8128
8397
|
const targetPort = coordinatedNodeById.get(edge.target.nodeId)?.ports?.find((port) => port.id === edge.target.portId);
|
|
8129
8398
|
const routeTextObstacles = textObstacles.filter((annotation) => !isEdgeConnectedTextAnnotation(edge, annotation)).map((annotation) => annotation.box);
|
|
8130
|
-
const corridor = edgeCorridorBox(
|
|
8131
|
-
source.box,
|
|
8132
|
-
target.box,
|
|
8133
|
-
options.routingGutter ?? 160
|
|
8134
|
-
);
|
|
8399
|
+
const corridor = edgeCorridorBox(source.box, target.box, queryGutter);
|
|
8135
8400
|
const routeNodeObstacles = queryBoxSpatialIndex(nodeObstacleIndex, corridor).map((entry) => entry.box).filter(
|
|
8136
8401
|
(obstacle) => !sameBox(obstacle, source.obstacleBox) && !sameBox(obstacle, target.obstacleBox)
|
|
8137
8402
|
);
|
|
@@ -8149,7 +8414,9 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
8149
8414
|
...routeTextObstacles
|
|
8150
8415
|
],
|
|
8151
8416
|
hardObstacles,
|
|
8152
|
-
|
|
8417
|
+
corridorMargin,
|
|
8418
|
+
...options.maxRoutingAttempts === void 0 ? {} : { maxRoutingAttempts: options.maxRoutingAttempts },
|
|
8419
|
+
...options.maxBacktrackingRatio === void 0 ? {} : { maxBacktrackingRatio: options.maxBacktrackingRatio }
|
|
8153
8420
|
});
|
|
8154
8421
|
diagnostics.push(
|
|
8155
8422
|
...route.diagnostics.map((diagnostic) => ({
|