@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.js
CHANGED
|
@@ -4161,7 +4161,10 @@ function findObstacleFreePath(source, target, obstacles, options = {}, diagnosti
|
|
|
4161
4161
|
detail: {
|
|
4162
4162
|
xsCount: xs.length,
|
|
4163
4163
|
ysCount: ys.length,
|
|
4164
|
-
maxNodes
|
|
4164
|
+
maxNodes,
|
|
4165
|
+
obstacleCount: obstacles.length,
|
|
4166
|
+
stage: "corridor-filtered",
|
|
4167
|
+
...corridorMargin === void 0 ? {} : { corridorMargin }
|
|
4165
4168
|
}
|
|
4166
4169
|
});
|
|
4167
4170
|
return null;
|
|
@@ -4207,7 +4210,14 @@ function findObstacleFreePath(source, target, obstacles, options = {}, diagnosti
|
|
|
4207
4210
|
severity: "warning",
|
|
4208
4211
|
code: "routing.astar.grid_overflow",
|
|
4209
4212
|
message: `A* full-retry grid overflow: ${xsFull.length * ysFull.length} nodes > ${maxNodes} limit. Falling back to heuristic routing.`,
|
|
4210
|
-
detail: {
|
|
4213
|
+
detail: {
|
|
4214
|
+
xsCount: xsFull.length,
|
|
4215
|
+
ysCount: ysFull.length,
|
|
4216
|
+
maxNodes,
|
|
4217
|
+
obstacleCount: obstacles.length,
|
|
4218
|
+
stage: "full-retry",
|
|
4219
|
+
...corridorMargin === void 0 ? {} : { corridorMargin }
|
|
4220
|
+
}
|
|
4211
4221
|
});
|
|
4212
4222
|
return null;
|
|
4213
4223
|
}
|
|
@@ -4450,6 +4460,64 @@ function areCollinear(a, b, c) {
|
|
|
4450
4460
|
return a.x === b.x && b.x === c.x || a.y === b.y && b.y === c.y;
|
|
4451
4461
|
}
|
|
4452
4462
|
|
|
4463
|
+
// src/routing/budget.ts
|
|
4464
|
+
var MIN_CORNER_BUDGET = 600;
|
|
4465
|
+
var MAX_CORNER_BUDGET = 3e3;
|
|
4466
|
+
var MIN_NODE_BUDGET = 4e3;
|
|
4467
|
+
var MAX_NODE_BUDGET = 64e3;
|
|
4468
|
+
var CORNERS_PER_OBSTACLE = 12;
|
|
4469
|
+
var CORNER_HEADROOM = 2;
|
|
4470
|
+
var GRID_SAFETY_FACTOR = 3;
|
|
4471
|
+
var CORRIDOR_SCALING_K = 0.5;
|
|
4472
|
+
var CORRIDOR_SCALING_BASE = 200;
|
|
4473
|
+
function computeRoutingBudget(cornerObstacles, allObstacles, corridorMargin, overrides = {}) {
|
|
4474
|
+
const adaptiveMaxCorners = deriveMaxCorners(
|
|
4475
|
+
cornerObstacles.length,
|
|
4476
|
+
corridorMargin
|
|
4477
|
+
);
|
|
4478
|
+
const adaptiveMaxNodes = deriveMaxNodes(
|
|
4479
|
+
allObstacles.length,
|
|
4480
|
+
corridorMargin
|
|
4481
|
+
);
|
|
4482
|
+
return {
|
|
4483
|
+
maxCorners: resolveBudget(
|
|
4484
|
+
overrides.maxCorners,
|
|
4485
|
+
adaptiveMaxCorners,
|
|
4486
|
+
MIN_CORNER_BUDGET,
|
|
4487
|
+
MAX_CORNER_BUDGET
|
|
4488
|
+
),
|
|
4489
|
+
maxNodes: resolveBudget(
|
|
4490
|
+
overrides.maxNodes,
|
|
4491
|
+
adaptiveMaxNodes,
|
|
4492
|
+
MIN_NODE_BUDGET,
|
|
4493
|
+
MAX_NODE_BUDGET
|
|
4494
|
+
),
|
|
4495
|
+
cornerObstacleCount: cornerObstacles.length,
|
|
4496
|
+
gridObstacleCount: allObstacles.length,
|
|
4497
|
+
corridorMargin
|
|
4498
|
+
};
|
|
4499
|
+
}
|
|
4500
|
+
function deriveMaxCorners(obstacleCount, corridorMargin) {
|
|
4501
|
+
const base = 2 + obstacleCount * CORNERS_PER_OBSTACLE * CORNER_HEADROOM;
|
|
4502
|
+
const corridorFactor = corridorScalingFactor(corridorMargin);
|
|
4503
|
+
return Math.ceil(base * corridorFactor);
|
|
4504
|
+
}
|
|
4505
|
+
function deriveMaxNodes(obstacleCount, corridorMargin) {
|
|
4506
|
+
const base = 4 * obstacleCount * obstacleCount + 4 * obstacleCount + 100;
|
|
4507
|
+
const corridorFactor = corridorScalingFactor(corridorMargin);
|
|
4508
|
+
return Math.ceil(base * GRID_SAFETY_FACTOR * corridorFactor);
|
|
4509
|
+
}
|
|
4510
|
+
function corridorScalingFactor(corridorMargin) {
|
|
4511
|
+
return 1 + corridorMargin / CORRIDOR_SCALING_BASE * CORRIDOR_SCALING_K;
|
|
4512
|
+
}
|
|
4513
|
+
function resolveBudget(override, adaptive, min, max) {
|
|
4514
|
+
const chosen = override !== void 0 && Number.isFinite(override) && override >= 1 ? override : adaptive;
|
|
4515
|
+
return clamp(chosen, min, max);
|
|
4516
|
+
}
|
|
4517
|
+
function clamp(value, min, max) {
|
|
4518
|
+
return Math.max(min, Math.min(max, value));
|
|
4519
|
+
}
|
|
4520
|
+
|
|
4453
4521
|
// src/routing/visibility-router.ts
|
|
4454
4522
|
function findCornerGraphPath(source, target, obstacles, options = {}, diagnostics) {
|
|
4455
4523
|
const margin = options.margin ?? 0;
|
|
@@ -4463,7 +4531,12 @@ function findCornerGraphPath(source, target, obstacles, options = {}, diagnostic
|
|
|
4463
4531
|
severity: "warning",
|
|
4464
4532
|
code: "routing.visibility.corner_overflow",
|
|
4465
4533
|
message: `Corner graph overflow: ${vertices.length} vertices > ${maxCorners}. Falling back to grid A*.`,
|
|
4466
|
-
detail: {
|
|
4534
|
+
detail: {
|
|
4535
|
+
vertexCount: vertices.length,
|
|
4536
|
+
maxCorners,
|
|
4537
|
+
obstacleCount: obstacles.length,
|
|
4538
|
+
...options.corridorMargin === void 0 ? {} : { corridorMargin: options.corridorMargin }
|
|
4539
|
+
}
|
|
4467
4540
|
});
|
|
4468
4541
|
return null;
|
|
4469
4542
|
}
|
|
@@ -4737,7 +4810,7 @@ function segmentCrossesAnyObstacle(a, b, obstacles) {
|
|
|
4737
4810
|
}
|
|
4738
4811
|
|
|
4739
4812
|
// src/routing/routes.ts
|
|
4740
|
-
function checkBacktracking(points, source, target, diagnostics) {
|
|
4813
|
+
function checkBacktracking(points, source, target, diagnostics, maxRatio) {
|
|
4741
4814
|
if (points.length < 2) return;
|
|
4742
4815
|
const direct = Math.hypot(target.x - source.x, target.y - source.y);
|
|
4743
4816
|
if (direct <= 0) return;
|
|
@@ -4747,7 +4820,7 @@ function checkBacktracking(points, source, target, diagnostics) {
|
|
|
4747
4820
|
const b = points[i + 1];
|
|
4748
4821
|
routeLen += Math.hypot(b.x - a.x, b.y - a.y);
|
|
4749
4822
|
}
|
|
4750
|
-
const threshold =
|
|
4823
|
+
const threshold = maxRatio ?? 20;
|
|
4751
4824
|
if (routeLen > direct * threshold) {
|
|
4752
4825
|
diagnostics.push({
|
|
4753
4826
|
severity: "warning",
|
|
@@ -4765,8 +4838,20 @@ function routeEdge(input) {
|
|
|
4765
4838
|
const diagnostics = [];
|
|
4766
4839
|
const softObstacles = input.obstacles ?? [];
|
|
4767
4840
|
const hardObstacles = input.hardObstacles ?? [];
|
|
4841
|
+
let bestRejectedPath;
|
|
4842
|
+
let bestRejectedCrossings = Number.POSITIVE_INFINITY;
|
|
4768
4843
|
const softObstacleIndex = input.obstacleIndex ?? createBoxSpatialIndex(indexedBoxes(softObstacles));
|
|
4769
4844
|
const hardObstacleIndex = input.hardObstacleIndex ?? createBoxSpatialIndex(indexedBoxes(hardObstacles));
|
|
4845
|
+
const recordRejected = (candidate) => {
|
|
4846
|
+
if (routeIntersectsObstacles(candidate, hardObstacles, hardObstacleIndex)) {
|
|
4847
|
+
return;
|
|
4848
|
+
}
|
|
4849
|
+
const crossings = countObstacleCrossings(candidate, softObstacles);
|
|
4850
|
+
if (crossings < bestRejectedCrossings) {
|
|
4851
|
+
bestRejectedCrossings = crossings;
|
|
4852
|
+
bestRejectedPath = candidate;
|
|
4853
|
+
}
|
|
4854
|
+
};
|
|
4770
4855
|
const maxAttempts = input.maxRoutingAttempts ?? 5;
|
|
4771
4856
|
const defaultAnchors = defaultAnchorsForGeometry(
|
|
4772
4857
|
input.source.box,
|
|
@@ -4826,20 +4911,32 @@ function routeEdge(input) {
|
|
|
4826
4911
|
targetAnchor
|
|
4827
4912
|
);
|
|
4828
4913
|
const allObstacles = [...softObstacles, ...hardObstacles];
|
|
4914
|
+
const corridorMargin = input.corridorMargin ?? 32;
|
|
4829
4915
|
const corridorObstacles = filterObstaclesByCorridor(
|
|
4830
4916
|
source,
|
|
4831
4917
|
target,
|
|
4832
4918
|
allObstacles,
|
|
4833
4919
|
[],
|
|
4834
4920
|
// endpointObstacles passed separately via options
|
|
4835
|
-
|
|
4921
|
+
corridorMargin
|
|
4836
4922
|
);
|
|
4837
4923
|
const cornerObstacles = corridorObstacles.length === 0 && allObstacles.length > 0 ? allObstacles : corridorObstacles;
|
|
4924
|
+
const budget = computeRoutingBudget(
|
|
4925
|
+
cornerObstacles,
|
|
4926
|
+
allObstacles,
|
|
4927
|
+
corridorMargin,
|
|
4928
|
+
{ maxCorners: input.maxCorners, maxNodes: input.maxNodes }
|
|
4929
|
+
);
|
|
4838
4930
|
let cornerPath = findCornerGraphPath(
|
|
4839
4931
|
source,
|
|
4840
4932
|
target,
|
|
4841
4933
|
cornerObstacles,
|
|
4842
|
-
{
|
|
4934
|
+
{
|
|
4935
|
+
endpointObstacles,
|
|
4936
|
+
margin: 2,
|
|
4937
|
+
maxCorners: budget.maxCorners,
|
|
4938
|
+
corridorMargin
|
|
4939
|
+
},
|
|
4843
4940
|
diagnostics
|
|
4844
4941
|
);
|
|
4845
4942
|
if (cornerPath === null && cornerObstacles.length < allObstacles.length) {
|
|
@@ -4847,7 +4944,12 @@ function routeEdge(input) {
|
|
|
4847
4944
|
source,
|
|
4848
4945
|
target,
|
|
4849
4946
|
allObstacles,
|
|
4850
|
-
{
|
|
4947
|
+
{
|
|
4948
|
+
endpointObstacles,
|
|
4949
|
+
margin: 2,
|
|
4950
|
+
maxCorners: budget.maxCorners,
|
|
4951
|
+
corridorMargin
|
|
4952
|
+
},
|
|
4851
4953
|
diagnostics
|
|
4852
4954
|
);
|
|
4853
4955
|
}
|
|
@@ -4855,7 +4957,12 @@ function routeEdge(input) {
|
|
|
4855
4957
|
source,
|
|
4856
4958
|
target,
|
|
4857
4959
|
allObstacles,
|
|
4858
|
-
{
|
|
4960
|
+
{
|
|
4961
|
+
endpointObstacles,
|
|
4962
|
+
margin: 0,
|
|
4963
|
+
corridorMargin,
|
|
4964
|
+
maxNodes: budget.maxNodes
|
|
4965
|
+
},
|
|
4859
4966
|
diagnostics
|
|
4860
4967
|
);
|
|
4861
4968
|
if (path !== null && path.length >= 2) {
|
|
@@ -4872,15 +4979,27 @@ function routeEdge(input) {
|
|
|
4872
4979
|
softObstacles,
|
|
4873
4980
|
softObstacleIndex
|
|
4874
4981
|
) && !routeIntersectsObstacles(finalized, hardObstacles, hardObstacleIndex)) {
|
|
4875
|
-
checkBacktracking(
|
|
4982
|
+
checkBacktracking(
|
|
4983
|
+
finalized,
|
|
4984
|
+
source,
|
|
4985
|
+
target,
|
|
4986
|
+
diagnostics,
|
|
4987
|
+
input.maxBacktrackingRatio
|
|
4988
|
+
);
|
|
4876
4989
|
return { points: finalized, diagnostics };
|
|
4877
4990
|
}
|
|
4991
|
+
recordRejected(finalized);
|
|
4878
4992
|
if (cornerPath !== null) {
|
|
4879
4993
|
const fullCornerPath = cornerObstacles.length < allObstacles.length ? findCornerGraphPath(
|
|
4880
4994
|
source,
|
|
4881
4995
|
target,
|
|
4882
4996
|
allObstacles,
|
|
4883
|
-
{
|
|
4997
|
+
{
|
|
4998
|
+
endpointObstacles,
|
|
4999
|
+
margin: 2,
|
|
5000
|
+
maxCorners: budget.maxCorners,
|
|
5001
|
+
corridorMargin
|
|
5002
|
+
},
|
|
4884
5003
|
diagnostics
|
|
4885
5004
|
) : null;
|
|
4886
5005
|
if (fullCornerPath !== null && fullCornerPath.length >= 2) {
|
|
@@ -4901,15 +5020,27 @@ function routeEdge(input) {
|
|
|
4901
5020
|
hardObstacles,
|
|
4902
5021
|
hardObstacleIndex
|
|
4903
5022
|
)) {
|
|
4904
|
-
checkBacktracking(
|
|
5023
|
+
checkBacktracking(
|
|
5024
|
+
fullFinalized,
|
|
5025
|
+
source,
|
|
5026
|
+
target,
|
|
5027
|
+
diagnostics,
|
|
5028
|
+
input.maxBacktrackingRatio
|
|
5029
|
+
);
|
|
4905
5030
|
return { points: fullFinalized, diagnostics };
|
|
4906
5031
|
}
|
|
5032
|
+
recordRejected(fullFinalized);
|
|
4907
5033
|
}
|
|
4908
5034
|
const gridPath = findObstacleFreePath(
|
|
4909
5035
|
source,
|
|
4910
5036
|
target,
|
|
4911
5037
|
allObstacles,
|
|
4912
|
-
{
|
|
5038
|
+
{
|
|
5039
|
+
endpointObstacles,
|
|
5040
|
+
margin: 0,
|
|
5041
|
+
corridorMargin,
|
|
5042
|
+
maxNodes: budget.maxNodes
|
|
5043
|
+
},
|
|
4913
5044
|
diagnostics
|
|
4914
5045
|
);
|
|
4915
5046
|
if (gridPath !== null && gridPath.length >= 2) {
|
|
@@ -4930,9 +5061,16 @@ function routeEdge(input) {
|
|
|
4930
5061
|
hardObstacles,
|
|
4931
5062
|
hardObstacleIndex
|
|
4932
5063
|
)) {
|
|
4933
|
-
checkBacktracking(
|
|
5064
|
+
checkBacktracking(
|
|
5065
|
+
gridFinalized,
|
|
5066
|
+
source,
|
|
5067
|
+
target,
|
|
5068
|
+
diagnostics,
|
|
5069
|
+
input.maxBacktrackingRatio
|
|
5070
|
+
);
|
|
4934
5071
|
return { points: gridFinalized, diagnostics };
|
|
4935
5072
|
}
|
|
5073
|
+
recordRejected(gridFinalized);
|
|
4936
5074
|
}
|
|
4937
5075
|
}
|
|
4938
5076
|
}
|
|
@@ -4996,7 +5134,8 @@ function routeEdge(input) {
|
|
|
4996
5134
|
finalizedClean,
|
|
4997
5135
|
candidate.points[0],
|
|
4998
5136
|
candidate.points[candidate.points.length - 1],
|
|
4999
|
-
diagnostics
|
|
5137
|
+
diagnostics,
|
|
5138
|
+
input.maxBacktrackingRatio
|
|
5000
5139
|
);
|
|
5001
5140
|
return { points: finalizedClean, diagnostics };
|
|
5002
5141
|
}
|
|
@@ -5062,13 +5201,41 @@ function routeEdge(input) {
|
|
|
5062
5201
|
code: "routing.obstacle.unavoidable",
|
|
5063
5202
|
message: "No bounded orthogonal route candidate avoided all soft obstacles."
|
|
5064
5203
|
});
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5204
|
+
const finalizedSoftBest = finalizeRoute(
|
|
5205
|
+
bestPoints2,
|
|
5206
|
+
softObstacles,
|
|
5207
|
+
hardObstacles,
|
|
5208
|
+
diagnostics
|
|
5209
|
+
);
|
|
5210
|
+
let softFallback = finalizedSoftBest;
|
|
5211
|
+
if (bestRejectedPath !== void 0) {
|
|
5212
|
+
const finalizedRejected = finalizeRoute(
|
|
5213
|
+
bestRejectedPath,
|
|
5068
5214
|
softObstacles,
|
|
5069
5215
|
hardObstacles,
|
|
5070
5216
|
diagnostics
|
|
5071
|
-
)
|
|
5217
|
+
);
|
|
5218
|
+
const rejectedCrossings = countObstacleCrossings(
|
|
5219
|
+
finalizedRejected,
|
|
5220
|
+
softObstacles
|
|
5221
|
+
);
|
|
5222
|
+
const heuristicCrossings = countObstacleCrossings(
|
|
5223
|
+
finalizedSoftBest,
|
|
5224
|
+
softObstacles
|
|
5225
|
+
);
|
|
5226
|
+
if (rejectedCrossings < heuristicCrossings) {
|
|
5227
|
+
softFallback = finalizedRejected;
|
|
5228
|
+
}
|
|
5229
|
+
}
|
|
5230
|
+
checkBacktracking(
|
|
5231
|
+
softFallback,
|
|
5232
|
+
softFallback[0],
|
|
5233
|
+
softFallback[softFallback.length - 1],
|
|
5234
|
+
diagnostics,
|
|
5235
|
+
input.maxBacktrackingRatio
|
|
5236
|
+
);
|
|
5237
|
+
return {
|
|
5238
|
+
points: softFallback,
|
|
5072
5239
|
diagnostics
|
|
5073
5240
|
};
|
|
5074
5241
|
}
|
|
@@ -5100,6 +5267,22 @@ function routeEdge(input) {
|
|
|
5100
5267
|
maxAttempts
|
|
5101
5268
|
);
|
|
5102
5269
|
}
|
|
5270
|
+
if (bestRejectedPath !== void 0) {
|
|
5271
|
+
diagnostics.push({
|
|
5272
|
+
severity: "warning",
|
|
5273
|
+
code: "routing.obstacle.unavoidable",
|
|
5274
|
+
message: "Using A* route with minor soft-obstacle crossings to avoid hard evidence obstacles."
|
|
5275
|
+
});
|
|
5276
|
+
return {
|
|
5277
|
+
points: finalizeRoute(
|
|
5278
|
+
bestRejectedPath,
|
|
5279
|
+
softObstacles,
|
|
5280
|
+
hardObstacles,
|
|
5281
|
+
diagnostics
|
|
5282
|
+
),
|
|
5283
|
+
diagnostics
|
|
5284
|
+
};
|
|
5285
|
+
}
|
|
5103
5286
|
diagnostics.push({
|
|
5104
5287
|
severity: "error",
|
|
5105
5288
|
code: "routing.evidence.crossing_forbidden",
|
|
@@ -5147,13 +5330,41 @@ function routeEdge(input) {
|
|
|
5147
5330
|
code: "routing.obstacle.unavoidable",
|
|
5148
5331
|
message: "No bounded orthogonal route candidate avoided all obstacles."
|
|
5149
5332
|
});
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5333
|
+
const finalizedBestPoints = finalizeRoute(
|
|
5334
|
+
bestPoints,
|
|
5335
|
+
softObstacles,
|
|
5336
|
+
hardObstacles,
|
|
5337
|
+
diagnostics
|
|
5338
|
+
);
|
|
5339
|
+
let fallbackPoints = finalizedBestPoints;
|
|
5340
|
+
if (bestRejectedPath !== void 0) {
|
|
5341
|
+
const finalizedRejected = finalizeRoute(
|
|
5342
|
+
bestRejectedPath,
|
|
5153
5343
|
softObstacles,
|
|
5154
5344
|
hardObstacles,
|
|
5155
5345
|
diagnostics
|
|
5156
|
-
)
|
|
5346
|
+
);
|
|
5347
|
+
const rejectedCrossings = countObstacleCrossings(
|
|
5348
|
+
finalizedRejected,
|
|
5349
|
+
softObstacles
|
|
5350
|
+
);
|
|
5351
|
+
const heuristicCrossings = countObstacleCrossings(
|
|
5352
|
+
finalizedBestPoints,
|
|
5353
|
+
softObstacles
|
|
5354
|
+
);
|
|
5355
|
+
if (rejectedCrossings < heuristicCrossings) {
|
|
5356
|
+
fallbackPoints = finalizedRejected;
|
|
5357
|
+
}
|
|
5358
|
+
}
|
|
5359
|
+
checkBacktracking(
|
|
5360
|
+
fallbackPoints,
|
|
5361
|
+
fallbackPoints[0],
|
|
5362
|
+
fallbackPoints[fallbackPoints.length - 1],
|
|
5363
|
+
diagnostics,
|
|
5364
|
+
input.maxBacktrackingRatio
|
|
5365
|
+
);
|
|
5366
|
+
return {
|
|
5367
|
+
points: fallbackPoints,
|
|
5157
5368
|
diagnostics
|
|
5158
5369
|
};
|
|
5159
5370
|
}
|
|
@@ -5652,6 +5863,24 @@ function routeIntersectsObstacles(points, obstacles, spatialIndex) {
|
|
|
5652
5863
|
}
|
|
5653
5864
|
return false;
|
|
5654
5865
|
}
|
|
5866
|
+
function countObstacleCrossings(points, obstacles) {
|
|
5867
|
+
let count = 0;
|
|
5868
|
+
for (const obstacle of obstacles) {
|
|
5869
|
+
validateBox(obstacle);
|
|
5870
|
+
for (let pointIndex = 0; pointIndex < points.length - 1; pointIndex += 1) {
|
|
5871
|
+
const a = points[pointIndex];
|
|
5872
|
+
const b = points[pointIndex + 1];
|
|
5873
|
+
if (a === void 0 || b === void 0) {
|
|
5874
|
+
continue;
|
|
5875
|
+
}
|
|
5876
|
+
if (intersectsAabb(segmentBox2(a, b), obstacle)) {
|
|
5877
|
+
count += 1;
|
|
5878
|
+
break;
|
|
5879
|
+
}
|
|
5880
|
+
}
|
|
5881
|
+
}
|
|
5882
|
+
return count;
|
|
5883
|
+
}
|
|
5655
5884
|
function routeIntersectsEndpointInteriors(points, endpointInteriors) {
|
|
5656
5885
|
for (let index = 0; index < points.length - 1; index += 1) {
|
|
5657
5886
|
const a = points[index];
|
|
@@ -5886,7 +6115,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
5886
6115
|
constrained.boxes,
|
|
5887
6116
|
constrained.locks,
|
|
5888
6117
|
options?.overlapSpacing ?? 40,
|
|
5889
|
-
Math.max(0, options?.minLaneGutter ?? 0)
|
|
6118
|
+
Math.max(0, options?.minLaneGutter ?? 0),
|
|
6119
|
+
options.distributeContainedChildren ?? true
|
|
5890
6120
|
);
|
|
5891
6121
|
removeResolvedOverlapDiagnostics(diagnostics, constrained.boxes);
|
|
5892
6122
|
diagnostics.push(...swimlaneContracts.diagnostics);
|
|
@@ -6053,7 +6283,8 @@ function solveDiagram(diagram, options = {}) {
|
|
|
6053
6283
|
diagram.direction,
|
|
6054
6284
|
options,
|
|
6055
6285
|
diagnostics,
|
|
6056
|
-
coordinatedGroups
|
|
6286
|
+
coordinatedGroups,
|
|
6287
|
+
contentBounds
|
|
6057
6288
|
);
|
|
6058
6289
|
const edgeTextAnnotations = coordinateEdgeTextAnnotations(
|
|
6059
6290
|
coordinatedEdges,
|
|
@@ -6472,7 +6703,7 @@ function reportCjkTypographyDiagnostics(path, typography, previousStyle, diagnos
|
|
|
6472
6703
|
function containsCjk(value) {
|
|
6473
6704
|
return /[\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff]/u.test(value);
|
|
6474
6705
|
}
|
|
6475
|
-
function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottomFlow, nodeBoxes, locks, overlapSpacing, laneGutter) {
|
|
6706
|
+
function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottomFlow, nodeBoxes, locks, overlapSpacing, laneGutter, distributeContainedChildren) {
|
|
6476
6707
|
const layouts = /* @__PURE__ */ new Map();
|
|
6477
6708
|
const diagnostics = [];
|
|
6478
6709
|
const movedChildIds = /* @__PURE__ */ new Set();
|
|
@@ -6491,7 +6722,9 @@ function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottom
|
|
|
6491
6722
|
locks,
|
|
6492
6723
|
diagnostics,
|
|
6493
6724
|
movedChildIds,
|
|
6494
|
-
laneGutter
|
|
6725
|
+
laneGutter,
|
|
6726
|
+
constraints,
|
|
6727
|
+
distributeContainedChildren
|
|
6495
6728
|
);
|
|
6496
6729
|
if (layout2 !== void 0) {
|
|
6497
6730
|
layouts.set(swimlane.id, layout2);
|
|
@@ -6683,7 +6916,7 @@ function isStackRunaway(boxes, nodes, direction, options) {
|
|
|
6683
6916
|
const maxWidth = Math.max(...nodeBoxes.map((box) => box.width));
|
|
6684
6917
|
return xSpread <= Math.max(maxWidth, options.overlapSpacing ?? 40);
|
|
6685
6918
|
}
|
|
6686
|
-
function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, locks, diagnostics, movedChildIds, laneGutter) {
|
|
6919
|
+
function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, locks, diagnostics, movedChildIds, laneGutter, constraints, distributeContainedChildren) {
|
|
6687
6920
|
const headerHeight = swimlane.headerHeight ?? 28;
|
|
6688
6921
|
const padding = swimlane.padding ?? 16;
|
|
6689
6922
|
const laneBounds = swimlane.lanes.map((lane) => {
|
|
@@ -6708,7 +6941,9 @@ function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes
|
|
|
6708
6941
|
locks,
|
|
6709
6942
|
diagnostics,
|
|
6710
6943
|
movedChildIds,
|
|
6711
|
-
laneGutter
|
|
6944
|
+
laneGutter,
|
|
6945
|
+
constraints,
|
|
6946
|
+
distributeContainedChildren
|
|
6712
6947
|
);
|
|
6713
6948
|
}
|
|
6714
6949
|
return applyHorizontalSwimlaneContract(
|
|
@@ -6723,13 +6958,29 @@ function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes
|
|
|
6723
6958
|
laneGutter
|
|
6724
6959
|
);
|
|
6725
6960
|
}
|
|
6726
|
-
function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, laneBounds, headerHeight, padding, locks, diagnostics, movedChildIds, laneGutter) {
|
|
6961
|
+
function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, laneBounds, headerHeight, padding, locks, diagnostics, movedChildIds, laneGutter, constraints, distributeContainedChildren) {
|
|
6727
6962
|
const populatedBounds = laneBounds.filter(
|
|
6728
6963
|
(box) => box !== void 0
|
|
6729
6964
|
);
|
|
6730
6965
|
const top = Math.min(...populatedBounds.map((box) => box.y));
|
|
6731
6966
|
const left = Math.min(...populatedBounds.map((box) => box.x));
|
|
6732
6967
|
const maxChildHeight = Math.max(...populatedBounds.map((box) => box.height));
|
|
6968
|
+
const containedChildIds = /* @__PURE__ */ new Set();
|
|
6969
|
+
if (distributeContainedChildren) {
|
|
6970
|
+
for (const c of constraints) {
|
|
6971
|
+
if (c.kind !== "containment") continue;
|
|
6972
|
+
if (nodeBoxes.get(c.containerId) === void 0) continue;
|
|
6973
|
+
const distributable = c.childIds.filter((childId) => {
|
|
6974
|
+
if (nodeBoxes.get(childId) === void 0) return false;
|
|
6975
|
+
const lock = locks.get(childId);
|
|
6976
|
+
return lock === void 0 || lock.source === "fixed-position";
|
|
6977
|
+
});
|
|
6978
|
+
if (distributable.length < 2) continue;
|
|
6979
|
+
for (const childId of distributable) {
|
|
6980
|
+
containedChildIds.add(childId);
|
|
6981
|
+
}
|
|
6982
|
+
}
|
|
6983
|
+
}
|
|
6733
6984
|
const flowRanks = topToBottomFlow ? rankVerticalSwimlaneChildren(swimlane, edges) : /* @__PURE__ */ new Map();
|
|
6734
6985
|
const maxRank = flowRanks.size === 0 ? 0 : Math.max(...Array.from(flowRanks.values()));
|
|
6735
6986
|
const rankStackGap = Math.max(8, padding / 2);
|
|
@@ -6746,7 +6997,8 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
6746
6997
|
nodeBoxes,
|
|
6747
6998
|
flowRanks,
|
|
6748
6999
|
locks,
|
|
6749
|
-
rankStackGap
|
|
7000
|
+
rankStackGap,
|
|
7001
|
+
containedChildIds
|
|
6750
7002
|
);
|
|
6751
7003
|
const slotWidth = Math.max(
|
|
6752
7004
|
Math.max(...populatedBounds.map((box) => box.width)),
|
|
@@ -6768,7 +7020,10 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
6768
7020
|
const distributable = lane.children.filter(
|
|
6769
7021
|
(childId) => !locks.has(childId)
|
|
6770
7022
|
);
|
|
6771
|
-
|
|
7023
|
+
const coveredByContainment = lane.children.some(
|
|
7024
|
+
(childId) => containedChildIds.has(childId)
|
|
7025
|
+
);
|
|
7026
|
+
if (!coveredByContainment && distributable.length >= CROSS_AXIS_SPREAD_THRESHOLD) {
|
|
6772
7027
|
moveRankedVerticalLaneChildren(
|
|
6773
7028
|
lane.children,
|
|
6774
7029
|
nodeBoxes,
|
|
@@ -6796,6 +7051,9 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
6796
7051
|
);
|
|
6797
7052
|
continue;
|
|
6798
7053
|
}
|
|
7054
|
+
const rankedCoveredByContainment = lane.children.some(
|
|
7055
|
+
(childId) => containedChildIds.has(childId)
|
|
7056
|
+
);
|
|
6799
7057
|
moveRankedVerticalLaneChildren(
|
|
6800
7058
|
lane.children,
|
|
6801
7059
|
nodeBoxes,
|
|
@@ -6806,7 +7064,8 @@ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBox
|
|
|
6806
7064
|
rankSpacing,
|
|
6807
7065
|
rankStackGap,
|
|
6808
7066
|
{ x: target.x, y: laneContentTop },
|
|
6809
|
-
slotWidth - padding * 2
|
|
7067
|
+
slotWidth - padding * 2,
|
|
7068
|
+
rankedCoveredByContainment
|
|
6810
7069
|
);
|
|
6811
7070
|
}
|
|
6812
7071
|
return {
|
|
@@ -6922,9 +7181,12 @@ function crossAxisSpreadWidth(items, gap) {
|
|
|
6922
7181
|
0
|
|
6923
7182
|
);
|
|
6924
7183
|
}
|
|
6925
|
-
function maxCrossAxisSpreadWidth(swimlane, nodeBoxes, flowRanks, locks, gap) {
|
|
7184
|
+
function maxCrossAxisSpreadWidth(swimlane, nodeBoxes, flowRanks, locks, gap, containedChildIds) {
|
|
6926
7185
|
let maxWidth = 0;
|
|
6927
7186
|
for (const lane of swimlane.lanes) {
|
|
7187
|
+
if (containedChildIds !== void 0 && lane.children.some((childId) => containedChildIds.has(childId))) {
|
|
7188
|
+
continue;
|
|
7189
|
+
}
|
|
6928
7190
|
for (const stack of rankStacks(
|
|
6929
7191
|
lane.children,
|
|
6930
7192
|
nodeBoxes,
|
|
@@ -6937,7 +7199,7 @@ function maxCrossAxisSpreadWidth(swimlane, nodeBoxes, flowRanks, locks, gap) {
|
|
|
6937
7199
|
}
|
|
6938
7200
|
return maxWidth;
|
|
6939
7201
|
}
|
|
6940
|
-
function moveRankedVerticalLaneChildren(childIds, nodeBoxes, locks, diagnostics, movedChildIds, flowRanks, rankSpacing, rankStackGap, target, contentWidth) {
|
|
7202
|
+
function moveRankedVerticalLaneChildren(childIds, nodeBoxes, locks, diagnostics, movedChildIds, flowRanks, rankSpacing, rankStackGap, target, contentWidth, suppressSpread) {
|
|
6941
7203
|
for (const [rank, stack] of rankStacks(childIds, nodeBoxes, flowRanks)) {
|
|
6942
7204
|
const unlocked = [];
|
|
6943
7205
|
for (const item of stack) {
|
|
@@ -6966,7 +7228,7 @@ function moveRankedVerticalLaneChildren(childIds, nodeBoxes, locks, diagnostics,
|
|
|
6966
7228
|
}
|
|
6967
7229
|
nodeBoxes.set(childId, next);
|
|
6968
7230
|
} else {
|
|
6969
|
-
const shouldSpread = unlocked.length >= CROSS_AXIS_SPREAD_THRESHOLD;
|
|
7231
|
+
const shouldSpread = !suppressSpread && unlocked.length >= CROSS_AXIS_SPREAD_THRESHOLD;
|
|
6970
7232
|
if (!shouldSpread) {
|
|
6971
7233
|
let yOffset = 0;
|
|
6972
7234
|
for (const { childId, box } of unlocked) {
|
|
@@ -8095,14 +8357,21 @@ function evidenceOverlapDiagnostic(block, conflict) {
|
|
|
8095
8357
|
}
|
|
8096
8358
|
};
|
|
8097
8359
|
}
|
|
8098
|
-
function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics, groups) {
|
|
8360
|
+
function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics, groups, contentBounds) {
|
|
8099
8361
|
const coordinated = [];
|
|
8100
8362
|
const coordinatedNodeById = new Map(
|
|
8101
8363
|
coordinatedNodes.map((node) => [node.id, node])
|
|
8102
8364
|
);
|
|
8365
|
+
const corridorMarginOption = options.corridorMargin ?? "auto";
|
|
8366
|
+
const corridorMargin = typeof corridorMarginOption === "number" ? corridorMarginOption : Math.max(
|
|
8367
|
+
200,
|
|
8368
|
+
Math.hypot(contentBounds.width, contentBounds.height) * 0.3
|
|
8369
|
+
);
|
|
8370
|
+
const routingGutter = options.routingGutter ?? 160;
|
|
8371
|
+
const queryGutter = (options.routeKind ?? "orthogonal") === "obstacle-avoiding" ? Math.max(routingGutter, corridorMargin) : routingGutter;
|
|
8103
8372
|
const nodeObstacleIndex = createBoxSpatialIndex(
|
|
8104
8373
|
obstacles.map((box, index) => ({ id: `node-obstacle:${index}`, box })),
|
|
8105
|
-
|
|
8374
|
+
queryGutter
|
|
8106
8375
|
);
|
|
8107
8376
|
for (const edge of edges) {
|
|
8108
8377
|
const source = nodes.get(edge.source.nodeId);
|
|
@@ -8124,11 +8393,7 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
8124
8393
|
const sourcePort = coordinatedNodeById.get(edge.source.nodeId)?.ports?.find((port) => port.id === edge.source.portId);
|
|
8125
8394
|
const targetPort = coordinatedNodeById.get(edge.target.nodeId)?.ports?.find((port) => port.id === edge.target.portId);
|
|
8126
8395
|
const routeTextObstacles = textObstacles.filter((annotation) => !isEdgeConnectedTextAnnotation(edge, annotation)).map((annotation) => annotation.box);
|
|
8127
|
-
const corridor = edgeCorridorBox(
|
|
8128
|
-
source.box,
|
|
8129
|
-
target.box,
|
|
8130
|
-
options.routingGutter ?? 160
|
|
8131
|
-
);
|
|
8396
|
+
const corridor = edgeCorridorBox(source.box, target.box, queryGutter);
|
|
8132
8397
|
const routeNodeObstacles = queryBoxSpatialIndex(nodeObstacleIndex, corridor).map((entry) => entry.box).filter(
|
|
8133
8398
|
(obstacle) => !sameBox(obstacle, source.obstacleBox) && !sameBox(obstacle, target.obstacleBox)
|
|
8134
8399
|
);
|
|
@@ -8146,7 +8411,9 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacle
|
|
|
8146
8411
|
...routeTextObstacles
|
|
8147
8412
|
],
|
|
8148
8413
|
hardObstacles,
|
|
8149
|
-
|
|
8414
|
+
corridorMargin,
|
|
8415
|
+
...options.maxRoutingAttempts === void 0 ? {} : { maxRoutingAttempts: options.maxRoutingAttempts },
|
|
8416
|
+
...options.maxBacktrackingRatio === void 0 ? {} : { maxBacktrackingRatio: options.maxBacktrackingRatio }
|
|
8150
8417
|
});
|
|
8151
8418
|
diagnostics.push(
|
|
8152
8419
|
...route.diagnostics.map((diagnostic) => ({
|