Três tipos de curvas de bézier
Explore como usar SkiaSharp para renderizar curvas cúbicas, quadráticas e conic Bézier
A curva de Bézier é nomeada em homenagem a Pierre Bézier (1910 – 1999), engenheiro francês da empresa automotiva Renault, que usou a curva para o design assistido por computador de carrocerias.
As curvas de Bézier são conhecidas por serem adequadas ao design interativo: elas são bem comportadas — em outras palavras, não há singularidades que fazem com que a curva se torne infinita ou desordenada — e geralmente são esteticamente agradáveis:
As estruturas de caracteres de fontes baseadas em computador geralmente são definidas com curvas de Bézier.
O artigo da Wikipédia sobre a curva de Bézier contém algumas informações úteis em segundo plano. O termo curva de Bézier realmente se refere a uma família de curvas semelhantes. O SkiaSharp dá suporte a três tipos de curvas de Bézier, chamadas de cúbica, quadrática e conic. O conic também é conhecido como quadrático racional.
A curva de Bézier cúbica
O cúbico é o tipo de curva de Bézier que a maioria dos desenvolvedores pensa quando o assunto das curvas de Bézier aparece.
Você pode adicionar uma curva de Bézier cúbica a um SKPath
objeto usando o CubicTo
método com três SKPoint
parâmetros ou a CubicTo
sobrecarga com parâmetros e y
separadosx
:
public void CubicTo (SKPoint point1, SKPoint point2, SKPoint point3)
public void CubicTo (Single x1, Single y1, Single x2, Single y2, Single x3, Single y3)
A curva começa no ponto atual do contorno. A curva completa de Bézier cúbica é definida por quatro pontos:
- ponto inicial: ponto atual no contorno ou (0, 0) se
MoveTo
não tiver sido chamado - primeiro ponto de controle:
point1
naCubicTo
chamada - segundo ponto de controle:
point2
naCubicTo
chamada - ponto de extremidade:
point3
naCubicTo
chamada
A curva resultante começa no ponto inicial e termina no ponto final. A curva geralmente não passa pelos dois pontos de controle; em vez disso, os pontos de controle funcionam muito como ímãs para puxar a curva em direção a eles.
A melhor maneira de ter uma noção da curva cúbica de Bézier é experimentando. Essa é a finalidade da página Curva de Bézier , que deriva de InteractivePage
. O arquivo BezierCurvePage.xaml cria uma instância do SKCanvasView
e de um TouchEffect
. O arquivo code-behind BezierCurvePage.xaml.cs cria quatro TouchPoint
objetos em seu construtor. O PaintSurface
manipulador de eventos cria um SKPath
para renderizar uma curva de Bézier com base nos quatro TouchPoint
objetos e também desenha linhas tangentes pontilhadas dos pontos de controle para os pontos finais:
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);
}
}
Aqui ele está em execução:
Matematicamente, a curva é um polinomial cúbico. A curva cruza uma linha reta em no máximo três pontos. No ponto inicial, a curva é sempre tangente para e, na mesma direção que, uma linha reta do ponto inicial até o primeiro ponto de controle. No ponto final, a curva é sempre tangente e, na mesma direção que, uma linha reta do segundo ponto de controle até o ponto final.
A curva cúbica de Bézier é sempre limitada por um quadrilátero convexo que conecta os quatro pontos. Isso é chamado de casco convexo. Se os pontos de controle estiverem na linha reta entre o ponto inicial e final, a curva de Bézier será renderizada como uma linha reta. Mas a curva também pode se cruzar, como demonstra a terceira captura de tela.
Um contorno de caminho pode conter várias curvas cúbicas de Bézier conectadas, mas a conexão entre duas curvas cúbicas de Bézier será suave somente se os três pontos a seguir forem colineares (ou seja, estar em uma linha reta):
- o segundo ponto de controle da primeira curva
- o ponto final da primeira curva, que também é o ponto inicial da segunda curva
- o primeiro ponto de controle da segunda curva
No próximo artigo sobre dados de caminho SVG, você descobrirá uma instalação para facilitar a definição de curvas de Bézier conectadas suaves.
Às vezes, é útil conhecer as equações paramétricas subjacentes que renderizam uma curva de Bézier cúbica. Para t variando de 0 a 1, as equações paramétricas são as seguintes:
x(t) = (1 – t)Consigox₀ + 3t(1 – t)²x₁ + 3t²(1 – t)x₂ + t consigox₃
y(t) = (1 – t)Tby₀ + 3t(1 – t)²y₁ + 3t²(1 – t)y₂ + t consigoy₃
O expoente mais alto de 3 confirma que são polinômios cúbicos. É fácil verificar se, quando t
é igual a 0, o ponto é (x₀, y₀), que é o ponto inicial e, quando t
é igual a 1, o ponto é (x₃, y₃), que é o ponto final. Próximo ao ponto inicial (para valores baixos de t
), o primeiro ponto de controle (x₁, y₁) tem um efeito forte e próximo ao ponto final (valores altos de 't') o segundo ponto de controle (x₂, y₂) tem um efeito forte.
Aproximação da curva de Bézier para arcos circulares
Às vezes, é conveniente usar uma curva de Bézier para renderizar um arco circular. Uma curva de Bézier cúbica pode aproximar um arco circular muito bem até um quarto de círculo, de modo que quatro curvas de Bézier conectadas podem definir um círculo inteiro. Essa aproximação é discutida em dois artigos publicados há mais de 25 anos:
Tor Dokken, et al, "Boa aproximação de círculos por Curvature-Continuous curvas de Bézier", Computer Aided Geometric Design 7 (1990), 33-41.
Michael Goldapp, "Aproximação de Arcos Circulares por Polinômios Cúbicos", Design Geométrico Auxiliado por Computador 8 (1991), 227-238.
O diagrama a seguir mostra quatro pontos rotulados pto
como , pt1
pt2
, e pt3
definindo uma curva de Bézier (mostrada em vermelho) que aproxima um arco circular:
As linhas dos pontos inicial e final para os pontos de controle são tangentes para o círculo e para a curva de Bézier, e têm um comprimento de L. O primeiro artigo citado acima indica que a curva de Bézier melhor se aproxima de um arco circular quando esse comprimento L é calculado desta forma:
L = 4 × tan(α / 4) / 3
A ilustração mostra um ângulo de 45 graus, portanto L é igual a 0,265. No código, esse valor seria multiplicado pelo raio desejado do círculo.
A página Arco Circular de Bézier permite que você experimente definir uma curva de Bézier para aproximar um arco circular para ângulos que variam até 180 graus. O arquivo BezierCircularArcPage.xaml cria uma instância do SKCanvasView
e um Slider
para selecionar o ângulo. O PaintSurface
manipulador de eventos no arquivo code-behind BezierCircularArgPage.xaml.cs usa uma transformação para definir o ponto (0, 0) para o centro da tela. Ele desenha um círculo centralizado nesse ponto para comparação e calcula os dois pontos de controle para a curva de 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);
}
Os pontos inicial e final (point0
e point3
) são calculados com base nas equações paramétricas normais para o círculo. Como o círculo é centralizado em (0, 0), esses pontos também podem ser tratados como vetores radiais do centro do círculo para a circunferência. Os pontos de controle estão em linhas que são tangentes ao círculo, portanto, eles estão em ângulos retos para esses vetores radiais. Um vetor em um ângulo reto para outro é simplesmente o vetor original com as coordenadas X e Y trocadas e uma delas tornada negativa.
Este é o programa em execução com ângulos diferentes:
Examine atentamente a terceira captura de tela, e você verá que a curva de Bézier se desvia notavelmente de um semicírculo quando o ângulo é de 180 graus, mas a tela do iOS mostra que parece caber um quarto de círculo muito bem quando o ângulo é de 90 graus.
Calcular as coordenadas dos dois pontos de controle é muito fácil quando o círculo de trimestre é orientado da seguinte maneira:
Se o raio do círculo for 100, L será 55, e esse é um número fácil de lembrar.
A página Equaring the Circle anima uma figura entre um círculo e um quadrado. O círculo é aproximado por quatro curvas de Bézier cujas coordenadas são mostradas na primeira coluna dessa definição de matriz na SquaringTheCirclePage
classe :
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() }
};
...
}
A segunda coluna contém as coordenadas de quatro curvas de Bézier que definem um quadrado cuja área é aproximadamente a mesma que a área do círculo. (Desenhar um quadrado com a área exata como um determinado círculo é o problema geométrico insolúvel clássico de esquartejar o círculo.) Para renderizar um quadrado com curvas de Bézier, os dois pontos de controle para cada curva são os mesmos, e eles são colinear com os pontos inicial e final, de modo que a curva de Bézier é renderizada como uma linha reta.
A terceira coluna da matriz destina-se a valores interpolados para uma animação. A página define um temporizador para 16 milissegundos e o PaintSurface
manipulador é chamado a essa taxa:
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);
}
}
Os pontos são interpolados com base em um valor oscilante sinusoidal de t
. Os pontos interpolados são então usados para construir uma série de quatro curvas de Bézier conectadas. Esta é a animação em execução:
Tal animação seria impossível sem curvas que são algorítmicas flexíveis o suficiente para serem renderizadas como arcos circulares e linhas retas.
A página Infinito de Bézier também aproveita a capacidade de uma curva de Bézier para aproximar um arco circular. Este é o PaintSurface
manipulador da BezierInfinityPage
classe :
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);
}
}
}
Pode ser um bom exercício plotar essas coordenadas em papel gráfico para ver como elas estão relacionadas. O sinal de infinito é centralizado em torno do ponto (0, 0) e os dois loops têm centros de (-150, 0) e (150, 0) e raios de 100. Na série de CubicTo
comandos, você pode ver coordenadas X de pontos de controle assumindo valores de –95 e –205 (esses valores são –150 mais e menos 55), 205 e 95 (150 mais e menos 55), bem como 250 e –250 para os lados direito e esquerdo. A única exceção é quando o sinal de infinito se cruza no centro. Nesse caso, os pontos de controle têm coordenadas com uma combinação de 50 e –50 para endireitar a curva perto do centro.
Este é o sinal de infinito:
É um pouco mais suave para o centro do que o sinal infinito renderizado pela página Arc Infinity do artigo Três Maneiras de Desenhar um Arco .
A curva quadrática de Bézier
A curva quadrática de Bézier tem apenas um ponto de controle e a curva é definida por apenas três pontos: o ponto inicial, o ponto de controle e o ponto final. As equações paramétricas são muito semelhantes à curva de Bézier cúbica, exceto que o expoente mais alto é 2, portanto, a curva é um polinomial quadrático:
x(t) = (1 – t)²x₀ + 2t(1 – t)x₁ + t²x₂
y(t) = (1 – t)²y₀ + 2t(1 – t)y₁ + t²y₂
Para adicionar uma curva quadrática de Bézier a um caminho, use o QuadTo
método ou a QuadTo
sobrecarga com coordenadas e y
separadasx
:
public void QuadTo (SKPoint point1, SKPoint point2)
public void QuadTo (Single x1, Single y1, Single x2, Single y2)
Os métodos adicionam uma curva da posição atual para point2
com point1
como o ponto de controle.
Você pode experimentar curvas quadráticas de Bézier com a página Curva Quadrática , que é muito semelhante à página Curva de Bezier , exceto que ela tem apenas três pontos de toque. Aqui está o PaintSurface
manipulador no arquivo code-behind QuadraticCurve.xaml.cs :
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);
}
}
E aqui está em execução:
As linhas pontilhadas são tangentes à curva no ponto inicial e no ponto de extremidade e se encontram no ponto de controle.
O Bézier quadrático é bom se você precisar de uma curva de uma forma geral, mas você prefere a conveniência de apenas um ponto de controle em vez de dois. O quadrático Bézier renderiza de forma mais eficiente do que qualquer outra curva, razão pela qual é usado internamente em Skia para renderizar arcos elípticos.
No entanto, a forma de uma curva quadrática de Bézier não é elíptica, e é por isso que vários Béziers quadráticos são necessários para aproximar um arco elíptico. O quadrático Bézier é, em vez disso, um segmento de uma parabola.
A Curva conic Bézier
A curva conic Bézier - também conhecida como a curva quadrática racional de Bézier - é uma adição relativamente recente à família de curvas de Bézier. Como a curva quadrática de Bézier, a curva quadrática racional de Bézier envolve um ponto de partida, um ponto de extremidade e um ponto de controle. Mas a curva quadrática racional de Bézier também requer um valor de peso . É chamado de quadrático racional porque as fórmulas paramétricas envolvem proporções.
As equações paramétricas para X e Y são proporções que compartilham o mesmo denominador. Aqui está a equação do denominador para t variando de 0 a 1 e um valor de peso de w:
d(t) = (1 – t)² + 2wt(1 – t) + t²
Em teoria, um quadrático racional pode envolver três valores de peso separados, um para cada um dos três termos, mas eles podem ser simplificados para apenas um valor de peso no termo médio.
As equações paramétricas para as coordenadas X e Y são semelhantes às equações paramétricas do Bézier quadrático, exceto que o termo médio também inclui o valor de peso e a expressão é dividida pelo denominador:
x(t) = ((1 – t)²x₀ + 2wt(1 – t)x₁ + t²x₂)) ÷ d(t)
y(t) = ((1 – t)²y₀ + 2wt(1 – t)y₁ + t²y₂)) ÷ d(t)
As curvas Bézier quadráticas racionais também são chamadas de conics porque podem representar exatamente segmentos de qualquer seção conic — hiperbolas, parabolas, reticências e círculos.
Para adicionar uma curva Bézier quadrática racional a um caminho, use o ConicTo
método ou a ConicTo
sobrecarga com coordenadas e y
separadasx
:
public void ConicTo (SKPoint point1, SKPoint point2, Single weight)
public void ConicTo (Single x1, Single y1, Single x2, Single y2, Single weight)
Observe o parâmetro final weight
.
A página Curva Conic permite que você experimente essas curvas. A classe ConicCurvePage
deriva de InteractivePage
. O arquivo ConicCurvePage.xaml cria uma instância de um Slider
para selecionar um valor de peso entre –2 e 2. O arquivo code-behind ConicCurvePage.xaml.cs cria três TouchPoint
objetos e o PaintSurface
manipulador simplesmente renderiza a curva resultante com as linhas tangentes para os pontos de controle:
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);
}
}
Aqui ele está em execução:
Como você pode ver, o ponto de controle parece puxar a curva em direção a ela mais quando o peso é maior. Quando o peso é zero, a curva se torna uma linha reta do ponto inicial até o ponto final.
Em teoria, pesos negativos são permitidos e fazem com que a curva se curve para longe do ponto de controle. No entanto, pesos de –1 ou inferior fazem com que o denominador nas equações paramétricas se torne negativo para valores específicos de t. Provavelmente, por esse motivo, os pesos negativos são ignorados nos ConicTo
métodos. O programa Curva Conic permite definir pesos negativos, mas como você pode ver experimentando, pesos negativos têm o mesmo efeito que um peso de zero e fazem com que uma linha reta seja renderizada.
É muito fácil derivar o ponto de controle e o peso para usar o ConicTo
método para desenhar um arco circular até (mas não incluindo) um semicírculo. No diagrama a seguir, as linhas tangentes dos pontos inicial e final se encontram no ponto de controle.
Você pode usar trigonometria para determinar a distância do ponto de controle do centro do círculo: é o raio do círculo dividido pelo cosseno de metade do ângulo α. Para desenhar um arco circular entre os pontos inicial e final, defina o peso como o mesmo cosseno de metade do ângulo. Observe que, se o ângulo for de 180 graus, as linhas tangentes nunca se encontrarão e o peso será zero. Mas para ângulos inferiores a 180 graus, a matemática funciona bem.
A página Arco Circular Conic demonstra isso. O arquivo ConicCircularArc.xaml cria uma instância de um Slider
para selecionar o ângulo. O PaintSurface
manipulador no arquivo code-behind ConicCircularArc.xaml.cs calcula o ponto de controle e o peso:
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);
}
}
Como você pode ver, não há diferença visual entre o ConicTo
caminho mostrado em vermelho e o círculo subjacente exibido para referência:
Mas defina o ângulo como 180 graus, e a matemática falhará.
É lamentável, nesse caso, que ConicTo
não dê suporte a pesos negativos, pois, em teoria (com base nas equações paramétricas), o círculo pode ser concluído com outra chamada para ConicTo
com os mesmos pontos, mas um valor negativo do peso. Isso permitiria criar um círculo inteiro com apenas duas ConicTo
curvas com base em qualquer ângulo entre (mas não incluindo) zero graus e 180 graus.