@skygraph/core 0.4.0 → 0.5.0

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/graph.cjs CHANGED
@@ -22,12 +22,17 @@ var graph_exports = {};
22
22
  __export(graph_exports, {
23
23
  aabbFromOBB: () => aabbFromOBB,
24
24
  createGraph: () => createGraph,
25
+ floatingAnchor: () => floatingAnchor,
25
26
  getBezierPath: () => getBezierPath,
27
+ getEdgePosition: () => getEdgePosition,
28
+ getNodeIntersection: () => getNodeIntersection,
29
+ inferSide: () => inferSide,
26
30
  nearestSide: () => nearestSide,
27
31
  obbContainsPoint: () => obbContainsPoint,
28
32
  obbCorners: () => obbCorners,
29
33
  pointsToPath: () => pointsToPath,
30
34
  pointsToRoundedPath: () => pointsToRoundedPath,
35
+ resolveEdgeEndpoint: () => resolveEdgeEndpoint,
31
36
  routeOrthogonal: () => routeOrthogonal
32
37
  });
33
38
  module.exports = __toCommonJS(graph_exports);
@@ -660,22 +665,27 @@ function createGraph(core, options) {
660
665
  function routeOrthogonal(start, end, options) {
661
666
  const opts = typeof options === "string" ? { preferred: options } : options ?? {};
662
667
  const gridSize = opts.gridSize ?? 10;
663
- const stubLength = opts.stubLength ?? Math.max(8, gridSize);
668
+ const stubLength = opts.stubLength ?? Math.max(20, gridSize);
669
+ const stepPosition = opts.stepPosition ?? 0.5;
664
670
  let sExit = null;
665
671
  let sStub = null;
666
672
  let sCorner = null;
673
+ let sSide = null;
667
674
  if (opts.sourceBounds) {
668
- const exitInfo = exitOnNearestSide(opts.sourceBounds, end);
675
+ const exitInfo = exitOnNearestSide(opts.sourceBounds, end, start);
669
676
  sExit = exitInfo.point;
677
+ sSide = exitInfo.side;
670
678
  sStub = extrudePoint(exitInfo.point, exitInfo.side, stubLength);
671
679
  sCorner = orthoBridge(start, sExit, exitInfo.side);
672
680
  }
673
681
  let tExit = null;
674
682
  let tStub = null;
675
683
  let tCorner = null;
684
+ let tSide = null;
676
685
  if (opts.targetBounds) {
677
- const enterInfo = exitOnNearestSide(opts.targetBounds, start);
686
+ const enterInfo = exitOnNearestSide(opts.targetBounds, start, end);
678
687
  tExit = enterInfo.point;
688
+ tSide = enterInfo.side;
679
689
  tStub = extrudePoint(enterInfo.point, enterInfo.side, stubLength);
680
690
  tCorner = orthoBridge(end, tExit, enterInfo.side);
681
691
  }
@@ -685,8 +695,12 @@ function routeOrthogonal(start, end, options) {
685
695
  const filteredObstacles = obstacles.length > 0 && (opts.sourceBounds || opts.targetBounds) ? obstacles.filter((o) => o !== opts.sourceBounds && o !== opts.targetBounds) : obstacles;
686
696
  let core;
687
697
  if (filteredObstacles.length === 0) {
688
- const snap2 = opts.snap ?? gridSize;
689
- core = lRoute(coreStart, coreEnd, opts.preferred ?? "auto", snap2);
698
+ if (sSide && tSide) {
699
+ core = smoothStepRoute(coreStart, sSide, coreEnd, tSide, stepPosition);
700
+ } else {
701
+ const snap2 = opts.snap ?? gridSize;
702
+ core = lRoute(coreStart, coreEnd, opts.preferred ?? "auto", snap2);
703
+ }
690
704
  } else {
691
705
  const inflate = opts.inflate ?? 0;
692
706
  const maxNodes = opts.maxNodes ?? 5e3;
@@ -710,6 +724,68 @@ function routeOrthogonal(start, end, options) {
710
724
  if (!pointsEqual(end, points[points.length - 1])) pushIfDistinct(points, end);
711
725
  return compressCollinear(points);
712
726
  }
727
+ function smoothStepRoute(coreStart, sSide, coreEnd, tSide, stepPosition) {
728
+ const [sx, sy] = coreStart;
729
+ const [tx, ty] = coreEnd;
730
+ const sAxis = sideAxis(sSide);
731
+ const tAxis = sideAxis(tSide);
732
+ const sDir = sideDir(sSide);
733
+ const tDir = sideDir(tSide);
734
+ if (sAxis === tAxis) {
735
+ if (sDir + tDir === 0) {
736
+ if (sAxis === "x") {
737
+ const cx = sx + (tx - sx) * stepPosition;
738
+ return [
739
+ [sx, sy],
740
+ [cx, sy],
741
+ [cx, ty],
742
+ [tx, ty]
743
+ ];
744
+ }
745
+ const cy = sy + (ty - sy) * stepPosition;
746
+ return [
747
+ [sx, sy],
748
+ [sx, cy],
749
+ [tx, cy],
750
+ [tx, ty]
751
+ ];
752
+ }
753
+ if (sAxis === "x") {
754
+ const extX = sDir > 0 ? Math.max(sx, tx) : Math.min(sx, tx);
755
+ return [
756
+ [sx, sy],
757
+ [extX, sy],
758
+ [extX, ty],
759
+ [tx, ty]
760
+ ];
761
+ }
762
+ const extY = sDir > 0 ? Math.max(sy, ty) : Math.min(sy, ty);
763
+ return [
764
+ [sx, sy],
765
+ [sx, extY],
766
+ [tx, extY],
767
+ [tx, ty]
768
+ ];
769
+ }
770
+ if (sAxis === "x") {
771
+ return [
772
+ [sx, sy],
773
+ [tx, sy],
774
+ [tx, ty]
775
+ ];
776
+ }
777
+ return [
778
+ [sx, sy],
779
+ [sx, ty],
780
+ [tx, ty]
781
+ ];
782
+ }
783
+ function sideAxis(s) {
784
+ return s === "left" || s === "right" ? "x" : "y";
785
+ }
786
+ function sideDir(s) {
787
+ return s === "right" || s === "bottom" ? 1 : -1;
788
+ }
713
789
  function pointsToPath(points) {
714
790
  if (points.length === 0) return "";
715
791
  const [head, ...rest] = points;
@@ -769,6 +845,31 @@ function getBezierPath(opts) {
769
845
  const [c2x, c2y] = controlPoint(opts.targetSide, tx, ty, sx, sy, curvature);
770
846
  return `M ${sx} ${sy} C ${c1x} ${c1y} ${c2x} ${c2y} ${tx} ${ty}`;
771
847
  }
848
+ function floatingAnchor(box, opposite) {
849
+ const cx = box.x + box.w / 2;
850
+ const cy = box.y + box.h / 2;
851
+ const dx = opposite[0] - cx;
852
+ const dy = opposite[1] - cy;
853
+ if (dx === 0 && dy === 0) {
854
+ return { point: [box.x + box.w, cy], side: "right" };
855
+ }
856
+ const hw = box.w / 2;
857
+ const hh = box.h / 2;
858
+ const tx = dx === 0 ? Infinity : hw / Math.abs(dx);
859
+ const ty = dy === 0 ? Infinity : hh / Math.abs(dy);
860
+ if (tx < ty) {
861
+ const sign2 = dx > 0 ? 1 : -1;
862
+ return {
863
+ point: [cx + sign2 * hw, cy + sign2 * (hw / Math.abs(dx)) * dy],
864
+ side: sign2 > 0 ? "right" : "left"
865
+ };
866
+ }
867
+ const sign = dy > 0 ? 1 : -1;
868
+ return {
869
+ point: [cx + sign * (hh / Math.abs(dy)) * dx, cy + sign * hh],
870
+ side: sign > 0 ? "bottom" : "top"
871
+ };
872
+ }
772
873
  function nearestSide(box, target) {
773
874
  const cx = box.x + box.w / 2;
774
875
  const cy = box.y + box.h / 2;
@@ -777,9 +878,107 @@ function nearestSide(box, target) {
777
878
  if (Math.abs(dx) >= Math.abs(dy)) return dx >= 0 ? "right" : "left";
778
879
  return dy >= 0 ? "bottom" : "top";
779
880
  }
780
- function exitOnNearestSide(box, target) {
881
+ function getNodeIntersection(intersectionBox, targetCenter) {
882
+ const w = intersectionBox.w / 2;
883
+ const h = intersectionBox.h / 2;
884
+ const x2 = intersectionBox.x + w;
885
+ const y2 = intersectionBox.y + h;
886
+ if (w === 0 || h === 0) return [x2, y2];
887
+ const x1 = targetCenter[0];
888
+ const y1 = targetCenter[1];
889
+ const xx1 = (x1 - x2) / (2 * w) - (y1 - y2) / (2 * h);
890
+ const yy1 = (x1 - x2) / (2 * w) + (y1 - y2) / (2 * h);
891
+ const denom = Math.abs(xx1) + Math.abs(yy1);
892
+ if (denom === 0) {
893
+ return [intersectionBox.x + intersectionBox.w, y2];
894
+ }
895
+ const a = 1 / denom;
896
+ const xx3 = a * xx1;
897
+ const yy3 = a * yy1;
898
+ const x = w * (xx3 + yy3) + x2;
899
+ const y = h * (-xx3 + yy3) + y2;
900
+ return [x, y];
901
+ }
902
+ function getEdgePosition(box, intersectionPoint) {
903
+ const nx = Math.round(box.x);
904
+ const ny = Math.round(box.y);
905
+ const px = Math.round(intersectionPoint[0]);
906
+ const py = Math.round(intersectionPoint[1]);
907
+ if (px <= nx + 1) return "left";
908
+ if (px >= nx + box.w - 1) return "right";
909
+ if (py <= ny + 1) return "top";
910
+ if (py >= ny + box.h - 1) return "bottom";
911
+ return "top";
912
+ }
913
+ function resolveEdgeEndpoint(sourceBox, targetBox, padding = 0) {
914
+ const targetCenter = [targetBox.x + targetBox.w / 2, targetBox.y + targetBox.h / 2];
915
+ const raw = getNodeIntersection(sourceBox, targetCenter);
916
+ const side = getEdgePosition(sourceBox, raw);
917
+ if (padding === 0) return { point: raw, side };
918
+ let point = raw;
919
+ switch (side) {
920
+ case "right":
921
+ point = [raw[0] + padding, raw[1]];
922
+ break;
923
+ case "left":
924
+ point = [raw[0] - padding, raw[1]];
925
+ break;
926
+ case "bottom":
927
+ point = [raw[0], raw[1] + padding];
928
+ break;
929
+ case "top":
930
+ point = [raw[0], raw[1] - padding];
931
+ break;
932
+ }
933
+ return { point, side };
934
+ }
935
+ function inferSide(box, anchor, opposite, tolerance = 1) {
936
+ const onLeft = Math.abs(anchor[0] - box.x) <= tolerance;
937
+ const onRight = Math.abs(anchor[0] - (box.x + box.w)) <= tolerance;
938
+ const onTop = Math.abs(anchor[1] - box.y) <= tolerance;
939
+ const onBottom = Math.abs(anchor[1] - (box.y + box.h)) <= tolerance;
940
+ const horizontalHit = onLeft || onRight;
941
+ const verticalHit = onTop || onBottom;
942
+ if (horizontalHit && verticalHit) {
943
+ const cx = box.x + box.w / 2;
944
+ const cy = box.y + box.h / 2;
945
+ const dx = opposite[0] - cx;
946
+ const dy = opposite[1] - cy;
947
+ if (Math.abs(dx) >= Math.abs(dy)) {
948
+ return { side: onRight ? "right" : "left", confident: true };
949
+ }
950
+ return { side: onBottom ? "bottom" : "top", confident: true };
951
+ }
952
+ if (onLeft) return { side: "left", confident: true };
953
+ if (onRight) return { side: "right", confident: true };
954
+ if (onTop) return { side: "top", confident: true };
955
+ if (onBottom) return { side: "bottom", confident: true };
956
+ return { side: nearestSide(box, opposite), confident: false };
957
+ }
958
+ function exitOnNearestSide(box, target, anchor) {
781
959
  const cx = box.x + box.w / 2;
782
960
  const cy = box.y + box.h / 2;
961
+ if (anchor) {
962
+ const tolerance = 1;
963
+ const onLeft = Math.abs(anchor[0] - box.x) <= tolerance;
964
+ const onRight = Math.abs(anchor[0] - (box.x + box.w)) <= tolerance;
965
+ const onTop = Math.abs(anchor[1] - box.y) <= tolerance;
966
+ const onBottom = Math.abs(anchor[1] - (box.y + box.h)) <= tolerance;
967
+ const horizontalHit = onLeft || onRight;
968
+ const verticalHit = onTop || onBottom;
969
+ if (horizontalHit && verticalHit) {
970
+ const dx2 = target[0] - cx;
971
+ const dy2 = target[1] - cy;
972
+ if (Math.abs(dx2) >= Math.abs(dy2)) {
973
+ return onRight ? { point: [box.x + box.w, cy], side: "right" } : { point: [box.x, cy], side: "left" };
974
+ }
975
+ return onBottom ? { point: [cx, box.y + box.h], side: "bottom" } : { point: [cx, box.y], side: "top" };
976
+ }
977
+ if (onRight) return { point: [box.x + box.w, cy], side: "right" };
978
+ if (onLeft) return { point: [box.x, cy], side: "left" };
979
+ if (onBottom) return { point: [cx, box.y + box.h], side: "bottom" };
980
+ if (onTop) return { point: [cx, box.y], side: "top" };
981
+ }
783
982
  const dx = target[0] - cx;
784
983
  const dy = target[1] - cy;
785
984
  if (Math.abs(dx) >= Math.abs(dy)) {
@@ -1054,12 +1253,17 @@ function obbContainsPoint(obb, p) {
1054
1253
  0 && (module.exports = {
1055
1254
  aabbFromOBB,
1056
1255
  createGraph,
1256
+ floatingAnchor,
1057
1257
  getBezierPath,
1258
+ getEdgePosition,
1259
+ getNodeIntersection,
1260
+ inferSide,
1058
1261
  nearestSide,
1059
1262
  obbContainsPoint,
1060
1263
  obbCorners,
1061
1264
  pointsToPath,
1062
1265
  pointsToRoundedPath,
1266
+ resolveEdgeEndpoint,
1063
1267
  routeOrthogonal
1064
1268
  });
1065
1269
  //# sourceMappingURL=graph.cjs.map