Главная » Разработка для Windows Phone 7 » Кривые Безье Windows Phone 7

0

Пьер Этьен Безье (1910-1999) работал инженером французской автомобилестроительной компании Рено с 1933 по 1975 год. В 1960-е годы компания начала переход от создания кузовов автомобилей с использованием глиняных моделей к компьютеризированным средствам проектирования. Для этого потребовались математические описания кривых, с которыми инженеры могли бы работать, не вникая в математические дебри. Результатом этих исканий стали кривые, которые теперь носят имя Пьера Безье.

Кривая Безье – это сплин, т.е. графическое представление гладкой непрерывной функции, являющейся аппроксимацией дискретных данных. Silverlight поддерживает не только стандартную двухмерную форму кубической кривой Безье, но также и квадратичные кривые Безье, которые проще и быстрее обрабатываются, поэтому начнем разговор с них.

Кривая Безье второго порядка (или квадратичная) определяется тремя точками, которые обычно обозначают p0, p1 и p2. Кривая начинается в точке p0 и заканчивается в точке p2. Точку p1 называют опорной точкой. Кривая обычно не проходит через pi, эта точка играет

роль своеобразного магнита, который притягивает кривую к себе. Из pa кривая выходит по касательной и в направлении прямой, проведенной из p0 в pi, в точке p2 кривая проходит по касательной и в направлении прямой, проведенной из p1 в p2.

Лучший способ понять кривые Безье – поэкспериментировать с ними. Приложение QuadraticBezier (Квадратичная кривая Безье) отрисовывает всего одну кривую Безье, но позволяет задавать разные три точки и наблюдать, к чему это приводит.

XAML-файл включает четыре элемента Path и Polyline, которые располагаются в Grid с одной ячейкой. Первый Path представляет квадратичную кривую Безье. Обратите внимание, что p0 обеспечивается свойством StartPoint объекта PathFigure, тогда как p1 и p2 соответствуют свойствам Point1 и Point2 объекта QuadraticBezierSegment (Сегмент-квадратичная кривая Безье):

Проект Silverlight: QuadraticBezier Файл: MainPage.xaml (фрагмент)

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <Path Stroke="{StaticResource PhoneForegroundBrush}" StrokeThickness="2"> <Path.Data>

<PathGeometry>

<PathFigure x:Name="pathFig"

StartPoint="10 0 100"> <QuadraticBezierSegment x:Name="pathSeg"

Point1="300 250" Point2="100 400" />

</PathFigure> </PathGeometry> </Path.Data> </Path>

<Polyline Name="ctrlLine"

Stroke="{StaticResource PhoneForegroundBrush}" StrokeDashArray="2 2"

Points="100 100, 300 250, 100 400" />

<Path Name="pt0Dragger"

Fill="{StaticResource PhoneAccentBrush}" Opacity="0.5"> <Path.Data>

<EllipseGeometry x:Name="pt0Ellipse" Center="100 100" RadiusX="4 8" RadiusY="4 8" />

</Path.Data> </Path>

<Path Name="pt1Dragger"

Fill="{StaticResource PhoneAccentBrush}" Opacity="0.5"> <Path.Data>

<EllipseGeometry x:Name="pt1Ellipse" Center="300 250" RadiusX="4 8" RadiusY="4 8" />

</Path.Data> </Path>

<Path Name="pt2Dragger"

Fill="{StaticResource PhoneAccentBrush}" Opacity="0.5"> <Path.Data>

<EllipseGeometry x:Name="pt2Ellipse" Center="100 400"

RadiusX="4 8" RadiusY="4 8" />

</Path.Data> </Path> </Grid>

Элемент Polyline отрисовывает пунктирную линию, соединяющую две конечные точки и опорную точку. Остальные три элемента можно перетягивать по экрану, т.е. они позволяют пользователю перемещать все три точки. Изначально на экран выводится следующее:

Всю логику перетягивания обеспечивает файл выделенного кода. Поскольку Silverlight for Windows Phone поддерживает привязки только для свойств, которые описаны классами, наследуемыми от FrameworkElement, я не смог связать соответствующие точки в XAML-файле. Каждую из них приходится задавать по отдельности в перегрузках Manipulation:

Проект Silverlight: QuadraticBezier Файл: MainPage.xaml.cs (фрагмент)

protected override void OnManipulationStarted(ManipulationStartedEventArgs args) {

if (args.OriginalSource == pt0Dragger || args.OriginalSource == pt1Dragger || args.OriginalSource == pt2Dragger)

{

args.ManipulationContainer = ContentPanel; args.Handled = true;

}

base.OnManipulationStarted(args);

}

protected override void OnManipulationDelta(ManipulationDeltaEventArgs args) {

Point translate = args.DeltaManipulation.Translation;

if (args.OriginalSource == pt0Dragger) {

pathFig.StartPoint = Move(pathFig.StartPoint, translate); ctrlLine.Points[0] = Move(ctrlLine.Points[0], translate); pt0Ellipse.Center = Move(pt0Ellipse.Center, translate); args.Handled = true;

else if (args.OriginalSource == pt1Dragger) {

pathSeg.Point1 = Move(pathSeg.Point1, translate); ctrlLine.Points[1] = Move(ctrlLine.Points[1], translate); pt1Ellipse.Center = Move(pt1Ellipse.Center, translate); args.Handled = true;

}

else if (args.OriginalSource == pt2Dragger) {

pathSeg.Point2 = Move(pathSeg.Point2, translate); ctrlLine.Points[2] = Move(ctrlLine.Points[2], translate); pt2Ellipse.Center = Move(pt2Ellipse.Center, translate); args.Handled = true;

}

base.OnManipulationDelta(args);

}

Point Move(Point point, Point translate) {

return new Point(point.X + translate.X, point.Y + translate.Y);

}

Являясь квадратичной, эта версия кривой Безье имеет только один изгиб и отличается регулярностью поведения:

Для построения квадратичных кривых Безье используются следующие параметрические уравнения:

x(t) = (1 – t)2x0 + 2t(l – t)xx + t2x2 y(t) = (1 – t)2y0 + 2t(l – Оуі + t2y2 для t = 0 до 1, где p0 = (X0, y0) и т.д.

Кубический сплайн Безье считается более стандартным и имеет две опорные точку, а не одну. Кривая определяется четырьмя точками, которые обычно называют p0, pi, p2 и p3. Кривая начинается в точке p0 и заканчивается в p3. Из p0 кривая выходит по касательной и в направлении прямой, проведенной из p0 в p1, в точке p3 кривая проходит по касательной к

прямой, проведенной из p3 в p2. Эту кривую описывают следующие параметрические уравнения:

x(t) = (1 – t)3x0 + 3t(l – t)2x1 + 3t2(l – t)x2 + t3x3 y(t) = (1 – t)3y0 + 3t(l – ґ)2Уі + 3t2(l – t)y2 + t3y3

В приложении CubicBezier (Кубическая кривая Безье) мною был использован несколько другой подход. Чтобы все упростить, я описал производный от UserControl класс PointDragger (Модуль перетягивания точки). Файл PointDragger.xaml определяет дерево визуальных элементов. В него входит лишь Grid, имеющий Path с непрозрачностью (Opacity) 0,5, и EllipseGeometry без точки Center:

Проект Silverlight: CubicBezier Файл: PointDragger.xaml

<UserControl

x: Class="CubicBezier.PointDragger"

xmlns="http://schemas.microsoft.com/winfx/2 0 0 6/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2 0 0 6/xaml">

<Grid x:Name="LayoutRoot">

<Path Fill="{StaticResource PhoneAccentBrush}" Opacity="0.5"> <Path.Data>

<EllipseGeometry x:Name="ellipseGeometry" RadiusX="4 8" RadiusY="4 8" />

</Path.Data> </Path> </Grid> </UserControl>

В файле выделенного кода описывается свойство-зависимость Point типа Point и событие PointChanged (Точка изменилась), которое формируется при изменении значения этого свойства. Обработчик события изменения свойства также задает значение EllipseGeometry, описанному в XAML-файле:

Проект Silverlight: CubicBezier Файл: PointDragger.xaml.cs (фрагмент)

public partial class PointDragger : UserControl {

public static readonly DependencyProperty PointProperty = DependencyProperty.Register("Point", typeof Point), typeof(PointDragger),

new PropertyMetadata(OnPointChanged)); public event RoutedPropertyChangedEventHandler<Point> PointChanged;

public PointDragger() {

InitializeComponent();

}

public Point Point {

set { SetValue(PointProperty, value); }

get { return (Point)GetValue(PointProperty); }

}

static void OnPointChanged(DependencyObject obj,

DependencyPropertyChangedEventArgs args)

(obj as PointDragger).OnPointChanged((Point)args.OldValue,

(Point)args.NewValue);

}

protected virtual void OnPointChanged(Point oldValue, Point newValue) {

ellipseGeometry.Center = newValue;

if (PointChanged != null) PointChanged(this,

new RoutedPropertyChangedEventArgs<Point>(oldValue, newValue));

}

}

Класс PointDragger также обрабатывает собственные события Manipulation, которые по сравнению с описываемыми в QuadraticBezier очень просты:

Проект Silverlight: CubicBezier Файл: PointDragger.xaml.cs (фрагмент)

protected override void OnManipulationStarted(ManipulationStartedEventArgs args) {

args.ManipulationContainer = VisualTreeHelper.GetParent(this) as UIElement;

args.Handled = true;

base.OnManipulationStarted(args);

}

protected override void OnManipulationDelta(ManipulationDeltaEventArgs args) {

Point translate = args.DeltaManipulation.Translation;

this.Point = new Point(this.Point.X + translate.X, this.Point.Y + translate.Y);

args.Handled = true;

base.OnManipulationDelta(args);

}

В файле MainPage.xaml описан Path с BezierSegment (Сегмент кривая Безье), два элемента Polyline для отрисовки касательных и четыре экземпляра PointDragger. Класс BezierSegment определяет свойства Point1, Point2 и Point3 для задания двух опорных точек и конечной точки.

Проект Silverlight: CubicBezier Файл: MainPage.xaml (фрагмент)

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <Path Stroke="{StaticResource PhoneForegroundBrush}" StrokeThickness="2"> <Path.Data>

<PathGeometry>

<PathFigure x:Name="pathFig"

StartPoint="10 0 100"> <BezierSegment x:Name="pathSeg" Point1="300 100" Point2="300 400" Point3="100 400" />

</PathFigure> </PathGeometry> </Path.Data> </Path>

<Polyline Name="ctrl1Line"

Stroke="{StaticResource PhoneForegroundBrush}" StrokeDashArray="2 2" Points="100 100, 300 100" />

<Polyline Name="ctrl2Line"

Stroke="{StaticResource PhoneForegroundBrush}" StrokeDashArray="2 2" Points="300 400, 100 400" />

<local:PointDragger x:Name="pt0Dragger" Point="100 100"

PointChanged="OnPointDraggerPointChanged" />

<local:PointDragger x:Name="pt1Dragger" Point="300 100"

PointChanged="OnPointDraggerPointChanged" />

<local:PointDragger x:Name="pt2Dragger" Point="300 400"

PointChanged="OnPointDraggerPointChanged" />

<local:PointDragger x:Name="pt3Dragger" Point="100 400"

PointChanged="OnPointDraggerPointChanged" />

</Grid>

Изначально на экран выводится следующее:

Обратите внимание на обработчики событий PointChanged элементов управления PointDragger. Реализация этих обработчиков – это практически все, что осталось сделать в MainPage.xaml.cs:

Проект Silverlight: CubicBezier Файл: MainPage.xaml.cs (фрагмент)

void OnPointDraggerPointChanged(object sender,

RoutedPropertyChangedEventArgs<Point> args)

{

Point translate = new Point(args.NewValue.X – args.OldValue.X,

args.NewValue.Y – args.OldValue.Y);

if (sender == pt0Dragger) {

pathFig.StartPoint = Move(pathFig.StartPoint, translate);

ctrl1Line.Points[0] = Move(ctrl1Line.Points[0], translate);

else if (sender == pt1Dragger)

pathSeg.Point1 = Move(pathSeg.Point1, translate); ctrl1Line.Points[1] = Move(ctrl1Line.Points[1], translate);

else if (sender == pt2Dragger)

pathSeg.Point2 = Move(pathSeg.Point2, translate); ctrl2Line.Points[0] = Move(ctrl2Line.Points[0], translate);

else if (sender == pt3Dragger)

pathSeg.Point3 = Move(pathSeg.Point3, translate); ctrl2Line.Points[1] = Move(ctrl2Line.Points[1], translate);

}

Point Move(Point point, Point translate) {

return new Point(point.X + translate.X, point.Y + translate.Y);

}

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

Кроме классов QuadraticBezierSegment и BezierSegment для описания одиночных кривых Безье могут также использоваться PolyQuadraticBezierSegment (Сегмент множество квадратичных кривых Безье) и PolyBezierSegment (Сегмент множество кривых Безье), описывающие множества кривых Безье. Каждая новая кривая берет начало в точке окончания предыдущей кривой. В обоих классах есть свойство Points типа PointCollection.

Для PolyQuadraticBezierSegment количество объектов Point в коллекции Points должно быть кратно 2. Первый, третий, пятый и все последующие нечетные члены коллекции являются опорным точками. Для PolyBezierSegment количество точек должно быть кратно 3.

При соединении множества кривых Безье конечная точка одной кривой становится начальной точкой последующей кривой. Результирующая кривая будет гладкой в точке соединения только в случае, если две опорные точки обеих соединяемых кривых являются коллинеарными, т.е. лежат на одной прямой.

Источник: Чарльз Петзольд, Программируем Windows Phone 7, Microsoft Press, © 2011.

По теме:

  • Комментарии