Trzy typy krzywych Béziera
Dowiedz się, jak używać skiaSharp do renderowania krzywych sześciennych, kwadratowych i conic Bézier
Krzywa Béziera nosi nazwę Pierre'a Béziera (1910 – 1999), francuskiego inżyniera w firmie motoryzacyjnej Renault, która wykorzystała krzywą do projektowania samochodów wspomaganych komputerowo.
Krzywe Bézier są znane z dobrego dopasowania do projektowania interakcyjnego: Są one dobrze zachowywane — innymi słowy, nie ma osobliwości, które powodują, że krzywa staje się nieskończona lub nieporęczna — i są one zazwyczaj przyjemne estetycznie:
Kontury znaków czcionek opartych na komputerach są zwykle definiowane za pomocą krzywych Bézier.
Artykuł w Wikipedii na temat krzywej Béziera zawiera kilka przydatnych informacji podstawowych. Termin Krzywa Béziera odnosi się do rodziny podobnych krzywych. SkiaSharp obsługuje trzy typy krzywych Bézier, zwane sześcienną, kwadratową i stożkową. Conic jest również znany jako racjonalizacja kwadratowa.
Krzywa Béziera sześcienna
Sześcienna jest typem krzywej Béziera, o której większość deweloperów myśli, gdy pojawia się temat krzywych Béziera.
Krzywą Béziera sześcienną można dodać do SKPath
obiektu przy użyciu CubicTo
metody z trzema SKPoint
parametrami lub CubicTo
przeciążenia z oddzielnymi x
parametrami i y
:
public void CubicTo (SKPoint point1, SKPoint point2, SKPoint point3)
public void CubicTo (Single x1, Single y1, Single x2, Single y2, Single x3, Single y3)
Krzywa zaczyna się od bieżącego punktu konturu. Kompletna krzywa beziera sześcienna jest definiowana przez cztery punkty:
- punkt początkowy: bieżący punkt w konturze lub (0, 0), jeśli
MoveTo
nie został wywołany - pierwszy punkt kontrolny:
point1
w wywołaniuCubicTo
- drugi punkt kontrolny:
point2
w wywołaniuCubicTo
- punkt końcowy:
point3
w wywołaniuCubicTo
Wynikowa krzywa rozpoczyna się od punktu początkowego i kończy się w punkcie końcowym. Krzywa zazwyczaj nie przechodzi przez dwa punkty kontrolne; zamiast tego punkty kontrolne działają podobnie jak magnesy, aby wyciągnąć krzywą w ich kierunku.
Najlepszym sposobem, aby poczuć się dla krzywej sześciennej Bézier jest eksperymentowanie. Jest to cel strony Krzywa Beziera, która pochodzi z .InteractivePage
Plik BezierCurvePage.xaml tworzy wystąpienie pliku SKCanvasView
i TouchEffect
. Plik BezierCurvePage.xaml.cs za pomocą kodu tworzy cztery TouchPoint
obiekty w konstruktorze. Procedura PaintSurface
obsługi zdarzeń tworzy element SKPath
w celu renderowania krzywej Béziera na podstawie czterech TouchPoint
obiektów, a także rysuje kropkowane linie tangensowe z punktów kontrolnych do punktów końcowych:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
// Draw path with cubic Bezier curve
using (SKPath path = new SKPath())
{
path.MoveTo(touchPoints[0].Center);
path.CubicTo(touchPoints[1].Center,
touchPoints[2].Center,
touchPoints[3].Center);
canvas.DrawPath(path, strokePaint);
}
// Draw tangent lines
canvas.DrawLine(touchPoints[0].Center.X,
touchPoints[0].Center.Y,
touchPoints[1].Center.X,
touchPoints[1].Center.Y, dottedStrokePaint);
canvas.DrawLine(touchPoints[2].Center.X,
touchPoints[2].Center.Y,
touchPoints[3].Center.X,
touchPoints[3].Center.Y, dottedStrokePaint);
foreach (TouchPoint touchPoint in touchPoints)
{
touchPoint.Paint(canvas);
}
}
W tym miejscu działa:
Matematycznie krzywa jest wielomianem sześciennym. Krzywa przecina linię prostą na maksymalnie trzy punkty. W punkcie początkowym krzywa jest zawsze tangensem, a w tym samym kierunku co linia prosta od punktu początkowego do pierwszego punktu kontrolnego. W punkcie końcowym krzywa jest zawsze tangensem, a w tym samym kierunku co linia prosta od drugiego punktu kontrolnego do punktu końcowego.
Sześcienna krzywa Béziera jest zawsze ograniczona przez wypukły czworokąt łączący cztery punkty. Jest to nazywane wypukłym kadłubem. Jeśli punkty kontrolne leżą na prostej linii między punktem początkowym i końcowym, krzywa Béziera renderuje się jako linia prosta. Ale krzywa może również przekroczyć się, jak pokazuje trzeci zrzut ekranu.
Kontur ścieżki może zawierać wiele połączonych krzywych sześciennych Bézier, ale połączenie między dwiema sześciennych krzywymi Béziera będzie gładkie tylko wtedy, gdy następujące trzy punkty są grubiarne (czyli leżą na linii prostej):
- drugi punkt kontrolny pierwszej krzywej
- punkt końcowy pierwszej krzywej, który jest również punktem początkowym drugiej krzywej
- pierwszy punkt kontrolny drugiej krzywej
W następnym artykule dotyczącym danych ścieżek SVG odkryjesz obiekt ułatwiający definiowanie gładkich połączonych krzywych Béziera.
Czasami warto znać podstawowe równania parametryczne, które renderuje krzywą sześcienną Béziera. W przypadku wartości od 0 do 1 równania parametryczne są następujące:
x(t) = (1 – t)³x₀ + 3t(1 – t)²x₁ + 3t²(1 – t)x ** + t³x₃
y(t) = (1 – t)³y₀ + 3t(1 – t)²y₁ + 3t²(1 – t)y ** + t³y₃
Najwyższy wykładnik 3 potwierdza, że są to wielomiany sześcienne. Łatwo sprawdzić, czy gdy t
wartość to 0, punkt to (x₀, y₀), który jest punktem początkowym, a gdy t
jest równy 1, punkt jest (x₃, y₃), który jest punktem końcowym. W pobliżu punktu początkowego (w przypadku niskich t
wartości) pierwszy punkt kontrolny (x₁, y₁) ma silny efekt, a w pobliżu punktu końcowego (wysokich wartości "t") drugi punkt kontrolny (xima, y) ma silny efekt.
Przybliżenie krzywej Beziera do łuków okrągłych
Czasami wygodne jest użycie krzywej Béziera do renderowania łuku okrągłego. Sześcienna krzywa Béziera może przybliżyć okrągły łuk bardzo dobrze do ćwierć okręgu, więc cztery połączone krzywe Béziera mogą definiować cały okrąg. To przybliżenie zostało omówione w dwóch artykułach opublikowanych ponad 25 lat temu:
Tor Dokken, et al, "Good Approximation of Circles by Curvature-Continuous Bézier krzywe" , Computer Aided Geometry Design 7 (1990), 33-41.
Michael Goldapp, "Przybliżenie łuków okrągłych według wielomianów sześciennych" , komputer wspomagał geometryczny projekt 8 (1991), 227-238.
Na poniższym diagramie przedstawiono cztery punkty oznaczone etykietą pto
, , pt1
pt2
i pt3
definiujące krzywą Béziera (pokazaną na czerwono), która przybliża łuk okrągły:
Linie od początku i punktu końcowego do punktów kontrolnych są tangensem do okręgu i do krzywej Bézier, a mają długość L. Pierwszy cytowany powyżej artykuł wskazuje, że krzywa Bézier najlepiej przybliża okrągły łuk, gdy ta długość L jest obliczana następująco:
L = 4 × tan(α / 4) / 3
Ilustracja przedstawia kąt 45 stopni, więc L jest równy 0,265. W kodzie ta wartość zostanie pomnożona przez żądany promień okręgu.
Strona Bezier Circular Arc umożliwia eksperymentowanie z definiowaniem krzywej Béziera w celu przybliżenia okrągłego łuku kątowego kątów o rozmiarze do 180 stopni. Plik BezierCircularArcPage.xaml tworzy wystąpienie SKCanvasView
elementu i , aby Slider
wybrać kąt. Procedura PaintSurface
obsługi zdarzeń w pliku BezierCircularArgPage.xaml.cs code-behind używa przekształcenia, aby ustawić punkt (0, 0) na środek kanwy. Rysuje okrąg wyśrodkowany w tym punkcie do porównania, a następnie oblicza dwa punkty kontrolne dla krzywej Bézier:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
// Translate to center
canvas.Translate(info.Width / 2, info.Height / 2);
// Draw the circle
float radius = Math.Min(info.Width, info.Height) / 3;
canvas.DrawCircle(0, 0, radius, blackStroke);
// Get the value of the Slider
float angle = (float)angleSlider.Value;
// Calculate length of control point line
float length = radius * 4 * (float)Math.Tan(Math.PI * angle / 180 / 4) / 3;
// Calculate sin and cosine for half that angle
float sin = (float)Math.Sin(Math.PI * angle / 180 / 2);
float cos = (float)Math.Cos(Math.PI * angle / 180 / 2);
// Find the end points
SKPoint point0 = new SKPoint(-radius * sin, radius * cos);
SKPoint point3 = new SKPoint(radius * sin, radius * cos);
// Find the control points
SKPoint point0Normalized = Normalize(point0);
SKPoint point1 = point0 + new SKPoint(length * point0Normalized.Y,
-length * point0Normalized.X);
SKPoint point3Normalized = Normalize(point3);
SKPoint point2 = point3 + new SKPoint(-length * point3Normalized.Y,
length * point3Normalized.X);
// Draw the points
canvas.DrawCircle(point0.X, point0.Y, 10, blackFill);
canvas.DrawCircle(point1.X, point1.Y, 10, blackFill);
canvas.DrawCircle(point2.X, point2.Y, 10, blackFill);
canvas.DrawCircle(point3.X, point3.Y, 10, blackFill);
// Draw the tangent lines
canvas.DrawLine(point0.X, point0.Y, point1.X, point1.Y, dottedStroke);
canvas.DrawLine(point3.X, point3.Y, point2.X, point2.Y, dottedStroke);
// Draw the Bezier curve
using (SKPath path = new SKPath())
{
path.MoveTo(point0);
path.CubicTo(point1, point2, point3);
canvas.DrawPath(path, redStroke);
}
}
// Vector methods
SKPoint Normalize(SKPoint v)
{
float magnitude = Magnitude(v);
return new SKPoint(v.X / magnitude, v.Y / magnitude);
}
float Magnitude(SKPoint v)
{
return (float)Math.Sqrt(v.X * v.X + v.Y * v.Y);
}
Punkty początkowe i końcowe (point0
i point3
) są obliczane na podstawie normalnych równań parametrowych dla okręgu. Ponieważ okrąg jest wyśrodkowany na (0, 0), punkty te mogą być również traktowane jako wektory promieniowe z środka okręgu do obwodu. Punkty kontrolne znajdują się w liniach, które są tangensem do okręgu, więc znajdują się pod kątem prostym do tych wektorów promieniowych. Wektor pod kątem prostym jest po prostu oryginalnym wektorem ze współrzędnymi X i Y zamienione, a jeden z nich stał się ujemny.
Oto program uruchomiony z różnymi kątami:
Przyjrzyj się bliżej trzeciemu zrzutowi ekranu i zobaczysz, że krzywa Béziera szczególnie odbiega od półokrągu, gdy kąt wynosi 180 stopni, ale ekran systemu iOS pokazuje, że wydaje się pasować do ćwierć okręgu dokładnie, gdy kąt wynosi 90 stopni.
Obliczanie współrzędnych dwóch punktów kontrolnych jest dość proste, gdy okrąg kwarty jest zorientowany w następujący sposób:
Jeśli promień okręgu wynosi 100, to L wynosi 55 i jest to łatwa liczba do zapamiętania.
Kwadrat strony Circle animuje figurę między okręgiem a kwadratem. Okrąg jest przybliżony przez cztery krzywe Bézier, których współrzędne są wyświetlane w pierwszej kolumnie tej definicji tablicy w SquaringTheCirclePage
klasie:
public class SquaringTheCirclePage : ContentPage
{
SKPoint[,] points =
{
{ new SKPoint( 0, 100), new SKPoint( 0, 125), new SKPoint() },
{ new SKPoint( 55, 100), new SKPoint( 62.5f, 62.5f), new SKPoint() },
{ new SKPoint( 100, 55), new SKPoint( 62.5f, 62.5f), new SKPoint() },
{ new SKPoint( 100, 0), new SKPoint( 125, 0), new SKPoint() },
{ new SKPoint( 100, -55), new SKPoint( 62.5f, -62.5f), new SKPoint() },
{ new SKPoint( 55, -100), new SKPoint( 62.5f, -62.5f), new SKPoint() },
{ new SKPoint( 0, -100), new SKPoint( 0, -125), new SKPoint() },
{ new SKPoint( -55, -100), new SKPoint(-62.5f, -62.5f), new SKPoint() },
{ new SKPoint(-100, -55), new SKPoint(-62.5f, -62.5f), new SKPoint() },
{ new SKPoint(-100, 0), new SKPoint( -125, 0), new SKPoint() },
{ new SKPoint(-100, 55), new SKPoint(-62.5f, 62.5f), new SKPoint() },
{ new SKPoint( -55, 100), new SKPoint(-62.5f, 62.5f), new SKPoint() },
{ new SKPoint( 0, 100), new SKPoint( 0, 125), new SKPoint() }
};
...
}
Druga kolumna zawiera współrzędne czterech krzywych Béziera, które definiują kwadrat, którego obszar jest w przybliżeniu taki sam jak obszar okręgu. (Rysowanie kwadratu z dokładnym obszarem jako danego okręgu jest klasycznym nierozwiązanym problemem geometrycznym kwadratu koła). W przypadku renderowania kwadratu za pomocą krzywych Bézier dwa punkty kontrolne dla każdej krzywej są takie same, i są one colinear z punktami początkowymi i końcowymi, więc krzywa Béziera jest renderowana jako linia prosta.
Trzecia kolumna tablicy jest dla wartości interpolowanych dla animacji. Strona ustawia czasomierz dla 16 milisekund, a PaintSurface
procedura obsługi jest wywoływana w tym tempie:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
canvas.Translate(info.Width / 2, info.Height / 2);
canvas.Scale(Math.Min(info.Width / 300, info.Height / 300));
// Interpolate
TimeSpan timeSpan = new TimeSpan(DateTime.Now.Ticks);
float t = (float)(timeSpan.TotalSeconds % 3 / 3); // 0 to 1 every 3 seconds
t = (1 + (float)Math.Sin(2 * Math.PI * t)) / 2; // 0 to 1 to 0 sinusoidally
for (int i = 0; i < 13; i++)
{
points[i, 2] = new SKPoint(
(1 - t) * points[i, 0].X + t * points[i, 1].X,
(1 - t) * points[i, 0].Y + t * points[i, 1].Y);
}
// Create the path and draw it
using (SKPath path = new SKPath())
{
path.MoveTo(points[0, 2]);
for (int i = 1; i < 13; i += 3)
{
path.CubicTo(points[i, 2], points[i + 1, 2], points[i + 2, 2]);
}
path.Close();
canvas.DrawPath(path, cyanFill);
canvas.DrawPath(path, blueStroke);
}
}
Punkty są interpolowane na podstawie sinusoidalnie oscylacyjnej wartości t
. Punkty interpolowane są następnie używane do konstruowania serii czterech połączonych krzywych Béziera. Oto uruchomiona animacja:
Taka animacja byłaby niemożliwa bez krzywych, które są wystarczająco elastyczne algorytmicznie, aby można je było renderować jako łuki okrągłe i linie proste.
Strona Bezier Infinity wykorzystuje również możliwość krzywej Béziera, aby przybliżyć okrągły łuk. PaintSurface
Oto procedura obsługi z BezierInfinityPage
klasy:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
using (SKPath path = new SKPath())
{
path.MoveTo(0, 0); // Center
path.CubicTo( 50, -50, 95, -100, 150, -100); // To top of right loop
path.CubicTo( 205, -100, 250, -55, 250, 0); // To far right of right loop
path.CubicTo( 250, 55, 205, 100, 150, 100); // To bottom of right loop
path.CubicTo( 95, 100, 50, 50, 0, 0); // Back to center
path.CubicTo( -50, -50, -95, -100, -150, -100); // To top of left loop
path.CubicTo(-205, -100, -250, -55, -250, 0); // To far left of left loop
path.CubicTo(-250, 55, -205, 100, -150, 100); // To bottom of left loop
path.CubicTo( -95, 100, -50, 50, 0, 0); // Back to center
path.Close();
SKRect pathBounds = path.Bounds;
canvas.Translate(info.Width / 2, info.Height / 2);
canvas.Scale(0.9f * Math.Min(info.Width / pathBounds.Width,
info.Height / pathBounds.Height));
using (SKPaint paint = new SKPaint())
{
paint.Style = SKPaintStyle.Stroke;
paint.Color = SKColors.Blue;
paint.StrokeWidth = 5;
canvas.DrawPath(path, paint);
}
}
}
Dobrym ćwiczeniem może być wykreślenie tych współrzędnych na papierze grafu, aby zobaczyć, jak są one powiązane. Znak nieskończoności jest wyśrodkowany wokół punktu (0, 0), a dwie pętle mają centra (–150, 0) i (150, 0) i promienie 100. W serii CubicTo
poleceń można zobaczyć współrzędne X punktów kontrolnych biorące wartości –95 i –205 (te wartości to –150 plus i minus 55), 205 i 95 (150 plus i minus 55), a także 250 i –250 dla prawej i lewej strony. Jedynym wyjątkiem jest to, że znak nieskończoności przecina się w środku. W takim przypadku punkty kontrolne mają współrzędne z kombinacją 50 i –50, aby wyprostować krzywą w pobliżu środka.
Oto znak nieskończoności:
Jest nieco łagodniejszy w kierunku środka niż znak nieskończoności renderowany przez stronę Nieskończoność Łuku z artykułu Trzy sposoby rysowania łuku.
Krzywa Kwadratowa Béziera
Krzywa Béziera kwadratowego ma tylko jeden punkt kontrolny, a krzywa jest definiowana tylko przez trzy punkty: punkt początkowy, punkt kontrolny i punkt końcowy. Równania parametryczne są bardzo podobne do krzywej sześciennej Béziera, z tą różnicą, że najwyższy wykładnik wynosi 2, więc krzywa jest wielomianem kwadratowym:
x(t) = (1 – t)²x₀ + 2t(1 – t)x₁ + t²x²
y(t) = (1 – t)²y₀ + 2t(1 – t)y₁ + t²y **
Aby dodać krzywą Béziera kwadratową do ścieżki, użyj QuadTo
metody lub QuadTo
przeciążenia z oddzielnymi x
i y
współrzędnymi:
public void QuadTo (SKPoint point1, SKPoint point2)
public void QuadTo (Single x1, Single y1, Single x2, Single y2)
Metody dodają krzywą z bieżącej pozycji do point2
elementu jako point1
punkt kontrolny.
Możesz eksperymentować z krzywymi Bézier kwadratową na stronie Krzywa kwadratowa, która jest bardzo podobna do strony Krzywa Beziera, z tą różnicą, że ma tylko trzy punkty dotykowe. PaintSurface
Oto procedura obsługi w pliku QuadraticCurve.xaml.cs za pomocą kodu:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
// Draw path with quadratic Bezier
using (SKPath path = new SKPath())
{
path.MoveTo(touchPoints[0].Center);
path.QuadTo(touchPoints[1].Center,
touchPoints[2].Center);
canvas.DrawPath(path, strokePaint);
}
// Draw tangent lines
canvas.DrawLine(touchPoints[0].Center.X,
touchPoints[0].Center.Y,
touchPoints[1].Center.X,
touchPoints[1].Center.Y, dottedStrokePaint);
canvas.DrawLine(touchPoints[1].Center.X,
touchPoints[1].Center.Y,
touchPoints[2].Center.X,
touchPoints[2].Center.Y, dottedStrokePaint);
foreach (TouchPoint touchPoint in touchPoints)
{
touchPoint.Paint(canvas);
}
}
W tym miejscu działa:
Kropkowane linie są tangensem do krzywej w punkcie początkowym i końcowym, a następnie spotykają się w punkcie kontrolnym.
Kwadratowe Bézier jest dobre, jeśli potrzebujesz krzywej ogólnego kształtu, ale preferujesz wygodę tylko jednego punktu kontrolnego, a nie dwóch. Kwadratowy Bézier renderuje wydajniej niż jakakolwiek inna krzywa, dlatego jest używana wewnętrznie w Skia do renderowania łuków wielokropowych.
Jednak kształt krzywej Béziera kwadratowego nie jest wielokropek, dlatego do zbliżenia łuku wielokropkowego wymagane jest wiele kwadransowych Béziers. Kwadratowy Bézier jest zamiast segmentu paraboli.
Krzywa Conic Bézier
Conic Bézier krzywa - znana również jako racjonalna krzywa kwadratowa Bézier - jest stosunkowo niedawnym dodatkiem do rodziny krzywych Béziera. Podobnie jak krzywa kwadratowa Béziera, racjonalna krzywa kwadratowa Béziera obejmuje punkt początkowy, punkt końcowy i jeden punkt kontrolny. Ale racjonalna krzywa kwadratowa Béziera również wymaga wartości wagi . Nazywa się to racjonalną ćwiartką, ponieważ formuły parametryczne obejmują proporcje.
Równania parametryczne dla X i Y są współczynnikami, które współdzielą ten sam mianownik. Oto równanie dla mianownika dla t od 0 do 1 i wartość wagi w:
d(t) = (1 – t)² + 2wt(1 – t) + t²
Teoretycznie racjonalizacja może obejmować trzy oddzielne wartości wagi, po jednym dla każdego z trzech terminów, ale można je uprościć tylko do jednej wartości wagi w średnim okresie.
Równania parametryczne współrzędnych X i Y są podobne do równań parametrowych dla kwadratowego Béziera, z tą różnicą, że termin środkowy zawiera również wartość wagi, a wyrażenie jest dzielone przez mianownik:
x(t) = ((1 – t)²x₀ + 2wt(1 – t)x₁ + t²x²)) ÷ d(t)
y(t) = ((1 – t)²y₀ + 2wt(1 – t)y₁ + t²y²y")) ÷ d(t)
Racjonalne krzywe Bézier są również nazywane conicami , ponieważ mogą dokładnie reprezentować segmenty dowolnej sekcji conic — hiperbolas, parabolas, wielokropek i okręgi.
Aby dodać racjonatyczną krzywą Béziera do ścieżki, użyj ConicTo
metody lub ConicTo
przeciążenia z oddzielnymi x
i y
współrzędnymi:
public void ConicTo (SKPoint point1, SKPoint point2, Single weight)
public void ConicTo (Single x1, Single y1, Single x2, Single y2, Single weight)
Zwróć uwagę na końcowy weight
parametr.
Strona Krzywa conic umożliwia eksperymentowanie z tymi krzywymi. Klasa ConicCurvePage
pochodzi z klasy InteractivePage
. Plik ConicCurvePage.xaml tworzy wystąpienie elementu , Slider
aby wybrać wartość wagi z zakresu od –2 do 2. Plik ConicCurvePage.xaml.cs za pomocą kodu tworzy trzy TouchPoint
obiekty, a PaintSurface
program obsługi po prostu renderuje wynikową krzywą z liniami tangentnymi do punktów kontrolnych:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
// Draw path with conic curve
using (SKPath path = new SKPath())
{
path.MoveTo(touchPoints[0].Center);
path.ConicTo(touchPoints[1].Center,
touchPoints[2].Center,
(float)weightSlider.Value);
canvas.DrawPath(path, strokePaint);
}
// Draw tangent lines
canvas.DrawLine(touchPoints[0].Center.X,
touchPoints[0].Center.Y,
touchPoints[1].Center.X,
touchPoints[1].Center.Y, dottedStrokePaint);
canvas.DrawLine(touchPoints[1].Center.X,
touchPoints[1].Center.Y,
touchPoints[2].Center.X,
touchPoints[2].Center.Y, dottedStrokePaint);
foreach (TouchPoint touchPoint in touchPoints)
{
touchPoint.Paint(canvas);
}
}
W tym miejscu działa:
Jak widać, punkt kontrolny wydaje się ściągnąć krzywą w jego kierunku bardziej, gdy waga jest wyższa. Gdy waga wynosi zero, krzywa staje się linią prostą od punktu początkowego do punktu końcowego.
Teoretycznie, ujemne wagi są dozwolone i powodują, że krzywa odchyla się od punktu kontrolnego. Jednak wagi –1 lub poniżej powodują, że mianownik w równaniach parametrowych staje się ujemny dla określonych wartości t. Prawdopodobnie z tego powodu wagi ujemne są ignorowane w ConicTo
metodach. Program Conic Curve pozwala ustawić ujemne wagi, ale jak widać, eksperymentując, wagi ujemne mają taki sam efekt jak waga zero i powodują renderowanie prostej linii.
Bardzo łatwo jest uzyskać punkt kontrolny i wagę, aby użyć ConicTo
metody w celu narysowania łuku okrągłego do (ale nie w tym) półokrągłego. Na poniższym diagramie linie tangensowe od punktów początkowych i końcowych spotykają się w punkcie kontrolnym.
Można użyć trygonometrii, aby określić odległość punktu kontrolnego od środka okręgu: jest to promień okręgu podzielonego przez cosinus połowy kąta α. Aby narysować łuk okrągły między punktami początkowymi i końcowymi, ustaw wagę na tę samą cosinus w połowie kąta. Zwróć uwagę, że jeśli kąt wynosi 180 stopni, to styczne linie nigdy nie spełniają, a waga wynosi zero. Ale w przypadku kątów mniejszych niż 180 stopni matematyka działa dobrze.
Na stronie Conic Circular Arc pokazano to. Plik ConicCircularArc.xaml tworzy wystąpienie Slider
elementu w celu wybrania kąta. Procedura PaintSurface
obsługi w pliku ConicCircularArc.xaml.cs code-behind oblicza punkt kontrolny i wagę:
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
// Translate to center
canvas.Translate(info.Width / 2, info.Height / 2);
// Draw the circle
float radius = Math.Min(info.Width, info.Height) / 4;
canvas.DrawCircle(0, 0, radius, blackStroke);
// Get the value of the Slider
float angle = (float)angleSlider.Value;
// Calculate sin and cosine for half that angle
float sin = (float)Math.Sin(Math.PI * angle / 180 / 2);
float cos = (float)Math.Cos(Math.PI * angle / 180 / 2);
// Find the points and weight
SKPoint point0 = new SKPoint(-radius * sin, radius * cos);
SKPoint point1 = new SKPoint(0, radius / cos);
SKPoint point2 = new SKPoint(radius * sin, radius * cos);
float weight = cos;
// Draw the points
canvas.DrawCircle(point0.X, point0.Y, 10, blackFill);
canvas.DrawCircle(point1.X, point1.Y, 10, blackFill);
canvas.DrawCircle(point2.X, point2.Y, 10, blackFill);
// Draw the tangent lines
canvas.DrawLine(point0.X, point0.Y, point1.X, point1.Y, dottedStroke);
canvas.DrawLine(point2.X, point2.Y, point1.X, point1.Y, dottedStroke);
// Draw the conic
using (SKPath path = new SKPath())
{
path.MoveTo(point0);
path.ConicTo(point1, point2, weight);
canvas.DrawPath(path, redStroke);
}
}
Jak widać, nie ma żadnej różnicy wizualnej ConicTo
między ścieżką wyświetlaną na czerwono a bazowym okręgiem wyświetlanym do celów referencyjnych:
Ale ustaw kąt na 180 stopni, a matematyka kończy się niepowodzeniem.
W tym przypadku niefortunne jest to, że ConicTo
nie obsługuje ujemnych wag, ponieważ teoretycznie (na podstawie równań parametrycznych) okrąg można ukończyć z innym wywołaniem z ConicTo
tymi samymi punktami, ale ujemną wartością wagi. Pozwoliłoby to na utworzenie całego okręgu z zaledwie dwoma ConicTo
krzywymi na podstawie dowolnego kąta między (ale nie w tym) zero stopni i 180 stopni.