174 lines
6.0 KiB
C#
174 lines
6.0 KiB
C#
using System.Diagnostics;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Media;
|
|
using System.Windows.Shapes;
|
|
|
|
namespace PamhagenSysCtrl.Helpers.Pipeline {
|
|
internal sealed class FlowArrows {
|
|
|
|
public const double SPEED = 20;
|
|
public const double SPACING = 15;
|
|
public const double LENGTH = 7;
|
|
public const double WIDTH = 7;
|
|
|
|
private static readonly Stopwatch Clock = Stopwatch.StartNew();
|
|
|
|
private readonly Canvas _layer;
|
|
private readonly Point[] _path;
|
|
private Point[] _points = [];
|
|
private double _length;
|
|
private double _pathOffset;
|
|
private bool _isActive;
|
|
|
|
public double Length => GetLength(_path[0], _path[1]) + GetLength(_path[1], _path[2]);
|
|
|
|
private FlowArrows(Canvas canvas, Point[] path) {
|
|
_path = path;
|
|
_layer = new Canvas() {
|
|
IsHitTestVisible = false,
|
|
Visibility = Visibility.Collapsed,
|
|
};
|
|
_layer.Unloaded += (_, _) => Stop();
|
|
canvas.Children.Add(_layer);
|
|
}
|
|
|
|
public static FlowArrows Create(Canvas canvas, Line first, Line second) {
|
|
return new FlowArrows(canvas, [
|
|
new Point(first.X1, first.Y1),
|
|
new Point(first.X2, first.Y2),
|
|
new Point(second.X2, second.Y2),
|
|
]);
|
|
}
|
|
|
|
public static FlowArrows Create(Canvas canvas, Polyline path) {
|
|
return new FlowArrows(canvas, [.. path.Points]);
|
|
}
|
|
|
|
public void Update((bool TowardEnd, double PathOffset)? flow) {
|
|
if (flow == null) {
|
|
Stop();
|
|
return;
|
|
}
|
|
|
|
_points = flow.Value.TowardEnd ? _path : [.. _path.Reverse()];
|
|
_length = GetLength(_points[0], _points[1]) + GetLength(_points[1], _points[2]);
|
|
_pathOffset = flow.Value.PathOffset;
|
|
if (!_isActive) {
|
|
CompositionTarget.Rendering += OnRendering;
|
|
_isActive = true;
|
|
}
|
|
_layer.Visibility = Visibility.Visible;
|
|
Render();
|
|
}
|
|
|
|
private void OnRendering(object? sender, EventArgs args) {
|
|
Render();
|
|
}
|
|
|
|
private void Render() {
|
|
var distance = ((Clock.Elapsed.TotalSeconds * SPEED - _pathOffset) % SPACING + SPACING) % SPACING;
|
|
var arrowIndex = 0;
|
|
while (distance <= _length) {
|
|
var arrow = GetArrow(arrowIndex++);
|
|
SetPosition(arrow, distance);
|
|
arrow.Visibility = Visibility.Visible;
|
|
distance += SPACING;
|
|
}
|
|
while (arrowIndex < _layer.Children.Count) {
|
|
_layer.Children[arrowIndex++].Visibility = Visibility.Collapsed;
|
|
}
|
|
}
|
|
|
|
private Polygon GetArrow(int index) {
|
|
if (index < _layer.Children.Count) {
|
|
return (Polygon)_layer.Children[index];
|
|
}
|
|
|
|
var arrow = new Polygon() {
|
|
Fill = new SolidColorBrush(Color.FromArgb(0x60, 0, 0, 0)),
|
|
IsHitTestVisible = false,
|
|
Points = [
|
|
new(-LENGTH / 2, -WIDTH / 2),
|
|
new(0, -WIDTH / 2),
|
|
new(LENGTH / 2, 0),
|
|
new(0, WIDTH / 2),
|
|
new(-LENGTH / 2, WIDTH / 2),
|
|
new(0, 0),
|
|
],
|
|
RenderTransform = new MatrixTransform(Matrix.Identity),
|
|
};
|
|
_layer.Children.Add(arrow);
|
|
return arrow;
|
|
}
|
|
|
|
private void SetPosition(Polygon arrow, double distance) {
|
|
const double blendDistance = WIDTH / 2;
|
|
double firstLength = GetLength(_points[0], _points[1]);
|
|
double blendStartDistance = firstLength - blendDistance;
|
|
double blendEndDistance = firstLength + blendDistance;
|
|
|
|
Vector v1 = Normalize(_points[0], _points[1]);
|
|
Vector v2 = Normalize(_points[1], _points[2]);
|
|
Point blendStart = _points[1] - v1 * blendDistance;
|
|
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 {
|
|
double d = distance - firstLength;
|
|
position = _points[1] + v2 * d;
|
|
direction = v2;
|
|
}
|
|
|
|
double ux = direction.X;
|
|
double uy = direction.Y;
|
|
(arrow.RenderTransform as MatrixTransform)?.Matrix = new Matrix(ux, uy, -uy, ux, position.X, position.Y);
|
|
}
|
|
|
|
private void Stop() {
|
|
if (_isActive) {
|
|
CompositionTarget.Rendering -= OnRendering;
|
|
_isActive = false;
|
|
}
|
|
_layer.Visibility = Visibility.Collapsed;
|
|
}
|
|
|
|
private static double GetLength(Point start, Point end) {
|
|
var dx = end.X - start.X;
|
|
var dy = end.Y - start.Y;
|
|
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;
|
|
}
|
|
}
|
|
}
|