MISCELLANEOUS TECHNICAL ARTICLES BY Dr A R COLLINS

3D Helix Drawing using Bézier Curves

Properties of the Helix

The paper describes a method of drawing a good approximation to a cylindrical helix using cubic Bézier curves. A helix defines a path in three dimensional space, all parts of the helix are self similar, the curve may be fabricated from a short arc segment copies of which can be rotated, translated and appended to extend the desired length of the helix. Hence only one cubic Bézier curve that forms a good approximation to a sector of the helix, needs to be described for the whole helix to be approximated with equal accuracy.

The advantage of using the Bézier curve approximation is that 3D Bézier curves may be projected onto a 2D surface by just projecting their control points. The 2D Bézier curve projection will still match the 2D projection of the helix. The computational effort required to do draw a projected helix is reduced to projecting just a few control points. The advantage of restricting the approximation to cubic Bézier curves rather than higher order Bézier curves, is that the HTML canvas element has native support for drawing 2D cubic Bézier curves given the coordinates of their control points.

The derivation of the approximation is described and the JavaScript code examples are presented. All the example 3D coordinate transformations and projection to the 2D canvas are handled by the Cango3D graphics library.

Helix definition

A helix is a type of smooth curve in three-dimensional space. It has the property that the tangent line at any point makes a constant angle with a fixed line called the axis. Helices can be either right-handed or left-handed. With the line of sight along the helix's axis, if a clockwise screwing motion moves the helix away from the observer, then it is called a right-handed helix; if towards the observer, then it is a left-handed helix. A right-handed helix cannot be turned or flipped to look like a left-handed one. Most machine screw threads are right-handed helices.

The pitch \(p\), of a helix is the vertical separation of points on the helix as it moves through 2π, a full circle in the x,y plane.

A cylindrical helix may be described by the following parametric equations:

$$ \begin{aligned} x &= r\cos(t)\\ y &= r\sin(t)\\ z &= pt, \quad\quad for \; t\; in \; [0,2\pi), \end{aligned} $$

where the radius of the helix is \(r\) and the pitch \(= 2\pi p\).

As the parameter \(t\) increases, the point \(( x(t), y(t), z(t) )\) traces a right-handed helix about the z-axis (in a right-handed coordinate system). Except for rotations, translations, and changes of scale, all right-handed helices are equivalent. The equivalent left-handed helix can be constructed by negating any one of the x, y or z components.

Method of fitting Bézier curves to a circular helix

The method used to fit Bézier curves to the helix is based on the fact that all parts of the helix are self similar, any section of the curve which sweeps out an angle of \( \phi \) may be rotated by \( \phi \) and translated by \( \phi p \) and will lie along the curve of the helix. Hence only one cubic Bézier curve that forms a good approximation to a sector of the helix needs to be described.

Since the projection of the circular helix onto the XY plane is always a circle, the x,y coordinates of the Bézier approximation will be those that approximate a 2D circle. The z coordinate of the nodes can then be raised to match the linear increase in the helical curve's z coordinate.

Fitting a cubic Bézier curve to a circular arc

One of the most accurate methods for fitting a cubic Bézier to a circular arc is given by Riškus [1] and summarised at [2]. This method calculates the Bézier nodes as follows:

Let the arc start at point \(b0\), with control points \(b1\) and \(b2\) and end at point \(b3\), placed in the XY plane at equal distances above and below the x-axis, spanning an arc of angle \(\phi =2\alpha\).

$$ \begin{aligned} b3_x &= r\cos(\alpha) \\ b3_y &= r\sin(\alpha)\\ b0_x &= b3_x \\ b0_y &= -b3_y \end{aligned} $$

The control points may be written as: [2]

$$ \begin{aligned} b2_x &= {\frac {4r - b3_x}{3}}\\ b2_y &= {\frac {(r- b3_x)(3r- b3_x)}{3 b3_y}}\\ b1_x &= b2_x \\ b1_y &= -b2_y \end{aligned} $$

The Bézier nodes must now be raised in the Z dimension to track the helix. The \(z\) coordinate of the start point \(b0\) and end point \(b3\) are set by the value helix pitch, and by symmetry \(b1_z = -b2_z\), hence

$$ \begin{aligned} b3_z &= \alpha p\\ b2_z &= b\\ b1_z &= -b\\ b0_z &= -b3_z \end{aligned} $$

Fitting a cubic Bézier curve to a circular helical arc

It remains to calculate the free parameter \(b\). The method follows that described by Juhasz [3] where \(b\) is constrained so that the osculating plane of the Bézier curve at end of the initial segment be common with the osculating plane at the beginning of the next Bézier segment. An osculating plane is defined by three points on the curve in the limit as the points converge. The osculating plane will contain the normal to the curve at that point. This will achieve second order (G2) continuity, ie. the following G2 requirements will be met:

  • The end point of the first segment and the and start point of the next segment are coincident (G0).
  • The tangents to the curves at the junction are equal (G1).
  • The curvature of the segments at the junction are equal (G2).

Define the initial segment of the Bézier curve to have nodes \(b0,b1,b2,b3\) and the succeeding segment to have nodes \(b0',b1',b2',b3'\). These two curves will have common osculating planes at the junction point \(b3 = b0'\) if \(b1,b2,b3(=b0'),b1',b2'\) are co-planar. Fig 3. shows the lines \(b1,b2\) and \(b2',b1'\) extrapolated, if they are coplanar they will meet at point \(m\) which will be normal to the curve at the junction point. From the symmetry of the points and the fact that \(b2_x = m_x\) the lines will be coplanar if the Z component of \(b2\) and \(m\) increase in the same ratio as the Y components, ie:

$$\frac{\alpha p}{b2_z} = \frac{m_y}{b2_y}$$

since \(b2_z= b\)

$$ \begin{aligned} m_y &= b2_x\tan(\alpha) \\ &= \frac{4r - r \cos(\alpha) }{3}\tan(\alpha) \\ \\ b &= \frac{b2_y \alpha p}{m_y}\\ &= \frac{(r - r \cos(\alpha) )(3r - r\cos(\alpha) ) \alpha p}{r\sin(\alpha) (4r - r\cos(\alpha) ) \tan(\alpha) } \end{aligned} $$

Hence the 3D nodes of the cubic Bézier approximation to the helical arc are given by:

$$ \begin{aligned} b0_z &= -\alpha p\\ b1_z &= -\frac{(r - r\cos(\alpha) )(3r - r\cos(\alpha) ) \alpha p}{r\sin(\alpha) (4r - r\cos(\alpha) ) \tan(\alpha) }\\ b2_z &= \frac{(r - r\cos(\alpha) )(3r - r\cos(\alpha) ) \alpha p}{r\sin(\alpha) (4r - r\cos(\alpha) ) \tan(\alpha) }\\ b3_z &= \alpha p \end{aligned} $$

JavaScript code to draw a helical arc

createHelicalArc = function(r, pitch, incAngle)
{
  // References:
  // 1. A. Riskus, "Approximation of a Cubic Bezier Curve by Circular Arcs and Vice Versa"
  // 2. Imre Juhasz, "Approximating the helix with rational cubic Bezier curves"

  const alpha = incAngle*Math.PI/360.0,  // half included angle
        p = pitch/(2*Math.PI),    // helix height per radian
        ax = r*Math.cos(alpha),
        ay = r*Math.sin(alpha),
        b = p*alpha*(r - ax)*(3*r - ax)/(ay*(4*r - ax)*Math.tan(alpha)),
        b0 = {x:ax, y:-ay, z:-alpha*p},
        b1 = {x:(4*r - ax)/3, y:-(r - ax)*(3*r - ax)/(3*ay), z:-b},
        b2 = {x:(4*r - ax)/3, y:(r - ax)*(3*r - ax)/(3*ay), z:b},
        b3 = {x:ax, y:ay, z:alpha*p};

  return ["M", b0.x,b0.y,b0.z, "C", b1.x,b1.y,b1.z, b2.x,b2.y,b2.z, b3.x,b3.y,b3.z];
}

Constructing a multi-turn helix approximation

Given that we have the formula to generate the coordinates of the Bézier curve approximating a helical arc, the method of creating a helix with an arbitrary number of turns is as follows.

  1. Chose an arc included angle \(\phi\) such that an integer number of arcs create the desired length helix. The code to generate the Bézier curve is valid for arc angles less than 180°. The smaller the arc the more accurate the approximation but the more segments needed to draw the helix. A good compromise is to use an arc length between 90° and 120°.
  2. Calculate the helical arc given the radius, pitch and included angle \(\phi\).
  3. Start constructing the array of Bézier curve segments approximating the helix with the initial arc.
  4. Duplicate the Bézier curve node points of the initial arc. Rotate the duplicate by the arc length and then translate it along the Z axis by \(\phi p\) where \(p\) (pitch per radian) is given by: $$ p = \frac{pitch}{2\pi} $$ Append this to the array of Bézier curve segments defining the helix.
  5. Repeat the previous step for each arc segment required to create the full helix.

JavaScript code to draw a multi-turn helix

Here is the code implementing the construction technique, the code use the Cango3D library to provide 3D curve manipulation methods. The object returned is suitable for rendering using the Cango3D render method.

createHelix = function(r, pitch, turns)
{
  function ZrotateTranslate(v, degs, d)
  {
    // rotate a 3D vector around the Z axis
    const A = Math.PI*degs/180.0,   // radians
          sinA = Math.sin(A),
          cosA = Math.cos(A),
          x = v.x,
          y = v.y;

    v.x = x*cosA - y*sinA;
    v.y = x*sinA + y*cosA;
    v.z += d;
  }

  // find integer number of segments needed with 90<incAngle<120 deg
  const nArcs = (turns < 1)? Math.ceil(3*turns): Math.floor(4*turns),
        arcsPerTurn = nArcs/turns,
        incAngle = 360/arcsPerTurn,
        arcData = createHelicalArc(r, pitch, incAngle);

  // rotate to 1st quadrant and translate to start in XY plane
  let theta = incAngle/2;
  let dz = pitch/(2*arcsPerTurn);
  let s = {x:arcData[1], y:arcData[2], z:arcData[3]};
  ZrotateTranslate(s, theta, dz);
  let c1 = {x:arcData[5], y:arcData[6], z:arcData[7]};
  ZrotateTranslate(c1, theta, dz);
  let c2 = {x:arcData[8], y:arcData[9], z:arcData[10]};
  ZrotateTranslate(c2, theta, dz);
  let e = {x:arcData[11], y:arcData[12], z:arcData[13]};
  ZrotateTranslate(e, theta, dz);

  const arc = ["M", s.x,s.y,s.z, "C",c1.x,c1.y,c1.z, c2.x,c2.y,c2.z, e.x,e.y,e.z];
  // start helix SVG array with first segment
  const helix = arc.slice(0);
  // copy, rotate and translate successive curve segments and append to helix array
  for (let i = 1; i<nArcs; i++)
  {
    theta = incAngle*(i % arcsPerTurn);
    dz = i*pitch/arcsPerTurn;

    c1 = {x:arc[5], y:arc[6], z:arc[7]};
    ZrotateTranslate(c1, theta, dz);
    c2 = {x:arc[8], y:arc[9], z:arc[10]};
    ZrotateTranslate(c2, theta, dz);
    e = {x:arc[11], y:arc[12], z:arc[13]};
    ZrotateTranslate(e, theta, dz);

    helix.push(c1.x,c1.y,c1.z, c2.x,c2.y,c2.z, e.x,e.y,e.z);
  }

  return helix;
}

Interactive 3D helix example

Here is a 3D drawing using the code presented above the helix may be rotated about any of the 3 axes using the 3 sliders. Note: The helix color has been set as 'brown' and line width as 3 pixels).

-180X Rotate180
-180Y Rotate180
-180Z Rotate180

Cubic Bézier approximation errors

To determine the approximation's deviation from the true helix, it is valid to consider only an arc since the helix is made from multiple identical arcs simply rotated and appended. Table 1 shows the approximation errors for the cubic Bézier approximation to a 90° circular helical arc with various values of pitch to radius ratios. The errors are calculated at uniform intervals along the helix. Column 1 shows the distance along the arc in degrees. Columns 2,3,4,5 and 6 show the error measured as the distance of a point on the true helix to the closest point on the approximation. Each column is calculated as the error relative to the radius for different values of helix pitch.

Distance along
90° arc (%)
Error/R
pitch=R/8
Error/R
pitch=R/4
Error/R
pitch=R/2
Error/R
pitch=R
Error/R
pitch=2*R
xxzzzzzzzzzz
xxzzzzzzzzzz
xxzzzzzzzzzz
xxzzzzzzzzzz
xxzzzzzzzzzz
xxzzzzzzzzzz
xxzzzzzzzzzz
xxzzzzzzzzzz
xxzzzzzzzzzz

Table 1. Approximation errors for a cubic Bézier curve approximation to the 90° helical arc shown in Fig. 3. Each error is the distance from the point on the true helix to the closest point on the approximation.

To show the effect of using longer arc length, Table 2 show similar error calculations but using a 120° arc. For brevity only the maximum error value is shown. From Table 1 it is clear that the maximum error occurs 25% along the arc.

Distance along
120° arc (%)
Error/R
pitch=R/8
Error/R
pitch=R/4
Error/R
pitch=R/2
Error/R
pitch=R
Error/R
pitch=2*R
xxzzzzzzzzzz

Table 2. Maximum error for a cubic Bézier curve approximation to a 120° helical arc. Each error is the distance from the point on the true helix to the closest point on the approximation.

From the tables above it can be seen that using a 3D cubic Bézier curve to approximate the helix results in normalised errors of less than 10-3 for pitch values up to twice the radius. These errors are achieved for approximations that use 90° arc segments. The code provided will use 90° segments unless then angular length of the helix is not a multiple of 90° when slightly larger segment length is used not exceeding 120°.

References:

  1. A. Riškus, "Approximation of a Cubic Bézier Curve by Circular Arcs and Vice Versa", Information Technology and Control, 2006
  2. Wikipedia article, Bézier spline
  3. I. Juhász, "Approximating the helix with rational cubic Bézier curves" Computer-Aided Design, 1995.