Udostępnij za pośrednictwem


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:

Przykładowa krzywa Beziera

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łaniu CubicTo
  • drugi punkt kontrolny: point2 w wywołaniu CubicTo
  • punkt końcowy: point3 w wywołaniu CubicTo

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:

Potrójny zrzut ekranu przedstawiający stronę Krzywa Beziera

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 twartoś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, , pt1pt2i pt3 definiujące krzywą Béziera (pokazaną na czerwono), która przybliża łuk okrągły:

Przybliżenie łuku okrągłego z krzywą Béziera

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:

Zrzut ekranu przedstawiający stronę Bezier Circular Arc

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:

Przybliżenie czwartego koła z krzywą Béziera

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:

Potrójny zrzut ekranu przedstawiający stronę Koła

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:

Potrójny zrzut ekranu przedstawiający stronę Bézier Infinity

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:

Potrójny zrzut ekranu przedstawiający stronę Krzywa kwadratowa

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:

Potrójny zrzut ekranu przedstawiający stronę Krzywa conic

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.

Conic łuk renderowania łuku okrągłego

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:

Potrójny zrzut ekranu przedstawiający stronę Conic Circular Arc

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.