home processing download documents tutorial python tutorial gallery source about
 Tutorials (back to the list of tutorials)

NURBS Curve and Surface

     Mathematics of NURBS Geometry

NURBS is abbreviation for Non-Uniform Rational B-Spline and it is a mathematical model to describe geometries in space. NURBS is one of the most popular mathematical models of geometries and used in many CAD/CAM software.
A NURBS geometry is defined by the following.
  • control points
  • weights
  • knot vectors
  • degree
Control points are an array of 3 dimensional coordinates for a NURBS curve, or a matrix of 3 dimensional coordinates for a NURBS surface. A weight is a positive number which can be assigned for each control point and it is usually 1.0. Knot vectors are an array of number used in the construction of the basis function shown below. A degree is a positive integer.
A NURBS curves is defined by a formula below, where Pi is i-th control point of total n control points, wi is i-th weight, Ni,d is i-th NURBS basis function of degree d, which is constructed with a knot vector and Bernstein polynomials. The formula below is a function of a parameter u and it returns spatial coordinates corresponding to the input u.

Similarly, a NURBS surface is defined by the formula below, where now control points Pi,j and weights wi,j are provided as n x m matrix. This is a function of two parameters u and v and it returns spatial coordinates corresponding to the input u and v.

For more detail description of NURBS geometries and basis functions, see Wikipedia pages of NURBS, B-Spline and Bernstein Polynomial


     Creating NURBS Curves and Surfaces

As we've already seen before, NURBS curves and surfaces can be created with ICurve and ISurface by providing an array or a 2 dimensional array of IVec and degrees. When you provide IVec, weights of NURBS geometry is all 1.0. If you want to specify weights, please use arrays of IVec4 which has w field as weight in addition to x, y and z field. Knot vectors are created automatically inside ICurve and ISurface and they are always unitized between 0.0 and 1.0.

import processing.opengl.*;
import igeo.*;

size( 480, 360, IG.GL );

IVec[] controlPoints1 = new IVec[4];
controlPoints1[0] = new IVec(0, 0, 0);
controlPoints1[1] = new IVec(20, 30, 30);
controlPoints1[2] = new IVec(40, -30, -30);
controlPoints1[3] = new IVec(60, 0, 0);

int deg1 = 3;
new ICurve(controlPoints1, deg1).clr(0);

IVec4[] controlPoints2 = new IVec4[4];
controlPoints2[0] = new IVec4(0, 0, 0, 1);
controlPoints2[1] = new IVec4(20, 30, 30, 0.5);
controlPoints2[2] = new IVec4(40, -30, -30, 0.5);
controlPoints2[3] = new IVec4(60, 0, 0, 1);

int deg2 = 3;
new ICurve(controlPoints2, deg2).clr(1.,0,0);

IVec[][] controlPoints3 = new IVec[][]{
  new IVec[]{ new IVec(-70,0,0), new IVec(-70,20,30), new IVec(-70,40,0) },
  new IVec[]{ new IVec(-50,30,30), new IVec(-50,50,60), new IVec(-50,70,30) },
  new IVec[]{ new IVec(-30,-30,-30), new IVec(-30,-10,0), new IVec(-30,10,-30) },
  new IVec[]{ new IVec(-10,0,30), new IVec(-10,20,60), new IVec(-10,40,30) }
};

int udeg3 = 3, vdeg3 = 2;
new ISurface(controlPoints3, udeg3, vdeg3).clr(0);

IVec4[][] controlPoints4 = new IVec4[][]{
  new IVec4[]{ new IVec4(-70,0,0,1), new IVec4(-70,20,30,.5), new IVec4(-70,40,0,1) },
  new IVec4[]{ new IVec4(-50,30,30,.5), new IVec4(-50,50,60,.5), new IVec4(-50,70,30,.5) },
  new IVec4[]{ new IVec4(-30,-30,-30,.5), new IVec4(-30,-10,0,.5), new IVec4(-30,10,-30,.5) },
  new IVec4[]{ new IVec4(-10,0,30,1), new IVec4(-10,20,60,.5), new IVec4(-10,40,30,1) }
};

int udeg4 = 3, vdeg4 = 2;
new ISurface(controlPoints4, udeg4, vdeg4).clr(1,.5,1);


     Continuity of NURBS Geometry

To match the tangent of the edge of NURBS curves or surfaces, you can align control points at the edge and another adjacent one on a straight line.

import processing.opengl.*;
import igeo.*;

size( 480, 360, IG.GL );

IVec curveEdgePt = new IVec(10,0,0);
IVec curveTangent = new IVec(20,20,20);

IVec[] cpts1 = new IVec[3];
cpts1[0] = curveEdgePt;
cpts1[1] = curveEdgePt.dup().sub(curveTangent);
cpts1[2] = new IVec(0, 40, -40);

IVec[] cpts2 = new IVec[4];
cpts2[0] = curveEdgePt;
cpts2[1] = curveEdgePt.dup().add(curveTangent);
cpts2[2] = new IVec(60,0,0);
cpts2[3] = new IVec(100,0,0);

new ICurve(cpts1, 2).clr(1.,0,0);
new ICurve(cpts2, 3).clr(.5,0,1);

// showing control points
for(int i=0; i < cpts1.length; i++){ new IPoint(cpts1[i]).clr(1.,0,0); }
for(int i=0; i < cpts2.length; i++){ new IPoint(cpts2[i]).clr(.5,0,1); }
// showing straight relationship
new ICurve(cpts1[1], cpts2[1]);

import processing.opengl.*;
import igeo.*;

size( 480, 360, IG.GL );

IVec surfaceTangent1 = new IVec(20, 0, -10);
IVec surfaceTangent2 = new IVec(20, -20, -10);
IVec surfaceEdgePt1 = new IVec(0, 0, 0);
IVec surfaceEdgePt2 = new IVec(0, 30, 0);

IVec[][] cpts3 = new IVec[3][2];
cpts3[0][0] = surfaceEdgePt1;
cpts3[0][1] = surfaceEdgePt2;
cpts3[1][0] = surfaceEdgePt1.dup().sub(surfaceTangent1);
cpts3[1][1] = surfaceEdgePt2.dup().sub(surfaceTangent2);
cpts3[2][0] = new IVec(-10, 0, -30);
cpts3[2][1] = new IVec(-10, 30, -30);

IVec[][] cpts4 = new IVec[3][2];
cpts4[0][0] = surfaceEdgePt1;
cpts4[0][1] = surfaceEdgePt2;
cpts4[1][0] = surfaceEdgePt1.dup().add(surfaceTangent1);
cpts4[1][1] = surfaceEdgePt2.dup().add(surfaceTangent2);
cpts4[2][0] = new IVec(10, 0, 30);
cpts4[2][1] = new IVec(10, 30, 30);

new ISurface(cpts3, 2, 1).clr(0,0,1.);
new ISurface(cpts4, 2, 1).clr(0,.5,1.);

// showing control points
for(int i=0; i < cpts3.length; i++){
  for(int j=0; j< cpts3[i].length; j++){ new IPoint(cpts3[i][j]).clr(0,0,1.); }
}
for(int i=0; i < cpts4.length; i++){
  for(int j=0; j< cpts4[i].length; j++){ new IPoint(cpts4[i][j]).clr(0,.5,1.); }
}
// showing straight relationship
new ICurve(cpts3[1][0], cpts4[1][0]);
new ICurve(cpts3[1][1], cpts4[1][1]);


     Point on NURBS Curve

To get a point on ICurve, you use pt() method with a double parameter u. Parameter u always ranges from 0.0 to 1.0.

import processing.opengl.*;
import igeo.*;

size( 480, 360, IG.GL );

IVec[] cpts=new IVec[]{new IVec(-60,0,0),new IVec(-20,0,0),
                       new IVec(20,0,60),new IVec(60,0,-30)};

ICurve curve = new ICurve(cpts, 2);

IVec pt1 = curve.pt(0.5);
IVec pt2 = curve.pt(0.8);
IVec pt3 = curve.pt(1.0);

// showing points
new IPoint(pt1).clr(1.,0,0);
new IPoint(pt2).clr(1.,.5,0);
new IPoint(pt3).clr(0,.5,0);

// using them for creating other geometries
new ICurve(new IVec(0,60,-20), pt1).clr(1.,0,0);
new ICurve(new IVec(0,60,-20), pt2).clr(1.,.5,0);
new ICurve(new IVec(0,60,-20), pt3).clr(0,.5,0);


     Tangent on NURBS Curve

A tangent vector on ICurve can be calculated by tan() method with a parameter u.

import processing.opengl.*;
import igeo.*;

size( 480, 360, IG.GL );

IVec[] cpts=new IVec[]{new IVec(-30,10,0),new IVec(-10,0,0),
                       new IVec(10,0,30),new IVec(30,0,-10)};

ICurve curve = new ICurve(cpts, 2);

IVec tan1 = curve.tan(0);
IVec tan2 = curve.tan(0.5);
IVec tan3 = curve.tan(0.8);
IVec tan4 = curve.tan(1.0);

// showing arrows at each point on the curve
tan1.show(curve.pt(0)).clr(1.,1.,0).size(10);
tan2.show(curve.pt(0.5)).clr(1.,0,0).size(10);
tan3.show(curve.pt(0.8)).clr(1,.5,0).size(10);
tan4.show(curve.pt(1.0)).clr(0,.5,0).size(10);


     Point on NURBS Surface

In the similar way to the ICurve, you can get a point on ISurface by pt() with u and v parameter. Parameter u and v both always range from 0.0 to 1.0.

import processing.opengl.*;
import igeo.*;

size( 480, 360, IG.GL );

IVec[][] cpts = new IVec[][]{ 
  new IVec[]{ new IVec(-40,-20,-20), new IVec(0,0,0), new IVec(40,0,-20) },
  new IVec[]{ new IVec(-40,30,20), new IVec(0,30,0), new IVec(40,30,-10) }
};

ISurface surface = new ISurface(cpts,1,2);

IVec pt1 = surface.pt(0.5,0.5);
IVec pt2 = surface.pt(0.5,1.0);
IVec pt3 = surface.pt(0.3,0.2);

// showing points
new IPoint(pt1).clr(1.,0,0);
new IPoint(pt2).clr(1,.5,0);
new IPoint(pt3).clr(0,.5,0);

// using them for creating other geometries
new ICurve(new IVec(-20,0,40), pt1).clr(1.,0,0);
new ICurve(new IVec(-20,0,40), pt2).clr(1,.5,0);
new ICurve(new IVec(-20,0,40), pt3).clr(0,.5,0);


     Tangent on NURBS Surface

You can get a tangent vector in u direction by utan() and in v direction by vtan().

import processing.opengl.*;
import igeo.*;

size( 480, 360, IG.GL );

IVec[][] cpts = new IVec[][]{ 
  new IVec[]{ new IVec(-40,-20,-20), new IVec(0,0,0), new IVec(40,0,-20) },
  new IVec[]{ new IVec(-40,30,20), new IVec(0,30,0), new IVec(40,30,-10) }
};

ISurface surface = new ISurface(cpts,1,2);

IVec utan1 = surface.utan(0.5,0.5);
IVec utan2 = surface.utan(0.5,1.0);
IVec vtan1 = surface.vtan(0.5,0.5);
IVec vtan2 = surface.vtan(0.5,1.0);

// showing arrows at each point on the surface
utan1.show(surface.pt(0.5,0.5)).clr(1.,0,0).size(5);
utan2.show(surface.pt(0.5,1.0)).clr(0,0,1.).size(5);
vtan1.show(surface.pt(0.5,0.5)).clr(1,.5,0).size(5);
vtan2.show(surface.pt(0.5,1.0)).clr(0,.5,1).size(5);


     Normal on NURBS Surface

You can get a normal vector of ISurface by nrml(). There are also aliases of this method, which are nml() and normal().

import processing.opengl.*;
import igeo.*;

size( 480, 360, IG.GL );

IVec[][] cpts = new IVec[][]{ 
  new IVec[]{ new IVec(-40,30,20), new IVec(0,30,0), new IVec(40,30,-10) },
  new IVec[]{ new IVec(-40,-20,-20), new IVec(0,0,0), new IVec(40,0,-20) }
};

ISurface surface = new ISurface(cpts,1,2);

IVec normal1 = surface.nrml(0.5,0.5).len(40);
IVec normal2 = surface.nrml(1.0,0.5).len(40);
IVec normal3 = surface.nrml(0.0,0.5).len(40);
IVec normal4 = surface.nrml(0.2,0.8).len(40);

// showing arrows at each point on the surface
normal1.show(surface.pt(0.5,0.5)).clr(1.,0,0).size(10);
normal2.show(surface.pt(1.0,0.5)).clr(1.,.5,0).size(10);
normal3.show(surface.pt(0.0,0.5)).clr(0,.5,0).size(10);
normal4.show(surface.pt(0.2,0.8)).clr(0,.5,1).size(10);


     Offset Point on NURBS Surface

You can get a point offset by normal with addition of pt() and nrml() like
surface.pt(0.5, 0.5).add( surface.nrml(0.5, 0.5).len(10) );
but there is also a method to get a point offset by normal, which is called also pt() but with three arguments of u, v and the offset length in the normal direction. The statement above can be written with this method like this.
surface.pt(0.5, 0.5, 10);

import processing.opengl.*;
import igeo.*;

size( 480, 360, IG.GL );

IVec[][] cpts = new IVec[][]{ 
  new IVec[]{ new IVec(-40,30,20), new IVec(0,30,0), new IVec(40,30,-10) },
  new IVec[]{ new IVec(-40,-20,-20), new IVec(0,0,0), new IVec(40,0,-20) }
};

ISurface surface = new ISurface(cpts,1,2);

IVec pt1 = surface.pt(0.5, 0.5, 10);
IVec pt2 = surface.pt(0.8, 0.5, 10);
IVec pt3 = surface.pt(0.8, 0.8, 10);
IVec pt4 = surface.pt(0.5, 0.8, 10);

// showing points
new IPoint(pt1).clr(1.,0,0);
new IPoint(pt2).clr(1.,.5,0);
new IPoint(pt3).clr(0,.5,0);
new IPoint(pt4).clr(0,.5,1);

// using them for creating other geometries
new ISurface(pt1,pt2,pt3,pt4).clr(.5,0,1);


(back to the list of tutorials)

HOME
FOR PROCESSING
DOWNLOAD
DOCUMENTS
TUTORIALS (Java / Python)
GALLERY
SOURCE CODE(GitHub)
ABOUT