- Main Page
- Related Pages
- Modules
- Namespaces
- Classes
- Examples

# cvColorShader.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.
    // ==========================================================================
    //+

    #include <math.h>

    #include <maya/MPxNode.h>
    #include <maya/MIOStream.h>
    #include <maya/MString.h>
    #include <maya/MTypeId.h>
    #include <maya/MPlug.h>
    #include <maya/MDataBlock.h>
    #include <maya/MDataHandle.h>
    #include <maya/MFnNumericAttribute.h>
    #include <maya/MFloatVector.h>
    #include <maya/MPointArray.h>
    #include <maya/MFnPlugin.h>
    #include <maya/MItMeshPolygon.h>
    #include <maya/MFnMesh.h>
    #include <maya/MItMeshVertex.h>
    #include <maya/MDagPath.h>
    #include <maya/MFnSingleIndexedComponent.h>
    #include <maya/MMutexLock.h>
    #include <maya/MRenderUtil.h>
    #include <maya/MSelectionList.h>

    class cvColorShader : public MPxNode
    {
        public:
                        cvColorShader();
        virtual         ~cvColorShader();

        virtual MStatus compute( const MPlug&, MDataBlock& );
        virtual void    postConstructor();

        static  void *  creator();
        static  MStatus initialize();

        //  Id tag for use with binary file format
        static  MTypeId id;

        private:

        static inline float dotProd(const MFloatVector &, const MFloatVector &);

        static MStatus      getTriangleInfo(
                                const MDagPath& meshPath,
                                long            triangleId,
                                MPointArray&    vertPositions,
                                MColorArray&    vertColours
                            );

        static MMutexLock   fCriticalSection;

        // Input attributes

        static MObject aReverseAlpha;

        static MObject aPointObj;               // Implicit attribute
        static MObject aPrimitiveId;            // Implicit attribute
        static MObject aObjectId;           // Implicit attribute

        // Output attributes
        static MObject aOutColor;
        static MObject aOutAlpha;
    };

    // Static data
    MTypeId cvColorShader::id( 0x8000f );
    MMutexLock cvColorShader::fCriticalSection;

    // Attributes
    MObject cvColorShader::aReverseAlpha;
    MObject cvColorShader::aPointObj;
    MObject cvColorShader::aPrimitiveId;
    MObject cvColorShader::aObjectId;
    MObject cvColorShader::aOutColor;
    MObject cvColorShader::aOutAlpha;

    void cvColorShader::postConstructor( )
    {
        setMPSafe(true);
    }

    cvColorShader::cvColorShader()
    {
    }

    cvColorShader::~cvColorShader()
    {
    }

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

    MStatus cvColorShader::initialize()
    {
        MFnNumericAttribute nAttr;

        aReverseAlpha = nAttr.create( "reverseAlpha", "ra",
                                      MFnNumericData::kBoolean);
        CHECK_MSTATUS ( nAttr.setDefault( true ) );

        aPointObj  = nAttr.createPoint( "pointObj", "po" );
        CHECK_MSTATUS ( nAttr.setStorable(false) );
        CHECK_MSTATUS ( nAttr.setHidden(true) );

        aPrimitiveId = nAttr.create( "primitiveId", "pi", MFnNumericData::kLong);
        CHECK_MSTATUS ( nAttr.setHidden(true) );

        aObjectId = nAttr.createAddr("objectId", "oi");
        CHECK_MSTATUS ( nAttr.setHidden(true) );

        aOutColor = nAttr.createColor( "outColor", "oc" );
        CHECK_MSTATUS ( nAttr.setStorable(false) );
        CHECK_MSTATUS ( nAttr.setReadable(true) );
        CHECK_MSTATUS ( nAttr.setWritable(false) );

        aOutAlpha = nAttr.create( "outAlpha", "oa", MFnNumericData::kFloat);
        CHECK_MSTATUS (  nAttr.setDisconnectBehavior(MFnAttribute::kReset) );
        CHECK_MSTATUS ( nAttr.setStorable(false) );
        CHECK_MSTATUS ( nAttr.setReadable(true) );
        CHECK_MSTATUS ( nAttr.setWritable(false) );

        CHECK_MSTATUS ( addAttribute(aPointObj) );
        CHECK_MSTATUS ( addAttribute(aOutColor) );
        CHECK_MSTATUS ( addAttribute(aOutAlpha) );
        CHECK_MSTATUS ( addAttribute(aReverseAlpha) );
        CHECK_MSTATUS ( addAttribute(aPrimitiveId) );
        CHECK_MSTATUS ( addAttribute(aObjectId) );

        CHECK_MSTATUS ( attributeAffects(aPointObj,     aOutColor) );
        CHECK_MSTATUS ( attributeAffects(aPrimitiveId,  aOutColor) );
        CHECK_MSTATUS ( attributeAffects(aObjectId,  aOutColor) );

        CHECK_MSTATUS ( attributeAffects(aReverseAlpha, aOutAlpha) );
        CHECK_MSTATUS ( attributeAffects(aPointObj,     aOutAlpha) );
        CHECK_MSTATUS ( attributeAffects(aPrimitiveId,  aOutAlpha) );
        CHECK_MSTATUS ( attributeAffects(aObjectId,  aOutAlpha) );

        return MS::kSuccess;
    }

    // dot product on vectors
    inline float cvColorShader::dotProd(
        const MFloatVector & v1,
        const MFloatVector & v2)
    {
        return  v1.x*v2.x +  v1.y*v2.y + v1.z*v2.z;
    }

    //
    //
    MStatus cvColorShader::compute( const MPlug& plug, MDataBlock& block )
    {
        if ((plug != aOutColor) && (plug.parent() != aOutColor) &&
            (plug != aOutAlpha))
            return MS::kUnknownParameter;

        MStatus status;
        MObject thisNode = thisMObject();

        bool rev_flag = block.inputValue(aReverseAlpha).asBool();
        long triangleId = block.inputValue(aPrimitiveId).asLong();
        void* objectId = block.inputValue(aObjectId).asAddr();

        // Location of the point we are shading
        MFloatVector& pointObj = block.inputValue( aPointObj ).asFloatVector();

        MColor resultColor;

        // It's only worth continuing if the renderer was able to supply us
        // with a surface.
        if (objectId != NULL) {
            // Get the mesh that we are shading.
            MDagPath        meshPath;
            MSelectionList  list;
            status = MRenderUtil::renderObjectItem(objectId, list);
            list.getDagPath(0, meshPath);

            // Get the positions and colours of the triangle's vertices.
            //
            // Note that we could get the triangle's vertices
            MPointArray pos;
            MColorArray colours;
            status = getTriangleInfo(meshPath, triangleId, pos, colours);

            MFloatVector pos1((float)pos[0].x, (float)pos[0].y, (float)pos[0].z);
            MFloatVector pos2((float)pos[1].x, (float)pos[1].y, (float)pos[1].z);
            MFloatVector pos3((float)pos[2].x, (float)pos[2].y, (float)pos[2].z);

            // Compute the barycentric coordinates of the sample.

            pointObj = pointObj - pos3;             // Translate pos3 to origin
            pos1 = pos1 - pos3;
            pos2 = pos2 - pos3;

            MFloatVector norm = pos1 ^ pos2;        // Triangle normal
            float len = dotProd(norm, norm);
            len = dotProd(norm, pointObj)/len;

            pointObj = pointObj - (len * norm);     // Make sure the point is
                                                    // in the triangle

            float aa = dotProd(pos1, pos1);
            float bb = dotProd(pos2, pos2);
            float ab = dotProd(pos1, pos2);
            float am = dotProd(pos1, pointObj);
            float bm = dotProd(pos2, pointObj);
            float det = aa*bb - ab*ab;

            // a, b, c are the barycentric coordinates (assuming pnt
            // is in the triangle plane, best least square fit
            // otherwise.
            //
            float a = (am*bb - bm*ab) / det;
            float b = (bm*aa - am*ab) / det;
            float c = 1-a-b;

            resultColor = (a*colours[0]) + (b*colours[1]) + (c*colours[2]);

            if( rev_flag == true )
                resultColor.a = 1.0f - resultColor.a;
        }

        MDataHandle outColorHandle = block.outputValue( aOutColor );
        MFloatVector& outColor = outColorHandle.asFloatVector();
        outColor.x = resultColor.r;
        outColor.y = resultColor.g;
        outColor.z = resultColor.b;
        outColorHandle.setClean();

        MDataHandle outAlphaHandle = block.outputValue( aOutAlpha );
        float& outAlpha = outAlphaHandle.asFloat();
        outAlpha = resultColor.a;
        outAlphaHandle.setClean();

        return MS::kSuccess;
    }

    MStatus cvColorShader::getTriangleInfo(
        const MDagPath& meshPath,
        long            triangleId,
        MPointArray&    vertPositions,
        MColorArray&    vertColours
    ) {
        MStatus st;

        //  'triangleId' refers to the triangle currently being shaded. We need
        //  to find the positions and colours of the triangle's three vertices.
        //
        //  We could use the 'vertexCamera*' render attributes to determine the
        //  positions of the triangle's vertices, but to determine the color at
        //  a vertex we need to know the vertex's index within the mesh and
        //  there is no render attribute which will give us that. We will have
        //  to determine it ourselves by finding the face to which the triangle
        //  belongs and then using MItMeshPolygon::getTriangle() to get the
        //  indices of the triangle's vertices. The call to getTriangle() also
        //  happens to return the positions of the vertices, so we won't bother
        //  with the 'vertexCamera*' render attributes.
        //
        //  The way we find the face to which the triangle belongs is by
        //  running through all of the mesh's faces and counting the number of
        //  triangles in each one. When the count exceeds the value of
        //  'triangleId', the face which put us over is the face containing the
        //  triangle.
        //
        //  The renderer does not assign triangle ids to the mesh all at once,
        //  but separately for each shading group. For example, let's say that
        //  a mesh has 20 faces comprised of a total of 30 triangles, assigned
        //  to two different shaders, as follows:
        //
        //  - 12 faces, having a total of 19 triangles, are assigned to
        //    the first shader
        //  - 5 faces, having a total of 7 triangles, are assigned to
        //    the second shader
        //  - the remaining 3 faces, having a total of 4 triangles,
        //    are not assigned to any shader.
        //
        //  The 19 triangles in the first shader will be given primitiveIds 0 to 18.
        //  The 7 trianges in the second shader will be given primitiveIds 19 to 25.
        //  The 4 triangles which are not assigned to any shader won't have any
        //  primitiveIds assigned to them.
        //
        //  So when we're counting triangles, we must do it in shader order.

        //  The first step is to get all of the shaders used by this mesh and the
        //  faces to which they are assigned.
        MFnMesh         meshFn(meshPath);
        MObjectArray    shaders;
        MObjectArray    components;

        meshFn.getConnectedSetsAndMembers(
            meshPath.instanceNumber(), shaders, components, true
        );

        int         polygonId = -1;
        int         triangleCount = 0;
        MIntArray   vertIndices;

        //  Step through each shader.
        for (unsigned int s = 0; (polygonId < 0) && (s < shaders.length()); ++s) {
            //  Iterate over the faces assigned to this shader.
            //
            //  The constructor for MItMeshPolygon is not threadsafe as it may
            //  initiate a recalculation of the mesh's normals. So we must lock
            //  the thread while making the call.
            //
            //  Similarly, MItMeshMeshPolygon::hasValidTriangulation() may
            //  trigger triangulation of the mesh, which is also not thread
            //  safe. So we want to keep our lock until after the first call to
            //  it.
            bool    isLocked = true;
            fCriticalSection.lock();
            MItMeshPolygon faceIter(meshPath, components[s]);

            for (; !faceIter.isDone(); faceIter.next()) {
                if (faceIter.hasValidTriangulation()) {
                    //  Get the number of triangles in the current face.
                    int nTri;
                    faceIter.numTriangles(nTri);

                    //  If this face will put the count over 'triangleId' then
                    //  the triangle must belong to this face.
                    if (triangleId < triangleCount + nTri) {
                        //  Get the positions and indices of the triangle's
                        //  vertices, then break out of the loop. We subtract
                        //  'triangleCount' from 'triangleId' to get the index
                        //  of the triangle within the face.
                        polygonId = faceIter.index();
                        st = faceIter.getTriangle(
                                triangleId - triangleCount,
                                vertPositions,
                                vertIndices,
                                MSpace::kObject
                            );

                        break;
                    }

                    //  We haven't found the right face yet. Add the number of
                    //  triangles in this face to the count and keep going.
                    triangleCount += nTri;
                }

                //  If hasValidTriangulation() was going to triangulate the
                //  mesh it will have done so by now. Subsequent calls will
                //  use the existing triangulation so it's safe to remove the
                //  lock now.
                if (isLocked) {
                    fCriticalSection.unlock();
                    isLocked = false;
                }
            }

            //  If the shader has no face components assigned to it then the
            //  'for' loop above will not have run and fCriticalSection will
            //  still be locked, in which case we must unlock it now.
            if (isLocked) {
                fCriticalSection.unlock();
            }
        }

        if ((polygonId == -1) || !st) {
            return MS::kFailure;
        }

        //  Now that we know the indices of the triangle's vertices, get their
        //  colours.
        MItMeshVertex vertIter(meshPath);
        int preIndex = 0;
        vertColours.setLength(3);

        CHECK_MSTATUS ( vertIter.setIndex( vertIndices[0], preIndex) );
        CHECK_MSTATUS ( vertIter.getColor( vertColours[0], polygonId ) );

        CHECK_MSTATUS ( vertIter.setIndex( vertIndices[1], preIndex) );
        CHECK_MSTATUS ( vertIter.getColor( vertColours[1], polygonId ) );

        CHECK_MSTATUS ( vertIter.setIndex( vertIndices[2], preIndex) );
        CHECK_MSTATUS ( vertIter.getColor( vertColours[2], polygonId ) );

        return MS::kSuccess;
    }

    MStatus initializePlugin( MObject obj )
    {
        const MString UserClassify( "utility/color" );

        MFnPlugin plugin( obj, PLUGIN_COMPANY, "5.0", "Any");
        CHECK_MSTATUS ( plugin.registerNode( "cvColorShader", cvColorShader::id,
                             cvColorShader::creator,
                             cvColorShader::initialize,
                             MPxNode::kDependNode, &UserClassify ) );

        return MS::kSuccess;
    }

    MStatus uninitializePlugin( MObject obj )
    {
        MFnPlugin plugin( obj );
        CHECK_MSTATUS ( plugin.deregisterNode( cvColorShader::id ) );

        return MS::kSuccess;
    }