Enhance flow arrows

This commit is contained in:
2026-08-03 15:54:19 +02:00
parent 19deed4fd7
commit ac45092e1d
6 changed files with 76 additions and 59 deletions
+2 -2
View File
@@ -431,13 +431,13 @@ namespace PamhagenSysCtrl.Helpers {
} }
} }
public void ColorPipesAdjacientToPath(Path path, Brush? color1, Brush? color2) { public void ColorPipesAdjacientToPath(Path path, Brush? color1, bool flowArrows, Brush? color2) {
var color = color1; var color = color1;
var flowPathOffset = 0.0; var flowPathOffset = 0.0;
HashSet<IEdge> visited = []; HashSet<IEdge> visited = [];
foreach (var (edge, node) in path.Hops) { foreach (var (edge, node) in path.Hops) {
edge.Highlight = color1; edge.Highlight = color1;
edge.Flow = color1 == PamhagenBrushes.Green ? (edge.End == node, flowPathOffset) : null; edge.Flow = flowArrows ? (edge.End == node, flowPathOffset) : null;
if (edge.Flow != null) flowPathOffset += edge.FlowLength; if (edge.Flow != null) flowPathOffset += edge.FlowLength;
visited.Add(edge); visited.Add(edge);
if (node is Pump) color = color2; if (node is Pump) color = color2;
+63 -45
View File
@@ -7,10 +7,10 @@ using System.Windows.Shapes;
namespace PamhagenSysCtrl.Helpers.Pipeline { namespace PamhagenSysCtrl.Helpers.Pipeline {
internal sealed class FlowArrow { internal sealed class FlowArrow {
private const double Speed = 45; public const double SPEED = 20;
private const double Spacing = 40; public const double SPACING = 15;
private const double HalfLength = 5; public const double LENGTH = 7;
private const double HalfWidth = 3.5; public const double WIDTH = 7;
private static readonly Stopwatch Clock = Stopwatch.StartNew(); private static readonly Stopwatch Clock = Stopwatch.StartNew();
@@ -51,17 +51,8 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
return; return;
} }
var points = flow.Value.TowardEnd _points = flow.Value.TowardEnd ? _path : [.. _path.Reverse()];
? _path _length = GetLength(_points[0], _points[1]) + GetLength(_points[1], _points[2]);
: [.. _path.Reverse()];
var length = GetLength(points[0], points[1]) + GetLength(points[1], points[2]);
if (length < 16) {
Stop();
return;
}
_points = points;
_length = length;
_pathOffset = flow.Value.PathOffset; _pathOffset = flow.Value.PathOffset;
if (!_isActive) { if (!_isActive) {
CompositionTarget.Rendering += OnRendering; CompositionTarget.Rendering += OnRendering;
@@ -76,13 +67,13 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
} }
private void Render() { private void Render() {
var distance = PositiveModulo(Clock.Elapsed.TotalSeconds * Speed - _pathOffset, Spacing); var distance = ((Clock.Elapsed.TotalSeconds * SPEED - _pathOffset) % SPACING + SPACING) % SPACING;
var arrowIndex = 0; var arrowIndex = 0;
while (distance <= _length) { while (distance <= _length) {
var arrow = GetArrow(arrowIndex++); var arrow = GetArrow(arrowIndex++);
SetPosition(arrow, distance); SetPosition(arrow, distance);
arrow.Visibility = Visibility.Visible; arrow.Visibility = Visibility.Visible;
distance += Spacing; distance += SPACING;
} }
while (arrowIndex < _layer.Children.Count) { while (arrowIndex < _layer.Children.Count) {
_layer.Children[arrowIndex++].Visibility = Visibility.Collapsed; _layer.Children[arrowIndex++].Visibility = Visibility.Collapsed;
@@ -95,12 +86,15 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
} }
var arrow = new Polygon() { var arrow = new Polygon() {
Fill = Brushes.Black, Fill = new SolidColorBrush(Color.FromArgb(0x60, 0, 0, 0)),
IsHitTestVisible = false, IsHitTestVisible = false,
Points = [ Points = [
new Point(HalfLength, 0), new(-LENGTH / 2, -WIDTH / 2),
new Point(-HalfLength, HalfWidth), new(0, -WIDTH / 2),
new Point(-HalfLength, -HalfWidth), new(LENGTH / 2, 0),
new(0, WIDTH / 2),
new(-LENGTH / 2, WIDTH / 2),
new(0, 0),
], ],
RenderTransform = new MatrixTransform(Matrix.Identity), RenderTransform = new MatrixTransform(Matrix.Identity),
}; };
@@ -109,29 +103,51 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
} }
private void SetPosition(Polygon arrow, double distance) { private void SetPosition(Polygon arrow, double distance) {
var firstLength = GetLength(_points[0], _points[1]); const double blendDistance = WIDTH / 2;
var secondLength = GetLength(_points[1], _points[2]); double firstLength = GetLength(_points[0], _points[1]);
Point start; double blendStartDistance = firstLength - blendDistance;
Point end; double blendEndDistance = firstLength + blendDistance;
double segmentDistance;
if ((firstLength > 0 && distance <= firstLength) || secondLength == 0) { Vector v1 = Normalize(_points[0], _points[1]);
start = _points[0]; Vector v2 = Normalize(_points[1], _points[2]);
end = _points[1]; Point blendStart = _points[1] - v1 * blendDistance;
segmentDistance = distance; Point blendEnd = _points[1] + v2 * blendDistance;
Point position;
Vector direction;
if (distance < blendStartDistance) {
position = _points[0] + v1 * distance;
direction = v1;
} else if (distance <= blendEndDistance) {
double t = (distance - blendStartDistance) / (blendEndDistance - blendStartDistance);
double u = 1.0 - t;
position = new Point(
u * u * blendStart.X +
2 * u * t * _points[1].X +
t * t * blendEnd.X,
u * u * blendStart.Y +
2 * u * t * _points[1].Y +
t * t * blendEnd.Y);
direction = new Vector(
2 * u * (_points[1].X - blendStart.X) +
2 * t * (blendEnd.X - _points[1].X),
2 * u * (_points[1].Y - blendStart.Y) +
2 * t * (blendEnd.Y - _points[1].Y));
direction.Normalize();
} else { } else {
start = _points[1]; double d = distance - firstLength;
end = _points[2]; position = _points[1] + v2 * d;
segmentDistance = distance - firstLength; direction = v2;
} }
var segmentLength = GetLength(start, end); double ux = direction.X;
if (segmentLength == 0) return; double uy = direction.Y;
var ux = (end.X - start.X) / segmentLength; (arrow.RenderTransform as MatrixTransform)?.Matrix = new Matrix(ux, uy, -uy, ux, position.X, position.Y);
var uy = (end.Y - start.Y) / segmentLength;
var x = start.X + ux * segmentDistance;
var y = start.Y + uy * segmentDistance;
((MatrixTransform)arrow.RenderTransform).Matrix = new Matrix(
ux, uy, -uy, ux, x, y);
} }
private void Stop() { private void Stop() {
@@ -142,14 +158,16 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
_layer.Visibility = Visibility.Collapsed; _layer.Visibility = Visibility.Collapsed;
} }
private static double PositiveModulo(double value, double modulus) {
return (value % modulus + modulus) % modulus;
}
private static double GetLength(Point start, Point end) { private static double GetLength(Point start, Point end) {
var dx = end.X - start.X; var dx = end.X - start.X;
var dy = end.Y - start.Y; var dy = end.Y - start.Y;
return Math.Sqrt(dx * dx + dy * dy); return Math.Sqrt(dx * dx + dy * dy);
} }
private static Vector Normalize(Point p1, Point p2) {
Vector v = p2 - p1;
if (v.Length > 0) v.Normalize();
return v;
}
} }
} }
+1 -1
View File
@@ -83,7 +83,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public void Update() { public void Update() {
_inner1?.Stroke = Highlight ?? Brushes.DarkGray; _inner1?.Stroke = Highlight ?? Brushes.DarkGray;
_inner2?.Stroke = Highlight ?? Brushes.DarkGray; _inner2?.Stroke = Highlight ?? Brushes.DarkGray;
_flowArrow?.Update(Highlight == PamhagenBrushes.Green ? Flow : null); _flowArrow?.Update(Flow);
} }
} }
} }
+6 -6
View File
@@ -49,7 +49,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
Y2 = y2, Y2 = y2,
StrokeThickness = 10, StrokeThickness = 10,
Stroke = Brushes.Black, Stroke = Brushes.Black,
StrokeStartLineCap = PenLineCap.Flat, StrokeStartLineCap = PenLineCap.Round,
StrokeEndLineCap = PenLineCap.Round, StrokeEndLineCap = PenLineCap.Round,
}; };
_inner1 = new Line() { _inner1 = new Line() {
@@ -59,7 +59,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
Y2 = y2, Y2 = y2,
StrokeThickness = 6, StrokeThickness = 6,
Stroke = Brushes.DarkGray, Stroke = Brushes.DarkGray,
StrokeStartLineCap = PenLineCap.Flat, StrokeStartLineCap = PenLineCap.Round,
StrokeEndLineCap = PenLineCap.Round, StrokeEndLineCap = PenLineCap.Round,
}; };
_outer2 = new Line() { _outer2 = new Line() {
@@ -79,22 +79,22 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
Y2 = y3, Y2 = y3,
StrokeThickness = 6, StrokeThickness = 6,
Stroke = Brushes.DarkGray, Stroke = Brushes.DarkGray,
StrokeStartLineCap = PenLineCap.Square, StrokeStartLineCap = PenLineCap.Round,
StrokeEndLineCap = PenLineCap.Square, StrokeEndLineCap = PenLineCap.Round,
}; };
canvas.Children.Add(_outer1); canvas.Children.Add(_outer1);
canvas.Children.Add(_outer2); canvas.Children.Add(_outer2);
canvas.Children.Add(_hopper);
canvas.Children.Add(_inner1); canvas.Children.Add(_inner1);
canvas.Children.Add(_inner2); canvas.Children.Add(_inner2);
_flowArrow = FlowArrow.Create(canvas, _inner1!, _inner2!); _flowArrow = FlowArrow.Create(canvas, _inner1!, _inner2!);
canvas.Children.Add(_hopper);
} }
public void Update() { public void Update() {
_inner1?.Stroke = Highlight ?? Brushes.DarkGray; _inner1?.Stroke = Highlight ?? Brushes.DarkGray;
_inner2?.Stroke = Highlight ?? Brushes.DarkGray; _inner2?.Stroke = Highlight ?? Brushes.DarkGray;
_hopper?.Fill = Highlight ?? Brushes.DarkGray; _hopper?.Fill = Highlight ?? Brushes.DarkGray;
_flowArrow?.Update(Highlight == PamhagenBrushes.Green ? Flow : null); _flowArrow?.Update(Flow);
} }
} }
} }
+1 -1
View File
@@ -54,7 +54,7 @@ namespace PamhagenSysCtrl.Helpers.Pipeline {
public void Update() { public void Update() {
_inner?.Stroke = Highlight ?? Brushes.DarkGray; _inner?.Stroke = Highlight ?? Brushes.DarkGray;
_flowArrow?.Update(Highlight == PamhagenBrushes.Green ? Flow : null); _flowArrow?.Update(Flow);
} }
} }
} }
@@ -76,7 +76,7 @@ namespace PamhagenSysCtrl.Windows {
e.Flow = null; e.Flow = null;
} }
foreach (var p in Graph.SelectedPaths) { foreach (var p in Graph.SelectedPaths) {
Graph.ColorPipesAdjacientToPath(p, PamhagenBrushes.Green, PamhagenBrushes.DimOrange); Graph.ColorPipesAdjacientToPath(p, PamhagenBrushes.Green, true, PamhagenBrushes.DimOrange);
} }
var hover = Graph.GetHover(pos.X, pos.Y); var hover = Graph.GetHover(pos.X, pos.Y);
if (_lastSource != null && (hover.Count > 0 || _path.Count > 0)) { if (_lastSource != null && (hover.Count > 0 || _path.Count > 0)) {
@@ -87,17 +87,16 @@ namespace PamhagenSysCtrl.Windows {
if (points.Count >= 2) { if (points.Count >= 2) {
var res = Graph.GetPath(points, (n) => n is not ISink && (n is not Valve v || !v.IsLocked)); var res = Graph.GetPath(points, (n) => n is not ISink && (n is not Valve v || !v.IsLocked));
if (res is Path path) { if (res is Path path) {
Graph.ColorPipesAdjacientToPath(path, PamhagenBrushes.Green, PamhagenBrushes.DimOrange); Graph.ColorPipesAdjacientToPath(path, PamhagenBrushes.Green, false, PamhagenBrushes.DimOrange);
} }
} }
} else if (_lastSource == null && hover.Count > 0 && Graph.SelectedPaths.Any(p => p.Start == hover.First())) { } else if (_lastSource == null && hover.Count > 0 && Graph.SelectedPaths.Any(p => p.Start == hover.First())) {
var path = Graph.SelectedPaths.First(p => p.Start == hover.First()); var path = Graph.SelectedPaths.First(p => p.Start == hover.First());
Graph.ColorPipesAdjacientToPath(path, PamhagenBrushes.Red, null); Graph.ColorPipesAdjacientToPath(path, PamhagenBrushes.Red, true, null);
} }
if (_lastSource != null) { if (_lastSource != null) {
foreach (var edge in _lastSource.Outputs) { foreach (var edge in _lastSource.Outputs) {
edge.Highlight = PamhagenBrushes.Green; edge.Highlight = PamhagenBrushes.Green;
edge.Flow = (true, 0);
} }
} }
Graph.Update(hover, down); Graph.Update(hover, down);