0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Analytic Derivation of Singularities in Cubic Bézier Curves

Posted at

以下の記事の英訳です。
3次ベジェ曲線の特異点の解析的導出

Abstract

Cubic Bézier curves can exhibit singularities such as self-intersections, cusps, and inflection points depending on the arrangement of control points. Although basic properties of Bézier curves are well documented and widely used in practice [1], explicit formulations for singular points are not always readily found in classical references. In this paper, we derive analytic expressions for this. We also provide a unified table for distinguishing each type of singularity and present a C implementation example to facilitate practical use.

Background and Related Work

Cubic Bézier curves are widely used to model smooth curves, but in industrial applications, the presence of self-intersections or cusps can have a significant impact, and it is necessary to guarantee that such undesirable features are not present in input data. In many cases, the input data contains a large number of Bézier curves, requiring a lightweight method for rapid processing.

Computing the singularities of a cubic Bézier curve involves solving polynomials derived from the curve's parametric functions and curvature. Stone and DeRose showed that by classifying singularities and transforming Bézier curves into canonical forms, each type can be mapped in the XY plane [2]. However, converting to canonical form has disadvantages, such as increased computation and issues handling inflection points near $t = 0$, which may require preprocessing to reverse the control point order.
白山 presented analytic expressions for directly computing self-intersection points from control point coordinates [3]. This paper improves on that to reduce numerical errors and derives expressions for inflection points. We then unify the discrimination and derivation of singularities.

Preliminaries

Let the coordinates of an arbitrary point on a cubic Bézier curve be:

\begin{align}
& x=f(t)=a_0 t^3+a_1 t^2+a_2 t+a_3 \\
& y=g(t)=b_0 t^3+b_1 t^2+b_2 t+b_3
\end{align}

where $t\in[0,1]$ and according to the definition of a cubic Bézier curve [4], the coefficients $a_i$ and $b_i$ are defined in terms of control points $B_0, B_1, B_2, B_3$:

\begin{align}
& a_0=-{B_0}_x+3{B_1}_x-3{B_2}_x+{B_3}_x \\
& a_1=3{B_0}_x-6{B_1}_x+3{B_2}_x \\
& a_2=-3{B_0}_x+3{B_1}_x \\
& a_3={B_0}_x \\
& b_0=-{B_0}_y+3{B_1}_y-3{B_2}_y+{B_3}_y \\
& b_1=3{B_0}_y-6{B_1}_y+3{B_2}_y \\
& b_2=-3{B_0}_y+3{B_1}_y \\
& b_3={B_0}_y
\end{align}

Also define:

\begin{align}
& v=a_0 b_1-a_1 b_0 \\
& w=a_0 b_2-a_2 b_0 \\
& u=a_1 b_2-a_2 b_1
\end{align}

Self-Intersection Points and Cusp Point

A self-intersection occurs when there exist two distinct parameters $t_1$ and $t_2$ such that $f(t_1)=f(t_2)$ and $g(t_1)=g(t_2)$.
For $x$ coordinates:

\begin{align}
& a_0 t_1^3+a_1 t_1^2+a_2 t_1+a_3=a_0 t_2^3+a_1 t_2^2+a_2 t_2+a_3 \\
& a_0 (t_1^3-t_2^3)+a_1 (t_1^2-t_2^2)+a_2 (t_1-t_2)=0
\end{align}

Dividing both sides by $(t_1-t_2)$ :

\tag{1} a_0 (t_1^2+t_1 t_2+t_2^2)+a_1 (t_1+t_2)+a_2=0

Similarly for $y$ :

\tag{2} b_0 (t_1^2+t_1 t_2+t_2^2)+b_1 (t_1+t_2)+b_2=0

Transforming $(1)$ and substituting it into $(2)$, and rearranging for $t_1$, we get:

\begin{align}
& \frac{b_0}{a_0} (-a_1 (t_1+t_2)-a_2)+b_1 (t_1+t_2)+b_2=0 \\
& t_1=-t_2-\frac{b_0 a_2-b_2 a_0}{b_0 a_1-b_1 a_0} \\
& t_1=-t_2-\frac{w}{v}
\end{align}

Substituting it into $(1)$ :

a_0 t_2^2 + \frac{a_0 w t_2}{v} + \frac{a_0 w^2}{v^2}-\frac{a_1 w}{v}+a_2=0

Multiplying both sides by $v / a_0$ gives:

\tag{3} v t_2^2 + w t_2 + \frac{w^2}{v} - \frac{a_1 w}{a_0} + \frac{a_2 v}{a_0}=0

When $v=0$, there are no self-intersection or cusp. When $v \ne 0$, by applying the quadratic formula and using the symmetry relationship between $t_1$ and $t_2$, we get:

t_1, t_2 = \frac{-w \pm \sqrt{-3 w^2 + 4 v u}}{2 v}

If both $t_1$ and $t_2$ lie within $[0,1]$, a self-intersection exists.
When $-3 w^2 + 4 v u = 0$, we have $t_1=t_2$ which corresponds to a cusp.

Inflection Points

The curvature of a parametric curve is:

\kappa = \frac{x'y''-y'x''}{\bigl({x'}^2+{y'}^2\bigr)\vphantom{'}^{3/2}}

[5], and inflection points occur when the numerator $\kappa_a=x'y''-y'x''$ is zero.
Substituting derivatives of $x$ and $y$:

\begin{align}
& x'=3a_0t^2+2a_1t+a_2 \\
& x''=6a_0t+2a_1 \\
& y'=3b_0t^2+2b_1t+b_2 \\
& y''=6b_0t+2b_1
\end{align}

into $\kappa_a$ and simplifying yields:

\begin{align}
& \kappa_a = 3(a_1b_0-a_0b_1)t^2+3(a_2b_0-a_0b_2)t+a_2b_1-a_1b_2 = 0\\
& \tag{4} 3vt^2+3wt+u=0
\end{align}

By applying the quadratic formula, we get:

t = \frac{-3w \pm \sqrt{3 (3w^2-4uv)}}{6v}

With special case when $v=0 \land w \ne 0$, $(4)$ becomes a linear equation, we get:

t = -\frac{u}{w}

If the resulting $t$ falls within $[0,1]$, it is an inflection point.
Note that there is no inflection points when $v=0 \land w=0$.

Unified Discrimination

All derived expressions include a common discriminant $D=4uv-3w^2$.
Using $D$, singularities can be classified and derived:

$D<0$ Inflection points $v \ne 0$ $t=\frac{-w \pm \sqrt{-\frac{D}{3}}}{2v}$
$v=0 \land w \ne 0$ $t=-\frac{u}{3w}$
$D=0 \land v \ne 0$ Cusp $t=-\frac{w}{2v}$
$D>0 \land v \ne 0$ Self-intersection $t=\frac{-w \pm \sqrt{D}}{2v}$

Discussion

In high-order polynomials, floating-point errors can significantly affect results. Normalizing control point coordinates can help mitigate these errors. The implementation example included normalizes all coordinates to the range $[0,1]$ before computation.

Conclusion

This paper presents analytic expressions for self-intersection points, cusps, and inflection points for cubic Bézier curves, along with a unified table for classification. A C language implementation example is provided in the appendix.

References

[1] P. Bézier
Mathematical and Practical Possibilities of UNISURF
Computer Aided Geometric Design - Proceedings of a Conference Held at The University of Utah, Salt Lake City, Utah, March 18-21, 1974, pp.127-152
ISBN 0-12-079050-5

[2] Maureen C. Stone, Tony D. DeRose
A Geometric Characterization of Parametric Cubic Curves
ACM Transactions on Graphics, Vol. 8, No. 3, July 1989, pp.147-163

[3] Zenn 白山風露
自己交差する3次ベジェ曲線の交差点を求める
https://zenn.dev/kazatsuyu/scraps/a362d9d1c35da6

[4] Gerald Farin
Curves and Surfaces for Computer Aided Geometric Design: A Practical Guide
Academic Press, ISBN 0-12-249050-9, p.42, 1988

[5] Wikipedia -- Curvature
https://en.wikipedia.org/wiki/Curvature

Appendix

A) Implementation Example

bzsingular.cpp
#include <cmath>
#include <QPointF>
#include <QList>
using namespace std;

#define EZERO   1e-14   // Values closer to zero than this are treated as zero

#define PW3(x)	((x)*(x)*(x))
#define PW2(x)	((x)*(x))

// Compute the coefficients of a cubic Bézier curve
void coefBezier(
  QList<QPointF> &cp,	// IN  Bézier curve control points
  double *a,			// OUT x-coordinate coefficients
  double *b)			// OUT y-coordinate coefficients
{
    a[0]=-cp[0].x()+3*cp[1].x()-3*cp[2].x()+cp[3].x();
    a[1]=3*cp[0].x()-6*cp[1].x()+3*cp[2].x();
    a[2]=-3*cp[0].x()+3*cp[1].x();
    a[3]=cp[0].x();
    b[0]=-cp[0].y()+3*cp[1].y()-3*cp[2].y()+cp[3].y();
    b[1]=3*cp[0].y()-6*cp[1].y()+3*cp[2].y();
    b[2]=-3*cp[0].y()+3*cp[1].y();
    b[3]=cp[0].y();
}

// Compute a point on a cubic parametric curve
QPointF pointBezier(
  double *a,			// IN  x-coordinate coefficients
  double *b,			// IN  y-coordinate coefficients
  double t)				// IN  Parameter
{
    return QPointF(a[0]*PW3(t)+a[1]*PW2(t)+a[2]*t+a[3],
                   b[0]*PW3(t)+b[1]*PW2(t)+b[2]*t+b[3]);
}

// Normalize control point coordinates to the range [0, 1]
void normCP(
  QList<QPointF> &cp,  // IN  Control points of curve
  QList<QPointF> &ncp) // OUT Normalized control points of curve
{
    double xmin=cp[0].x();
    double xmax=cp[0].x();
    double ymin=cp[0].y();
    double ymax=cp[0].y();
    foreach(QPointF p,cp.mid(1)){
        if(xmin>p.x()) xmin=p.x();
        if(xmax<p.x()) xmax=p.x();
        if(ymin>p.y()) ymin=p.y();
        if(ymax<p.y()) ymax=p.y();
    }
    double m=max(xmax-xmin,ymax-ymin);
    ncp.clear();
    foreach(QPointF p,cp)
        ncp<<QPointF((p.x()-xmin)/m,(p.y()-ymin)/m);
}

// Compute singular points of cubic Bézier curve
bool vBezierSingular(   // RETURN true: success
  QList<QPointF> &cp,   // IN  Control points of cubic Bézier curve (4 points)
  QList<QPointF> &opI,	// OUT Inflection points (0 to 2 points)
  QList<QPointF> &opC,	// OUT Cusp point (0 or 1 point)
  QList<QPointF> &opS)	// OUT Self-intersection point (0 or 1 point)
{
    opI.clear();
    opC.clear();
    opS.clear();
    if(cp.size()!=4)
        return false;

    // Compute coefficients of the Bézier curve
    double a[4],b[4];
    coefBezier(cp,a,b);

    // Compute normalized coordinates of control points and coefficients
    QList<QPointF> ncp;
    normCP(cp, ncp);
    double na[4],nb[4];
    coefBezier(ncp,na,nb);

    // Compute singular points
    double v=na[0]*nb[1]-na[1]*nb[0];
    double w=na[0]*nb[2]-na[2]*nb[0];
    double u=na[1]*nb[2]-na[2]*nb[1];
    double D=4*u*v-3*PW2(w);
    if(abs(v)>=EZERO){
        if(D<=-EZERO){
            // Inflection points
            double sqd=sqrt(-D/3);
            double t1=(-w+sqd)/2/v;
            double t2=(-w-sqd)/2/v;
            if(t1>=0&&t1<=1)
                opI<<pointBezier(a,b,t1);
            if(t2>=0&&t2<=1)
                opI<<pointBezier(a,b,t2);
        }else if(D<EZERO){
            // Cusp
            double t=-w/2/v;
            if(t>=0&&t<=1)
                opC<<pointBezier(a,b,t);
        }else{
            // Self-intersection
            double sqd=sqrt(D);
            double t1=(-w+sqd)/2/v;
            double t2=(-w-sqd)/2/v;
            if(t1>=0&&t1<=1&&t2>=0&&t2<=1)
                opS<<pointBezier(a,b,t1);
        }
    }else{
        if(D<=-EZERO&&abs(w)>=EZERO){
            // Inflection point
            double t=-u/3/w;
            if(t>=0&&t<=1)
                opI<<pointBezier(a,b,t);
        }
    }
    return true;
}

B) Qt Sample Application

When you start the program, a cubic Bézier curve and a cusp point (cyan) will be displayed.
Dragging and moving the control points will update the curve and detect singular points. A self-intersection point (red) and inflection points (green) will be displayed when they are existing.

Example of display:
1.png

mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QGraphicsScene>
#include <QGraphicsItem>
#include <QGraphicsSceneMouseEvent>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class Scene : public QGraphicsScene
{
    Q_OBJECT
public:
    explicit Scene(QObject *parent = nullptr){ Q_UNUSED(parent) }

signals:
    void mouseMove(QGraphicsSceneMouseEvent *mouseEvent);
    void mousePress(QGraphicsSceneMouseEvent *mouseEvent);

protected:
    virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent) override
    { emit mouseMove(mouseEvent); }
    virtual void mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) override
    { emit mousePress(mouseEvent); }
};

class MainWindow : public QMainWindow
{
	Q_OBJECT

public:
	MainWindow(QWidget *parent = nullptr);
	~MainWindow();

private slots:
	void mouseMove(QGraphicsSceneMouseEvent *mouseEvent);
	void mousePress(QGraphicsSceneMouseEvent *mouseEvent);

private:
	Scene scene;
    QGraphicsEllipseItem *rc[4];
    QGraphicsPathItem *ci;
    QList<QGraphicsEllipseItem *> ri;
    QList<QPointF> cp;
    int scp;

private:
	Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"

bool vBezierSingular(
    QList<QPointF> &cp,     // IN  Control points of cubic Bézier curve (4 points)
    QList<QPointF> &opI,	// OUT Inflection points (0 to 2 points)
    QList<QPointF> &opC,	// OUT Cusp point (0 or 1 point)
    QList<QPointF> &opS);	// OUT Self-intersection point (0 or 1 point)

MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
	ui->setupUi(this);
    scene.setSceneRect(0,0,ui->graphicsView->width()-2,ui->graphicsView->height()-2);
    ui->graphicsView->setScene(&scene);
    ui->graphicsView->setRenderHint(QPainter::Antialiasing);
    // example of cusp
    cp<<QPointF(120,50);
    cp<<QPointF(120,150);
    cp<<QPointF(220,150);
    cp<<QPointF(20,50);
    for(int i=0;i<4;i++){
        rc[i]=scene.addEllipse(cp[i].x()-2.5,cp[i].y()-2.5,5,5,QPen(Qt::blue));
    }
    {
        QPainterPath curve(cp[0]);
        curve.cubicTo(cp[1],cp[2],cp[3]);
        ci=scene.addPath(curve,QPen(QBrush(Qt::blue),1));
    }
    QList<QPointF> opI,opC,opS;
    vBezierSingular(cp,opI,opC,opS);
    foreach (QPointF p,opI)
        ri<<scene.addEllipse(p.x()-2.5,p.y()-2.5,5,5,QPen(Qt::green),QBrush(Qt::green));
    foreach (QPointF p,opC)
        ri<<scene.addEllipse(p.x()-2.5,p.y()-2.5,5,5,QPen(Qt::cyan),QBrush(Qt::cyan));
    foreach (QPointF p,opS)
        ri<<scene.addEllipse(p.x()-2.5,p.y()-2.5,5,5,QPen(Qt::red),QBrush(Qt::red));
    scp=-1;

    connect(&scene,SIGNAL(mouseMove(QGraphicsSceneMouseEvent*)),this,SLOT(mouseMove(QGraphicsSceneMouseEvent*)));
    connect(&scene,SIGNAL(mousePress(QGraphicsSceneMouseEvent*)),this,SLOT(mousePress(QGraphicsSceneMouseEvent*)));
}

MainWindow::~MainWindow()
{
	delete ui;
}

void MainWindow::mouseMove(QGraphicsSceneMouseEvent *mouseEvent)
{
    if((mouseEvent->buttons()&Qt::LeftButton)&&scp>=0){
        QPointF pos=mouseEvent->scenePos();
        cp[scp]=pos;
        rc[scp]->setRect(pos.x()-2.5,pos.y()-2.5,5,5);
        QPainterPath curve(cp[0]);
        curve.cubicTo(cp[1],cp[2],cp[3]);
        ci->setPath(curve);
        foreach(QGraphicsEllipseItem *gi,ri){
            scene.removeItem(gi);
            delete gi;
        }
        ri.clear();
        QList<QPointF> opI,opC,opS;
        vBezierSingular(cp,opI,opC,opS);
        foreach (QPointF p,opI)
            ri<<scene.addEllipse(p.x()-2.5,p.y()-2.5,5,5,QPen(Qt::green),QBrush(Qt::green));
        foreach (QPointF p,opC)
            ri<<scene.addEllipse(p.x()-2.5,p.y()-2.5,5,5,QPen(Qt::cyan),QBrush(Qt::cyan));
        foreach (QPointF p,opS)
            ri<<scene.addEllipse(p.x()-2.5,p.y()-2.5,5,5,QPen(Qt::red),QBrush(Qt::red));
    }
}

void MainWindow::mousePress(QGraphicsSceneMouseEvent *mouseEvent)
{
	if(mouseEvent->buttons() & Qt::LeftButton){
        QPointF pos = mouseEvent->scenePos();
        scp=-1;
        for(int i=0;i<4;i++){
            if(QLineF(pos,rc[i]->rect().center()).length()<=5)
                scp=i;
        }
    }
}
mainwindow.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>261</width>
    <height>261</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <widget class="QGraphicsView" name="graphicsView">
    <property name="geometry">
     <rect>
      <x>10</x>
      <y>10</y>
      <width>241</width>
      <height>241</height>
     </rect>
    </property>
    <property name="minimumSize">
     <size>
      <width>100</width>
      <height>100</height>
     </size>
    </property>
    <property name="mouseTracking">
     <bool>true</bool>
    </property>
    <property name="alignment">
     <set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
    </property>
   </widget>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?