ShapeMonitor.cpp

//-
// ==========================================================================
// Copyright (C) 1995 - 2006 Autodesk, Inc. and/or its licensors.  All 
// rights reserved.
//
// The coded instructions, statements, computer programs, and/or related 
// material (collectively the "Data") in these files contain unpublished 
// information proprietary to Autodesk, Inc. ("Autodesk") and/or its 
// licensors, which is protected by U.S. and Canadian federal copyright 
// law and by international treaties.
//
// The Data is provided for use exclusively by You. You have the right 
// to use, modify, and incorporate this Data into other products for 
// purposes authorized by the Autodesk software license agreement, 
// without fee.
//
// The copyright notices in the Software and this entire statement, 
// including the above license grant, this restriction and the 
// following disclaimer, must be included in all copies of the 
// Software, in whole or in part, and all derivative works of 
// the Software, unless such copies or derivative works are solely 
// in the form of machine-executable object code generated by a 
// source language processor.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. 
// AUTODESK DOES NOT MAKE AND HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED 
// WARRANTIES INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF 
// NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 
// PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE, OR 
// TRADE PRACTICE. IN NO EVENT WILL AUTODESK AND/OR ITS LICENSORS 
// BE LIABLE FOR ANY LOST REVENUES, DATA, OR PROFITS, OR SPECIAL, 
// DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES, EVEN IF AUTODESK 
// AND/OR ITS LICENSORS HAS BEEN ADVISED OF THE POSSIBILITY 
// OR PROBABILITY OF SUCH DAMAGES.
//
// ==========================================================================
//+

//
//
//
//  The ShapeMonitor is a singleton class that watches shape or texture nodes,
//  and keep track of which one changed since the last export. It is used to keep
//  the IFX scenegraph up-to-date in respect to textures.
//
//  Client code can:
//
//      - Ask for a pointer to the ShapeMonitor. (using the instance() function)
//        If it doesn't already exist, it is created.
//      - Ask the ShapeMonitor to watch a specific maya node name (specifying whether it's
//        a texture, or a shape) and give a unique texture name. Doing so creates some callbacks
//        on the specified node, so that we know when it changes. When a specific node changes,
//        it's unique name is appended to the list (actually, set) of dirty textures.
//      - Ask for the list of dirty textures. Those should be removed from the IFX scenegraph,
//        and will possibly be regenerated, if they still exist.
//      - Clear the list of dirty textures.
//
//  Additionally, once the ShapeMonitor is no longer necessary (eg: the scene is being closed), 
//  it can be destroyed using the destroy() function. Finally, all callbacks and data structures
//  can be cleared by calling the initialize() function.

#include <assert.h>

#include <maya/MSelectionList.h>
#include <maya/MConditionMessage.h>
#include <maya/MFnDagNode.h>
#include <maya/MDagPath.h>

#include "ShapeMonitor.h"



//
// PUBLIC INTERFACE
// ----------------
//


/* static */ 
ShapeMonitor* ShapeMonitor::instance()
{
    // If there is no texture monitor in use, create one.
    if (privateInstance == NULL)
    {
        privateInstance = new ShapeMonitor();
    }

    return privateInstance;
}

/* static */
ShapeMonitor* ShapeMonitor::initialize()
{
    destroy();
    return instance();
}

/* static */
void ShapeMonitor::destroy()
{
    if (privateInstance != NULL)
        delete privateInstance;
}

void ShapeMonitor::watch(MString mayaNodeName, MonitoredObject* pMon)
//
// Description:
//
//    Start watching the specified mayaNodeName for changes. (If the node is part of the DAG, that
// should be a fully-specified DAG name). This function will store all available information
// inside a MonitorObject for future reference.
//
// Arguments:
//      mayaNodeName: DG (or fully-qualified DAG) name.
//      uniqueTextureName: The corresponding texture name. This texture should correspond
//                         directly to an IFX texture resource.
{
    // Check if, per chance, this monitored object already exists.
    // If it does, no need to create a new one.
    if (retrieveMonitorObject(mayaNodeName))
        return;

    MStatus stat;

    //
    // Get the node.
    //
    MObject node;
    MSelectionList selList;
    selList.add(mayaNodeName);
    selList.getDependNode(0, node);

    //
    //  Attach the callbacks (dirty or renamed node)
    //
    pMon->mayaNodeName = mayaNodeName;

    pMon->dirtyCallbackId = MNodeMessage::addNodeDirtyCallback(node, watchedObjectDirtyCallback, pMon, &stat ); 
    assert(stat);

    pMon->renamedCallbackId = MNodeMessage::addNameChangedCallback ( node, watchedObjectRenamedCallback, pMon, &stat); 
    assert(stat);

    // Store the pertinent information in the Monitored Objects Array.
    monitoredObjectsPtrArray.append(pMon);
}



// Stop watching node(s) that share the given uniqueTextureName.
// In addition, the uniqueTextureName is automatically added to the list of dirty textures.
// (If more than one node has the given uniqueTextureName, we stop to watch all of those that match. 
// This is done to minimize callback overhead.)
void ShapeMonitor::stopWatching(MString mayaNodeName)
{
    for (int i = monitoredObjectsPtrArray.length()-1; i >= 0; i--)
    {
        // If this record's node name matches...
        if (monitoredObjectsPtrArray[i]->mayaNodeName == mayaNodeName)
        {
            // Remove the callbacks for this node.
            removeCallbacks(monitoredObjectsPtrArray[i]);

            // Remove this element from the monitored objects array.
            delete monitoredObjectsPtrArray[i];
            monitoredObjectsPtrArray.removeElements(i, i, false);
        }
    }
}

// Stop watching all nodes. This detaches the callbacks.
void ShapeMonitor::stopWatchingAll()
{
    for (int i = monitoredObjectsPtrArray.length()-1; i >= 0; i--)
    {
        // Remove the callbacks for this node.
        removeCallbacks(monitoredObjectsPtrArray[i]);

        // Remove this element from the monitored objects array.
        delete monitoredObjectsPtrArray[i];
        monitoredObjectsPtrArray.removeElements(i, i, false);
    }
}

// Remove any DAG object not in the selectedObjects list.
void ShapeMonitor::stopWatchingUnselectedDagObjects(MSelectionList& selectedObjects)
{
    // For each monitored object...
    for (int i = monitoredObjectsPtrArray.length()-1; i >= 0; i--)
    {
        MonitoredObject *pMonObject = monitoredObjectsPtrArray[i];

        MStatus stat;

        // Get an MObject for the MonitoredObject->mayaNodeName.
        MDagPath dagpath;
        MSelectionList selList;
        selList.add(pMonObject->mayaNodeName);
        stat = selList.getDagPath(0, dagpath);

        // If the MObject is a DAG node...
        if (stat)
        {
            bool found = false;

            // Check if the dag path is included in the selectedObjects list.
            // For example, say that dagpath = "|group1|group2|pSphere|pSphereShape",
            // selectedObjects contains "|group1|group2".
            // We first check if dagpath is included in selectedObjects. If that's not the
            // case, we pop() one component, so that dagpath = "|group1|group2|pSphere", then
            // check again. We do that until either the dagpath is found to be included in
            // the selectedObjects list, or until there's no component left in dagpath.
            while (!found && dagpath.length() > 0)
            {
                // Since we store the shape name (as opposed to the parent transform dagpath),
                // we need to pop() to get the parent transform dagpath.
                dagpath.pop();
                
                MObject component;

                // Check if the dag path is included in the objects list.
                if (selectedObjects.hasItemPartly(dagpath, component))
                    found = true;
            }

            // If the object was not in the selectedObjects list, stop watching it.
            if (!found)
                stopWatching(pMonObject->mayaNodeName);
        }
    }
}



//
// PRIVATE IMPLEMENTATION
// ----------------------
//


// The private instance points to the only texture monitor in memory, 
// or NULL if there is no instance.
ShapeMonitor* ShapeMonitor::privateInstance = NULL;


// Both the constructor and destructor assume that "this" is the only instance
// of ShapeMonitor in memory. We can ensure this condition since both
// functions are private.
ShapeMonitor::ShapeMonitor()
{
}

ShapeMonitor::~ShapeMonitor()
{
    stopWatchingAll();
}

MonitoredObject* ShapeMonitor::retrieveMonitorObject(MString mayaNodeName)
{
    for (int i = 0; i < (int) monitoredObjectsPtrArray.length(); i++)
    {
        // If this element's node name and filename matches...
        if (monitoredObjectsPtrArray[i]->mayaNodeName == mayaNodeName)
            return monitoredObjectsPtrArray[i];
    }

    return NULL;
}

// Attempt to detach all of the callbacks for a specific monitoredObject.
// Assert if any of them fails.
void ShapeMonitor::removeCallbacks(MonitoredObject *mon)
{
    MStatus stat;

    stat = MMessage::removeCallback(mon->dirtyCallbackId);
    assert(stat);

    stat = MMessage::removeCallback(mon->renamedCallbackId);
    assert(stat);
}

void ShapeMonitor::watchedObjectDirtyCallback( void* clientData )
{
    MonitoredObject *mon = (MonitoredObject*) clientData;

    privateInstance->stopWatching(mon->mayaNodeName);
    // Note: after this call the monitored object has been deleted, 
    // so don't do anything with this pointer!
}


void ShapeMonitor::watchedObjectRenamedCallback( MObject & node, void* clientData )
{
    MonitoredObject *mon = (MonitoredObject*) clientData;

    privateInstance->stopWatching(mon->mayaNodeName);
    // Note: after this call the monitored object has been deleted, 
    // so don't do anything with this pointer!
}