PtexExtractor/PtexPaintExporter.cpp


//**************************************************************************/

// Copyright (c) 2010 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: August 2010

//**************************************************************************/



// This file makes it possible to export an existing paint layer to a ptex file. It first examines the mesh,

// and if it is possible, it directly saves the texture pieces of a face into the ptex file. Otherwise it does 

// a full map extraction process.



#include "PtexPaintExporter.h"

#include "PtexUtilizer.h"

#include <math.h>



IMPLEMENT_CLASS( PtexPaintExporter, PaintLayerExporter, "ptexpaintexporter" );



Preferences::Bool g_bIncludeBaseMesh(

    NTR("Save mesh data in PTEX files"),

    NTR("Files"),

    QObject::tr("Save mesh data in PTEX files"),

    QObject::tr("Files"),

    true );



// The ptex file can contain four different data formats, so four different file types are returned here.

QVector<FileExtension> PtexPaintExporter::SupportedExtensions( void )

{

    QVector<FileExtension> s;

    s.append( FileExtension( "ptx", QObject::tr("Ptex file [8 bit Integer, RGBA]"), Image::e8integer ) );

    s.append( FileExtension( "ptx", QObject::tr("Ptex file [16 bit Integer, RGBA]"), Image::e16integer ) );

    s.append( FileExtension( "ptx", QObject::tr("Ptex file [16 bit Floating point, RGBA]"), Image::e16float ) );

    s.append( FileExtension( "ptx", QObject::tr("Ptex file [32 bit Floating point, RGBA]"), Image::e32float ) );

    return s;

};



// This function exports one paint layer as a ptex file.

void PtexPaintExporter::Export( const QString &sFileName, int iFileTypeIndex, const Mesh *pSourceSurface, TexturePool *pSource )

{

    // We always use the base level of the meshes.

    pSourceSurface = pSourceSurface->Geometry()->LowestLevel();



    // First the function examines the mesh, and if possible, it directly writes the texture pieces for each face

    // into the ptex file. This is only possible if the mesh is a quadric mesh, and the UV for the mesh was generated

    // using the UVlessPainting plugin (in which case each face has a rectangular area on the texture with a size

    // power of two)

    if ( pSourceSurface->Type() == Mesh::typeQuadric )

    {

        const UVGeneratorNode *pG = pSourceSurface->ChildByClass<UVGeneratorNode>( false );

        if ( pG )

        {

            switch ( iFileTypeIndex )

            {

            case 0:

                FastExport<unsigned char, 255>( sFileName, iFileTypeIndex, pSourceSurface, pSource, pG );

                return;

            case 1:

                FastExport<unsigned short, 65525>( sFileName, iFileTypeIndex, pSourceSurface, pSource, pG );

                return;

            case 2:

                FastExport<half_, 1>( sFileName, iFileTypeIndex, pSourceSurface, pSource, pG );

                return;

            case 3:

                FastExport<float, 1>( sFileName, iFileTypeIndex, pSourceSurface, pSource, pG );

                return;

            default:

                MB_ERROR( "Unknown filetype" );

            };

        };

    };



    // Otherwise, a full map extraction process has to be executed, which extracts the given paint layer into

    // the ptex file.

    SubdivisionLevel *pSL = dynamic_cast<SubdivisionLevel *>( (Mesh *)pSourceSurface );

    SubdivisionLevel *pBL = pSL->Geometry()->LowestLevel();



    // Create a map extraction node, set all the basic parameters in it.

    Instance<MapExtractor> m;

    m->SetTargetCount( 1 );

    m->SetTarget( 0, pBL );

    m->SetSourceCount( 1 );

    m->SetSource( 0, pBL );

    m->SetUtilizerType( PtexUtilizer::StaticClass() );

    m->SetLocatorType( ClassDesc::ByName( NTR( "SubdivisionLocator" ) ) );



    // We only need the color transfer component, so we set the data about the paint layer, and enable it.

    Component *pC = m->ComponentByClassName( "ColorTransfer" );

    MB_ONBUG( !pC )

        return;

    const Layer *pL = dynamic_cast<const Layer *>( pSource );

    MB_ONBUG( !pL )

        return;

    const LayerContainer *pLC = pL->Container();

    MB_ONBUG( !pLC )

        return;



    // The paint layer is identified by the name, and the layer index.

    pC->SetAttributeValue( "channelname", pLC->Name() );

    pC->SetAttributeValue( "layerindex", QString("%1").arg(pLC->LayerIndex( pL )) );

    pC->m_bEnabled.SetValue( true );



    // In the utilizer, we have to set the file name.

    Utilizer *pU = pC->m_pUtilizer;

    MB_SAFELY( pU )

    {

        pU->m_sFileMask.SetValue( sFileName );

        pU->SetAttributeValue( "includemeshdata", g_bIncludeBaseMesh ? "true" : "false" );

    };



    // In the layout we have to set the reolution. This resolution is a guess based on the paint layer

    // resolution and the face count in the base mesh. Ideally the user could control it somehow.

    PtexLayout *pPL = dynamic_cast<PtexLayout *>( m->Layout() );

    MB_SAFELY( pPL )

    {

        unsigned int iPixelCount = 0;

        for ( unsigned int t = 0; t < pSource->TileCount(); t++ )

        {

            const Texture *pT = pSource->Tile( t );

            iPixelCount += pT->Width()*pT->Height();

        };

        pPL->m_iDirectResolution = 0;

        unsigned int iP = iPixelCount / pBL->FaceCount();

        while ( iP > 1 )

        {

            pPL->m_iDirectResolution++;

            iP >>= 2;

        };

    };



    // Set the format of the ptex file.

    PtexUtilizer *pPU = dynamic_cast<PtexUtilizer *>( pU );

    MB_SAFELY( pPU )

        pPU->SetFormat( (PtexUtilizer::Format)iFileTypeIndex );



    // Execute the extraction silently.

    m->Execute( false );

};



// This function export a paint layer to a ptex file by copying rectangular areas from the current texture into the ptex file.

// This is better than a full extraction process, because it is faster, and it preserves the detail (there is no filtering).

template < typename tType, int iMultiplier >

void PtexPaintExporter::FastExport( const QString &sFileName, int iFileTypeIndex, const Mesh *pSourceSurface, TexturePool *pSource, const UVGeneratorNode *pG )

{

    Kernel()->ProgressStart( QObject::tr("Exporting to Ptex file..."), pSourceSurface->FaceCount() );

    unsigned int iTileWidth = 0, iTileHeight = 0;



    // Count the number of tiles in the mesh

    AxisAlignedBoundingBox d;

    for ( unsigned int i = 0; i < pSource->TileCount(); i++ )

        d.Extend( pSource->TileArea( i ) );

    unsigned int iXW = ceilf( d.m_vEnd.x ), iYW = ceilf( d.m_vEnd.y );



    // Get a copy of the textures as an image, which later can be readed comfortably.

    QVector<Image *> aImages( iXW*iYW, NULL );

    for ( unsigned int x = 0; x < iXW; x++ )

        for ( unsigned int y = 0; y < iYW; y++ )

        {

            Texture *pT = pSource->Tile( AxisAlignedBoundingBox( Vector( x, y, 0.5f ), Vector( x+1, y+1, 0.5f ) ) );

            if ( pT )

            {

                unsigned char iLevel = pT->getProxyLevel();

                pT->setProxyLevel(0);

                Image *pI = aImages[x+y*iXW] = CreateInstance<Image>();

                pT->CopyTo( pI, false );

                pT->setProxyLevel(iLevel);



                if ( iTileWidth == 0 )

                {

                    iTileWidth = pT->Width();

                    iTileHeight = pT->Height();

                }

                else

                {

                    MB_ONBUG( iTileWidth != pT->Width() || iTileHeight != pT->Height() )

                    {

                        for ( unsigned int j = 0; j < iXW*iYW; j++ )

                            delete aImages[j];

                        return;

                    };

                    MB_ONBUG( iTileWidth != pI->Width() || iTileHeight != pI->Height() )

                    {

                        for ( unsigned int j = 0; j < iXW*iYW; j++ )

                            delete aImages[j];

                        return;

                    };

                };

            };

        };



    // Create the ptex file.

    Ptex::String sError;

    QByteArray qbaFileMask = QFile::encodeName( sFileName );

    Ptex::DataType aFormats[4] = { Ptex::dt_uint8, Ptex::dt_uint16, Ptex::dt_half, Ptex::dt_float };

    PtexWriter *pWriter = PtexWriter::open( qbaFileMask.constData(), Ptex::mt_quad, aFormats[iFileTypeIndex], 4, 3, pSourceSurface->FaceCount(), sError );

    MB_ONBUG( pWriter == NULL || iTileWidth == 0 || iTileHeight == 0 )

    {

        for ( unsigned int j = 0; j < iXW*iYW; j++ )

            delete aImages[j];

        return;

    };



    // Go through all the faces of the mesh, and for each face, write a ptex face into the file.

    for ( unsigned int f = 0; f < pSourceSurface->FaceCount(); f++ )

    {

        unsigned int iOrientation = pG->FaceOrientation( f );

        Ptex::FaceInfo sInfo;



        // Store the adjacency information for the face. This is used by the ptex library to do filtering at face edges.

        unsigned int af[4], ae[4];

        for ( int c = 0; c < 4; c++ )

        {

            unsigned int a = pSourceSurface->QuadAdjacency( f, c );

            // When the returned value is 0xffffffff it means there is no adjacent face in that direction, so it is an open edge.

            if ( a == 0xffffffff )

            {

                // When there is no adjacent face, the adjacent face index must be set to -1 (edge index is ignored)

                af[c] = 0xffffffff;

                ae[c] = 0;

            }

            else

            {

                af[c] = a/4;

                ae[c] = a%4;

            };

        };

        sInfo.setadjfaces( af[0], af[1], af[2], af[3] );

        sInfo.setadjedges( ae[0], ae[1], ae[2], ae[3] );



        // Calculate the corners of the texture rectangle.

        unsigned int iXS = pG->FaceUVPosition(f)[0];

        unsigned int iYS = pG->FaceUVPosition(f)[1];

        unsigned int iXD = pG->FaceSizeExponent(f)[0];

        unsigned int iYD = pG->FaceSizeExponent(f)[1];

        // Calculate the index of the tile

        unsigned int iTX = pG->FaceUVArea(f)[0], iTY = pG->FaceUVArea(f)[1];

        Image *i = aImages[iTX+iTY*iXW];

        MB_ONBUG( i == NULL )

        {

            for ( unsigned int j = 0; j < iXW*iYW; j++ )

                delete aImages[j];

            return;

        };



        // Based on the orientation of the face, we might have to modify the given values.

        unsigned int iXX = 1, iXY = 0, iYX = 0, iYY = 1;

        switch ( iOrientation )

        {  

        case 0:

            break;

        case 1:

            {

                iYS += (1<<iXD)-1;



                iXX = iYY = 0;

                iXY = 1;

                iYX = -1;

            };

            break;

        case 2:

            iXX = iYY = -1;

            iXS += (1<<iXD);

            iYS += (1<<iYD);

            break;

        case 3:

            {

                iXS += (1<<iYD)-1;



                iXX = iYY = 0;

                iXY = -1;

                iYX = 1;

            };

            break;

        };



        // Set the size of the face. The width and height of a face can be different, but it is always power of two.

        unsigned int iW = 1<<iXD, iH = 1<<iYD;

        sInfo.res = Ptex::Res( iXD, iYD );



        // Allocate a temporary buffer to hold the data.

        tType *pData = new tType[iW*iH*4];



        // Copy the pixels from the texture to the temporary buffer.

        for ( unsigned int y = 0; y < iH; y++ )

            for ( unsigned int x = 0; x < iW; x++ )

            {

                Color c = i->ColorAt( iXS+x*iXX+y*iXY, iYS+x*iYX+y*iYY );

                pData[(x+y*iW)*4+0] = c.r*iMultiplier;

                pData[(x+y*iW)*4+1] = c.g*iMultiplier;

                pData[(x+y*iW)*4+2] = c.b*iMultiplier;

                pData[(x+y*iW)*4+3] = c.a*iMultiplier;

            };



        // Write the data to the file, and free the buffer.

        pWriter->writeFace( f, sInfo, pData );

        delete pData;

        Kernel()->ProgressAdd();

    };



    // Close and release the file.

    if ( g_bIncludeBaseMesh )

        PtexUtilizer::WriteMeshData( pWriter, pSourceSurface );

    pWriter->close( sError );

    pWriter->release();



    for ( unsigned int j = 0; j < iXW*iYW; j++ )

        delete aImages[j];



    Kernel()->ProgressEnd();

};