// Shave and a Haircut // (c) 2019 Epic Games // US Patent 6720962 #include #include #include #include #include "shaveEngine.h" #include "writeRib.h" #define INSTANCE_UVS_IMPLEMENTED 0 #define PRIM_BUG_FIXED 1 #define SINGLE_RIB_FILE_SUPPORT 0 #define ERR(v,msg) if (v) { fprintf( stderr, "writeRib: Error: " msg "\n"); } #define WARN(v,msg) if (v) { fprintf( stderr, "writeRib: Warning: " msg "\n"); } #define DOFREE(p) if (*p) { free(*p); *p = NULL; } typedef struct { RtBound bbox; char *fileName; } WriteRibVoxelInfo; typedef struct { RtPointer *params; RtToken *tokens; RtFloat *widths; RtInt *vertIndices; RtPoint *verts; RtInt *vertsPerHair; RtColor *rootColors; RtColor *tipColors; RtNormal *normals; RtNormal *surfaceNormals; RtColor *vertColors; RtFloat *sCoords; RtFloat *tCoords; RtFloat *wCoords; unsigned int curveIdx; unsigned int vertIdx; unsigned int widthIdx; } WriteRibBuffers; static void addCurve( const WriteRibFlags * flags, WriteRibBuffers * buffers, int firstVertIdxIdx, int lastVertIdxIdx, const int *vertIndices, const VERT * verts, const VERT * vertColours, const VERT * rootColour, const VERT * tipColour, float hairU, float hairV, float rootRadius, float tipRadius, const VERT * hairNormal ) { VERT colourPerVert = { 0.0f, 0.0f, 0.0f }; int numHairVerts = lastVertIdxIdx - firstVertIdxIdx + 1; buffers->vertsPerHair[buffers->curveIdx] = numHairVerts; if( flags->cubicCurves ) buffers->vertsPerHair[buffers->curveIdx] += 2; // // If per-vertex colours were not supplied then we'll have to // interpolate them from the root and tip colours. // if( ( vertColours == NULL ) && ( rootColour != NULL ) && ( tipColour != NULL ) ) { float numSegs = ( float ) ( numHairVerts - 1 ); colourPerVert.x = ( tipColour->x - rootColour->x ) / numSegs; colourPerVert.y = ( tipColour->y - rootColour->y ) / numSegs; colourPerVert.z = ( tipColour->z - rootColour->z ) / numSegs; } // // Set up all the per-vertex parameter values. // int i; int j; for( i = firstVertIdxIdx; i <= lastVertIdxIdx; i++ ) { int vertIdx = vertIndices[i]; // // We use a loop to double up on the first and last CVs when // generating cubic curves. // for( j = 0; j < 2; j++ ) { buffers->verts[buffers->vertIdx][0] = verts[vertIdx].x; buffers->verts[buffers->vertIdx][1] = verts[vertIdx].y; buffers->verts[buffers->vertIdx][2] = verts[vertIdx].z; if( flags->vertexColors ) { if( vertColours ) { buffers->vertColors[buffers->vertIdx][0] = vertColours[vertIdx].x; buffers->vertColors[buffers->vertIdx][1] = vertColours[vertIdx].y; buffers->vertColors[buffers->vertIdx][2] = vertColours[vertIdx].z; } else if( rootColour && tipColour ) { float portion = ( float ) ( i - firstVertIdxIdx ); buffers->vertColors[buffers->vertIdx][0] = rootColour->x + colourPerVert.x * portion; buffers->vertColors[buffers->vertIdx][1] = rootColour->y + colourPerVert.y * portion; buffers->vertColors[buffers->vertIdx][2] = rootColour->z + colourPerVert.z * portion; } else { buffers->vertColors[buffers->vertIdx][0] = 0.5; buffers->vertColors[buffers->vertIdx][1] = 0.5; buffers->vertColors[buffers->vertIdx][2] = 0.5; } } buffers->vertIdx++; // // If this isn't the first or last cv of a // cubic curve then don't double up on it. // if( !flags->cubicCurves || ( ( i != firstVertIdxIdx ) && ( i != lastVertIdxIdx ) ) ) { break; } } } // // We need one width for each unique cv in the curve. So it doesn't // matter whether we're doing linear or cubic curves as we output the // same number of widths in both cases. // float radiusPerVert = ( tipRadius - rootRadius ) / ( float ) ( numHairVerts - 1 ); for( j = 0; j < numHairVerts; j++ ) { buffers->widths[buffers->widthIdx] = ( rootRadius + radiusPerVert * ( float ) j ) * 2.0f; if( flags->wCoords ) { // // It should be impossible to have just one vert as that would // imply no segments in the curve. Still, better safe than // sorry. // if( numHairVerts == 1 ) buffers->wCoords[buffers->widthIdx] = 0.5; else { buffers->wCoords[buffers->widthIdx] = ( float ) j / ( float ) ( numHairVerts - 1 ); } } buffers->widthIdx++; } // // Put the hair-specific info into their arrays. // if( flags->rootTipColors ) { if( rootColour ) { buffers->rootColors[buffers->curveIdx][0] = rootColour->x; buffers->rootColors[buffers->curveIdx][1] = rootColour->y; buffers->rootColors[buffers->curveIdx][2] = rootColour->z; } else if( vertColours ) { int firstVertIdx = vertIndices[firstVertIdxIdx]; buffers->rootColors[buffers->curveIdx][0] = vertColours[firstVertIdx].x; buffers->rootColors[buffers->curveIdx][1] = vertColours[firstVertIdx].y; buffers->rootColors[buffers->curveIdx][2] = vertColours[firstVertIdx].z; } if( tipColour ) { buffers->tipColors[buffers->curveIdx][0] = tipColour->x; buffers->tipColors[buffers->curveIdx][1] = tipColour->y; buffers->tipColors[buffers->curveIdx][2] = tipColour->z; } else { int lastVertIdx = vertIndices[lastVertIdxIdx]; buffers->tipColors[buffers->curveIdx][0] = vertColours[lastVertIdx].x; buffers->tipColors[buffers->curveIdx][1] = vertColours[lastVertIdx].y; buffers->tipColors[buffers->curveIdx][2] = vertColours[lastVertIdx].z; } } if( flags->surfaceNormals && hairNormal ) { buffers->surfaceNormals[buffers->curveIdx][0] = hairNormal->x; buffers->surfaceNormals[buffers->curveIdx][1] = hairNormal->y; buffers->surfaceNormals[buffers->curveIdx][2] = hairNormal->z; } if( flags->uvs ) { // // For curves, UVs are defined to be per-hair. // buffers->sCoords[buffers->curveIdx] = hairU; buffers->tCoords[buffers->curveIdx] = hairV; } buffers->curveIdx++; } static void beginFile( const WriteRibFlags * flags, const char *filename, const char *ribText ) { // // Set up the RIB file's formatting. // if( flags->binary ) { RtString format[1] = { "binary" }; RiOption( "rib", "format", ( RtPointer ) format, RI_NULL ); } else { RtString format[1] = { "ascii" }; RiOption( "rib", "format", ( RtPointer ) format, RI_NULL ); } if( flags->gzip ) { RtString str[1] = { "gzip" }; RiOption( "rib", "compression", ( RtPointer ) str, RI_NULL ); } else { RtString str[1] = { "none" }; RiOption( "rib", "compression", ( RtPointer ) str, RI_NULL ); } // // Begin the RIB file. // if( ( filename == NULL ) || ( *filename == '\0' ) ) RiBegin( RI_NULL ); else RiBegin( ( char * ) filename ); // // If there is global RIB text, output it as a verbatim record. // if( ( ribText != NULL ) && ( *ribText != '\0' ) ) RiArchiveRecord( RI_VERBATIM, "%s\n", ribText ); // // Begin the scene description. // RiTransformBegin( ); } static void clearCurves( WriteRibBuffers * buffers ) { buffers->curveIdx = 0; buffers->vertIdx = 0; buffers->widthIdx = 0; } static void endFile( ) { RiTransformEnd( ); RiEnd( ); } static void freeBuffers( WriteRibBuffers * buffers ) { // // Free up the various data buffers. // DOFREE( &buffers->verts ); DOFREE( &buffers->vertsPerHair ); DOFREE( &buffers->rootColors ); DOFREE( &buffers->tipColors ); DOFREE( &buffers->surfaceNormals ); DOFREE( &buffers->vertColors ); DOFREE( &buffers->sCoords ); DOFREE( &buffers->tCoords ); DOFREE( &buffers->vertIndices ); DOFREE( &buffers->normals ); DOFREE( &buffers->widths ); DOFREE( &buffers->wCoords ); // // Free up the parameter list buffers. // DOFREE( &buffers->params ); DOFREE( &buffers->tokens ); // // Clear the counts. // clearCurves( buffers ); } static int getNumParams( const WriteRibFlags * flags, unsigned char isInstanced ) { // // At a minimum, we will need position information for the // vertices. // int numParams = 1; if( flags->rootTipColors ) numParams += 2; if( flags->surfaceNormals ) numParams++; if( flags->vertexColors ) numParams++; if( flags->uvs ) numParams += 2; if( isInstanced ) { if( flags->normals ) numParams++; } else { numParams++; // curve width if( flags->wCoords ) numParams++; } return numParams; } static void initBuffers( WriteRibFlags * flags, WriteRibBuffers * buffers, int numHairs, int numVerts, int numFaceVerts, unsigned char isInstanced ) { // // Allocate the parameter list used to call Ri geometry functions. // int numParams = getNumParams( flags, isInstanced ); int paramIdx = 0; buffers->params = ( RtPointer * ) malloc( sizeof( RtPointer ) * numParams ); buffers->tokens = ( RtToken * ) malloc( sizeof( RtToken ) * numParams ); buffers->widths = NULL; buffers->vertIndices = NULL; buffers->verts = NULL; buffers->vertsPerHair = NULL; buffers->rootColors = NULL; buffers->tipColors = NULL; buffers->normals = NULL; buffers->surfaceNormals = NULL; buffers->vertColors = NULL; buffers->sCoords = NULL; buffers->tCoords = NULL; buffers->wCoords = NULL; buffers->curveIdx = 0; buffers->widthIdx = 0; buffers->vertIdx = 0; // // Allocate buffers and fill in the parameter list. // if( !isInstanced ) { int numWidths = numVerts; buffers->widths = ( RtFloat * ) malloc( sizeof( RtFloat ) * numWidths ); buffers->tokens[1] = RI_WIDTH; buffers->params[1] = buffers->widths; paramIdx = 2; // // If we are creating cubic curves, then we need to double up on the // start and end cvs to anchor the hair's endpoints. // // Note that we only supply widths for each unique cv, so we do not // want to increment the width count to include these extra cvs. // if( flags->cubicCurves ) numVerts += 2 * numHairs; // // For curves, the number of face-verts and the number of verts // should be the same. // numFaceVerts = numVerts; } else { buffers->vertIndices = ( RtInt * ) malloc( sizeof( RtInt ) * numFaceVerts ); paramIdx = 1; } buffers->verts = ( RtPoint * ) malloc( sizeof( RtPoint ) * numVerts ); buffers->tokens[0] = RI_P; buffers->params[0] = buffers->verts; buffers->vertsPerHair = ( RtInt * ) malloc( sizeof( RtInt ) * numHairs ); if( flags->rootTipColors ) { buffers->rootColors = ( RtColor * ) malloc( sizeof( RtColor ) * numHairs ); buffers->tipColors = ( RtColor * ) malloc( sizeof( RtColor ) * numHairs ); buffers->tokens[paramIdx] = "rootcolor"; buffers->params[paramIdx] = buffers->rootColors; paramIdx++; buffers->tokens[paramIdx] = "tipcolor"; buffers->params[paramIdx] = buffers->tipColors; paramIdx++; } // // We only do vertex normals for instance geometry. // if( flags->normals && isInstanced ) { buffers->normals = ( RtNormal * ) malloc( sizeof( RtNormal ) * numVerts ); buffers->tokens[paramIdx] = RI_N; buffers->params[paramIdx] = buffers->normals; paramIdx++; } if (isInstanced==0) if( flags->surfaceNormals ) { buffers->surfaceNormals = ( RtNormal * ) malloc( sizeof( RtNormal ) * numHairs ); buffers->tokens[paramIdx] = "N_Srf"; buffers->params[paramIdx] = buffers->surfaceNormals; paramIdx++; } if( flags->vertexColors ) { // // For instance geometry we output a colour per face-vert. // For hair curves we output a colour per vertex. // if( isInstanced ) buffers->vertColors = ( RtColor * ) malloc( sizeof( RtColor ) * numFaceVerts ); else buffers->vertColors = ( RtColor * ) malloc( sizeof( RtColor ) * numVerts ); buffers->tokens[paramIdx] = RI_CS; buffers->params[paramIdx] = buffers->vertColors; paramIdx++; } if( flags->uvs ) { // // For instance geometry we output the per face-vert UVs from the // original geometry. // // For hair curves the entire curve acquires the UV of the // point on the growth surface to which it is rooted, so the UVs // are per-curve. // if( isInstanced ) { buffers->sCoords = ( RtFloat * ) malloc( sizeof( RtFloat ) * numFaceVerts ); buffers->tCoords = ( RtFloat * ) malloc( sizeof( RtFloat ) * numFaceVerts ); } else { buffers->sCoords = ( RtFloat * ) malloc( sizeof( RtFloat ) * numHairs ); buffers->tCoords = ( RtFloat * ) malloc( sizeof( RtFloat ) * numHairs ); } buffers->tokens[paramIdx] = RI_S; buffers->params[paramIdx] = buffers->sCoords; paramIdx++; buffers->tokens[paramIdx] = RI_T; buffers->params[paramIdx] = buffers->tCoords; paramIdx++; } if( !isInstanced ) { if( flags->wCoords ) { buffers->wCoords = ( RtFloat * ) malloc( sizeof( RtFloat ) * numVerts ); buffers->tokens[paramIdx] = "w"; buffers->params[paramIdx] = buffers->wCoords; paramIdx++; } } clearCurves( buffers ); } static void myFree( void *ptr ) { } static void outputCurves( WriteRibFlags * flags, WriteRibBuffers * buffers, int numCurves, float shutterOpen, float shutterClose, unsigned char isShutterOpen ) { int numParams = getNumParams( flags, 0 ); if( flags->motionBlur && isShutterOpen ) RiMotionBegin( 2, shutterOpen, shutterClose ); RiCurvesV( ( flags->cubicCurves ? RI_CUBIC : RI_LINEAR ), numCurves, buffers->vertsPerHair, RI_NONPERIODIC, numParams, buffers->tokens, buffers->params ); if( flags->motionBlur && !isShutterOpen ) RiMotionEnd( ); } static void outputInstanceGeom( WriteRibFlags * flags, float shutterOpen, float shutterClose, unsigned char isShutterOpen, int numInsts, int numFaces, int numVerts, int numFaceVerts, const int *faceList, const int *faceStarts, const int *faceEnds, const VERT * verts, const VERT * vertColors, const VERT * vertNormals, const VERT * growthSurfNormal, int index ) { WriteRibBuffers buffers; int facesPerInst; int faceVertsPerInst; int i; int numParams; if( ( numInsts < 1 ) || ( numFaces < 1 ) || ( numVerts < 1 ) || ( numFaceVerts < 1 ) || ( faceList == NULL ) || ( faceStarts == NULL ) || ( faceEnds == NULL ) || ( verts == NULL ) ) { return; } facesPerInst = numFaces / numInsts; faceVertsPerInst = numFaceVerts / numInsts; // // We can only do those parameters for which we have been passed // appropriate data. // WriteRibFlags savedFlags; memcpy( &savedFlags, flags, sizeof( savedFlags ) ); flags->normals = flags->normals && ( vertNormals != NULL ); flags->rootTipColors = 0; flags->surfaceNormals = flags->surfaceNormals && ( growthSurfNormal != NULL ); flags->vertexColors = flags->vertexColors && ( vertColors != NULL ); #if !INSTANCE_UVS_IMPLEMENTED flags->uvs = 0; #endif // // Now that we've adjusted some of the settings, get the number of // parameters which we'll be dumping and initialize the data buffers. // numParams = getNumParams( flags, 1 ); initBuffers( flags, &buffers, numFaces, numVerts, numFaceVerts, 1 ); for( i = 0; i < numFaces; i++ ) buffers.vertsPerHair[i] = faceEnds[i] - faceStarts[i]; for( i = 0; i < numFaceVerts; i++ ) buffers.vertIndices[i] = faceList[i]; for( i = 0; i < numVerts; i++ ) { buffers.verts[i][0] = verts[i].x; buffers.verts[i][1] = verts[i].y; buffers.verts[i][2] = verts[i].z; } if( flags->normals ) { for( i = 0; i < numVerts; i++ ) { buffers.normals[i][0] = vertNormals[i].x; buffers.normals[i][1] = vertNormals[i].y; buffers.normals[i][2] = vertNormals[i].z; } } if (0==1) if( flags->surfaceNormals ) { // // At the moment, we only ever get passed one surface normal which // must then be applied to all the faces (which will generally be // multiple strands of the same hair). // for( i = 0; i < numFaces; i++ ) { buffers.surfaceNormals[i][0] = growthSurfNormal->x; buffers.surfaceNormals[i][1] = growthSurfNormal->y; buffers.surfaceNormals[i][2] = growthSurfNormal->z; } } if( flags->vertexColors ) { for( i = 0; i < numFaceVerts; i++ ) { buffers.vertColors[i][0] = vertColors[faceList[i]].x; buffers.vertColors[i][1] = vertColors[faceList[i]].y; buffers.vertColors[i][2] = vertColors[faceList[i]].z; } } if( flags->uvs ) { #if INSTANCE_UVS_IMPLEMENTED // // %%% Get 'instUVs' from somewhere. Assume that it contains one // UV per face-vert for one instance. // VERT *instUVs = // ???; int j; int uvIdx = 0; for( i = 0; i < numInsts; i++ ) { for( j = 0; j < faceVertsPerInst; j++ ) { buffers.sCoords[uvIdx] = instUVs[j].x; buffers.tCoords[uvIdx] = instUVs[j].y; uvIdx++; } } #endif } if( flags->motionBlur && isShutterOpen ) RiMotionBegin( 2, shutterOpen, shutterClose ); RiPointsPolygonsV( numFaces, buffers.vertsPerHair, buffers.vertIndices, numParams, buffers.tokens, buffers.params ); if( flags->motionBlur && !isShutterOpen ) RiMotionEnd( ); freeBuffers( &buffers ); // // Restore the flags. // memcpy( flags, &savedFlags, sizeof( savedFlags ) ); } // // Output those declarations which will be the same for all objects, // regardless of whether they are hairs or instances. // static void outputGlobalDecls( const WriteRibFlags * flags ) { // // Output declarations. // if( flags->cubicCurves ) RiBasis( RiCatmullRomBasis, 1, RiCatmullRomBasis, 1 ); RiDeclare( RI_WIDTH, "varying float" ); if( flags->rootTipColors ) { RiDeclare( "rootcolor", "uniform color" ); RiDeclare( "tipcolor", "uniform color" ); } if( flags->surfaceNormals ) RiDeclare( "N_Srf", "uniform normal" ); // // The w texture coord runs up the length of the hair, so it's // different for each vertex along the hair. // if( flags->wCoords ) RiDeclare( "w", "varying float" ); } static void outputObjSpecificDecls( const WriteRibFlags * flags, unsigned char isInstanced ) { if( flags->vertexColors ) { // // For instance geometry we output a colour per face-vert. // For hair curves we output a colour per vert. // if( isInstanced ) RiDeclare( RI_CS, "facevarying color" ); else RiDeclare( RI_CS, "vertex color" ); } if( flags->uvs ) { // // For instance geometry we output the per face-vert UVs from the // original geometry. // // For hair curves the entire curve acquires the UV of the point on // the growth surface to which it is rooted, so the UVs are per // curve. // if( isInstanced ) { RiDeclare( RI_S, "facevarying float" ); RiDeclare( RI_T, "facevarying float" ); } else { RiDeclare( RI_S, "uniform float" ); RiDeclare( RI_T, "uniform float" ); } } } // // Write out a RIB file for each node's voxels, and have the main file // include them all as delayed read archives. // // 'ribFile' gives the name of the main RIB file to be generated (e.g. // 'myFile.rib'). Per-voxel RIB files will use that name as a base (e.g. // 'myFile_1_5.rib'). If 'ribFile' is NULL then the main RIB file will be // written to the console and the per-voxel RIB files will use an empty // base name (e.g. '_1_5.rib'). // // 'numNodesToWrite' gives the number of hair nodes from 'draFile' for // which RIB files are to be generated. // // 'nodesToWrite' is an array of 'numNodesToWrite' elements containing the // names of the hair nodes inside 'draFile' for which RIB files are to be // generated. The array is mandatory and must contain 'numNodesToWrite' // elements. // // 'numInsts' is an array of 'numNodesToWrite' elements giving the number // of instances in each hair node, with zero indicating a non-instanced // node. If none of the nodes are instanced the array may be omitted and // NULL passed in its stead. // // 'nodeRibText' is an array of 'numNodesToWrite' elements giving the // node-specific RIB text for each node. If a given node has no RIB text // it's entry may be NULL or the empty string (""). If none of the nodes // have node-specific RIB text then the array may be omitted and NULL // passed in its stead. // // Returns 1 on success, 0 on failure. // static int writeMultipleRibs( WriteRibFlags * flags, const char *ribFile, const char *draFile, float shutterOpen, float shutterClose, int numNodesToWrite, char **nodesToWrite, const int *facesPerInst, char **ribTextPerNode, const char *globalRibText ) { char *baseFileName = ""; int baseFileNameLen = 0; const unsigned int numBlurPasses = ( flags->motionBlur ? 2 : 1 ); int numVoxels; int sn; WriteRibBuffers buffers; WriteRibVoxelInfo ***voxelInfo; voxelInfo = ( WriteRibVoxelInfo *** ) malloc( sizeof( WriteRibVoxelInfo ** ) * numNodesToWrite ); // // Initialize Shave's PRIM library from the archive. // PRIMlogin( ); numVoxels = PRIMinit_hairstack( ( char * ) draFile ); if( numVoxels < 1 ) { WARN( flags->verbose, "archive file contains no voxels." ); return 0; } // // Strip the extension from 'ribFile' to get the base file name. // if( ribFile ) { char *tempStr; baseFileName = strdup( ribFile ); tempStr = strrchr( baseFileName, '.' ); if( tempStr && ( strrchr( baseFileName, '/' ) < tempStr ) && ( strrchr( baseFileName, '\\' ) < tempStr ) ) { *tempStr = '\0'; } baseFileNameLen = strlen( baseFileName ); } // // Process each node separately. // for( sn = 0; sn < numNodesToWrite; sn++ ) { int i; int numInstFaces; int nodeNameLen; const char *nodeRibText; voxelInfo[sn] = NULL; if( nodesToWrite[sn] == NULL ) { WARN( flags->verbose, "one or more node names are missing." ); continue; } nodeNameLen = strlen( nodesToWrite[sn] ); numInstFaces = ( facesPerInst ? facesPerInst[sn] : 0 ); nodeRibText = ( ribTextPerNode ? ribTextPerNode[sn] : NULL ); voxelInfo[sn] = ( WriteRibVoxelInfo ** ) malloc( sizeof( WriteRibVoxelInfo * ) * numVoxels ); // // Step through each voxel for this node and output its hair // into a separate RIB file. // for( i = 0; i < numVoxels; i++ ) { VERT bbMin; VERT bbMax; unsigned int blurPass; int j; int numInsts = 0; // // Get the hair which this shaveHairShape has within this voxel. // HAIRTYPE hair; PRIMfetch_voxel_by_name( i, nodesToWrite[sn], &hair, 0 ); voxelInfo[sn][i] = NULL; // // If there isn't any hair, then skip this voxel. // if( hair.totalfaces == 0 ) continue; voxelInfo[sn][i] = ( WriteRibVoxelInfo * ) malloc( sizeof( WriteRibVoxelInfo ) ); if( numInstFaces > 0 ) numInsts = hair.totalfaces / numInstFaces; // // Create the name for this voxel's RIB file and save it for // later output to the main RIB file. // voxelInfo[sn][i]->fileName = ( char * ) malloc( baseFileNameLen + nodeNameLen + 15 ); sprintf( voxelInfo[sn][i]->fileName, "%s_%s_%d.rib", baseFileName, nodesToWrite[sn], i ); // // Get the voxel's bounding box and save it for later output to // the main RIB file. // PRIMfetch_bbox( i, &bbMin, &bbMax ); voxelInfo[sn][i]->bbox[0] = bbMin.x; voxelInfo[sn][i]->bbox[1] = bbMax.x; voxelInfo[sn][i]->bbox[2] = bbMin.y; voxelInfo[sn][i]->bbox[3] = bbMax.y; voxelInfo[sn][i]->bbox[4] = bbMin.z; voxelInfo[sn][i]->bbox[5] = bbMax.z; // // Start the RIB file for this voxel. // beginFile( flags, voxelInfo[sn][i]->fileName, nodeRibText ); outputGlobalDecls( flags ); outputObjSpecificDecls( flags, numInsts > 0 ); if( numInsts == 0 ) { initBuffers( flags, &buffers, hair.totalfaces, hair.totalverts, hair.totalfverts, 0 ); } // // If motion blur is on, we need to create separate motion // blocks for shutter open and close. We do this by making two // passes over the data. // for( blurPass = 0; blurPass < numBlurPasses; blurPass++ ) { // // If this is the second pass for motion blur, add the // velocities to the vertex positions to get their // positions at shutter close. // if( blurPass == 1 ) { for( j = 0; j < hair.totalverts; j++ ) { hair.v[j].x += hair.velocity[j].x; hair.v[j].y += hair.velocity[j].y; hair.v[j].z += hair.velocity[j].z; } } // // At this point, curves and instances must part company as // they are handled quite differently. // if( numInsts > 0 ) { #if !PRIM_BUG_FIXED // // %%% There is a bug in the PRIM functions such that // they don't return the correct face-vertex counts // or lists for instances, so we have to do those // ourselves. // int facesPerInst = instanceMeshFn.numPolygons( ); int faceVertsPerInst = instanceMeshFn.numFaceVertices( ); int numInsts = hair.totalfaces / facesPerInst; int numFaceVerts = numInsts * faceVertsPerInst; int vertsPerInst = instanceMeshFn.numVertices( ); if( hair.totalfverts < numFaceVerts ) { if( hair.facelist ) free( hair.facelist ); if( hair.face_start ) free( hair.face_start ); if( hair.face_end ) free( hair.face_end ); hair.totalfverts = numFaceVerts; hair.facelist = ( int * ) malloc( sizeof( int ) * numFaceVerts ); hair.face_start = ( int * ) malloc( sizeof( int ) * hair.totalfaces + 1 ); hair.face_end = ( int * ) malloc( sizeof( int ) * hair.totalfaces + 1 ); int faceStart = 0; int faceEnd = 0; int faceNum; int instNum; for( faceNum = 0; faceNum < facesPerInst; faceNum++ ) { MIntArray vertIds; instanceMeshFn.getPolygonVertices( faceNum, vertIds ); faceEnd = faceStart + ( int ) vertIds.length( ); for( instNum = 0; instNum < numInsts; instNum++ ) { int vertOffset = instNum * vertsPerInst; int faceVertOffset = instNum * faceVertsPerInst; int fv; for( fv = 0; fv < ( int ) vertIds.length( ); fv++ ) { hair.facelist[faceVertOffset + faceStart + fv] = vertOffset + vertIds[fv]; } hair.face_start[instNum * facesPerInst + faceNum] = faceVertOffset + faceStart; hair.face_end[instNum * facesPerInst + faceNum] = faceVertOffset + faceEnd; } faceStart = faceEnd; } hair.face_start[hair.totalfaces] = hair.face_end[hair.totalfaces - 1]; hair.face_end[hair.totalfaces] = hair.face_end[hair.totalfaces - 1]; } #endif outputInstanceGeom( flags, shutterOpen, shutterClose, ( blurPass == 0 ), numInsts, hair.totalfaces, hair.totalverts, hair.totalfverts, hair.facelist, hair.face_start, hair.face_end, hair.v, hair.color, hair.surfNorm, NULL,hair.index[0] ); } else { int hairIdx; // // Clear out any curve data from previous passes. // clearCurves( &buffers ); // // Fill in the arrays for the hairs. // for( hairIdx = 0; hairIdx < hair.totalfaces; hairIdx++ ) { int faceStart = hair.face_start[hairIdx]; int faceEnd = hair.face_end[hairIdx] - 1; addCurve( flags, &buffers, faceStart, faceEnd, hair.facelist, hair.v, NULL, &hair.colorroot[hairIdx], &hair.colortip[hairIdx], hair.uvw[hair.facelist[faceStart]].x, hair.uvw[hair.facelist[faceStart]].y, hair.radiusroot[hairIdx], hair.radiustip[hairIdx], &hair.surfNorm[hairIdx] ); } outputCurves( flags, &buffers, hair.totalfaces, shutterOpen, shutterClose, ( blurPass == 0 ) ); } } endFile( ); if( numInsts == 0 ) freeBuffers( &buffers ); PRIMfree_hairtype( &hair ); } } // // Create the main RIB file. // beginFile( flags, ribFile, globalRibText ); for( sn = 0; sn < numNodesToWrite; sn++ ) { // // Output delayed read archives for each of the node's voxel // files. // RtString args[1]; unsigned int v; if( voxelInfo[sn] == NULL ) continue; for( v = 0; v < numVoxels; v++ ) { WriteRibVoxelInfo *voxel = voxelInfo[sn][v]; char *filename; if( voxel == NULL ) continue; filename = voxel->fileName; // // If the fullPaths option was not specified, then strip the // path from the filename. // if( !flags->fullPaths ) { char *temp = strrchr( filename, '/' ); if( temp ) filename = temp + 1; temp = strrchr( filename, '\\' ); if( temp ) filename = temp + 1; } args[0] = filename; RiProcedural( ( RtPointer ) args, voxel->bbox, RiProcDelayedReadArchive, myFree ); free( voxel->fileName ); free( voxel ); } free( voxelInfo[sn] ); } free( voxelInfo ); endFile( ); PRIMlogout( ); if( ribFile ) free( baseFileName ); return 1; } #if SINGLE_RIB_FILE_SUPPORT static int writeSingleRib( WriteRibFlags * flags, MObjectArray shaveHairShapes ) { WriteRibBuffers buffers; // // Start the file. // beginFile( flags, fFilename, globalRibStuff ); outputGlobalDecls( flags ); // // Output the hair for each shave node. // unsigned int sn; unsigned int blurPass; const unsigned int numBlurPasses = ( fDoMotionBlur ? 2 : 1 ); for( sn = 0; sn < shaveHairShapes.length( ); sn++ ) { MFnDependencyNode nodeFn( shaveHairShapes[sn] ); shaveHairShape *theShaveNode = ( shaveHairShape * ) nodeFn.userNode( ); bool isInstanced = theShaveNode->isInstanced( ); theShaveNode->makeCurrent( ); outputObjSpecificDecls( isInstanced ); if( fDoMotionBlur ) { // // If motion blur is on, and we have multiple shaveHairShapes, // then the previous shaveHairShape will have left us at the // shutterClose time, in which case we need to move back to // shutterOpen. // if( sn > 0 ) shaveUtil::setFrame( fShutterOpen ); // // Generate this shaveHairShape's texture info. // // (When motion blur is off, texture info for all of the nodes // is generated at once, back near the start of this method.) // MObjectArray shaveHairShapeAsAnArray; shaveHairShapeAsAnArray.append( shaveHairShapes[sn] ); initTexInfoLookup( shaveHairShapeAsAnArray, 0, fUVSet ); } // // If this shaveHairShape has RIB text, output it as a verbatim // record. // MPlug plug = nodeFn.findPlug( shaveHairShape::ribInsert ); MString nodePreamble; plug.getValue( nodePreamble ); if( nodePreamble != "" ) RiArchiveRecord( RI_VERBATIM, "%s\n", nodePreamble.asChar( ) ); SHAVENODE *hairNode = theShaveNode->getHairNode( ); int hairGroup = theShaveNode->getHairGroup( ); int numHairs = hairNode->shavep.haircount[hairGroup]; int numPasses = hairNode->shavep.passes[hairGroup]; int numSegs = hairNode->shavep.segs[hairGroup]; int pass; WFTYPE wf; init_geomWF( &wf ); // // If we're generating the hair as curves, we need to figure out // how big our arrays have to be. // CURVEINFO curveInfo; MObject instanceMesh; unsigned int totalCurves = 0; unsigned int totalCVs = 0; if( isInstanced ) { // // Get the original instance mesh. // plug = nodeFn.findPlug( shaveHairShape::instanceMesh ); plug.getValue( instanceMesh ); } else { for( pass = 0; pass < numPasses; pass++ ) { int hair; for( hair = 0; hair < numHairs; hair++ ) { /// SHAVEmake_a_curve( /// pass, hairGroup, hair, 1, &wf, &curveInfo /// ); SHAVEmake_a_curveROOT( pass, hairGroup, hair, &wf, &curveInfo ); // // Make sure that we're keeping this hair (i.e. // it's not being eliminated by a cutmap, etc). // if( !curveInfo.killme ) { // // Each face is effectively a separate hair. // totalCurves += wf.totalfaces; } } } // // If we ended up with no curves, then skip this shaveHairShape. // if( totalCurves == 0 ) continue; totalCVs = totalCurves * ( numSegs + 1 ); initBuffers( flags, buffers, totalCurves, totalCVs, totalCVs, 0 ); } for( blurPass = 0; blurPass < numBlurPasses; blurPass++ ) { // // If this is the second pass for motion blur, move to the // shutter close time. // if( blurPass == 1 ) { shaveUtil::setFrame( fShutterClose ); // // Changing time causes all shaveHairShapes to reload // themselves, which means that whichever shaveHairShape was // last is the one which will now be loaded into Shave. So // let's make sure that *this* shaveHairShape is the one that's // loaded. // theShaveNode->makeCurrent( ); } // // At this point, curves and polys must part company as they are // handled quite differently. // if( isInstanced ) { // // Generate each instance as a poly. // for( pass = 0; pass < numPasses; pass++ ) { int hair; for( hair = 0; hair < numHairs; hair++ ) { vert col; /// SHAVEmake_a_curve( /// pass, hairGroup, hair, 1, &wf, &curveInfo /// ); SHAVEmake_a_curveROOT( pass, hairGroup, hair, &wf, &curveInfo ); col=wf.color[0]; if( curveInfo.killme ) continue; SHAVEmake_a_hair( pass, hairGroup, hair, numSegs, &wf ); // // Note that currently motion blur is not supported // when doing geometry output in non-voxel mode, // because the overhead is simply too great. // // So the second arg in the call below is // meaningless. Which is a good thing because // otherwise we would end up generating a whole // bunch of RiMotionBegin's followed by a bunch of // RiMotionEnd's, rather than having them be // properly paired up. // // for (xx=0;xxgetHairGroup( ) == 4 ) { u = curveInfo.wgt[0]; v = curveInfo.wgt[1]; } else { u = curveInfo.u; v = curveInfo.v; } addCurve( flags, &buffers, wf.face_start[face], wf.face_end[face] - 1, wf.facelist, wf.v, wf.color, NULL, NULL, u, v, curveInfo.baserad, curveInfo.tiprad, &curveInfo.norm ); } } } outputCurves( flags, &buffers, totalCurves, shutterOpen, shutterClose, ( blurPass == 0 ) ); } // end of curves vs. polys split } // end of blur pass loop // // If we were generating curves, then clean up the buffers we // created. // if( !isInstanced ) freeBuffers( &buffers ); } // end of shaveHairShape loop endFile( ); return st; } #endif // // Write a RIB file for the specified 'draFile'. // // If 'ribFile' is NULL then output to stdout. // int writeRib( const WriteRibFlags * origFlags, const char *ribFile, const char *draFile, float shutterOpen, float shutterClose, int numNodesToWrite, char **nodesToWrite, const int *facesPerInst, char **nodeRibText, const char *globalRibText ) { WriteRibFlags flags; // // Make a copy of the flags which we can modify to adjust for non-fatal // errors. // memcpy( &flags, origFlags, sizeof( flags ) ); if( flags.gzip && !flags.binary ) { fprintf( stderr, "writeRib: Warning: 'gzip' is only valid with 'binary'.\n" ); flags.gzip = 0; } if( numNodesToWrite < 1 ) { WARN( flags.verbose, "no nodes specified." ); return 1; } if( nodesToWrite == NULL ) { ERR( flags.verbose, "no node names supplied" ); return 0; } if( flags.verbose ) { if( ribFile ) fprintf( stderr, "Writing rib to file '%s' ...\n", ribFile ); else fprintf( stderr, "Writing rib to console ...\n" ); } #if !SINGLE_RIB_FILE_SUPPORT // // We don't currently support outputting to a single RIB file, so let's // force it to use multiple files. // flags.voxels = 1; #endif // // Generate the rib file(s). // if( flags.voxels ) { writeMultipleRibs( &flags, ribFile, draFile, shutterOpen, shutterClose, numNodesToWrite, nodesToWrite, facesPerInst, nodeRibText, globalRibText ); } #if SINGLE_RIB_FILE_SUPPORT else { writeSingleRib( &flags, ribFile, draFile, shutterOpen, shutterClose, numNodesToWrite, nodesToWrite, facesPerInst, nodeRibText globalRibText, ); } #endif if( flags.verbose ) fprintf( stderr, "Done writing rib file.\n" ); return 1; }