RenderMan
Post Processing Curve Normals


return to main index



Introduction

This tutorial is a continuation of "RenderMan: Post Processing Curve Widths". The introduction to that turorial provides background information about RenderMan Studio (RMS) and RenderMan's Curves primitive. With the current release of RMS it is not possible to assign normals to the segments of a Maya curve. This tutorial describes a technique that relies on taking information from the Maya scene and "encoding" it into the name assigned to the shape node of a curve. In the example shown in this tutorial, information about the normals of the curve are "sampled" along each curve where the parametric value v is 0.0, 0.33, 0.66 and 0.999. The rib file(s) produced by RMS are post processed in such a way that the data previously "hidden" in the names of curves is de-coded and added, as "N" values, to the curve.



Figure 1
Without Normals


Figure 2
With Normals


RMS Directory Structure & File Names

RMS has a complex structure of directories and multiple rib files per frame. If a Maya scene, say "untitled", has a curve named "curve1" the rib data for the curve will be found (in the scenes project directory) at,

    renderman/untitled/job/curveShape1_0.job.rib

If "curve1" in Maya is renamed so that data about its normals (to three decimal places of accuracy) forms part of its name the rib file generated by RMS would look something like,

    curveShape1_n408_p816_p408_n764_n445_p465_p929_n357_p86_p711_n405_n573_0.job.rib

Based on the file name following the prefix curveShape1 the xyz components of the normal at the first cv are,

    -0.408 0.816 0.408

The job.rib file will contain statements similiar to those shown below.

    ##RenderMan RIB
    version 3.04
    Declare "cubic" ""
    Declare "nonperiodic" ""
    Curves "cubic" [8] "nonperiodic" 
        "P" [-1 0 0 -1 0 0 -1 0 0 1 1 0
             -2 2 1 1 3 4 1 3 4 1 3 4]
        "constant float constantwidth" [0.2]
        "constant string primtype" ["curves"]

In particular, notice there is no reference to the name of the curve within the file. For example, the rib statement,

    Attribute "identifier" "name" "..."

is not present. Therefore, the name of the job.rib must be used as the sole source of information about the normals of the curve. How the information about the normals at each cv is encoded into the name of the shape node is the subject of the next section.


Curve Creation and Encoding

Three mel scripts are responsible for creating a cluster of curves, naming the shape nodes and adding the appropriate rman shaded geometric attributes that ensure the curves will be rendered by prman ie.

    curveEncode.mel
    curveCreate.mel
    curveAddSharedAttrRI


Listing 1 renames the shape node of a curve so that data about the normals at each of its 4 cv's is encoded in its name. The xyz components of the normals are represented by a four character string, accurate to three decimal places, where the first character, "p" or "n", indicates a positive or a negative value.


Listing 1 (curveEncode.mel)


// Assumes the shape node is a curve with 4 cv's
global proc string curveEncode(string $shapeNode)
{
string $newname = $shapeNode;
for($i = 0; $i < 4; $i++)
    {
    vector $v = `pointOnCurve -nn -pr ($i * 0.333) $shapeNode`;
    $newname += "_" + floatToStr($v.x);
    $newname += "_" + floatToStr($v.y);
    $newname += "_" + floatToStr($v.z);
    }
rename -ignoreShape $shapeNode $newname;
return $newname;
}
  
// Input: 0.02 returned as p02
//       -0.02 returned as n02
global proc string floatToStr(float $f)
{
string $out;
if($f >= 0 && $f < 0.001)
    $out = "p001";
else if($f < 0 && $f > -0.001)
    $out = "n001";
else if($f > 0.999)
    $out = "p999";
else if($f < -0.999)
    $out = "n999";
else if($f < 0)
    $out = "n" + abs(trunc($f * 1000));
else
    $out = "p" + trunc($f * 1000);
return $out;
}

Listing 2 creates an arbitary number curves that are intended to look like a "clump" of grass.


Listing 2 (curvesCreate.mel)


// Creates an arbitary number of curves. Returns the names
// of the transform nodes of the curves.
global proc string[] curveCreate(int $num, vector $dir, vector $position)
{
string $shapeNode[];
string $transNodes[];
  
for($n = 0; $n < $num; $n++) {
    $transNodes[$n] = doCurve($dir);
    $shapeNode = `listRelatives -s`;
    $shapeNode[0] = curveEncode($shapeNode[0]);
    }
return $transNodes;
}
  
// Creates a curve and returns the name of its transform node.
proc string doCurve(vector $dir)
{
$dir = $dir + unit(sphrand(1) * 0.25);
vector $d = $dir/3;
vector $cv0 = <<rand(-0.25,0.25), 0, rand(-0.25,0.25)>>;
vector $cv1 = $d * 1;
vector $cv2 = $d * 2;
vector $cv3 = $d * 3;
float $len = length($dir.x, $dir.z);
float $droop3 = $len * rand(2, 0.950);
float $droop2 = $len * 0.25;
float $droop1 = $len * 0.15;
$cv1 = <<$cv1.x, $cv1.y - $droop1, $cv1.z>>;
$cv2 = <<$cv2.x, $cv2.y - $droop2, $cv2.z>>;
$cv3 = <<$cv3.x, $cv3.y - $droop3, $cv3.z>>;
return  `curve -d 3  -p ($cv0.x) ($cv0.y) ($cv0.z)
       -p ($cv1.x) ($cv1.y) ($cv1.z)
       -p ($cv2.x) ($cv2.y) ($cv2.z)
       -p ($cv3.x) ($cv3.y) ($cv3.z)`;
}
  
// Returns the length of a 2d vector
proc float length(float $a, float $b)
{
return sqrt($a * $a + $b * $b);
}

Listing 3 creates a Shared Geometric Attribute (SGA), adds three rman attributes to it, and then assigns the SGA to the curves.


Listing 3 (curveAddSharedAttrRI.mel)


global proc curveAddSharedAttrRI(string $transformNodes[], 
                                 float $base, 
                                 float $tip)
{
rmanEd_CreateSettings kGeometric;
customReqAttrsRefresh("rmanSettings.rmanSettings");
rmanAddAttr "rmanSettings" rman__torattr___curveBaseWidth $base;
rmanAddAttr "rmanSettings" rman__torattr___curveTipWidth  $tip;
string $attrnm=`rmanGetAttrName customShadingGroup`;
rmanAddAttr "rmanSettings" $attrnm "";
  
select $transformNodes;
rmanEd_AttachSettings kGeometric;
}

The scripts can be tested with the following mel commands entered into Maya's script window.

    source "FULL_PATH/curveEncode.mel";
    source "FULL_PATH/curveCreate.mel";
    source "FULL_PATH/curveAddSharedAttrRI.mel";
    
    select -all;
    delete;
    string $grasses[] = curveCreate(50, <<0,2,0>>,<<0,0,0>>);
    curveAddSharedAttrRI($grasses, 0.05, 0.01);

After executing the commands shown above, generate the rib files with the following command.

    rman genrib

Now that the rib files have been produced you are ready to post-process them. The rib files that require post-processing will be found in the Maya project directory ie.

    renderman/untitled/rib/job


Decoding and Re-writing the Job.rib File

Listing 4 provides a Tcl script that will decode and modify the job.rib files.


Listing 4 (processJobFiles.tcl)


proc decode { input } {
    if { [string equal [string index $input 0] p] } {
        set out 0.[string trimleft $input p]
        append out " "
    } else {
        set out -0.[string trimleft $input n]
        append out " "
        }
    return $out
    }
    
proc processJobFiles { src_dir } {
    # Get a preliminary list of job files.
    set files [glob -nocomplain -directory $src_dir *.job.rib]
    foreach item $files {
        # Paths ending with "_attr.job.rib" or "jobCompile.job.rib"
        # will be ignored
        if { [regexp {(?w)_attr.job.rib} $item] == 0 && 
             [regexp {(?w)jobCompile.job.rib} $item] == 0} {
            set filename [file tail $item]
            set tokens [split $filename _]
            # Skip the filename if it does not have exactly 14 tokens
            if {[llength $tokens] != 14} {
                continue
                }
            # Ignore the first token it will be the curve name, such
            # as "curveShape1", also ignore the last token is will be
            # something similiar to "0.job.rib"
            set tokens [lrange $tokens 1 12]
            set N "\"N\" \[ "
            # Because RfM Pro repeats the first and last cv's
            # we must also repeat the first and last normals
            for {set i 0} {$i < 12} {incr i 3} {
                set x [lindex $tokens $i]
                set y [lindex $tokens [expr $i + 1]]
                set z [lindex $tokens [expr $i + 2]]
                set xyz "[decode $x][decode $y][decode $z]"
                if {$i == 0 || $i == 9} {
                    append N $xyz$xyz
                } else {
                    append N $xyz
                    }
                }
            append N "\]"
            # Open the rib file and append the string that specifies
            # the list of normals
            set f [open $item a]
            puts $f $N
            close $f
            }
        }
    }
  
set src PATH_TO/maya/projects/default/renderman/untitled/rib/job
set dst PATH_TO/maya/projects/default/renderman/untitled/rib/job    
  
processJobFiles $src




© 2002- Malcolm Kesson. All rights reserved.