Как смоделировать висячий кабель в WPF?

У меня есть приложение, которое очень "основано на подключении", то есть несколько входов/выходов.

Концепция пользовательского интерфейса "кабеля" - именно то, что я ищу, чтобы сделать концепцию понятной пользователю. Propellerhead применил аналогичный подход в своем программном обеспечении Reason для аудиокомпонентов, проиллюстрированном в этом видео на YouTube (быстрая перемотка вперед до 2 м: 50 секунд).

Я могу заставить эту концепцию работать в GDI, рисуя сплайн из точки A в точку B, для этого нужно использовать более элегантный способ использования Paths или что-то в WPF, но с чего вы начинаете? Есть ли хороший способ имитировать анимацию кабельного размаха, когда вы хватаете ее и встряхиваете?

Я также открыт для управления библиотеками (коммерческими или с открытым исходным кодом), если это колесо уже было изобретено для WPF.

Обновление: Благодаря ссылкам в ответах до сих пор я почти там.

alt text

Я создал программный код BezierCurve, а точка 1 - (0, 0), точка 2 - нижняя точка "зависания", а точка 3 - везде, где находится курсор мыши. Я создал PointAnimation для Point 2 с функцией ослабления ElasticEase, примененной к нему, чтобы дать эффект "Swinging" (т.е. Отскакивая среднюю точку вокруг бит).

Проблема только в том, что анимация кажется немного запоздалой. Я начинаю раскадровку каждый раз, когда движется мышь, есть ли лучший способ сделать эту анимацию? Мое решение до сих пор находится здесь:

Игровая площадка Безье

код:

private Path _path = null;
private BezierSegment _bs = null;
private PathFigure _pFigure = null;
private Storyboard _sb = null;
private PointAnimation _paPoint2 = null;
ElasticEase _eEase = null;

private void cvCanvas_MouseMove(object sender, MouseEventArgs e)
{
    var position = e.GetPosition(cvCanvas);
    AdjustPath(position.X, position.Y);
}

// basic idea: when mouse moves, call AdjustPath and draw line from (0,0) to mouse position with a "hang" in the middle
private void AdjustPath(double x, double y)
{
    if (_path == null)
    {
        _path = new Path();
        _path.Stroke = new SolidColorBrush(Colors.Blue);
        _path.StrokeThickness = 2;
        cvCanvas.Children.Add(_path);

        _bs = new BezierSegment(new Point(0, 0), new Point(0, 0), new Point(0, 0), true);

        PathSegmentCollection psCollection = new PathSegmentCollection();
        psCollection.Add(_bs);

        _pFigure = new PathFigure();
        _pFigure.Segments = psCollection;
        _pFigure.StartPoint = new Point(0, 0);


        PathFigureCollection pfCollection = new PathFigureCollection();
        pfCollection.Add(_pFigure);

        PathGeometry pathGeometry = new PathGeometry();
        pathGeometry.Figures = pfCollection;

        _path.Data = pathGeometry;
    }

    double bottomOfCurveX = ((x / 2));
    double bottomOfCurveY = (y + (x * 1.25));

    _bs.Point3 = new Point(x, y);

    if (_sb == null)
    {
        _paPoint2 = new PointAnimation();

        _paPoint2.From = _bs.Point2;
        _paPoint2.To = new Point(bottomOfCurveX, bottomOfCurveY);
        _paPoint2.Duration = new Duration(TimeSpan.FromMilliseconds(1000));
        _eEase = new ElasticEase();

        _paPoint2.EasingFunction = _eEase;
        _sb = new Storyboard();

        Storyboard.SetTarget(_paPoint2, _path);
        Storyboard.SetTargetProperty(_paPoint2, new PropertyPath("Data.Figures[0].Segments[0].Point2"));

        _sb.Children.Add(_paPoint2);
        _sb.Begin(this);                
    }

    _paPoint2.From = _bs.Point2;
    _paPoint2.To = new Point(bottomOfCurveX, bottomOfCurveY);

    _sb.Begin(this);
}

Ответы

Ответ 1

Если вы хотите истинное динамическое движение (т.е. когда вы "встряхиваете" указатель мыши, вы можете создавать волны, которые перемещаются вдоль шнура), вам нужно будет использовать методы конечных элементов. Однако, если вы в порядке со статическим поведением, вы можете просто использовать кривые Безье.

Сначала я кратко опишу подход конечных элементов, а затем более подробно рассмотрим статический подход.

Динамический подход

Разделите свой "шнур" на большое количество (1000 или около того) "элементов", каждый с позицией и скоростью Vector. Используйте событие CompositionTarget.Rendering для вычисления каждой позиции элемента следующим образом:

  • Вычислите натяжение каждого элемента вдоль "шнура" из смежных элементов, которое пропорционально расстоянию между элементами. Предположим, что сам шнур безмассовый.

  • Вычислить вектор силы в каждом "элементе", который состоит из тяги от каждого смежного элемента вдоль шнура плюс постоянная сила тяжести.

  • Используйте константу массы для преобразования вектора силы в ускорение и обновите положение и скорость, используя уравнения движения.

  • Нарисуйте линию с помощью сборки StreamGeometry с BeginFigure, а затем PolyLineTo. С таким количеством точек нет оснований для дополнительных вычислений для создания кубического приближения безье.

Статический подход

Разделите ваш шнур на, возможно, 30 сегментов, каждый из которых является кубическим безьевым приближением к коленуме y = a cosh (x/a). Ваши конечные контрольные точки должны быть на контактной кривой, параллели должны касаться катенариев, а длины управляющих линий установлены на основе второй производной от контактной.

В этом случае вы, вероятно, также захотите отобразить StreamGeometry, используя BeginFigure и PolyBezierTo для его создания.

Я бы реализовал это как пользовательский подкласс Shape "Catenary", похожий на Rectangle и Ellipse. В этом случае вам нужно переопределить свойство DefiningGeometry. Для эффективности я бы также переопределил CacheDefiningGeometry, GetDefiningGeometryBounds и GetNaturalSize.

Сначала вы должны решить, как параметризовать вашу контактную группу, а затем добавить DependencyProperties для всех ваших параметров. Убедитесь, что вы установите флаги AffectsMeasure и AffectsRender в свой файл FrameworkPropertyMetadata.

Одной возможной параметризацией будет XOffset, YOffset, Length. Другим может быть XOffset, YOffset, SagRelativeToWidth. Это будет зависеть от того, с чем проще всего привязываться.

После определения свойств DependencyProperties выполните свое свойство DefiningGeometry, чтобы вычислить кубические контрольные точки безье, построить StreamGeometry и вернуть его.

Если вы сделаете это, вы можете сбросить контроль над цепью в любом месте и получить контактную кривую.

Ответ 3

ИМХО "подвесные" (физически смоделированные) кабели являются случаем чрезмерного использования - благоприятствуют просмотру удобства использования.

Вы уверены, что не просто загромождали пользовательский опыт?

В пользовательском интерфейсе node/connection я обнаруживаю четкие соединения (например, в Quartz Composer: http://ellington.tvu.ac.uk/ma/wp-content/uploads/2006/05/images/Quartz%20Composer_screenshot_011.png), что более важно, чем такие как качающиеся кабели, которые направляются в другом направлении (вниз из-за силы тяжести), чем там, где фактически находится точка соединения. (И в то же время съедают CPU-циклы для моделирования, которые могут быть более полезными в других местах)

Просто мои $0.02