MStatus lrutils::getMetaChildByName(MObject metaNodeObj, MString name, MObject& metaChildObj) {
    MStatus status = MS::kFailure;

    MFnDependencyNode metaNodeFn( metaNodeObj );
    MPlug metaChildrenPlug = metaNodeFn.findPlug( "metaChildren", true, &status );
    MyCheckStatusReturn(status, "MFnDependencyNode.findPlug() failed");

    //follow the plug connection to the connected plug on the other object
    MPlugArray connectedChildPlugs;
    metaChildrenPlug.connectedTo(connectedChildPlugs,false,true,&status);
    MyCheckStatusReturn(status,"MPlug.connectedTo() failed");

    for (unsigned int i = 0; i < connectedChildPlugs.length(); i++) {
        MPlug connectedPlug = connectedChildPlugs[i];
        MObject connectedNodeObj = connectedPlug.node(&status);
        MyCheckStatusReturn(status, "MPlug.node() failed");

        MFnDependencyNode connectedNodeFn( connectedNodeObj );
        MString connectedNodeName = connectedNodeFn.name();
        int index = connectedNodeName.indexW( name );
        if( index != -1 ) {
            metaChildObj = connectedNodeObj;
            status = MS::kSuccess;
            break;
        }
    }

    return status;
}
MStatus lrutils::getMetaChildByRigId(MObject metaNodeObj, MString rigId, MObject& metaChildObj) {
    MStatus status = MS::kFailure;

    MFnDependencyNode metaNodeFn( metaNodeObj );
    MPlug metaChildrenPlug = metaNodeFn.findPlug( "metaChildren", true, &status );
    MyCheckStatusReturn(status, "MFnDependencyNode.findPlug() failed");

    //follow the plug connection to the connected plug on the other object
    MPlugArray connectedChildPlugs;
    metaChildrenPlug.connectedTo(connectedChildPlugs,false,true,&status);
    MyCheckStatusReturn(status,"MPlug.connectedTo() failed");

    for (unsigned int i = 0; i < connectedChildPlugs.length(); i++) {
        MPlug connectedPlug = connectedChildPlugs[i];
        MObject connectedNodeObj = connectedPlug.node(&status);
        MyCheckStatusReturn(status, "MPlug.node() failed");

        MFnDependencyNode connectedNodeFn( connectedNodeObj );

        //get the rigId number held in the rigId attribute
        MPlug rigIdPlug = connectedNodeFn.findPlug(MString("rigId"),true,&status);
        MyCheckStatusReturn(status,"findPlug failed");
        MString childRigId;
        rigIdPlug.getValue(childRigId);
        //if rigId is in childRigId then return the object
        if( childRigId.indexW(rigId) != -1 ) {
            metaChildObj = connectedNodeObj;
            status = MS::kSuccess;
            break;
        } 
    }

    return status;
}
MStatus lrutils::stringReplaceAll(MString &source, MString search, MString replace) {
    MStatus status = MS::kFailure;

    //part of the string that has already been searched
    MString sourceProcessed;
    //part of the string that has not been searched yet
    MString sourceUnprocessed = source;
    
    int index = 0;
    int index2 = 0;
    //if the search and replace strings are the same, don't bother to change the string
    if( search == replace )
        return MS::kSuccess;
    for(;;) {
        index = sourceUnprocessed.indexW( search );
        if (index == -1 || sourceUnprocessed.numChars() == 0) {
            //if the search string is not found, end and add the rest of the string to
            //the part already processed
            sourceProcessed += sourceUnprocessed;
            break;
        }
        MString subString1 = source.substringW(0,index-1);
        index2 = index + search.numChars();
        MString subString2;
        if (index2 < (int)(source.numChars() - 1) ) {
            subString2 = source.substringW(index2,source.numChars()-1);
        }
        sourceProcessed += subString1 + replace;
        sourceUnprocessed = subString2;
        status = MS::kSuccess;
    }
    source = sourceProcessed;

    return status;
}
// In order for the new animation curve positions to match the controller's previous global position,
// first we need to get the controller's global transform matrix. The new local position of the controller
// will be the controller's global transform matrix multiplied by the inverse of the new parent group's
// global transform matrix.
// So if:
//  [A] = controller's global transformation matrix
//  [B] = parent group's global transformation matrix
//  [X] = controller's new local transformation matrix
// Then:
//  [X] = [A]inverse([B])
//
// The controller's new local transformation matrix needs to be calculated per keyframe, which is why
// a map of world position matrices for the controller are passed in as a parameter. The key for the map
// is the time at which the associated world matrix was stored.
MStatus lrutils::updateAnimCurves(MObject transformObj, std::map<double, MMatrix> ctlWorldMatrices, MMatrix ctlGroupMatrix) {
    MStatus status = MS::kFailure;
    MFnTransform transformFn( transformObj, &status );
    MyCheckStatusReturn(status, status.errorString() );

    //get all of the connections from the transform node
    MPlugArray transformConnections;
    status = transformFn.getConnections( transformConnections );
    MyCheckStatusReturn(status, status.errorString() );
    for(unsigned int i = 0; i < transformConnections.length(); i++) {
        //get all of the plugs this plug is connected to as a destination
        MPlugArray connectedPlugs;
        transformConnections[i].connectedTo(connectedPlugs,true,false,&status);
        MyCheckStatusReturn(status, status.errorString() );
        MString plugName = transformConnections[i].partialName(false,false,false,false,false,true);
        for(unsigned int j = 0; j < connectedPlugs.length(); j++) {
            MObject connectedObj = connectedPlugs[j].node();
            MFn::Type animType = connectedObj.apiType();
            //if the connected object is an animCurveT[L,A], then transform it
            if( (animType == MFn::kAnimCurveTimeToDistance) || (animType == MFn::kAnimCurveTimeToAngular) ) {
                MFnAnimCurve animCurve(connectedObj, &status);
                MyCheckStatusReturn(status, status.errorString() );
                //iterate through every key in the curve
                for(unsigned int k = 0; k < animCurve.numKeys(); k++) {
                    double keyTime = animCurve.time(k, &status).value();
                    MyCheckStatusReturn( status, status.errorString() );
                    double keyVal = animCurve.value(k, &status);
                    MyCheckStatusReturn( status, status.errorString() );

                    float tangentX, tangentY;
                    status = animCurve.getTangent(k, tangentX, tangentY, false);
                    MyCheckStatusReturn(status, status.errorString() );
                    //stringstream tmp;
                    //tmp << "out tangent = (" << tangentX << "," << tangentY << ")";
                    //MGlobal::displayInfo(tmp.str().c_str());

                    //calculate the controller's local transformation matrix
                    //first retrieve the corresponding world matrix for this key time
                    MMatrix ctlWorldMatrix = ctlWorldMatrices[keyTime];
                        //if the transformation matrices are the same, don't update the curve
                    //if( ctlWorldMatrix == ctlGroupMatrix) {
                    //    continue;
                    //}
                    MTransformationMatrix ctlGroupTransMatrix(ctlGroupMatrix);
                    MMatrix ctlGroupMatrixInverse = ctlGroupTransMatrix.asMatrixInverse();
                    MMatrix newCtlLocalMatrix = ctlWorldMatrix * ctlGroupMatrixInverse;
                    MTransformationMatrix newCtlLocalTransMatrix(newCtlLocalMatrix);

                    //determine if the curve is for translation data
                    if(animType == MFn::kAnimCurveTimeToDistance) {
                        MVector vTransform = newCtlLocalTransMatrix.translation(MSpace::kTransform);
                        if( plugName.indexW( MString("X") ) != -1 ) {
                            keyVal = vTransform.x;
                        } else if ( plugName.indexW( MString("Y") ) != -1 ) {
                            keyVal = vTransform.y;
                        } else if ( plugName.indexW( MString("Z") ) != -1 ) {
                            keyVal = vTransform.z;
                        }
                    //if the curve is for rotation data
                    } else if (animType == MFn::kAnimCurveTimeToAngular) {
                        double* rotation = new double[3];
                        MTransformationMatrix::RotationOrder rotOrder = MTransformationMatrix::kXYZ;
                        newCtlLocalTransMatrix.getRotation(rotation,rotOrder);
                        if( plugName.indexW( MString("X") ) != -1 ) {
                            keyVal = rotation[0];
                        } else if ( plugName.indexW( MString("Y") ) != -1 ) {
                            keyVal = rotation[1];
                        } else if ( plugName.indexW( MString("Z") ) != -1 ) {
                            keyVal = rotation[2];
                        }                        
                    }
                    animCurve.setValue(k, keyVal);
                    status = MS::kSuccess;
                }
            }
        }
    }
    return status;
}