Lesson 28 - NURBS

 

Introduction

Non-uniform rational basis spline (NURBS) is a mathematical model commonly used in computer graphics for generating and representing curves and surfaces. It offers great flexibility and precision for handling both analytic (surfaces defined by common mathematical formulae) and modeled shapes. NURBS are a generealization of Bézier curves, named after Pierre Bézier, who began using them at the French automaker Renault to define the curves of the bodywork. However, it was  Paul de Casteljau using de Casteljau's algorithm, a numerically stable method to evaluate Bézier curves at Citroën, another French automaker. The mathematics of Bezier curves and NURBS are beyond the scope of this lsson, but a good introductory article is here and there are further references in its bibliography.

To be honest, this lesson is largely a copy of the example of NURBS in the three.js site. So this lesson is pretty short. The code is based on the NURBS libraries that are part of the three.js repo at github. Those libraries were (apparently) written by "renej" but further information is not readily available.

Creating the NURB Surface

The meat of this lesson is in the methd createSurface. The first part of the code simply defines the control points as a 3x4 array of coordinates. It is 3x4 because a Bézier curve has 4 control points and there are 3 such curves across the surface. However, NURBS are a bit more complex than "simple" Bézier curves. In addition to the Bézier control points, NURBS also define a set of "knots"(the knot vector), and the degrees of the curves.

The call to the NURBS library to create the surface then is:

var degree1 = 2;
var degree2 = 3;
var knots1 = [0, 0, 0, 1, 1, 1];
var knots2 = [0, 0, 0, 0, 1, 1, 1, 1];
var nurbsSurface = new THREE.NURBSSurface(degree1, degree2, knots1, knots2, controlPoints);

We then load a texture which we can map onto the surface:

var map = new THREE.TextureLoader().load( 'images/UV_Grid_Sm.jpg' );
map.wrapS = map.wrapT = THREE.RepeatWrapping;
map.anisotropy = 4;

This image is fact a grid of UV coordinates (which we talked about a couple of times so far). But while we have created a NURBS surface, what we need is a Geometry to map the texture onto. The NURBS surface is just a mathematical model. Now we need to actually create the Geometry and map the texture to it. This is done by creating a ParametricBufferGeometry, similar to what was done in Lesson 11.

getSurfacePoint = function(u, v) {
    return nurbsSurface.getPoint(u, v);
};

var geometry = new THREE.ParametricBufferGeometry(getSurfacePoint, 20, 20);
var material = new THREE.MeshLambertMaterial({map: map, side: THREE.DoubleSide});
var mesh = new THREE.Mesh(geometry, material);

We simply specify the size of the Geometry and provide a callback function that takes the (u,v) coordinates and then calls the getSurfacePoint method on the nurbSurface to return the three-dimensional coordinate for the Geometry. Then we map the UV texture image on to the Geometry to create the mesh.

Finally, we render the control points as a series of small red spheres and then connect them with some yellow lines.

for ( var i=0; i<3; i++ ) {
    for (var j=0; j<4; j++ ) {

       var cpGeom = new THREE.SphereGeometry(0.1);
       var cpMat = new THREE.MeshLambertMaterial({color: 0xff0000});
       var cpMesh = new THREE.Mesh(cpGeom, cpMat);

       cpMesh.position.set(controlPoints[i][j].x, 
                           controlPoints[i][j].y,           
                           controlPoints[i][j].z);
       gfxScene.add(cpMesh);
   }
}

for ( var k=0; k<3; k++ ) {
    var verts = new THREE.Geometry();
    for (var n=0; n<4; n++ ) {
       verts.vertices.push( new THREE.Vector3(controlPoints[k][n].x, 
                                              controlPoints[k][n].y, 
                                              controlPoints[k][n].z));
    }
    var line = new THREE.Line( verts, new THREE.LineBasicMaterial( { color: 0xffff80 } ) );
    gfxScene.add(line);
}

for (var p=0; p<4; p++ ) {
   var verts2 = new THREE.Geometry();
   for ( var q=0; q<3; q++ ) {
      verts2.vertices.push( new THREE.Vector3(controlPoints[q][p].x, 
                                              controlPoints[q][p].y, 
                                              controlPoints[q][p].z));
   }
   var line2 = new THREE.Line( verts2, new THREE.LineBasicMaterial( { color: 0xffff80 } ) );
   gfxScene.add(line2);
}

And that's it! Click on this link to see the actual rendered demo in all it's Bezier glory!

As always, the original sources are on github here.



About Us | Contact Us | ©2017 Geo-F/X