/*
Mesh2Blobby_Rif.cpp
Malcolm Kesson
Jan 28 2016
Modified Nov 21 2017 to read a custom attribute for the randomization
of the sizes of the blobs. The attribute can be assigned to the shape
node of a mesh as a Pre-Shape MEL command. For example,
    RiAttribute("user", "float[2] _blob_range", 0.1, 0.4);
Refer to,
    http://fundza.com/devkit/rifplugins/mesh2blobby/index.html
for information about using this rif.
*/
#include <RifPlugin.h>
#include <RixInterfaces.h>
#include <RifFilter.h>
#include <ri.h>
#include <list>
#include <string>
#include <cstdlib>
#include <rx.h>
  
class Mesh2Blobby_Rif : public RifPlugin {
    public:
              Mesh2Blobby_Rif();
    virtual ~Mesh2Blobby_Rif() { }
    virtual RifFilter &GetFilter();
    
    private:
        RifFilter       m_filters;
        RixRenderState *m_rstate;
        
        // Callbacks_____________________________________________________________
        static RtVoid   pointsGeneralPolygonsV(RtInt npolys, RtInt *nloops, RtInt *nverts, 
                                RtInt* verts, RtInt n, RtToken nms[], RtPointer vals[]);        
        static RtVoid   hierarchicalSubdivisionMeshV(RtToken mask, RtInt nf,
                                RtInt nverts[], RtInt verts[], RtInt nt, RtToken tags[],
                                RtInt nargs[], RtInt intargs[], RtFloat floatargs[],
                                RtToken stringargs[], RtInt, RtToken[], RtPointer[]);
        // Utilities_____________________________________________________________
        static RtPoint    *getVerticesPtr(RtInt paramNum, RtToken paramNames[], RtPointer paramVals[]);
        static RtInt    countVertices(RtInt npolys, RtInt nvertices[], RtInt vertices[]);
        static RtVoid     writeBlobby(RtInt numverts, RtPoint *vertices, RtFloat minSize, RtFloat maxSize);
        virtual int        getBlobRange(std::string name, RtFloat    result[2]);
        virtual RtFloat getIdentifierId();
        static RtFloat    randBetween(RtFloat min, RtFloat max);
    };
//_______________________________________________________________________
// Entry point called by PRMan.
RifPlugin *RifPluginManufacture(int argc, char **argv) {
    return new Mesh2Blobby_Rif();
    }
        
//_______________________________________________________________________
// Constructor
Mesh2Blobby_Rif::Mesh2Blobby_Rif() {
    m_filters.PointsGeneralPolygonsV = pointsGeneralPolygonsV;
    m_filters.HierarchicalSubdivisionMeshV = hierarchicalSubdivisionMeshV;
    m_filters.Filtering = RifFilter::k_Continue;
    
    RixContext  *rixContext = RxGetRixContext();
    m_rstate = (RixRenderState*) rixContext->GetRixInterface(k_RixRenderState);
    }
  
//_______________________________________________________________________
RifFilter& Mesh2Blobby_Rif::GetFilter() { 
    return m_filters; 
    }
//_______________________________________________________________________
// Callback:
RtVoid Mesh2Blobby_Rif::pointsGeneralPolygonsV(RtInt npolys, RtInt* nloops, RtInt nverts[], RtInt verts[], 
                                     RtInt paramNum, RtToken paramNames[], RtPointer paramVals[]) {
    Mesh2Blobby_Rif *self = static_cast<Mesh2Blobby_Rif*> (RifGetCurrentPlugin());
  
    RtFloat    blobsizes[2];
    int result = self->getBlobRange("user:blob_range", blobsizes);
    //if(result)
    //    std::cout << "user:blob_range = " << blobsizes[0] << "  "  << blobsizes[1] << "\n";
    if(result == 0) {
        RiPointsGeneralPolygonsV(npolys, nloops, nverts, verts, 
                                 paramNum, paramNames, paramVals);
        return;
        }
    int numverts = countVertices(npolys, nverts, verts);
    RtPoint *vertices = getVerticesPtr(paramNum, paramNames, paramVals);
    writeBlobby(numverts, vertices, blobsizes[0], blobsizes[1]);
    }
//_______________________________________________________________________
// Callback:
RtVoid Mesh2Blobby_Rif::hierarchicalSubdivisionMeshV(RtToken mask, 
                    RtInt nf, RtInt nverts[], RtInt verts[], 
                    RtInt nt, RtToken tags[], RtInt nargs[], RtInt intargs[], RtFloat floatargs[],
                    RtToken stringargs[], RtInt paramNum, RtToken paramNames[], RtPointer paramVals[]) {
    Mesh2Blobby_Rif *self = static_cast<Mesh2Blobby_Rif*> (RifGetCurrentPlugin());
    RtFloat    blobsizes[2];
    int result = self->getBlobRange("user:blob_range", blobsizes);
    //if(result)
    //    std::cout << "user:blob_range = " << blobsizes[0] << "  "  << blobsizes[1] << "\n";
    if(result == 0) {
        RiHierarchicalSubdivisionMeshV(mask, nf, nverts, verts, nt, tags, nargs, intargs, floatargs,
                                        stringargs, paramNum, paramNames, paramVals);
        return;
        }
    int numverts = countVertices(nf, nverts, verts);
    RtPoint *vertices = getVerticesPtr(paramNum, paramNames, paramVals);
    writeBlobby(numverts, vertices, blobsizes[0], blobsizes[1]);
    }
//_______________________________________________________________________
// Utility:
// Given the number of points in a vertex array and a scaling factor this 
// method emits a RiBlobby. For example, if "numverts" is 100 this method 
// will output a blobby consisting of 100 merged blobs.
void Mesh2Blobby_Rif::writeBlobby(RtInt numverts, RtPoint *vertices, RtFloat minSize, RtFloat maxSize) {
    Mesh2Blobby_Rif *self = static_cast<Mesh2Blobby_Rif*> (RifGetCurrentPlugin());
    RtFloat id = self->getIdentifierId();
    std::srand((int)id);
    
    RtInt     numBlobs = numverts;
    RtInt     numEllipsoidCodes = numBlobs * 2;
    RtInt     numBlendingCodes = 2 + numBlobs;
    RtInt    numTotalCodes = numEllipsoidCodes + numBlendingCodes;
    RtInt     codes[numTotalCodes];
    int     mat_index = 0, n = 0;
    while(n < numEllipsoidCodes) {
        codes[n] = 1001;
        codes[n+1] = mat_index;
        mat_index += 16;
        n += 2;
        }
    // Blending 
    codes[n++] = 0;            // opcode to additively blend all blobs
    codes[n++] = numBlobs;    // number of blobs to blend
    
    RtInt index = 0;
    while(n < numTotalCodes)
        codes[n++] = index++;    
    RtInt     numFloats = 16 * numBlobs;
    RtFloat mat[numFloats]; // 16 values per matrix
    RtFloat *fPtr = (RtFloat*)vertices;
    RtFloat size;
    for(int n = 0; n < numBlobs * 16; n += 16) {
        size = randBetween(minSize, maxSize);
        mat[n] = size;  
        mat[n+1] = mat[n+2] = mat[n+3] = 0;
        mat[n+4] = 0;   
        mat[n+5] = size;     
        mat[n+6] = mat[n+7] = 0;
        mat[n+8] = mat[n+9] = 0; 
        mat[n+10] = size; 
        mat[n+11] = 0;
        mat[n+12] = *fPtr++; 
        mat[n+13] = *fPtr++; 
        mat[n+14] = *fPtr++;
        mat[n+15] = 1;
        }
    RtInt nstrings = 1;
    char *str[1];
    str[0] = (char*)"\0";
    RiBlobby(numBlobs, numTotalCodes, codes, numFloats, mat, nstrings, str, RI_NULL);
    }
  
//_______________________________________________________________________
// Utility:
// The writeBlobby() method must know the number of number of blobs - a value we derive
// from the number of unique vertices specified by the rib statements "PointsGeneralPolygons"
// and "HierarchicalSubdivisionMesh". Many of the indices in their nvertices array 
// are duplicates but the use of std::list.unique() ensures we a correct count.
RtInt Mesh2Blobby_Rif::countVertices(RtInt npolys, RtInt* nvertices, RtInt* vertices) {
    // Get the raw indices:
    int vertCount = 0;
    for(int n = 0; n < npolys; n++)
        vertCount += nvertices[n];
    // Extract the unique indices:
    std::list<int> int_list;
    for(int n = 0; n < vertCount; n++)
        int_list.push_back(vertices[n]);
    int_list.sort();
    int_list.unique();
    return int_list.size();
    }
    
//_______________________________________________________________________
// Utility:
// Scans the paramNames (token names) until RI_P ("P") is found,
// then returns a pointer (address) to the first vertex of the points array.
RtPoint* Mesh2Blobby_Rif::getVerticesPtr(RtInt paramNum, RtToken paramNames[], RtPointer paramVals[]) {
    for(int n = 0; n < paramNum; n++) {
        if(paramNames[n] == RI_P || strcmp(paramNames[n], RI_P) == 0)
            return (RtPoint*)paramVals[n];
        }
    return NULL;
    }
//_______________________________________________________________________
// Utility:
// Returns two values of the "user:blob_range" attribute. For example, in RIB
// the attribute might be,
//    Attribute "user" "float[2] blob_range" [0.9 1]
// This attr can be added via a Pre-Shape MEL, for example,
//    RiAttribute("user", "float[2] blob_range", 0.1, 0.4);
int Mesh2Blobby_Rif::getBlobRange(std::string name, RtFloat    result_range[2]) {
    RtInt     resultCount, resultError, resultLen = sizeof(int*);
    RixRenderState::Type resultType;
    resultError = m_rstate->GetAttribute(name.c_str(), result_range, resultLen,
                        &resultType, &resultCount);
    if(resultError == 0)
        return 1;
    return 0;
    }
//_______________________________________________________________________
// Utility:
// Returns the value of,
//    Attribute "identifier" "float id" [2]
RtFloat Mesh2Blobby_Rif::getIdentifierId() {
    std::string name = "identifier:id";
    RtFloat    result_id = -1;
    RtInt     resultCount, resultError, resultLen = sizeof(int*);
    RixRenderState::Type resultType;
    resultError = m_rstate->GetAttribute(name.c_str(), &result_id, resultLen,
                        &resultType, &resultCount);
    if(resultError == 0)
        return result_id;
    return 1; // an arbitrary value for the random seed 
    }  
RtFloat Mesh2Blobby_Rif::randBetween(RtFloat min, RtFloat max) {
    return ((RtFloat)rand()/RAND_MAX) * (max - min) + min;
    }