lensDistortionCallback/lensDistortionCallback.cpp
 
 
 
lensDistortionCallback/lensDistortionCallback.cpp
#include "lensDistortionCallback.h"

#include <maya/MGlobal.h>
#include <maya/MString.h>
#include <maya/MDagPath.h>
#include <maya/MMatrix.h>
#include <maya/MPlug.h>
#include <maya/MPlugArray.h>
#include <maya/MImage.h>
#include <maya/MIOStream.h>
#include <maya/MFnCamera.h>
#include <maya/MFnDependencyNode.h>
#include <maya/MColor.h>
#include <maya/MFnPlugin.h>
#include <maya/MHardwareRenderer.h>
#include <maya/MGLdefinitions.h>

#define kFloatEpsilon           1.0e-10
#define kRemoveFlag                     "-r"
#define kRemoveFlagLong         "-remove"
#define kExistFlag                      "-ex"
#define kExistFlagLong          "-exists"
#define kListFlag                       "-l"
#define kListFlagLong           "-list"

lensDistortionCompute* lensDistortionCompute::currentLensDistortionCompute[MAX_MODEL_PANEL];

// ------------------------------------------------------------
// lensDistortionCallback implementation
// ------------------------------------------------------------

lensDistortionCallback::lensDistortionCallback()
:
        mRemoveOperation( false )
,       mExistOperation( false )
,       mListOperation( false )
{
        mPanelName = MString("");
}

lensDistortionCallback::~lensDistortionCallback()
{
}

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

MSyntax lensDistortionCallback::newSyntax()
{
        MSyntax syntax;
        syntax.addFlag( kRemoveFlag,    kRemoveFlagLong,        MSyntax::kString);
        syntax.addFlag( kExistFlag,             kExistFlagLong,         MSyntax::kString);
        syntax.addFlag( kListFlag,              kListFlagLong );
        syntax.addArg(MSyntax::kString);
        return syntax;
}

MStatus lensDistortionCallback::parseArgs(const MArgList& args)
{
        MStatus                 status;
        MArgDatabase    argDatabase( syntax(), args, &status);

        if ( status != MS::kSuccess ) return status;

        if ( args.length() == 1 )
        {
                MString thisArg = args.asString( 0, &status );
                if ( thisArg == kListFlag || thisArg == kListFlagLong )
                {
                        mListOperation = true;
                }
                else
                {
                        // Adding callback to given panel 
                        mRemoveOperation = false;
                        mExistOperation  = false;
                        mPanelName = argDatabase.commandArgumentString( 0, &status );
                }
        }
        else if ( args.length() == 2 )
        {
                MString thisArg = args.asString( 0, &status );
                if ( thisArg == kRemoveFlag || thisArg == kRemoveFlagLong )
                {
                        // Removing callback from given panel 
                        mRemoveOperation = true;
                        mPanelName = argDatabase.flagArgumentString( kRemoveFlag, 0, &status );
                }
                else if ( thisArg == kExistFlag || thisArg == kExistFlagLong )
                {
                        mExistOperation = true;
                        mPanelName = argDatabase.flagArgumentString( kExistFlag, 0, &status );
                }
        }
        else
        {
                return MStatus::kFailure;
        }
        return MS::kSuccess;
}

MStatus lensDistortionCallback::doIt(const MArgList& args)
{
        MStatus status;

        // Parse argument
        status = parseArgs(args);

        if (status != MS::kSuccess){    
                displayError( "Argument should have a panel name and proper option" );
                return status; 
        }

        // Process list operation
        if ( mListOperation )
        {
                MStringArray panelNames;
                status =  lensDistortionCompute::listCallback( panelNames );
                setResult ( panelNames );
                return status;
        }

        // Check if the given model panel name is valid or invalid.
        M3dView view;
        status = M3dView::getM3dViewFromModelPanel ( mPanelName,  view );

        if (status != MS::kSuccess){    
                displayError( "Specified model panel is not valid!" );
                return status; 
        }

        // Process add/remove/query existence operation
        if ( mExistOperation )
        {
                // Query existence of callback
                if ( lensDistortionCompute::panelHasCallback( mPanelName ) )
                {
                        // Callback is found
                        setResult( true );
                }
                else
                {
                        // Callback is not found
                        setResult( false );
                }
        }
        else if ( mRemoveOperation )
        {
                // Remove an instance of lensDistortionCompute
                status = lensDistortionCompute::removeCallbackFromPanel( mPanelName );
        }
        else  
        {
                // Add an instance of lensDistortionCompute
                status = lensDistortionCompute::addCallbackToPanel( mPanelName );
                if ( status == MS::kSuccess )
                {
                        setResult( mPanelName );
                }
        }

        return status;
}

// ------------------------------------------------------------
// lensDistortionCompute implementation
// ------------------------------------------------------------

lensDistortionCompute::lensDistortionCompute(const MString &panelName)
:
        mTextureIndex( 0 )
,       mTextureWidth( 0 )
,       mTextureHeight( 0 )
,       mMultipleDrawPassCount( 1 )
{
        MStatus status;

        // Set panel name and operator for post rendering
        mPanelName = panelName;

        // Add the callbacks
        //
        mDeleteId
                = MUiMessage::add3dViewDestroyMsgCallback(panelName,
                                                                                   &lensDistortionCompute::deleteCB,
                                                                                   (void *) this, &status);
        if (mDeleteId == 0)
                MGlobal::displayError( MString("Could not attach view deletion callback to panel ") + panelName);

        mPreRenderId
                = MUiMessage::add3dViewPreRenderMsgCallback(panelName,
                                                                                  &lensDistortionCompute::preRenderCB,
                                                                                  (void *) this, &status);
        if (mPreRenderId == 0)
                MGlobal::displayError( MString("Could not attach view prerender callback to panel ") + panelName);

        mPostRenderId
                = MUiMessage::add3dViewPostRenderMsgCallback(panelName,
                                                                                   &lensDistortionCompute::postRenderCB,
                                                                                   (void *) this, &status);
        if (mPostRenderId == 0)
                MGlobal::displayError( MString("Could not attach view postrender callback to panel ") + panelName);

        mPreMultipleDrawPassId  
                = MUiMessage::add3dViewPreMultipleDrawPassMsgCallback(panelName,        
                                                                                &lensDistortionCompute::preMultipleDrawPassCB,  (void *) this, &status);
        if (mPreMultipleDrawPassId == 0)        
                MGlobal::displayError( MString("Could not attach view pre-multiple draw pass callback to panel ") + panelName);

        mPostMultipleDrawPassId 
                = MUiMessage::add3dViewPostMultipleDrawPassMsgCallback(panelName, 
                                                                                &lensDistortionCompute::postMultipleDrawPassCB, (void *) this, &status);
        if (mPostMultipleDrawPassId == 0)       
                MGlobal::displayError( MString("Could not attach view post-multiple draw pass callback to panel ") + panelName);

}

lensDistortionCompute::~lensDistortionCompute()
{

        // Clear all callback that associated with this model panel.
        clearCallbacks();

        // Reset any global pointer pointing to this compute
        for (unsigned int i=0; i<MAX_MODEL_PANEL; i++)
        {
                if (currentLensDistortionCompute[i] && 
                        (currentLensDistortionCompute[i])->getPanelName() == mPanelName)
                {
                        currentLensDistortionCompute[i] = 0;
                }
        }

        // Delete GL texture if it is created.
        if ( mTextureIndex )
        {
                // Make rendering context current.
                MHardwareRenderer *pRenderer = MHardwareRenderer::theRenderer();
                if ( pRenderer )
                {
                        const MString& backEnd = pRenderer->backEndString();
                        pRenderer->makeResourceContextCurrent( backEnd );
                        glDeleteTextures( 1, &mTextureIndex );
                }
                else
                {
                        MGlobal::displayError( "Rendering context is not current! Memory leak will occurs.");
                }
        }
}


// Add an instance of lensDistortionCompute to given panal 
MStatus lensDistortionCompute::addCallbackToPanel( MString& pPanelName )
{
        MStatus status = MS::kFailure;
        // Check if the panel is already created.
        // Check if callback list has an empty entry.
        bool    found           = false;
        int             emptyIndex      = -1;

        for ( unsigned int i = 0; i < MAX_MODEL_PANEL; i++ )
        {
                if ( currentLensDistortionCompute[i] )
                { 
                        if ( currentLensDistortionCompute[i]->getPanelName() == pPanelName )
                        {
                                found = true;
                        }
                }
                else
                {
                        emptyIndex = i;
                }
        }

        if ( found )
        {
                // The panel already has a callback
                MGlobal::displayError("Specified model panel already has a callback!");
                status = MS::kFailure;
        }
        else if ( emptyIndex == -1 ) 
        {
                // Callback list doesn't have empty entry.
                // Any panel will not be added.
                MGlobal::displayError("Maximum number of callbacks are registered. Delete another callback");
                status = MS::kFailure;
        }
        else
        {
                // The panel doesn't has a callback and callback list has empty entry.
                // Add an instance of lensDistortionCompute.
                currentLensDistortionCompute[emptyIndex] = new lensDistortionCompute( pPanelName );
                status = MS::kSuccess;
        }

        return status;
}


// Remove an instance of lensDistortionCompute from panel
MStatus 
lensDistortionCompute::removeCallbackFromPanel( MString& pPanelName )
{

        // Check if the callback which associated with given panel is already created.
        for ( unsigned int i = 0; i < MAX_MODEL_PANEL; i++ )
        {
                if ( currentLensDistortionCompute[i] && 
                         currentLensDistortionCompute[i]->getPanelName() == pPanelName )
                {
                        
                        // Remove an instance of lensDistortionCompute
                        delete currentLensDistortionCompute[i];
                        currentLensDistortionCompute[i] = NULL;
                        return MS::kSuccess;
                }
        }

        // The panel doesn't have a callback.
        MGlobal::displayError("Specified model panel doesn't have a callback!");
        return MS::kFailure;
}

// Query existence of instance of lensDistortionCompute for panel
bool 
lensDistortionCompute::panelHasCallback( MString& pPanelName )
{
        // Does this panel have the callback?
        for ( unsigned int i = 0; i < MAX_MODEL_PANEL; i++ )
        {
                if ( currentLensDistortionCompute[i] && 
                        (currentLensDistortionCompute[i])->getPanelName() == pPanelName )
                {
                        // Panel is found.
                        return true;
                }
        }

        // Panel is not found
        return false;
}

// List names of the panels which have a callback attached
MStatus         
lensDistortionCompute::listCallback( MStringArray& pPanelNames )
{
        pPanelNames.clear();
        for ( unsigned int i = 0; i < MAX_MODEL_PANEL; i++ )
        {
                if ( currentLensDistortionCompute[i])
                {
                        pPanelNames.append( (currentLensDistortionCompute[i])->getPanelName() );
                }
        }
        return MS::kSuccess;
}


void lensDistortionCompute::clearCallbacks()
{       
        if (mDeleteId)
                MMessage::removeCallback(mDeleteId);

        if (mPreRenderId)
                MMessage::removeCallback(mPreRenderId);

        if (mPostRenderId)
                MMessage::removeCallback(mPostRenderId);

        if (mPreMultipleDrawPassId)
                MMessage::removeCallback( mPreMultipleDrawPassId );

        if (mPostMultipleDrawPassId)
                MMessage::removeCallback( mPostMultipleDrawPassId );
}

// Called when associated model panel deleted
void lensDistortionCompute::deleteCB(const MString& panelName, void * data)
{
        lensDistortionCompute *thisCompute = (lensDistortionCompute *) data;

        // Check if this panel name is renamed.
        if ( panelName != thisCompute->mPanelName )
        {
                MGlobal::displayError("lensDistortionCallback does not support renaming of panels. Callback removed.");
        }
        // Delete callback.
        delete thisCompute;
}


void lensDistortionCompute::preRenderCB(const MString& panelName, void * data)
{
        // Get a pointer to current callback
        lensDistortionCompute *thisCompute = (lensDistortionCompute *) data;
        if (!thisCompute)
                return;

        MStatus status = M3dView::getM3dViewFromModelPanel(panelName, thisCompute->mCurrentView );
        if (status != MS::kSuccess) return;

        // Store current object display status and multiple draw pass count.
        thisCompute->mObjectDisplayState = thisCompute->mCurrentView.objectDisplay();
        thisCompute->mMultipleDrawPassCount = thisCompute->mCurrentView.multipleDrawPassCount();

        // Enable multi pass draw;
        thisCompute->mCurrentView.setMultipleDrawEnable( true );
        thisCompute->mCurrentView.setMultipleDrawPassCount( 2 );
}

void lensDistortionCompute::postRenderCB(const MString& panelName, void * data)
{       
        lensDistortionCompute *thisCompute = (lensDistortionCompute *) data;
        if (!thisCompute)
                return;

        // Draw 
        thisCompute->draw();

        // Disable multi pass draw.
        thisCompute->mCurrentView.setMultipleDrawEnable( false );

        // Restore object display state and multiple draw pass count.
        thisCompute->mCurrentView.setObjectDisplay( thisCompute->mObjectDisplayState );
        thisCompute->mCurrentView.setMultipleDrawPassCount( thisCompute->mMultipleDrawPassCount );

}


void 
lensDistortionCompute::preMultipleDrawPassCB( const MString& pPanelName, unsigned int passIndex, void * data)
{
        lensDistortionCompute *thisCompute = (lensDistortionCompute *) data;

        // Update view
        thisCompute->mCurrentView.beginGL();
        MColor currentBackgroundColor = thisCompute->mCurrentView.backgroundColor();

        if( passIndex == 0 /*Drawing other than image plane pass*/)
        {
                //      Clear background with alpha = 0.0 for blending.
                //
                //      Since a specific blending function is used to blend the lens distorted models with image plane, 
                //      transparent objects are not blend with properly. 
                glClearColor( currentBackgroundColor.r, currentBackgroundColor.g, currentBackgroundColor.b, 0.0 ); 
                glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

                // Hide image plane.
                thisCompute->mCurrentView.setObjectDisplay( thisCompute->mObjectDisplayState & ~M3dView::kDisplayImagePlane);

        }
        else /* Drawing image plane pass */
        {
                // Restore clear color.
                glClearColor( currentBackgroundColor.r, currentBackgroundColor.g, currentBackgroundColor.b,  currentBackgroundColor.a );
                glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

                // Hide other than image plane.
                thisCompute->mCurrentView.setObjectDisplay( M3dView::kDisplayCameras | M3dView::kDisplayImagePlane );
        }

        thisCompute->mCurrentView.endGL();
}

void 
lensDistortionCompute::postMultipleDrawPassCB( const MString& pPanelName, unsigned int passIndex, void * data)
{
        lensDistortionCompute *thisCompute = (lensDistortionCompute *) data;

        thisCompute->mCurrentView.beginGL();
        if ( passIndex == 0 /*Drawing other than image plane pass*/)
        {
                // Store rendering result to mPrimaryBuffer for post-render callback.
                MStatus status = thisCompute->mCurrentView.readColorBuffer( thisCompute->mPrimaryBuffer, true );
                if ( status != MS::kSuccess )
                {
                        MGlobal::displayError("Storing rendering result to buffer failed because of memory shortage. Please delete unused callbacks or decrease panel size.");
                }
        }
        else /* Drawing image plane pass */
        {
        }
        thisCompute->mCurrentView.endGL();
}

void 
lensDistortionCompute::textureUpdate()
{       
        
        unsigned int width, height;
        mPrimaryBuffer.getSize( width, height );

        // Rebuild gl texture if model panel size is changed.
        if ( mTextureWidth != width || mTextureHeight != height )
        {
                if ( mTextureIndex )
                {
                        glDeleteTextures( 1, &mTextureIndex );
                }
                mTextureIndex = 0;
        }

        if (!mTextureIndex ){
                
                glGenTextures( 1, &mTextureIndex );
                glBindTexture( GL_TEXTURE_2D, mTextureIndex );

                glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, MGL_CLAMP_TO_EDGE );        // Refreshed texture should not be wrapped.
                glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, MGL_CLAMP_TO_EDGE );        // Refreshed texture should not be wrapped.
                glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
                glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );

                glTexImage2D( 
                        GL_TEXTURE_2D   // Target
                        , 0                             // Level
                        , GL_RGBA               // Inernal Format
                        , width                 // Width
                        , height                // Height
                        , 0                             // Border
                        , GL_RGBA               // Format
                        , GL_UNSIGNED_BYTE // Type
                        , mPrimaryBuffer.pixels() // Data
                );

                // Save texture size to check model panel size change.
                mTextureWidth  = width;
                mTextureHeight = height;
                
        }
        else
        {
                // Copy color buffer to generated texture object.
                glBindTexture( GL_TEXTURE_2D, mTextureIndex );

                glTexSubImage2D(
                        GL_TEXTURE_2D   // Target
                        , 0                             // Level
                        , 0                     // X offset
                        , 0                     // Y offset
                        , width                 // Width
                        , height        // Height
                        , GL_RGBA               // Format
                        , GL_UNSIGNED_BYTE // Type
                        , mPrimaryBuffer.pixels() // Data
                );
        }
}

void
lensDistortionCompute::draw()
{
        textureUpdate();

        if (!mTextureIndex)
        {
                // Texture is not initialized correctly. retunr.
                MGlobal::displayError( "Texture is not initialized correctly");
                return;
        }


        // Get lens distortion related param
        MDagPath camera;
        mCurrentView.getCamera( camera );
        MObject cameraNode = camera.node();

        double width = mCurrentView.portWidth();
        double height = mCurrentView.portHeight();
        int        previewResolutionX           = getIntValueFromCameraAttr( cameraNode,"previewResolutionX" );
        int        previewResolutionY           = getIntValueFromCameraAttr( cameraNode,"previewResolutionY" );
        double renderResolutionX                = getDoubleValueFromCameraAttr( cameraNode,"renderResolutionX" );
        double renderResolutionY                = getDoubleValueFromCameraAttr( cameraNode,"renderResolutionY" );

        double principalPointX                  = getDoubleValueFromCameraAttr( cameraNode,"principalPointX");
        double principalPointY                  = getDoubleValueFromCameraAttr( cameraNode,"principalPointY" );
        double kc1                                              = getDoubleValueFromCameraAttr( cameraNode,"radialDistortionCoef1" );
        double kc2                                              = getDoubleValueFromCameraAttr( cameraNode,"radialDistortionCoef2" );
        double kc3                                              = getDoubleValueFromCameraAttr( cameraNode,"tangentialDistortionCoef1" );
        double kc4                                              = getDoubleValueFromCameraAttr( cameraNode,"tangentialDistortionCoef2" );
        double horizontalFilmAperture   = getDoubleValueFromCameraAttr( cameraNode,"horizontalFilmAperture" )  * 25.4;
        double verticalFilmAperture             = getDoubleValueFromCameraAttr( cameraNode,"verticalFilmAperture" ) * 25.4;
        bool   drawWireframe                    = getBoolValueFromCameraAttr( cameraNode, "drawWireframe" );

        // Begin OpenGL 
        mCurrentView.beginGL();

        // Push current matrix and load inverse projection matrix
        MMatrix projMatrix;
        mCurrentView.projectionMatrix(projMatrix);
        glPushMatrix();
        glLoadMatrixd( (const GLdouble *)projMatrix.inverse().matrix );

        // Store all OpenGL state
        glPushAttrib( GL_ALL_ATTRIB_BITS );

        // Draw lens distortion plane with blending
        glEnable( GL_BLEND );
        glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
        glEnable( GL_TEXTURE_2D );
        glTexEnvf( GL_TEXTURE_ENV,      GL_TEXTURE_ENV_MODE, GL_REPLACE );

        // Enable polygon offset fill when wireframe is on.
        if ( drawWireframe )
        {
                glEnable( GL_POLYGON_OFFSET_FILL );
                glPolygonOffset( 0.95f, 1.0f ); 
        }

        drawLensDistortionPlane( 
                GL_QUADS
                ,renderResolutionX, renderResolutionY
                ,previewResolutionX, previewResolutionY 
                , width, height
                , principalPointX, principalPointY
                , kc1, kc2, kc3, kc4
                , horizontalFilmAperture, verticalFilmAperture
                );

        glDisable( GL_TEXTURE_2D );

        // Draw lens distortion plane with wireframe
        if ( drawWireframe )
        {
                glDisable( GL_POLYGON_OFFSET_FILL );
                glColor3f( 0.0f, 0.0f, 0.0f );

                drawLensDistortionPlane( 
                        GL_LINE_LOOP
                        , renderResolutionX, renderResolutionY
                        , previewResolutionX, previewResolutionY
                        , width, height
                        , principalPointX, principalPointY
                        , kc1, kc2, kc3, kc4
                        , horizontalFilmAperture, verticalFilmAperture
                );
        }

        // Restore OpenGL state
        glPopAttrib();

        // Restore matrix
        glPopMatrix();

        // End OpenGL 
        mCurrentView.endGL();
}

void
lensDistortionCompute::drawLensDistortionPlane(
        GLenum drawMethod
        , double renderResolutionX
        , double renderResolutionY
        , int    previewResolutionX
        , int    previewResolutionY
        , double width
        , double height
        , double principalPointX
        , double principalPointY
        , double kc1, double kc2, double kc3, double kc4
        , double horizontalFilmAperture
        , double verticalFilmAperture)
{
        if ( previewResolutionX < 1 || previewResolutionY < 1 )
        {
                MGlobal::displayError("The previewResolutionX and previewResolutionY has to be more than 1.");
                return;
        }
        double xSub                                             = 1.0 / (double)previewResolutionX;
        double ySub                                             = 1.0 / (double)previewResolutionY;
        for ( int i = 0; i < previewResolutionX ; i ++ )
        {
                for ( int j = 0; j < previewResolutionY; j++ )
                {

                        // Draw rectangle with applying lens distortion
                        glBegin( drawMethod );

                        double x, y;
                        double u, v;
                        
                        // P0 
                        x = u =  i * xSub;
                        y = v =  j * ySub;

                        applyLensDistortion( 
                                x, y 
                                ,renderResolutionX, renderResolutionY
                                , width, height
                                , principalPointX, principalPointY
                                , kc1, kc2, kc3, kc4
                                , horizontalFilmAperture, verticalFilmAperture 
                                );

                        glTexCoord2d(  u, v );
                        glVertex2d(  x, y );

                        // P1 
                        x = u =  i * xSub;
                        y = v =  (j + 1) * ySub;

                        applyLensDistortion( 
                                x, y 
                                ,renderResolutionX, renderResolutionY
                                , width, height
                                , principalPointX, principalPointY
                                , kc1, kc2, kc3, kc4
                                , horizontalFilmAperture, verticalFilmAperture 
                                );              

                        glTexCoord2d(  u, v );
                        glVertex2d(  x, y );

                        // P2
                        x = u =  (i + 1) * xSub;
                        y = v =  (j + 1) * ySub;

                        applyLensDistortion( 
                                x, y 
                                ,renderResolutionX, renderResolutionY
                                , width, height
                                , principalPointX, principalPointY
                                , kc1, kc2, kc3, kc4
                                , horizontalFilmAperture, verticalFilmAperture 
                                );

                        glTexCoord2d(  u, v );
                        glVertex2d(  x, y );

                        // P3
                        x = u =  (i + 1) * xSub;
                        y = v =  j * ySub;

                        applyLensDistortion( 
                                x, y 
                                ,renderResolutionX, renderResolutionY
                                , width, height
                                , principalPointX, principalPointY
                                , kc1, kc2, kc3, kc4
                                , horizontalFilmAperture, verticalFilmAperture 
                                );
                                
                        glTexCoord2d(  u, v );
                        glVertex2d(  x, y );
                
                        glEnd();

                }
        }
}

// Apply lens distortion to given x/y positions which are normalized in screen space
// with most commonly used lens distortion technique which uses decomposed radial
// and tangential distortion coefficient.
//
// Since this using lens model uses radial and tangential distortion coeffs which 
// are calibrated in millimeter unit space, positions must be converted from normalized 
// screen space to physical millimeter space to get correct result.
// 
// See "Manual of Photogrammetry, fourth ed., C.C. Slama, ed., 
// Falls Church, Va.: Am. Soc. Photogramettry, 1980" for more details on using lens model.
//
//  ------------------------------------------------------------------------
//  
//      [Apply lens distortion]
//      Xn, Yn  :       Current position                                        ( in normalized screen space )
//      Xd, Yd  :       Lens distortion applied posittion       ( in normalized screen space )
//
//      X0, Y0  :       Center of image                                         ( in pixel space )
//      rW, rH  :       Resolution width/ height of image       ( in pixel space )
//                              Used for offsetting center of image 
//
//      fH, fV  :       Horizontal/vertical film aperture       ( in millimeter )
//                              Used for converting from normalized screen space to millimeter space
//
//      kc1             :       Radial Distortion coeff 1                       ( in millimeter * 10^-2 )
//      kc2             :       Radial Distortion coeff 2                       ( in millimeter * 10^-4 )
//      kc3             :       Tangential Distortion coeff 1           ( in millimeter * 10^-1 )
//      kc4             :       Tangential Distortion coeff 2           ( in millimeter * 10^-1 )
//                              These coeffs should be calibrated in millimeter unit space
//
//  ------------------------------------------------------------------------
//
//      // Offset center 
//      Xn' = Xn - X0/rW        Yn' = Yn - Y0/rH
//
//  // Convert from normlized screen space to physical space ( millimeter )
//      // Xn'', Yn'' : Current position in physical space ( millimeter )
//      Xn'' = Xn' * fH         Yn'' = Yn' * fW
//
//      // Apply lens distortion model
//      RR = Xn''^2 + Yn''^2 
//      Q = 1 / (4*kc1*RR + 6*kc2*rn^4 + 8*kc3*Yn'' + 8*kc4*Xn'' + 1 )
//
//      Xd''= Xn'' - Q * ( Xn'' *( kc1 * RR + kc2 * RR^2) + 2*kc3 * Xn'' * Yn'' + kc4 * ( RR + 2*Xn''^2) )
//      Yd''= Yn'' - Q * ( Yn'' *( kc1 * RR + kc2 * RR^2) + kc3 * ( RR+ 2*Yn''^2) + 2 * kc4 * Xn'' * Yn'' ) 
//
//  // Convert from physical space ( millimeter ) to normalized screen space.
//      Xd' = Xd'' / fH         Yd' = Yd'' / hW
//
//      // Restore center offset
//      Xd =  Xd' + X0/rW       Yd = Yd' + Y0/rH
//
void 
lensDistortionCompute::applyLensDistortion( 
                double& Xd
                , double& Yd
                , double renderResolutionX
                , double renderResolutionY
                , double width
                , double height
                , double principalPointX
                , double principalPointY
                , double kc1, double kc2, double kc3, double kc4
                , double horizontalFilmAperture
                , double verticalFilmAperture
                )
{
        double Xn = Xd ;
        double Yn = ( 1.0 - Yd ) ;

        // Determine center offset by given principal point and normalize position.
        // Principal point is calibrated "center of lens".
        double centerOffsetX = principalPointX / renderResolutionX;
        double centerOffsetY = principalPointY / renderResolutionY;

        // Convert physical space (millimeter).
        // Assume image and film are fitted perfectly. 
        Xn = (Xn - centerOffsetX ) * (horizontalFilmAperture );
        Yn = (Yn - centerOffsetY ) * (verticalFilmAperture) ;

        // Apply generic pinhole camera model 

        if ( fabs( kc1 ) < kFloatEpsilon &&
                fabs( kc2 )  < kFloatEpsilon &&
                fabs( kc3 )  < kFloatEpsilon &&
                fabs( kc4 )  < kFloatEpsilon 
        )
        {
                // Normalize it 
                Xd = Xd * 2.0 - 1.0;
                Yd  = Yd * 2.0 - 1.0;

                return;
        }

        // Apply lens distortion formula
        double RR = (Xn * Xn) + (Yn * Yn);
        double Q = 1 / ( (4 * kc1 * RR) + (6 * kc2 * RR*RR) + (8 * kc3 * Yn) + (8 * kc4 * Xn) + 1 );

        Xd = Xn - Q * ( Xn * ( kc1 * RR + kc2 * RR * RR )       + 2 * kc3 * Xn * Yn +   kc4 * ( RR + 2 * Xn * Xn) );
        Yd = Yn - Q * ( Yn * ( kc1 * RR + kc2 * RR * RR )       +kc3 * ( RR + 2 * Yn * Yn) +2 * kc4 * Xn * Yn );

        //  Convert to normalized space
        Xd = Xd / horizontalFilmAperture + centerOffsetX;
        Yd = Yd / verticalFilmAperture + centerOffsetY ;
        Yd = 1.0 - Yd;

        // Normalize it 
        Xd  = Xd * 2.0 - 1.0;
        Yd  = Yd * 2.0 - 1.0;

}

// Get an int attribute value from associated node;
int 
lensDistortionCompute::getIntValueFromCameraAttr( MObject& node, MString attrName )
{
        MStatus status;
        MFnDependencyNode nodeFn ( node );

        MPlug plug = nodeFn.findPlug( attrName, status );
        if (status == MS::kSuccess )
        {
                return plug.asInt();
        }
        else
        {
                MGlobal::displayError( "Please add :" + attrName + " attribute to " + nodeFn.name() );
        }
        return 0;
}

// Get a double attribute value from given node;
double 
lensDistortionCompute::getDoubleValueFromCameraAttr(  MObject& node, MString attrName )
{
        MStatus status;
        MFnDependencyNode nodeFn ( node );

        MPlug plug = nodeFn.findPlug( attrName, status );
        if (status == MS::kSuccess )
        {
                return plug.asDouble();
        }
        else
        {
                MGlobal::displayError( "Please add :" + attrName + " attribute to " + nodeFn.name() );
        }
        return 0.0;
}

// Get a bool attribute value from given node;
bool 
lensDistortionCompute::getBoolValueFromCameraAttr(  MObject& node, MString attrName )
{
        MStatus status;
        MFnDependencyNode nodeFn ( node );

        MPlug plug = nodeFn.findPlug( attrName, status );
        if (status == MS::kSuccess )
        {
                return plug.asBool();
        }
        else
        {
                MGlobal::displayError( "Please add :" + attrName + " attribute to " + nodeFn.name() );
        }
        return false;
}

MStatus initializePlugin( MObject obj )
{ 
        MStatus   status;
        MFnPlugin plugin( obj, "Autodesk", "1.0", "Any");

        // Register the command so we can actually do some work
        //
        for (unsigned int i=0; i<MAX_MODEL_PANEL; i++)
        {
                lensDistortionCompute::currentLensDistortionCompute[i] = 0;
        }

        status = plugin.registerCommand("lensDistortionCallback",
                                                                        lensDistortionCallback::creator,
                                                                        lensDistortionCallback::newSyntax);

        if (!status)
        {
                MGlobal::displayError("registerCommand");
        }

        return status;
}

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

        // Remove all computation class + callbacks
        for (unsigned int i=0; i<MAX_MODEL_PANEL; i++)
        {
                if ( lensDistortionCompute::currentLensDistortionCompute[i] )
                {
                        delete lensDistortionCompute::currentLensDistortionCompute[i];
                        lensDistortionCompute::currentLensDistortionCompute[i] = 0;
                }
        }

        // Deregister the command
        //
        status = plugin.deregisterCommand("lensDistortionCallback");

        if (!status)
        {
                MGlobal::displayError("deregisterCommand");
        }

        return status;
}