Как мне смоделировать подвесной кабель в WPF? - PullRequest
14 голосов
/ 17 ноября 2010

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

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

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

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

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

alt text

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

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

Детская площадка Bezier Curve

Код:

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);
}

Ответы [ 3 ]

9 голосов
/ 17 ноября 2010

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

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

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

Разделите ваш «шнур» на большое количество (1000 или около того) «элементов», каждый с вектором положения и скорости. Используйте событие 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 и верните его.

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

1 голос
/ 17 ноября 2010

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

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

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

Просто мои $ 0,02

1 голос
/ 17 ноября 2010

Сегменты кривой Безье пользователя в пути.

http://www.c -sharpcorner.com / UploadFile / dbeniwal321 / WPFBezier01302009015211AM / WPFBezier.aspx

...