The RenderMan 3.1 spec provided one function for writing procedural primtives, RiProcedural(). The user would write a subdivide() function and a free() function, and pass function pointers through the RenderMan procedural interface. A severe limitation of this feature was that it could not be used from RIB. Due to the popularity of RIB, procedural primitives were rarely used.
RenderDotC supports three new types of procedural primitives that can be used from RIB. Each is described below.
Procedural "DelayedReadArchive" ["sphere.rib"] [-1 1 -1 1 -1 1]Where "sphere.rib" is the name of the RIB file and [-1 1 -1 1 -1 1] is the object space bound of the geometry in the RIB file. DelayedReadArchive is similar to ReadArchive except that the archive is loaded at render time when the bound of the object is encountered. If the object is off screen or entirely occluded by other geometry, then the archive won't be loaded at all. For these reasons, it's generally preferable to use DelayedReadArchive rather than ReadArchive. Some cases where you cannot use DelayedReadArchive are within a motion block (procedural primitives may not explicitly undergo deformational motion blur) or when accurate bounds are not known.
In the procedural interface, the syntax for DelayedReadArchive is:
RtString filename = strdup("sphere.rib");
RtBound bound = {-1, 1, -1, 1, -1, 1};
RiProcedural(&filename, bound, RiDelayedReadArchive, free);
Procedural "RunProgram" ["gencurve" "50"] [-1 1 -1 1 0 0]Where "gencurve" is the name of an executable program, "50" is an arbitrary data string meaningful to the helper program, and [-1 1 -1 1 0 0] is the object space bound of the geometry to be generated by the program. If and when the bound is encountered at render time, RenderDotC will launch the program and send it a string on its standard input. The format of the string is:
"403.1 50\n"Where 403.1 is the area of the bound in pixels, 50 is the data string from the Procedural command above, and \n is the newline character. The helper program should then emit RIB on its standard output, presumably using a RIB client library.
Here is a complete example of a helper program:
#include <stdio.h>The most important thing to note about this program is that it doesn't exit until its standard input is closed. Instead, it runs as a loop, dispatching requests from the renderer. Each loop is terminated with RiArchiveRecord(RI_COMMENT, "\377"), indicating to the renderer that it has finished processing one request. RenderDotC keeps helper programs open in case they are used again. In order to avoid wasting resources by launching and relaunching programs, helper programs should be designed with a main loop as above. In the example, RiBegin() and RiEnd() are inside the main loop. This is particularly important if the helper program generates compressed RIB, as the renderer will be expecting a new gzip header for each iteration.
#include <stdlib.h>
#include "ri.h"int main(int argc, char **argv)
{
RtPoint *P;
RtInt *nvertices;
RtFloat constantwidth = 0.01;
RtFloat detail;
int i, j, ncurves = 50, nsegs = 2, ncvs;
char buf[256];while (gets(buf) != NULL) {
sscanf(buf, "%g %d", &detail, &ncurves);
ncvs = 3 * nsegs + 1;
P = (RtPoint *)malloc(ncvs * ncurves * sizeof(RtPoint));
nvertices = (RtInt *)malloc(ncurves * sizeof(RtInt));
RiBegin(RI_NULL);
for (i = 0; i < ncurves; i++) {
nvertices[i] = ncvs;
P[i*ncvs][0] = 2 * ((RtFloat)rand() / RAND_MAX) - 1;
P[i*ncvs][1] = 2 * ((RtFloat)rand() / RAND_MAX) - 1;
P[i*ncvs][2] = 0.0;
for (j = 1; j < ncvs; j++) {
P[i*ncvs+j][0] = 2 * ((RtFloat)rand() / RAND_MAX) - 1;
P[i*ncvs+j][1] = 2 * ((RtFloat)rand() / RAND_MAX) - 1;
P[i*ncvs+j][2] = 0.0;
}
}
RiCurves(RI_CUBIC, ncurves, nvertices, RI_NONPERIODIC,
RI_P, P, RI_CONSTANTWIDTH, (RtPointer)&constantwidth,
RI_NULL);
RiArchiveRecord(RI_COMMENT, "\377");
RiEnd();
}
return 0;
}
The procedural interface for RunProgram is:
RtVoid freestrings(RtString *twostrings) {
free((void *)twostrings[0]);
free((void *)twostrings[1]);
free((void *)twostrings);
}
RtString *twostrings = (RtString *)malloc(2 * sizeof(RtString);
twostrings[0] = strdup("gencurve");
twostrings[1] = strdup("50");
RtBound bound = {-1, 1, -1, 1, 0, 0};
RiProcedural(twostrings, bound, RiProcRunProgram, (RtFreeFunc)freestrings);
Procedural "DynamicLoad" ["sphere" "1 -1 1 0 360"] [-1 1 -1 1 -1 1]Where "sphere" is the name of the compiled shared library without the extension (".so" on Unix, ".dll" on Windows), "1 -1 1 0 360" is an arbitrary string understood by the shared library, and [-1 1 -1 1 -1 1] is the object space bounds. When the bounds is encountered at render-time, RenderDotC searches for the shared library on the path set by PROCEDURALS or Option "searchpath" "procedural". If found, the shared library is loaded and the renderer makes use of three entry points:
RtPointer ConvertParameters(RtString paramstr)ConvertParameters takes the data string from the RIB (e.g. "1 -1 1 0 360") and converts it to dynamically allocated blind data for the Subdivide() and Free() functions. All DynamicLoad procedurals must define all three entry points with exactly the names given above. On Windows, these functions must be explicitly exported from the DLL. If your source code is written in C++, then the three functions must be further declared extern "C". If the subdivide function wishes to defer to one or more simple procedural primitives, it should call RiProcedural(data, bound, Subdivide, Free) and not RiProcedural(data, bound, RiProcDynamicLoad, Free). See the example below.
RtVoid Subdivide(RtPointer data, RtFloat detail)
RtVoid Free(RtPointer data)
Here's a non-trivial way to render a sphere:
#include <stdlib.h>Compilation details depend up the platform.
#include <math.h>
#include <float.h>
#include "ri.h"#if defined(_Windows) || defined(_WIN32)
#define DLLEXPORT __declspec(dllexport)
#else
#define DLLEXPORT
#endifstruct spheredata {
float radius;
float zmin, zmax;
float thetamin, thetamax;
};#define min(a, b) ((a) < (b) ? (a) : (b))
#define max(a, b) ((a) > (b) ? (a) : (b))/*
* Forward declarations
*/
RtPointer DLLEXPORT ConvertParameters(RtString paramstr);
RtVoid DLLEXPORT Subdivide(RtPointer data, float detail);
RtVoid DLLEXPORT Free(RtPointer data);RtPointer DLLEXPORT ConvertParameters(RtString paramstr)
{
struct spheredata *sd =
(struct spheredata *)malloc(sizeof(struct spheredata));
sscanf(paramstr, "%f %f %f %f %f",
&sd->radius,
&sd->zmin,
&sd->zmax,
&sd->thetamin,
&sd->thetamax);
return (RtPointer)sd;
}static RtVoid boundRevolve(RtBound b, float rmin, float rmax,
float thetamin, float thetamax)
{
/*
* Cache commonly used sines and cosines.
*/
float ctm = cosf(thetamin);
float stm = sinf(thetamin);
float ctx = cosf(thetamax);
float stx = sinf(thetamax);/*
* Compute x bounds
*/
float atm = fabsf(thetamin);
float atx = fabsf(thetamax);
float m = min(ctx, ctm); /* Consider theta with minimum x */
if (atx > M_PI/2 && atm < 3*M_PI/2) {
b[0] = -rmax; /* Best when patch crosses x-axis */
if (atx < M_PI || atm > M_PI)
b[0] *= -m; /* Doesn't cross, scale by cosine */
}
else /* xmin > 0: use rmin */
b[0] = rmin * m; /* Scale by cosine */m = max(ctx, ctm); /* Consider theta with maximum x */
if (atm < M_PI/2 || atx > 3*M_PI/2) /* xmax > 0: use rmax */
b[1] = rmax * m; /* Can't cross x-axis, just meet it */
else /* xmax < 0: use rmin */
b[1] = rmin * m; /* Scale by cosine *//*
* Compute y bound
*/
atm = fabsf(M_PI/2 - thetamin);
atx = fabsf(M_PI/2 - thetamax);
m = min(stx, stm); /* Consider theta with minimum y */
if (atx > M_PI/2 && atm < 3*M_PI/2) {
b[2] = -rmax; /* Best when patch crosses y-axis */
if (atx < M_PI || atm > M_PI)
b[2] *= -m; /* Doesn't cross, scale by sine */
}
else /* ymin > 0: use rmin */
b[2] = rmin * m; /* Scale by sine */atm = fabsf(M_PI/2 + thetamin);
atx = fabsf(M_PI/2 + thetamax);
m = max(stx, stm); /* Consider theta with maximum y */
if (atx > M_PI/2 && atm < 3*M_PI/2) {
b[3] = rmax; /* Best when patch crosses y-axis */
if (atx < M_PI || atm > M_PI)
b[3] *= m; /* Doesn't cross, scale by sine */
}
else /* ymax > 0: use rmin */
b[3] = rmin * m; /* Scale by sine */
}static RtVoid Bound(struct spheredata *s, RtBound b)
{
/*
* rmax is the maximum distance from the z-axis to a point on the sphere.
* rmin is the minimum.
* z0 is a temporary to help compute them.
* Don't attempt to take the sqrt of a negative number.
*/
float rmax, rmin, z0;
if (s->zmin < 0 && s->zmax > 0)
rmax = s->radius;
else {
z0 = min(fabsf(s->zmin), fabsf(s->zmax));
if (s->radius - z0 > FLT_EPSILON)
rmax = sqrtf(s->radius * s->radius - z0 * z0);
else
rmax = 0;
}z0 = max(fabsf(s->zmin), fabsf(s->zmax));
if (s->radius - z0 > FLT_EPSILON)
rmin = sqrtf(s->radius * s->radius - z0 * z0);
else
rmin = 0;boundRevolve(b, rmin, rmax, M_PI*s->thetamin/180, M_PI*s->thetamax/180);
/*
* Compute z bounds
*/
b[4] = s->zmin;
b[5] = s->zmax;
}RtVoid DLLEXPORT Subdivide(RtPointer data, float detail)
{
struct spheredata *sd = (struct spheredata *)data;if (detail < 100 || detail > 0.9 * RI_INFINITY) {
RiTransformBegin();
RiRotate(sd->thetamin, 0, 0, 1);
RiSphere(sd->radius, sd->zmin, sd->zmax, sd->thetamax - sd->thetamin,
RI_NULL);
RiTransformEnd();
}
else {
struct spheredata *sd0 =
(struct spheredata *)malloc(sizeof(struct spheredata));
struct spheredata *sd1 =
(struct spheredata *)malloc(sizeof(struct spheredata));
struct spheredata *sd2 =
(struct spheredata *)malloc(sizeof(struct spheredata));
struct spheredata *sd3 =
(struct spheredata *)malloc(sizeof(struct spheredata));float zhalf = 0.5 * (sd->zmin + sd->zmax);
float thetahalf = 0.5 * (sd->thetamin + sd->thetamax);RtBound b;
sd0->radius = sd->radius;
sd0->zmin = sd->zmin;
sd0->zmax = zhalf;
sd0->thetamin = sd->thetamin;
sd0->thetamax = thetahalf;
Bound(sd0, b);
RiProcedural((RtPointer)sd0, b, Subdivide, Free);sd1->radius = sd->radius;
sd1->zmin = zhalf;
sd1->zmax = sd->zmax;
sd1->thetamin = sd->thetamin;
sd1->thetamax = thetahalf;
Bound(sd1, b);
RiProcedural((RtPointer)sd1, b, Subdivide, Free);sd2->radius = sd->radius;
sd2->zmin = sd->zmin;
sd2->zmax = zhalf;
sd2->thetamin = thetahalf;
sd2->thetamax = sd->thetamax;
Bound(sd2, b);
RiProcedural((RtPointer)sd2, b, Subdivide, Free);sd3->radius = sd->radius;
sd3->zmin = zhalf;
sd3->zmax = sd->zmax;
sd3->thetamin = thetahalf;
sd3->thetamax = sd->thetamax;
Bound(sd3, b);
RiProcedural((RtPointer)sd3, b, Subdivide, Free);
}
}RtVoid DLLEXPORT Free(RtPointer data)
{
free(data);
}
SGI mips3: CC -I$RDCROOT/include -mips3 -n32 -O
-elf -shared sphere.c -o sphere.so
SGI mips4: CC -I$RDCROOT/include -mips4 -n32
-O -elf -shared sphere.c -o sphere.so
Visual C++: cl -I%RDCROOT%/include -LD sphere.c
Borland C++: bcc32 -I%RDCROOT%/include -tWD sphere.c
Linux: g++ -I$RDCROOT/include
-shared sphere.c -o sphere.so
BSD/OS: g++ -I$RDCROOT/include
-shared sphere.c -o sphere.so
The procedural interface for DynamicLoad is:
RtVoid freestrings(RtString *twostrings) {
free((void *)twostrings[0]);
free((void *)twostrings[1]);
free((void *)twostrings);
}
RtString *twostrings = (RtString *)malloc(2 * sizeof(RtString);
twostrings[0] = strdup("sphere");
twostrings[1] = strdup("1 -1 1 0 360");
RtBound bound = {-1, 1, -1, 1, -1, 1};
RiProcedural(twostrings, bound, RiProcDynamicLoad, (RtFreeFunc)freestrings);