The New (and Improved!) Surface Subdivision Suite

A few years back I developed a set of macros for the Persistence of Vision Ray Tracer which implemented a Loop subdivision surface scheme, albeit in a limited way. The implementation was hampered by a number of limitations which greatly reduced its usefulness:


Feature Old Surface Subdivision Suite New Surface Subdivision Suite
Border edges Not properly implementedProperly implemented
Creases NoYes
Individual texturing of facesNoYes
UV mappingNoYes
Face typesTriangles onlyTriangles and Quadrilaterals


The quadrilateral support was provided by extending the Loop subdivision method to include quadrilaterals. This eliminates a kind of artifacting to which uv-mapped Loop surfaces were prone.


The Download

is right here:


new_sss.zip


Using the file:

Extract the file new_sss.zip into the directory where you keep your POV files. At some point in your scene code (prior to invoking any of the macros) insert this line of code:

     #include "nsss.htm"

This file will include all of the other files.


Naming Conventions

The NSSS makes use of several arrays, variables, and macros, the names of which are written into the code. The arrays and variables begin with sss, and the macro names begin with SSS, so avoid using labels beginning with sss or SSS.


The Macros

The NSSS is designed around these new macros. They operate slightly differently from the old SSS, so some scene code modification will be in order.


SSS_ImportBasicMesh(Points,Faces)

This is the macro for importing mesh data into the NSSS. Points is a user-provided array containing the vertices of the mesh, and Faces is a user-provided two-dimensional array containing zero-based indices into Points, describing the faces of the basic mesh. Faces[i][0] contains the index of the first vertex of face i, Faces[i][1] contains the index of the second vertex of face i, Faces[i][2] contains the index of the third vertex of face i, and Faces[i][3] contains the index of the fourth vertex of face i (or -1 if face i is triangular). Faces can be of the form [n][3] if all of the faces are triangular, otherwise it must be of the form [n][4].

This macro copies all of the data in the arrays, so after returning from the macro you can #undef (or otherwise modify) the arrays Points and Faces without affecting the mesh you've imported.

This macro completely eliminates any NSSS data from earlier parsing in the scene.


SSS_AddTextures(Texts)

This macro enables you to specify a separate texture for each of the faces in the basic mesh. Texts is an array of zero-based indices into an array of textures you will supply later; the order of the entries in Texts matches the order of the Faces array supplied to the macro SSS_ImportBasicMesh( ). Any value of -1 specifies that no individual texturing is applied for that face.

You don't need an entry for each face you've defined. If you set up Faces so that all of the individually-textured faces are at the beginning of the array, and make Texts large enough to cover only those faces, the rest will receive an index of -1.

When faces are subdivided, each child inherits its parent's texture.

This macro copies all of the data in the array you supply, so after returning from the macro you can #undef (or otherwise modify) the array Texts without affecting what you've imported.

THIS SECTION CORRECTED 30 AUG 2004

SSS_AddSharpData(Sharps)

This array allows you to define internal edges (edges with two faces bordering on them) as sharp edges, so that for one or more levels of subdivision, the smoothing calculations are not done for that edge. Sharps[i][0] is the index of the starting vertex of the edge to be sharpened, Sharps[i][1] is the index of the ending vertex of the edge to be sharpened, and Sharps[i][2] is the number of subdivision levels for which smoothing calculations are not performed. Specifying a sharpness of -1 causes smoothing to be turned on for all levels of subdivision.

Border edges (that is, edges having only one face bordering on them) have a sharpness value of -1 built into the code, and any attempt to assign a different value will be ignored.

This macro copies all of the data into its own data array, so after returning from the macro you can #undef (or otherwise modify) the array Sharps without affecting what you've imported.


SSS_AddUVMapping(Maps)

This macro adds the uv-mapping. Maps[i][0] is a <u,v> vector specifying the mapping for the first vertex of face i, and so on for Maps[i][1], Maps[i][2], and Maps[i][3].

As with individual texturing, you can group the faces to be uv-mapped at the beginning of the array supplied to SSS_ImportBasicMesh( ), and then specify only that many entries for Maps; default values will be supplied for the remaining faces.

Please note that the NSSS does not support texture interpolation.

This macro copies all of the data in the arrays, so after returning from the macro you can #undef (or otherwise modify) the array Maps without affecting what you've imported.


SSS_Subdivide()

This macro performs one level of subidivision. Each face is subdivided into four smaller faces of the same kind. Repeated execution results in a smoother mesh (quadrupled in memory size, so don't go hog-wild). Subdivision beyond the fourth iteration is highly expensive memory-wise but yields little perceptible improvement in object quality.


SSS_BuildFlatMesh()

This macro builds a mesh{ } object and places it in the scene. The faces are not smoothed and no texturing (except for the currently-defined default) is applied. You can put this call within an object{ } and apply texturing and transformations to it.


SSS_BuildSmoothMesh()

This macro builds a mesh{ } object with smoothed faces and places it in the scene. The faces are not textured (except for the currently-defined default). You can put this call within an object{ } and apply texturing and transformations to it.


SSS_BuildTexturedFlatMesh(Texts)

This macro builds a mesh{ } object and places it in the scene. The faces are not smoothed, but the textures in the array Texts are applied to every face (or child of a face) that received a non-negative index from a call to SSS_AddTextures( ); the remaining faces receive the default texture. You can put this call within an object{ } and apply transformations and/or supply a different default texturing.

This macro will apply whatever uv mapping was specified if SSS_AddUVMapping( ) has been called. Remember that the uv_mapping keyword must appear in the texture definitions.


SSS_BuildTexturedSmoothMesh(Texts)

This macro builds a mesh{ } object and places it in the scene. The faces are smoothed and the textures in the array Texts are applied to every face (or child of a face) that received a non-negative index from a call to SSS_AddTextures( ); the remaining faces receive the default texture. You can put this call within an object{ } and apply transformations and/or supply a different default texturing.

This macro will apply whatever uv mapping was specified if SSS_AddUVMapping( ) has been called. Remember that the uv_mapping keyword must appear in the texture definitions.


SSS_Roughen(Seed,Amount)

This macro randomly perturbs the vertices of the array. Seed is the return value from a seed() function call, and Amount is the amount of perturbing to be done.


SSS_BuildFrame(Radius,PlainEdge)

This macro, which I wrote for debugging purposes, builds a wireframe by making a cylinder for each edge of the mesh. Radius specifies the radius of the cylinders. Sharp edges are colored a light pink. The edge with the index matching PlainEdge is pigmented vivid magenta. All other edges receive the default pigment.


SSS_ColorVerts(Radius,PlainVert)

This macro, which I wrote for debugging purposes, places a sphere at each vertex of the mesh. The vertex whose index matches the value PlainVert is endowed with a light-gray pigment; the rest receive the default pigment.


SSS_Porcupine(Radius,PlainSpine)

This macro, which I wrote for debugging purposes, places a cone in the center of each face of the mesh, with the point of the cone facing in or out. Radius specifies the radius of the base of the cylinder, and PlainSpine specifies the index of the spine that is to receive a light gray pigment; the rest receive the default pigment.


SSS_DumpData()

This macro dumps some of the mesh data to the debug stream.


SSS_ProgressOn()

This turns the progress reports on (which is the default).


SSS_ProgressOff()

This turns the progress reports off.


Other Macros

The following macros are called when needed by the NSSS. If you call them, you run the risk of screwing things up.


SSS_BuildNormals()

This macro figures out which faces are used for calculating each normal. The NSSS calls this as needed.


SSS_CalculateNormals()

This macro computes the cross products of each face corner and adds them up, resulting in the normals used for smoothing. The NSSS calls this as needed.


SSS_OrientFaces()

This macro adjusts the order of the vertices of each face so that they are all going clockwise or counter-clockwise. This is needed for other macros to work; the NSSS calls it when necessary.


SSS_EdgeDir()

This macro tells whether the order of vertices in an edge matches the order of vertices in a face bordering on that edge.


SSS_OtherSide()

This macro, given an edge and one of the faces bordering on that edge, returns the index of the face bordering on the other side


SSS_DefaultSharpData()

This macro sets up the default sharpness data for the mesh. The default is that all interior edges are smooth and all border edges are sharp.


The Internal Data

The NSSS makes use of several variables and arrays. The NSSS builds these data structures as it needs them. For most applications you won't have any need to access them, but if you're looking to add some capability that I haven't provided, here's what you need to know:


VariablePurpose
ssscVThis holds the number of vertices in the mesh.
sssaV[ssscV]This the vertices of the mesh.
ssscFThis holds the number of faces in the mesh.
sssaFV[ssscF][4]This array defines the faces of the mesh using indices into sssaV to specify the vertices of each face.
sssaFE[ssscF][4]This array specifies the edges which border on the face.
sssaFN[ssscF][4]This array specifies the indices used for each vertex of the face.
sssaFM[ssscF][4]This array specifies the uv mapping coordinates for each vertex of the face.
sssaFT[ssscF]This array specifies the index of the texture applied to each face of the mesh.
ssscEThis holds the number of edges in the mesh.
sssaES[ssscE]This holds the sharpness of each edge.
sssaEV[ssscE][2]This array holds the indices of the starting and ending vertices of each edge.
sssaEF[ssscE][2]This array holds the indices of the faces that border on the edge.
ssscNThis holds the number of smoothing normals used by the mesh.
sssaN[ssscN]This array contains the normals used by the mesh.
sssfProgressReportsThis variable controls whether progress reports are sent to the debug stream during parsing. It is set to on by default. You can set it to off if you don't want the messages.


Version History

30 Aug 2004: One of the directions above said "off" when it should have said "on". Fixed.

26 Aug 2004: When a subdivision results in a sharp edge becoming smooth, the old normals are no longer valid, but they were still hanging around. This made the normals look strange. Fixed.

5 Jul 2004: For some reason the uploaded .ZIP had old files in it. Dunno why. Fixed.

30 May 2004: The macro SSS_BuildFlatMesh() had a typo: A variable named vN was mistyped as nV several times, resulting in show-stopping parse errors. Fixed.

28 Apr 2004: Included a test version of a file that wasn't in the archive. Consequence: Fatal parse errors. Fixed.

21 Apr 2004: Observed that heavily-subdivided quads looked very funny when a flat mesh was built. It turns out that the normal calculations used to make each quad look like a single flat polygon were slightly buggy.

12 Jan 2004: Adjusted the way normals are built and calculated. Originally, SSS_BuildNormals() and SSS_CalculateNormals() were done in one step, and only when a smooth surface was needed. I later determined that the subdivision process could easily accomodate faces with supplied normals, and so now the NSSS calls SSS_BuildNormals() when the mesh is imported, and SSS_CalculateNormals() when the smoothing is needed. SSS_BuildNormals() is a very slow macro, and so calling it prior to subidivsion saves a lot of time; the extra load of subdividing the normal interpolation wasn't that much. Parsing time for my pink bunny model went from 52 seconds down to 29 seconds. I also added the functions SSS_ProgressOn() and SSS_ProgressOff().

18 Oct 2003: Discovered that the uploaded ZIP was the wrong one. Doh!

12 Oct 2003: Modified the SSS_BuildFrame( ) macro so that it would call SSS_DefaultSharpData( ) if necessary.

10 Oct 2003: Found a bug in the macro which imports the basic data. The bug caused a face to be identified with the wrong edge, when there were border edges. I also changed the SSS_BuildFrame( ) macro to give a different pigment to the sharp and smooth edges.

30 Aug 2003: Original version released to worldwide acclaim.


Bug Reports

You can e-mail me at evilsnack at hotmail dot com.




Back to John's Freeloading Home Page
Hosted by www.Geocities.ws

1