| Name | Message | Date |
|---|---|---|
| 📄 EdgeAvoidance.cs | 5 days ago | |
| 📄 GraphLayout.cs | 5 days ago | |
| 📄 GraphScene.cs | 5 days ago | |
| 📄 PhysicsLayout.cs | 5 days ago |
📄
ui/src/Graph/EdgeAvoidance.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
using System; using System.Collections.Generic; using System.Threading; using Avalonia; namespace Cfa.Ui.Graph; internal static class EdgeAvoidance { public static void Apply( PhysicsNode[] nodes, Point[] positions, Vector[] forces, PhysicsOptions options, CancellationToken token ) { if (options.EdgeCrossingStrength == 0 && options.EdgeNodeStrength == 0) return; var cellSize = Math.Max(240, options.EdgeClearance * 2); var cells = new Dictionary<(int X, int Y), List<int>>(); for (var i = 1; i < nodes.Length; i++) { token.ThrowIfCancellationRequested(); var start = positions[nodes[i].Parent]; var end = positions[i]; foreach (var key in Keys(start, end, options.EdgeClearance, cellSize)) { if (!cells.TryGetValue(key, out var edges)) cells[key] = edges = []; edges.Add(i); } } var pairs = new HashSet<(int, int)>(); for (var i = 1; i < nodes.Length && options.EdgeCrossingStrength > 0; i++) { token.ThrowIfCancellationRequested(); var a = nodes[i].Parent; foreach (var key in Keys(positions[a], positions[i], options.EdgeClearance, cellSize)) { foreach (var j in cells[key]) { if (j <= i || !pairs.Add((i, j))) continue; var b = nodes[j].Parent; if (a == b || a == j || b == i) continue; var crossing = Crosses(positions[a], positions[i], positions[b], positions[j]); var bestDistance = double.PositiveInfinity; var endpoint = -1; var edgeStart = -1; var edgeEnd = -1; var fraction = 0d; var offset = default(Vector); Consider(a, b, j); Consider(i, b, j); Consider(b, a, i); Consider(j, a, i); if (!crossing && bestDistance >= options.EdgeClearance) continue; var direction = bestDistance > 1e-8 ? offset / bestDistance : Normal(positions[edgeStart], positions[edgeEnd], endpoint); // A crossing endpoint moves through the opposing edge, not farther // into its current side. Near misses instead repel continuously. var magnitude = crossing ? bestDistance + options.EdgeClearance : options.EdgeClearance - bestDistance; var force = direction * (magnitude * options.EdgeCrossingStrength * (crossing ? -1 : 1)); forces[endpoint] += force; forces[edgeStart] -= force * (1 - fraction); forces[edgeEnd] -= force * fraction; void Consider(int point, int first, int second) { var t = Fraction(positions[point], positions[first], positions[second]); var closest = positions[first] + (positions[second] - positions[first]) * t; var delta = new Vector(positions[point].X - closest.X, positions[point].Y - closest.Y); if (delta.Length >= bestDistance) return; bestDistance = delta.Length; endpoint = point; edgeStart = first; edgeEnd = second; fraction = t; offset = delta; } } } } if (options.EdgeNodeStrength == 0) return; for (var i = 0; i < nodes.Length; i++) { token.ThrowIfCancellationRequested(); var key = ((int)Math.Floor(positions[i].X / cellSize), (int)Math.Floor(positions[i].Y / cellSize)); if (!cells.TryGetValue(key, out var edges)) continue; foreach (var edge in edges) { var parent = nodes[edge].Parent; if (i == edge || i == parent) continue; var t = Fraction(positions[i], positions[parent], positions[edge]); if (t <= 0 || t >= 1) continue; var closest = positions[parent] + (positions[edge] - positions[parent]) * t; var offset = new Vector(positions[i].X - closest.X, positions[i].Y - closest.Y); var distance = offset.Length; if (distance >= options.EdgeClearance) continue; var direction = distance > 1e-8 ? offset / distance : Normal(positions[parent], positions[edge], i); var force = direction * ((options.EdgeClearance - distance) * options.EdgeNodeStrength); forces[i] += force; forces[parent] -= force * (1 - t); forces[edge] -= force * t; } } } private static double Fraction(Point point, Point start, Point end) { var dx = end.X - start.X; var dy = end.Y - start.Y; var squared = dx * dx + dy * dy; return squared < 1e-12 ? 0 : Math.Clamp(((point.X - start.X) * dx + (point.Y - start.Y) * dy) / squared, 0, 1); } private static Vector Normal(Point start, Point end, int id) { var vector = new Vector(start.Y - end.Y, end.X - start.X); return vector.Length > 1e-8 ? vector / vector.Length * (id % 2 == 0 ? 1 : -1) : new Vector(0, 1); } private static bool Crosses(Point a, Point b, Point c, Point d) { static double Side(Point p, Point q, Point r) => (q.X - p.X) * (r.Y - p.Y) - (q.Y - p.Y) * (r.X - p.X); return Side(a, b, c) * Side(a, b, d) < 0 && Side(c, d, a) * Side(c, d, b) < 0; } private static IEnumerable<(int X, int Y)> Keys(Point start, Point end, double margin, double size) { for ( var x = (int)Math.Floor((Math.Min(start.X, end.X) - margin) / size); x <= (int)Math.Floor((Math.Max(start.X, end.X) + margin) / size); x++ ) for ( var y = (int)Math.Floor((Math.Min(start.Y, end.Y) - margin) / size); y <= (int)Math.Floor((Math.Max(start.Y, end.Y) + margin) / size); y++ ) yield return (x, y); } }