math - pierre - curvas de bezier graficacion
¿Cómo aproximar mejor un arco geométrico con una curva Bezier? (8)
Al dibujar un arco en 2D, utilizando una aproximación de curva Bezier, ¿cómo se calculan los dos puntos de control dado que tiene un punto central de un círculo, un ángulo inicial y final y un radio?
Esta es una pregunta de 8 años, pero con la que luché recientemente, así que pensé en compartir lo que se me ocurrió. Pasé mucho tiempo tratando de usar la solución (9) de este texto y no pude sacar ningún número sensible de ella hasta que hice algunas búsquedas en Google y aprendí que, aparentemente, había algunos errores tipográficos en las ecuaciones. Según las correcciones enumeradas en esta publicación de blog , dados los puntos de inicio y finalización del arco ([x1, y1] y [x4, y4], respectivamente) y el centro del círculo ([xc, yc]), se puede derive los puntos de control para una curva de bezier cúbica ([x2, y2] y [x3, y3]) de la siguiente manera:
ax = x1 – xc
ay = y1 – yc
bx = x4 – xc
by = y4 – yc
q1 = ax * ax + ay * ay
q2 = q1 + ax * bx + ay * by
k2 = 4/3 * (√(2 * q1 * q2) – q2) / (ax * by – ay * bx)
x2 = xc + ax – k2 * ay
y2 = yc + ay + k2 * ax
x3 = xc + bx + k2 * by
y3 = yc + by – k2 * bx
Espero que esto ayude a alguien que no sea yo!
Estoy respondiendo a esta vieja pregunta (que debería pertenecer a Matemáticas, por lo que escribir las fórmulas va a ser horrible) con algunas demostraciones.
Suponga que P0 y P3 son su punto inicial y final de su arco, P1 y P2 los puntos de control de la curva de Bézier, yx es la medida del ángulo dividido por dos. Supongamos que x es menor que pi / 2.
Sea PM el punto medio del segmento P0P3 y PH el punto medio del arco. Para aproximar el arco, queremos que la curva de Bézier comience en P0 , pase a través de PH , finalice en P3 y sea tangente al arco en P0 y P3 .
(Haga clic en "Ejecutar fragmento de código" para mostrar la figura. Las maldiciones para imgur aún no admiten SVG).
<svg xmlns="http://www.w3.org/2000/svg" viewBox="10 20 80 80">
<style>text{font-size:40%;font-style:italic;text-anchor:middle}tspan{font-size:50%;font-style:normal}</style>
<rect x="10" y="20" width="80" height="80" fill="none" stroke="gray"></rect>
<path stroke="gray" stroke-dasharray="3,2" fill="none" d="M25,30 62.6,31.62 80,65 22.19,95.13 25,30 80,65 M22.19,95.13 62.6,31.62"></path>
<path stroke="black" fill="none" d="M25,30A65.19 65.19 0 0 1 80,65"></path>
<circle r="1" fill="red" cx="25" cy="30"></circle>
<circle r="1" fill="green" cx="80" cy="65"></circle>
<circle r="1" fill="magenta" cx="22.19" cy="95.13"></circle>
<circle r="1" fill="darkgreen" cx="52.5" cy="47.5"></circle>
<circle r="1" fill="yellow" cx="57.19" cy="40.13"></circle>
<circle r="1" fill="maroon" cx="62.6" cy="31.62"></circle>
<circle r="1" fill="orange" cx="48.27" cy="31"></circle>
<circle r="1" fill="teal" cx="69.24" cy="44.35"></circle>
<text x="25" y="28">P<tspan>0</tspan></text>
<text x="48.27" y="29">P<tspan>1</tspan></text>
<text x="71.24" y="42.35">P<tspan>2</tspan></text>
<text x="83" y="63">P<tspan>3</tspan></text>
<text x="62.6" y="29.62">P<tspan>E</tspan></text>
<text x="59.19" y="47.13">P<tspan>H</tspan></text>
<text x="54.5" y="54.5">P<tspan>M</tspan></text>
</svg>
Sea PE la intersección de las líneas tangentes al arco en P0 y P3 . Para que la curva sea tangente al arco, P1 debe estar en el segmento P0PE , y P2 debe estar en P3PE . Sea k la relación P0P1 / P0PE (también igual a P3P2 / P3PE ):
P1 = (1 - k ) P0 + k PE
P2 = (1 - k ) P3 + k PE
También tenemos lo siguiente (hacer algunas proporciones):
PM = ( P0 + P3 ) / 2
PH = PM / cos ( x ) = PM sec ( x ) = ( P0 + P3 ) sec ( x ) / 2
PE = PH / cos ( x ) = PM seg ( x ) ^ 2 = ( P0 + P3 ) seg ( x ) ^ 2/2
Para simplificar nuestros cálculos, he considerado que todos los puntos vectoriales están basados en el centro, pero al final no importará.
La curva de Bézier genérica de 4 puntos viene dada por la fórmula
C ( t ) = t ^ 3 P3 + 3 (1 - t ) t ^ 2 P2 + 3 (1 - t ) ^ 2 t P1 + (1 - t ) ^ 3 P0
Debemos tener C (1/2) = PH , entonces
C (1/2) = ( P0 + 3 P1 + 3 P2 + P3 ) / 8
= (( P0 + P3 ) + 3 (1 - k ) P0 + 3 k PE + 3 (1 - k ) P3 + 3 k PE ) / 8
= (( P0 + P3 ) + 3 (1 - k ) ( P0 + P3 ) + 6 k PE ) / 8
= ( P0 + P3 ) (1 + 3 (1 - k ) + 3 k seg ( x ) ^ 2) / 8
Entonces, esta es nuestra ecuación (multiplicada por 8) para encontrar k :
8 C (1/2) = 8 PH
=> ( P0 + P3 ) (4 - 3 k + 3 k seg ( x ) ^ 2) = 4 ( P0 + P3 ) seg ( x )
Vamos a deshacernos de los vectores ( P0 + P3 ), y obtenemos:
4 - 3 k + 3 k seg ( x ) ^ 2 = 4 seg ( x )
=> 3 k (sec ( x ) ^ 2 - 1) = 4 (sec ( x ) - 1)
=> k = 4/3 (sec ( x ) + 1)
Ahora ya sabes dónde colocar los puntos de control. ¡Hurra!
Si tienes x = pi / 4, obtendrás k = 0.552 ... Es posible que hayas visto este valor.
Cuando se trata de arcos elípticos, todo lo que tiene que hacer es escalar las coordenadas de los puntos en consecuencia.
Si tienes que lidiar con ángulos más grandes, sugiero dividirlos en más curvas. Eso es realmente lo que hacen algunos softwares cuando dibujan arcos, ya que calcular una curva de Bézier es a veces más rápido que usar senos y cosenos.
Hay un código de Mathematica en Wolfram MathWorld: Aproximación de la curva de un arco de Bézier , que te ayudará a comenzar.
Ver también:
He tenido éxito con esta solución general para cualquier arco elíptico como una curva Bezier cúbica. Incluso incluye los ángulos de inicio y final en la formulación, por lo que no se necesita rotación adicional (lo que sería un problema para una elipse no circular).
Me encontré con este problema recientemente. Compilé una solución de los artículos mencionados aquí en forma de módulo.
Acepta el ángulo de inicio, el ángulo final, el centro y el radio como entrada.
Se aproxima bastante bien a los arcos pequeños (<= PI / 2). Si necesita aproximar los arcos de algo de PI / 2 a 2 * PI, siempre puede dividirlos en partes <PI / 2, calcular las curvas correspondientes y unirlas después.
Esta solución es agnóstica para el orden de los ángulos de inicio y finalización: siempre selecciona el arco menor.
Como resultado, obtiene los cuatro puntos que necesita para definir una curva bezier cúbica en coordenadas absolutas.
Creo que esto se explica mejor en código y comentarios:
''use strict'';
module.exports = function (angleStart, angleEnd, center, radius) {
// assuming angleStart and angleEnd are in degrees
const angleStartRadians = angleStart * Math.PI / 180;
const angleEndRadians = angleEnd * Math.PI / 180;
// Finding the coordinates of the control points in a simplified case where the center of the circle is at [0,0]
const relControlPoints = getRelativeControlPoints(angleStartRadians, angleEndRadians, radius);
return {
pointStart: getPointAtAngle(angleStartRadians, center, radius),
pointEnd: getPointAtAngle(angleEndRadians, center, radius),
// To get the absolute control point coordinates we just translate by the center coordinates
controlPoint1: {
x: center.x + relControlPoints[0].x,
y: center.y + relControlPoints[0].y
},
controlPoint2: {
x: center.x + relControlPoints[1].x,
y: center.y + relControlPoints[1].y
}
};
};
function getRelativeControlPoints(angleStart, angleEnd, radius) {
// factor is the commonly reffered parameter K in the articles about arc to cubic bezier approximation
const factor = getApproximationFactor(angleStart, angleEnd);
// Distance from [0, 0] to each of the control points. Basically this is the hypotenuse of the triangle [0,0], a control point and the projection of the point on Ox
const distToCtrPoint = Math.sqrt(radius * radius * (1 + factor * factor));
// Angle between the hypotenuse and Ox for control point 1.
const angle1 = angleStart + Math.atan(factor);
// Angle between the hypotenuse and Ox for control point 2.
const angle2 = angleEnd - Math.atan(factor);
return [
{
x: Math.cos(angle1) * distToCtrPoint,
y: Math.sin(angle1) * distToCtrPoint
},
{
x: Math.cos(angle2) * distToCtrPoint,
y: Math.sin(angle2) * distToCtrPoint
}
];
}
function getPointAtAngle(angle, center, radius) {
return {
x: center.x + radius * Math.cos(angle),
y: center.y + radius * Math.sin(angle)
};
}
// Calculating K as done in https://pomax.github.io/bezierinfo/#circles_cubic
function getApproximationFactor(angleStart, angleEnd) {
let arc = angleEnd - angleStart;
// Always choose the smaller arc
if (Math.abs(arc) > Math.PI) {
arc -= Math.PI * 2;
arc %= Math.PI * 2;
}
return (4 / 3) * Math.tan(arc / 4);
}
Raphael 2.1.0 tiene soporte para Arc-> Cubic (path2curve-function), y después de corregir un error en la normalización de las rutas S y T, parece funcionar ahora. Actualicé * el Generador de rutas aleatorias * para que solo genere arcos, por lo que es fácil probar todas las combinaciones de rutas posibles:
Prueba y si falla algún camino, estaré encantado de recibir un informe.
EDIT: Acabo de darse cuenta de que este es el hilo de 3 años ...
Se proporciona una buena explicación en "Aproximación de una" curva de curva cúbica por arcos circulares "
En pocas palabras: con las curvas de Bézier se puede lograr un error mínimo de 1.96 × 10 ^ -4, lo cual está muy bien para la mayoría de las aplicaciones.
Para un arco de cuadrante positivo, use los siguientes puntos:
p0 = [0, radius]
p1 = [radius * K, radius]
p2 = [radius, radius * K]
p3 = [radius, 0]
donde K es un llamado "número mágico", que es un número no racional. Puede ser aproximado de la siguiente manera:
K = 0.5522847498