peltOverlapCmd.cpp

//-
// ==========================================================================
// Copyright 1995,2006,2008 Autodesk, Inc. All rights reserved.
//
// Use of this software is subject to the terms of the Autodesk
// license agreement provided at the time of installation or download,
// or which otherwise accompanies this software in either electronic
// or hard copy form.
// ==========================================================================
//+

//
// peltOverlapCmd.cpp
//
// This plugin checks and returns the overlapping faces in pairs for a given
// list of shading groups. The faces can be from the same mesh or different meshes, i.e.
//
// 1) Return the first overlapping faces associated to "shadingGroupName1"
// peltOverlap "shadingGroupName1";
//
// 2) Return the first 100 overlapping faces associated to "shadingGroupName1" 
//       and the first 100 overlapping faces associated to "shadingGroupName2".
// peltOverlap -exitAfterNthError 100 "shadingGroupName1" "shadingGroupName2";
//

#include <maya/MIOStream.h>
#include <maya/MFnPlugin.h>
#include <maya/MString.h>
#include <maya/MFloatArray.h>
#include <maya/MArgList.h>

#include <maya/MPxCommand.h>
#include <maya/MSyntax.h>
#include <maya/MArgDatabase.h>

#include <maya/MGlobal.h>
#include <maya/MDagPath.h>
#include <maya/MItSelectionList.h>
#include <maya/MSelectionList.h>
#include <maya/MItMeshPolygon.h>

#include <maya/MFnDependencyNode.h>
#include <maya/MFnSet.h>
#include <maya/MPlugArray.h>
#include <maya/MPlug.h>

#include <math.h>

#define kExitFlag   "-ea"
#define kExitFlagLong   "-exitAfterNthPairs"

//
// Command class declaration
//
class peltOverlap : public MPxCommand
{
public:
                         peltOverlap();
        virtual     ~peltOverlap(); 

        MStatus     doIt( const MArgList& args );

    static MSyntax  newSyntax();
    static void*    creator();

private:

    MStatus     parseArgs( const MArgList &args );
        void            createBoundingCircle(const MStringArray &flattenFaces, MFloatArray &center, MFloatArray &radius);
        bool            createRayGivenFace(const MString &face, MFloatArray &orig, MFloatArray &vec);
        float           area(const MFloatArray &orig);
        unsigned int    checkCrossingEdges(MFloatArray &face1Orig, 
                       MFloatArray &face1Vec, 
                       MFloatArray &face2Orig, 
                       MFloatArray &face2Vec);
        void            numOverlapUVFaces(const MString &shadingGroup, MStringArray &flattenFaces);

    unsigned int    fNthPairs;
        MStringArray    fShadingGroups;
};

peltOverlap::peltOverlap()
    : fNthPairs (1)
{}

peltOverlap::~peltOverlap() {}

void* peltOverlap::creator()
{
    return new peltOverlap;
}

MSyntax peltOverlap::newSyntax()
{
     MSyntax syntax;
     syntax.addFlag(kExitFlag, kExitFlagLong, MSyntax::kUnsigned);
     syntax.setObjectType(MSyntax::kStringObjects);
     return syntax;
}

MStatus peltOverlap::parseArgs( const MArgList& args )
{
    MStatus     status = MS::kSuccess;
    MArgDatabase    argData(syntax(), args);

    if (argData.isFlagSet(kExitFlag)) {
        status = argData.getFlagArgument(kExitFlag, 0, fNthPairs);
        if (status != MS::kSuccess) {
            MGlobal::displayError("-ea/exitAfterNthPairs is missing an int argument");
        return status;
        }
    }

    status = argData.getObjects(fShadingGroups);
    if (status != MS::kSuccess || fShadingGroups.length() < 1) {
            MGlobal::displayError("Missing shading group(s) input");
        status = MS::kFailure;
    }

    return status;
}

MStatus peltOverlap::doIt( const MArgList& args )
//
// Description
//     Return the overlapping faces in pairs for given shading groups.
//
{
    MStatus status = parseArgs ( args );
    if ( !status ) return status;

    // Iterate over shading groups
    for (unsigned int i = 0; i < fShadingGroups.length(); i++ ) 
    {
        // Get a list of faces through mel because it's so much easier this way.
        // It should be fine because this is not performance critical.
        MStringArray faces, flattenFaces;
        MGlobal::executeCommand("sets -q " + fShadingGroups[i], faces, false, false);
        MGlobal::clearSelectionList();
        for(unsigned int j = 0; j < faces.length(); j++) 
        {
        MString nodeType = MGlobal::executeCommandStringResult("nodeType " + faces[j]);
        if (nodeType == "mesh") {
            MGlobal::selectByName(faces[j]); // add to list
        }
        }
        MGlobal::executeCommand("ConvertSelectionToFaces");
        MGlobal::executeCommand("ls -sl -flatten", flattenFaces, false, false);

        numOverlapUVFaces(fShadingGroups[i], flattenFaces);
    }
    return MS::kSuccess;
}

void peltOverlap::createBoundingCircle(const MStringArray &flattenFaces, MFloatArray &center, MFloatArray &radius)
//
// Description
//     Represent a face by a center and radius, i.e.
//     center = {center1u, center1v, center2u, center2v, ... }
//     radius = {radius1, radius2,  ... }
//
{
        center.setLength(2 * flattenFaces.length());
        radius.setLength(flattenFaces.length());
    for(unsigned int i = 0; i < flattenFaces.length(); i++) {
        MSelectionList selList;
        selList.add(flattenFaces[i]);
        MDagPath dagPath;
        MObject  comp;
        selList.getDagPath(0, dagPath, comp);
        MItMeshPolygon iter(dagPath, comp);

        MFloatArray uArray, vArray;
        iter.getUVs(uArray, vArray);
        // Loop through all vertices to construct edges/rays
        float cu = 0.f;
        float cv = 0.f;
        unsigned int j;
        for(j = 0; j < uArray.length(); j++) {
                cu += uArray[j];
            cv += vArray[j];
        }
        cu = cu / uArray.length();
        cv = cv / vArray.length();
        float rsqr = 0.f;
        for(j = 0; j < uArray.length(); j++) {
                float du = uArray[j] - cu;
            float dv = vArray[j] - cv;
            float dsqr = du*du + dv*dv;
            rsqr = dsqr > rsqr ? dsqr : rsqr;
        }
        center[2*i]   = cu;
        center[2*i+1] = cv;
        radius[i]  = sqrt(rsqr);
    }
}

bool peltOverlap::createRayGivenFace(const MString &face, MFloatArray &orig, MFloatArray &vec)
//
// Description
//     Represent a face by a series of edges(rays), i.e.
//     orig = {orig1u, orig1v, orig2u, orig2v, ... }
//     vec  = {vec1u,  vec1v,  vec2u,  vec2v,  ... }
//
//     return false if no valid uv's. 
{
        MSelectionList selList;
    selList.add(face);
    MDagPath dagPath;
    MObject  comp;
    selList.getDagPath(0, dagPath, comp);
    MItMeshPolygon iter(dagPath, comp);

    MFloatArray uArray, vArray;
    iter.getUVs(uArray, vArray);

    if (uArray.length() == 0 || vArray.length() == 0) return false;

    orig.setLength(2 * uArray.length());
    vec.setLength( 2 * uArray.length());

    // Loop through all vertices to construct edges/rays
    float u = uArray[uArray.length() - 1];
    float v = vArray[vArray.length() - 1];
    for(unsigned int j = 0; j < uArray.length(); j++) {
        orig[2*j]   = uArray[j];
        orig[2*j+1] = vArray[j];
        vec[2*j]    = u - uArray[j];
        vec[2*j+1]  = v - vArray[j];
        u = uArray[j];
        v = vArray[j];
    }
    return true;
}

float peltOverlap::area(const MFloatArray &orig)
{
    float sum = 0.f;
    unsigned int num = orig.length() / 2;
    for (unsigned int i = 0; i < num; i++) {
        unsigned int idx  = 2 * i;
        unsigned int idy  = (i + 1 ) % num;
        idy = 2 * idy + 1;
        unsigned int idy2 = (i + num - 1) % num; 
        idy2 = 2 * idy2 + 1;
        sum += orig[idx] * (orig[idy] - orig[idy2]);
    }
    return fabs(sum) * 0.5f;
}

unsigned int peltOverlap::checkCrossingEdges(
MFloatArray &face1Orig, 
MFloatArray &face1Vec, 
MFloatArray &face2Orig, 
MFloatArray &face2Vec
)
// Check if there are crossing edges between two faces. Return true 
// if there are crossing edges and false otherwise. A face is represented
// by a series of edges(rays), i.e. 
// faceOrig[] = {orig1u, orig1v, orig2u, orig2v, ... }
// faceVec[]  = {vec1u,  vec1v,  vec2u,  vec2v,  ... }
{
    unsigned int face1Size = face1Orig.length();
    unsigned int face2Size = face2Orig.length();
    for (unsigned int i = 0; i < face1Size; i += 2) {
        float o1x = face1Orig[i];
        float o1y = face1Orig[i+1];
        float v1x = face1Vec[i];
        float v1y = face1Vec[i+1];
        float n1x =  v1y;
        float n1y = -v1x;
        for (unsigned int j = 0; j < face2Size; j += 2) {
            // Given ray1(O1, V1) and ray2(O2, V2)
            // Normal of ray1 is (V1.y, V1.x)
            float o2x = face2Orig[j];
            float o2y = face2Orig[j+1];
            float v2x = face2Vec[j];
            float v2y = face2Vec[j+1];
            float n2x =  v2y;
            float n2y = -v2x;

            // Find t for ray2
            // t = [(o1x-o2x)n1x + (o1y-o2y)n1y] / (v2x * n1x + v2y * n1y)
            float denum = v2x * n1x + v2y * n1y;
            // Edges are parallel if denum is close to 0.
            if (fabs(denum) < 0.000001f) continue;
            float t2 = ((o1x-o2x)* n1x + (o1y-o2y) * n1y) / denum;
            if (t2 < 0.00001f || t2 > 0.99999f) continue;

            // Find t for ray1
            // t = [(o2x-o1x)n2x + (o2y-o1y)n2y] / (v1x * n2x + v1y * n2y)
            denum = v1x * n2x + v1y * n2y;
            // Edges are parallel if denum is close to 0.
            if (fabs(denum) < 0.000001f) continue;
            float t1 = ((o2x-o1x)* n2x + (o2y-o1y) * n2y) / denum;

            // Edges intersect
            if (t1 > 0.00001f && t1 < 0.99999f) return 1;
        }
    }
    return 0;
}

void peltOverlap::numOverlapUVFaces(const MString& shadingGroup, MStringArray& flattenFaces)
//
// Description
//     Return overlapping faces in pairs for given a shading group and its associated faces.
//
{
    MFloatArray  face1Orig, face1Vec, face2Orig, face2Vec, center, radius;
    // Loop through face i
    unsigned int numOverlap = 0;
    createBoundingCircle(flattenFaces, center, radius);
    for(unsigned int i = 0; i < flattenFaces.length() && numOverlap < fNthPairs; i++) {
            if(!createRayGivenFace(flattenFaces[i], face1Orig, face1Vec)) continue;
        const float cui  = center[2*i];
        const float cvi  = center[2*i+1];
        const float ri  = radius[i];
        // Exclude the degenerate face
        // if(area(face1Orig) < 0.000001) continue;
        // Loop through face j where j != i
        for(unsigned int j = i+1; j < flattenFaces.length() && numOverlap < fNthPairs; j++) {
            const float &cuj = center[2*j];
            const float &cvj = center[2*j+1];
            const float &rj  = radius[j];
            float du = cuj - cui;
            float dv = cvj - cvi;
            float dsqr = du*du + dv*dv;
            // Quick rejection if bounding circles don't overlap
            if (dsqr >= (ri+rj)*(ri+rj)) continue;

            if(!createRayGivenFace(flattenFaces[j], face2Orig, face2Vec)) continue;
            // Exclude the degenerate face
            // if(area(face2Orig) < 0.000001) continue;
            if (checkCrossingEdges(face1Orig, face1Vec, face2Orig, face2Vec)) {
                numOverlap++;
                appendToResult(flattenFaces[i]);
                appendToResult(flattenFaces[j]);
                continue;
            }
        }
    }
}


//
// The following routines are used to register/unregister
// the command we are creating within Maya
//
MStatus initializePlugin( MObject obj )
{
    MStatus   status;
    MFnPlugin plugin( obj, PLUGIN_COMPANY, "3.0", "Any");

    status = plugin.registerCommand( "peltOverlap",
                     peltOverlap::creator,
                     peltOverlap::newSyntax);
    if (!status) {
        status.perror("registerCommand");
        return status;
    }

    return status;
}

MStatus uninitializePlugin( MObject obj)
{
    MStatus   status;
    MFnPlugin plugin( obj );

    status = plugin.deregisterCommand( "peltOverlap" );
    if (!status) {
        status.perror("deregisterCommand");
        return status;
    }

    return status;
}