The Subdivision Surface API

The Subdivision Surface API

October, 2009

Introduction

The subdivision surface API provideds the ability to subdivide a mesh to a fixed number of steps (limited only by what fits in memory), as well as limit surface evaluation, one face at a time. Developers can take advantage of the API to implement, for example, an interactive modelling application that wants to allow the user to subdivide the polygon cage to approximate the subdivision surface, or a fur generating DSO that wants to place the hair roots on the surface of a subdiv. The API supports the full scope of the subdivision surface primitive, as well as all manifold and manifold data types: float, vector, point types, etc.

Details of the interface extension are provided in the RixSubdivisionSurfaceEvaluation section of the Rix Interfaces Reference. This developer's note provides two basic examples of the API's usage. Users should note that the API does consume its own license.

A Simple Subdivision

This is a trivial example that takes a mesh, subdivides it one level, and outputs the result as a new mesh (in RIB form; it makes another RiSubdivisionMesh call).

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <vector>
#include <algorithm>

// The hash_map header file lives in different locations on different
// platforms, and the classes live in different namespaces.
#if defined(_MSC_VER)
// Visual C++
#include <hash_map>
using stdext::hash_map;
#elif defined(linux) && defined(__GNUC__) && (__GNUC__ < 3)
// GCC under linux, gcc <= 3
#include <hash_map>
using std::hash_map;
#else
// GCC and ICC under Linux and OSX
#include <ext/hash_map>
#include <ext/hash_set>
using __gnu_cxx::hash_map;
#endif

#include "ri.h"
#include "rx.h"
#include "RixInterfaces.h"


// Takes a subdivision mesh and generates a new subdivision mesh which
// represents one subdivided step. Does not reproduce any tags.
void subdividemesh(RixSubdivisionMesh *mesh) {

    int i;
    int *nverts = 0;
    int *verts  = 0;
    int nf = 0;
    int nvertsize = 0;
    int vertexcount = 0;
    hash_map<int, int> vertexmap;
    hash_map<int, int> vertexinvmap;

    // First pass: subdivide coarse faces and count the number of
    // children faces that result
    for (i = 0; i < mesh->GetNumCoarseFaces(); ++i) {
        RixSubdivisionFace *face = mesh->GetFace(i);
        face->Subdivide();
        for (int j = 0; j < std::max(4, face->GetNumVertices()); ++j) {
            RixSubdivisionFace *child = face->GetChildFace(j);
            if (child) {
                nf++;
                nvertsize += child->GetNumVertices();
            }
        }

    }

    // Allocate storage
    nverts = new RtInt[nf];
    verts = new RtInt[nvertsize];
    nf = 0;
    nvertsize = 0;

    // Second pass: create vertex arrays
    for (i = 0; i < mesh->GetNumCoarseFaces(); ++i) {
        RixSubdivisionFace *face = mesh->GetFace(i);
        for (int j = 0; j < std::max(4, face->GetNumVertices()); ++j) {
            RixSubdivisionFace *child = face->GetChildFace(j);
            if (child) {
                nverts[nf++] = child->GetNumVertices();
                for (int k = 0; k < child->GetNumVertices(); ++k) {
                    if (vertexmap.find(child->GetVertex(k)->GetID()) == vertexmap.end()) {
                        vertexmap[child->GetVertex(k)->GetID()] = vertexcount;
                        vertexinvmap[vertexcount] = child->GetVertex(k)->GetID();
                        vertexcount++;
                    }
                    verts[nvertsize++] = vertexmap[child->GetVertex(k)->GetID()];
                }
            }
        }
    }

    // Get P values
    RtPoint *P = new RtPoint[vertexcount];
    for (int j = 0; j < vertexcount; ++j) {
        RixSubdivisionVertex *v = mesh->GetVertex(vertexinvmap[j]);
        v->GetValue(RI_P, 3, P[j]);
    }

    RxTransformPoints("camera", "object", vertexcount, P, 0);
    
    RtToken tokens[1] = { RI_P };
    RtPointer parms[1] = { P };

    // Output the new mesh
    RiSubdivisionMeshV(mesh->GetMask(),
                       nf,
                       nverts,
                       verts,
                       0, NULL, NULL, NULL, NULL,
                       1, tokens, parms);

    
    delete[] verts;
    delete[] P;
}

int
main (int argc, char **argv) {
    PRManBegin(argc, argv);
    {
        RiBegin(RI_RENDER);
        {
            RiFrameBegin(1);
            {
                RiHider("null", NULL);
                RiWorldBegin();
                {
                    RiReadArchive("teapot_geometry.rib", NULL, NULL);
                    RixContext* context = RxGetRixContext();
                    RixSubdivisionSurfaceEvaluation* sdEval = (RixSubdivisionSurfaceEvaluation*) context->GetRixInterface(k_RixSubdivisionSurfaceEvaluation);

                    // Save the rendering context before outputting RIB
                    RtContextHandle renderContext = RiGetContext();
                    RiBegin(argv[1]);
                    {
                        RixSubdivisionMesh *teapotMesh = sdEval->GetSubdivisionMesh("teapotbody");
                        if (teapotMesh) {
                            subdividemesh(teapotMesh);
                        }
                        RixSubdivisionMesh *teapotLidMesh = sdEval->GetSubdivisionMesh("teapotlid");
                        if (teapotLidMesh) {
                            subdividemesh(teapotLidMesh);
                        }
                    }
                    RiEnd();
                    // Restore the rendering context
                    RiContext(renderContext);
                }
                RiWorldEnd();
            }
            RiFrameEnd();
        }
        RiEnd();
    }
    PRManEnd();
}

A Procedural Implementation

This example is a procedural primitive that grows RiPoints on the surface of a subdivision mesh, much like any DSO intended to grow hair. To that end, the procedural attempts to be as optimal as possible by emitting one further level of RiProcedural calls, one per high level face of the subdiv mesh (thus this exercises the GetBound portion of the API). Only after getting down this extra level of RiProcedurals does it start to grow RiPoints.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <algorithm>
#include "ri.h"
#include "rx.h"
#include "RixInterfaces.h"
// In R16, we break out the Rix interfaces by function
#ifdef _PRMANAPI_VERSION_
#include "RixSubdEval.h"
#endif

#ifdef NT
#define export __declspec(dllexport)
#define strtok_r(str,delim,saveptr) strtok((str),(delim))
#else
#define export
#endif

extern "C" {
    export RtPointer ConvertParameters(RtString paramstr);
    export RtVoid Subdivide(RtPointer data, RtFloat detail);
    export RtVoid Free(RtPointer data);
}

struct procData {
    char *name;
    RixSubdivisionMesh *mesh;
    int face;
    float interval;
    float offset;
};

static const float s_pointwidth = 0.1f;

export RtPointer ConvertParameters(RtString paramstr) {

    struct procData* data;
    char* parameters;
    char* token, *tokenstate;
    int i, j;

    parameters = strdup(paramstr);
    token = strtok_r(parameters, " ", &tokenstate);
    data = (struct procData*) malloc(sizeof(struct procData));

    RixContext* context = RxGetRixContext();
    RixSubdivisionSurfaceEvaluation* sdEval = (RixSubdivisionSurfaceEvaluation*)
        context->GetRixInterface(k_RixSubdivisionSurfaceEvaluation);

    data->name = strdup(token);
    data->mesh = sdEval->GetSubdivisionMesh(token);
    data->face = -1;
    token = strtok_r(0, " ", &tokenstate);
    data->interval = atof(token);
    token = strtok_r(0, " ", &tokenstate);
    data->offset = atof(token);

    free(parameters);

    // We keep a reference count of the procedural and its children in
    // global storage. When the reference count drops to zero across
    // all threads, we free the mesh via a call to RiResource.
    RixStorage* storage = (RixStorage*) context->GetRixInterface(k_RixGlobalData);
    storage->Lock();
    int *mydata = (int*) storage->Get(data->name);
    if (mydata == NULL) {
        mydata = (int*) malloc(sizeof(int));
        *mydata = 0;
        storage->Set(data->name, mydata, 0);
    }
    *mydata = *mydata + 1;
    storage->Unlock();
    
    return (RtPointer)(data);
}

void growpoints(RixSubdivisionFace *face, float interval, float offset) {
    RtPoint P[3];
    if (face->HasLimitSurface()) {

        bool limit = true;

        // Evaluate the limit surface at three corners of the face.
        // Compute the distance between them in object space to figure
        // out the number of steps to take.
        float u, v;
        u = 0; v = 0;
        limit = limit &&
            face->EvaluateAtLimitMultiple(1, &u, &v, RI_P, 3, RI_OBJECT, P[0]);
        u = 1; v = 0;
        limit = limit &&
            face->EvaluateAtLimitMultiple(1, &u, &v, RI_P, 3, RI_OBJECT, P[1]);
        u = 0; v = 1;
        limit = limit &&
            face->EvaluateAtLimitMultiple(1, &u, &v, RI_P, 3, RI_OBJECT, P[2]);
                                            
        if (limit) {
            float distance = sqrtf( (P[1][0] - P[0][0]) * (P[1][0] - P[0][0]) +
                                    (P[1][1] - P[0][1]) * (P[1][1] - P[0][1]) +
                                    (P[1][2] - P[0][2]) * (P[1][2] - P[0][2]) );

            int isteps = (int) (distance / interval);

            distance = sqrtf( (P[2][0] - P[0][0]) * (P[2][0] - P[0][0]) +
                              (P[2][1] - P[0][1]) * (P[2][1] - P[0][1]) +
                              (P[2][2] - P[0][2]) * (P[2][2] - P[0][2]) );

            int jsteps = (int) (distance / interval);

            if (isteps == 0) isteps = 1;
            if (jsteps == 0) jsteps = 1;

            int npts = isteps * jsteps;

            float *evalu = new float[npts];
            float *evalv = new float[npts];
            RtPoint *pointdata = new RtPoint[npts];
            RtPoint *normals = new RtPoint[npts];
            int i, j, k = 0;
            for (i = 0; i < isteps; ++i) {
                for (j = 0; j < jsteps; ++j) {
                    evalu[k] = (float) i / isteps;
                    evalv[k] = (float) j / jsteps;
                    k++;
                }
            }

            // Evaluate P and N at the limit surface
            face->EvaluateAtLimitMultiple(npts, evalu, evalv, RI_P, 3,
                RI_OBJECT, &pointdata[0][0]);
            face->EvaluateAtLimitMultiple(npts, evalu, evalv, RI_N, 3,
                RI_OBJECT, &normals[0][0]);

            for (i = 0; i < npts; ++i) {
                pointdata[i][0] += offset * normals[i][0];
                pointdata[i][1] += offset * normals[i][1];
                pointdata[i][2] += offset * normals[i][2];
            }

            // Grow points on the limit surface offset by N
            RiAttributeBegin();
            RtToken tokens[2] = { RI_P, RI_CONSTANTWIDTH };
            RtFloat width[1] = { s_pointwidth };
            RtPointer parms[2] = { pointdata, width };
            RtColor red = {1.0f, 0.5f, 0.5f};
            RiColor(red);
            RiPointsV(isteps * jsteps, 2, tokens, parms);
            RiAttributeEnd();

            delete[] normals;
            delete[] pointdata;
            delete[] evalv;
            delete[] evalu;
        }
    } else {
        // The face is not a limit surface. We must subdivide and try
        // again on the subfaces.
        face->Subdivide();
        const int nv = face->GetNumVertices();
        for (int j = 0; j < nv; ++j) {
            RixSubdivisionFace *child = face->GetChildFace(j);
            if (child) {
                growpoints(child, interval, offset);
            }
        }
    }
}

export RtVoid Subdivide(RtPointer data, RtFloat detail) {
    struct procData* pData = (struct procData*) data;

    if (pData->mesh) {
        // "Split" for top level face
        if (pData->face == -1) {
            const int nf = pData->mesh->GetNumCoarseFaces();
            for (int i = 0; i < nf; ++i) {
                RixSubdivisionFace *face = pData->mesh->GetFace(i);
                if (face) {
                    RtBound bound;
                    face->GetBound(RI_OBJECT, bound);                    
                    float expansion = pData->offset + s_pointwidth;
                    bound[0] -= expansion;
                    bound[1] += expansion;
                    bound[2] -= expansion;
                    bound[3] += expansion;
                    bound[4] -= expansion;
                    bound[5] += expansion;
                    struct procData *pnewData = (struct procData *)
                        malloc(sizeof(struct procData));
                    *pnewData = *pData;
                    pnewData->face = i;
                    // Increment face count in global storage
                    RixContext* context = RxGetRixContext();    
                    RixStorage* storage = (RixStorage*)
                        context->GetRixInterface(k_RixGlobalData);
                    storage->Lock();
                    int *mydata = (int*) storage->Get(pData->name);
                    if (mydata == NULL) {
                        mydata = (int*) malloc(sizeof(int));
                        *mydata = 0;
                        storage->Set(pData->name, mydata, 0);
                    }
                    *mydata = *mydata + 1;
                    storage->Unlock();
                    RiProcedural(pnewData, bound, Subdivide, Free);
                }
            }
        }
        // Generate points for individual faces
        else {
            RixSubdivisionFace *face = pData->mesh->GetFace(pData->face);
            if (face) {
                growpoints(face, pData->interval, pData->offset);
            }
        }
    }
    
}

export RtVoid Free(RtPointer data) {
    struct procData* pData = (struct procData*) data;
    // Decrement face count in global storage
    RixContext* context = RxGetRixContext();    
    RixStorage* storage = (RixStorage*) context->GetRixInterface(k_RixGlobalData);
    storage->Lock();
    int* mydata = (int*) storage->Get(pData->name);
    *mydata = *mydata - 1;
    int refcount = *mydata;        
    storage->Unlock();
    if (refcount == 0) {
        RtToken obsolete = "obsolete";
        RiResource(pData->name, "subdivisionmeshevaluation",
                   "string lifetime", &obsolete, NULL);
    }
    free(data);
}