/*
Mesh2Blobby.cpp
Refer to,
    www.fundza.com/devkit/rifplugins/mesh2blobby/index.html
for information about this rif.
  
Malcolm Kesson: Jan 28 2016
Modified: July 25th 2018. See line 131. With RfM22 vertex data
is now tagged "vertex point P" rather than plain old "P".
*/
#include <RifPlugin.h>
#include <RixInterfaces.h>
#include <RifFilter.h>
#include <ri.h>
#include <list>
#include <string>
#include <cstdlib>
  
class Mesh2Blobby : public RifPlugin {
    public:
              Mesh2Blobby(RtFloat scale);
    virtual ~Mesh2Blobby() { }
    virtual RifFilter &GetFilter();
    
    private:
        RifFilter   m_filters;
        RtFloat      m_default_blobsize;
        RtUString    m_blobby_ustr;
        RtUString    m_blob_min_ustr;
        RtUString    m_blob_max_ustr;
        RixRenderState *m_rstate;
        
        // Utilities_____________________________________________________________
        static RtPoint    *getVerticesPtr(RtInt paramNum, RtToken paramNames[], RtPointer paramVals[]);
        static RtFloat  getPrimVar(RtUString name, 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 RtFloat    getIdentifierId();
        static RtFloat    randBetween(RtFloat min, RtFloat max);
        
        // 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[]);
    };
  
// Entry point called by PRMan.
RifPlugin *RifPluginManufacture(int argc, char **argv) {
    RtFloat defaultsize = 0.5;
    if(argc == 1 && *argv[0] != '\0')
        defaultsize = atof(argv[0]);
    return new Mesh2Blobby(defaultsize);
    }
        
// Constructor
Mesh2Blobby::Mesh2Blobby(RtFloat size) {
    m_default_blobsize = size;
    m_filters.PointsGeneralPolygonsV = pointsGeneralPolygonsV;
    m_filters.HierarchicalSubdivisionMeshV = hierarchicalSubdivisionMeshV;
    m_filters.Filtering = RifFilter::k_Continue;
    
    RixContext  *rixContext = RixGetContext();
    m_rstate = (RixRenderState*) rixContext->GetRixInterface(k_RixRenderState);
  
    m_blobby_ustr = RtUString("constant float blobby");
    m_blob_min_ustr = RtUString("constant float blob_min");
    m_blob_max_ustr = RtUString("constant float blob_max");
    }
  
RifFilter& Mesh2Blobby::GetFilter() { 
    return m_filters; 
    }
//___________________________________________________________________
// Callback:
// Handles the pointsGeneralPolygonsV primitive.
RtVoid Mesh2Blobby::pointsGeneralPolygonsV(RtInt npolys, RtInt* nloops, RtInt nverts[], RtInt verts[], 
                                     RtInt paramNum, RtToken paramNames[], RtPointer paramVals[]) {
    Mesh2Blobby *self = static_cast<Mesh2Blobby*> (RifGetCurrentPlugin());
    RtBoolean convertToBlobby = (getPrimVar(self->m_blobby_ustr, paramNum, paramNames, paramVals) > 0) ? true : false;
    if(convertToBlobby == false) {
        RiPointsGeneralPolygonsV(npolys, nloops, nverts, verts, paramNum, paramNames, paramVals);
        return;
        }    
    RtFloat blobMin = self->getPrimVar(self->m_blob_min_ustr, paramNum, paramNames, paramVals);
    RtFloat blobMax = self->getPrimVar(self->m_blob_max_ustr, paramNum, paramNames, paramVals);
    if(blobMin == -1)
        blobMin = self->m_default_blobsize;
    if(blobMax == -1)
        blobMax = self->m_default_blobsize;    
    int numverts = countVertices(npolys, nverts, verts);
    RtPoint *vertices = getVerticesPtr(paramNum, paramNames, paramVals);
    writeBlobby(numverts, vertices, blobMin, blobMax);
    }
//___________________________________________________________________
// Callback:
// Handles the HierarchicalSubdivisionMesh primitive.
RtVoid Mesh2Blobby::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 *self = static_cast<Mesh2Blobby*> (RifGetCurrentPlugin());
    RtBoolean convertToBlobby = (getPrimVar(self->m_blobby_ustr, paramNum, paramNames, paramVals) > 0) ? true : false;
    if(convertToBlobby == false) {
        RiHierarchicalSubdivisionMeshV(mask, nf, nverts, verts, nt, tags, nargs, intargs, floatargs,
                                    stringargs, paramNum, paramNames, paramVals);
        return;
        }    
    RtFloat blobMin = self->getPrimVar(self->m_blob_min_ustr, paramNum, paramNames, paramVals);
    RtFloat blobMax = self->getPrimVar(self->m_blob_max_ustr, paramNum, paramNames, paramVals);
    if(blobMin == -1)
        blobMin = self->m_default_blobsize;
    if(blobMax == -1)
        blobMax = self->m_default_blobsize;
    int numverts = countVertices(nf, nverts, verts);
    RtPoint *vertices = getVerticesPtr(paramNum, paramNames, paramVals);
    writeBlobby(numverts, vertices, blobMin, blobMax);
    }
//___________________________________________________________________
// Utility:
// Given the number of points in a vertex array and min and max blob sizes this 
// method emits a RiBlobby. For example, if "numverts" is 100 this method 
// will output a blobby consisting of 100 merged blobs.
void Mesh2Blobby::writeBlobby(RtInt numverts, RtPoint *vertices, RtFloat minSize, RtFloat maxSize) {
    Mesh2Blobby *self = static_cast<Mesh2Blobby*> (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;
    const char *str[1] = {"\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 return a correct count.
RtInt Mesh2Blobby::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 RfM21, or until "vertex point P"
// RfM22 is found. Returns a pointer (address) to the first vertex of the points array.
RtPoint* Mesh2Blobby::getVerticesPtr(RtInt paramNum, RtToken paramNames[], RtPointer paramVals[]) {
    for(int n = 0; n < paramNum; n++) {
        if(paramNames[n] == RI_P || strcmp(paramNames[n], "vertex point P") == 0)
            return (RtPoint*)paramVals[n];
        }
    return NULL;
    }
//___________________________________________________________________
// Utility:
RtFloat Mesh2Blobby::getPrimVar(RtUString name, RtInt paramNum, RtToken paramNames[], RtPointer paramVals[]) {
    for(int n = 0; n < paramNum; n++)
        if(strcmp(paramNames[n], name.CStr()) == 0)
            return *(RtFloat*)paramVals[n];
    return -1;
    }
//_______________________________________________________________________
// Utility:
// Returns the value of,
//    Attribute "identifier" "float id" [1234567]
RtFloat Mesh2Blobby::getIdentifierId() {
    RtUString    identifier_name = RtUString( "identifier:id");
    RtFloat        result_id = -1;
    RtInt        resultCount, resultError, resultLen = sizeof(int*);
    RixRenderState::Type resultType;
    resultError = m_rstate->GetAttribute(identifier_name, &result_id, resultLen,
                        &resultType, &resultCount);
    if(resultError == 0)
        return result_id;
    return 1; // an arbitrary value for the random seed 
    }
//___________________________________________________________________
// Utility:
RtFloat Mesh2Blobby::randBetween(RtFloat min, RtFloat max) {
    return ((RtFloat)rand()/RAND_MAX) * (max - min) + min;
    }