Turntable/Turntable.cpp

//**************************************************************************/
// Copyright (c) 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.
//
//**************************************************************************/
// DESCRIPTION:
// CREATED: December 2008
//**************************************************************************/

#include "Turntable.h"
#include "TurntableDialog.h"
#include <QtCore/QFileInfo>
#include <QtGui/QImage>
#include <math.h>

// This macro provides some information about the plugin to Mudbox. It also
// specifies a function that will be called after all the plug-ins are loaded
MB_PLUGIN( "Turntable", "Create a turntable animation", "Autodesk", "http://www.mudbox3d.com", Turntable::Initializer );

// Initialize static class variables
int Turntable::s_iWidth = 0;
int Turntable::s_iHeight = 0;
int Turntable::s_iFrames = 120;
bool Turntable::s_bAntiAlias = false;
QString Turntable::s_sFolderPath;


void Turntable::Initializer()
// This is called after the plug-ins are all loaded.
//
// Add the menu item for this function to the Mudbox interface at the bottom of the Create menu.
{
    Kernel()->AddCallbackMenuItem(Kernel::menuPlugins, QString::null, tr("Turntable Movie"), Turntable::CreateTurntable, NTRQ("Turntable Movie") );
}


void Turntable::CreateTurntable()
{
    // If no size has been previously specified, use the current screen size
    if (s_iWidth == 0) s_iWidth = Kernel()->ViewPort()->Width();
    if (s_iHeight == 0) s_iHeight = Kernel()->ViewPort()->Height();
    
    // Open a dialog to allow the user the change the settings
    TurntableDialog* dlg = new TurntableDialog( NULL, s_iWidth, s_iHeight, s_iFrames, s_bAntiAlias, s_sFolderPath );

    // Put up the dialog and see if the user clicked OK
    if ( dlg->exec() == QDialog::Accepted ) {
    
        // get the new values
        s_iWidth = dlg->Width();
        s_iHeight = dlg->Height();
        s_iFrames = dlg->Frames();
        s_bAntiAlias = dlg->Antialias();
        s_sFolderPath = dlg->FolderPath();
        
        // check against limits
        if ( s_iWidth < 50 ) s_iWidth = 50;
        if ( s_iHeight < 50 ) s_iHeight = 50;
        if ( s_iFrames < 1 ) s_iFrames = 1;
        
        // if the folder doesn't exist, warn the user
        QFileInfo fi(s_sFolderPath);
        if ( !fi.isDir() )
        {
            Kernel()->HUDMessageShow(tr("Turntable creation failed.<p>Specified folder does not exist."), Kernel::HUDmsgFade );
        } else {
            CreateTurntableImages(s_iWidth, s_iHeight, s_iFrames, s_bAntiAlias, s_sFolderPath);
        }
    }
    delete dlg;

}


// Convenience function.  Rotates a coordinate in 3d space around the Y axis.
static Vector rotateAroundYAxis( const Vector& start, float theta )
{
    float cx = start.x;
    float cy = start.z;
    
    float cosTheta = cos(theta);
    float sinTheta = sin(theta);
    
    float rx = cx * cosTheta + cy * sinTheta;
    float ry = cy * cosTheta - cx * sinTheta;
    
    return Vector(rx,start.y,ry);
}


void Turntable::CreateTurntableImages( int width, int height, int numberOfFrames, bool antialias, const QString& folderPath )
{
    // Get the current camera
    mudbox::Camera* camera = Kernel()->Scene()->ActiveCamera();
    
    // Assume we will be rotating around the Y axis.
    // Calculate the step size in radians
    const float twoPi = 2.0 * 3.1415926535;
    float step = ( twoPi / float(numberOfFrames) );
    
    // Save the current camera position and orientation
    Vector cameraForward( camera->Forward() );
    Vector cameraUp( camera->Up() );
    Vector cameraRight( camera->Right() );
    Vector position( camera->Position() );
    
    // Rotate the camera to point exactly at the Y axis.  This will make the motion
    // look a little nicer
    float flatDistanceToCenter = sqrt( position.x * position.x + position.z * position.z );
    float flatSizeOfForward = sqrt( cameraForward.x * cameraForward.x + cameraForward.z * cameraForward.z );
    float heightAtCenter = 0.0;
    if ( flatSizeOfForward > 0.0 )
    {
        heightAtCenter = position.y - (flatDistanceToCenter/flatSizeOfForward) * cameraForward.y;
        camera->SetTarget(Vector(0.0f, heightAtCenter, 0.0f));
    }
    
    // display a progress bar so the user can interrupt
    Kernel()->ProgressStart(QString(tr("Saving frames...")), numberOfFrames );
    
    bool finishedOk = true;
    
    // For each frame
    for ( int i=0; i < numberOfFrames; i++ )
    {
        if ( Kernel()->ProgressIsCancelRequested() ) {
            finishedOk = false;
            break;
        }
        
        // update the progress bar
        Kernel()->ProgressAdd();
        
        // Grab an image and save it.  If antialiasing is on, capture the frame at a larger
        // size, then reduce it.
        Image* frame = Kernel()->RenderScene( width * (antialias?4:1), height * (antialias?4:1) );
        QString fileName = QString("%1/frame_%2.png").arg(folderPath).arg(i);
        
        // We don't want a transparent background for a turntable.  So we convert the image
        // to an 8-bit RGB (no alpha) format
        QImage *sampleQImage = frame->ConvertToQImage();
        (*sampleQImage) = sampleQImage->convertToFormat(QImage::Format_RGB888);
        delete frame;

        if ( antialias )
        {
            (*sampleQImage) = sampleQImage->scaled(width,height,Qt::KeepAspectRatio,Qt::SmoothTransformation);
        }
        
        sampleQImage->save(fileName, "png");
        delete sampleQImage;
        
        float Theta = step * float(i+1);

        // Rotate the camera to the next frame position
        //camera->Transformation()->AddRotation( Vector( 0.0f, -step, 0.0f), true );
        
        Vector newPosition( rotateAroundYAxis(position,Theta) );
        camera->SetPosition( newPosition);      
        newPosition = camera->Position();
        
        camera->SetTarget(Vector(0.0f, heightAtCenter, 0.0f));
    }
    
    // Remove the progress bar
    Kernel()->ProgressEnd();
    
    // Restore the camera to where it was
    camera->SetPosition( position );
    camera->SetForwardUpRight(cameraForward, cameraUp, cameraRight);
    
    // Tell the user we're done
    if ( finishedOk )
    {
        Kernel()->HUDMessageShow(tr("Turntable is complete"), Kernel::HUDmsgFade );
    } else {
        Kernel()->HUDMessageShow(tr("Turntable creation cancelled"), Kernel::HUDmsgFade );
    }
}