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 ) const
{
    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;
};

PtexPaintExporter::PtexPaintExporter() :
m_bUseBaseLevel(this, NTR("Use Base Level"))
{
    m_bUseBaseLevel.SetValue( true );
};

// This function exports one paint layer as a ptex file.
void PtexPaintExporter::Export( const QString &sFileName, int iFileTypeIndex, const Mesh *pSourceSurface, TexturePool *pSource )
{
    if( m_bUseBaseLevel )
    {
        // 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 = m_bUseBaseLevel ? pSL->Geometry()->LowestLevel() : pSL;

    // 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->Geometry()->HighestLevel() );
    m->SetUtilizerType( PtexUtilizer::StaticClass() );
    m->SetLocatorType( ClassDesc::ByName( NTR( "SubdivisionLocator" ) ) );

    // We only need the paint layer 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->SetAttributeValue( "filename", 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 )
    {
        if ( pBL->UVlessPaintingStatus() == 2 )
            pPL->m_eDistribution = PtexLayout::distCustom;
        else
        {
            unsigned int iPixelCount = 0;
            for ( unsigned int t = 0; t < pSource->TileCount(); t++ )
            {
                const Texture *pT = pSource->Tile( t );
                iPixelCount += pT->Width()*pT->Height();
            };
            if ( pSource->TileCount() > 0 )
            {
                pPL->m_eDistribution = PtexLayout::distUVArea;
                pPL->m_iDesiredTexelCount = pSource->Tile( 0 )->Width()*pSource->Tile( 0 )->Height()/2*pSource->TileCount();
            };
        };
    };

    // 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 );
};

inline bool isUVRightHanded(const Mesh & mesh, unsigned face) {
    MB_ASSERT(mesh.Type() == Topology::typeQuadric);
    TC o = mesh.QuadVertexTC(face, 0);
    TC a = mesh.QuadVertexTC(face, 1) - o;
    TC b = mesh.QuadVertexTC(face, 3) - o;
    float zOfCrossProduct = a.u*b.v - b.u*a.v;
    return zOfCrossProduct > 0;
}

// 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()->Interface()->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 if( pT->Location() != TexturePool::locationUnknown && pT->Format() != Image::eUnknown )
                {
                    // its possible that pT is an unallocated texture. The reason is that this code assumes the 
                    // tiles of the texture pool fit perfectly into a rectangle in UV tile space, but that's
                    // not always the case. So the uv tile at (x,y) doesn't nessecarily have any texture data 
                    // allocated for it. So here we'll just check that the valid textures have the same dimensions.
                    // (The TexturePool class *is* supposed to just allocate a tile on-demand though
                    // I don't know why that isn't working in this case, but anyways, its not a problem here)
                    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++ )
    {
        bool swapUV = !isUVRightHanded(*pSourceSurface, 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;
        if ( swapUV && iOrientation % 2 )
            iOrientation = 4 - iOrientation;
        switch ( iOrientation )
        {  
        case 0:
            break;
        case 1:
            {
                iYS += (1<<iXD)-1;

                iXX = iYY = 0;
                iXY = 1;
                iYX = 0xffffffff;
            };
            break;
        case 2:
            iXX = iYY = 0xffffffff;
            iXS += (1<<iXD);
            iYS += (1<<iYD);
            break;
        case 3:
            {
                iXS += (1<<iYD)-1;

                iXX = iYY = 0;
                iXY = 0xffffffff;
                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 iW = swapUV ? 1<<iYD : 1<<iXD,
                 iH = swapUV ? 1<<iXD : 1<<iYD;
        sInfo.res = swapUV ? Ptex::Res( iYD, iXD ) : 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;
                if (swapUV)
                    c = i->ColorAt( iXS+y*iXX+x*iXY, iYS+y*iYX+x*iYY );
                else
                    c = i->ColorAt( iXS+x*iXX+y*iXY, iYS+x*iYX+y*iYY );
                if ( c.a )
                {
                    float f = 1/c.a;
                    c.r *= f;
                    c.g *= f;
                    c.b *= f;
                };
                unsigned index = x+y*iW;
                pData[index*4+0] = c.r*iMultiplier;
                pData[index*4+1] = c.g*iMultiplier;
                pData[index*4+2] = c.b*iMultiplier;
                pData[index*4+3] = c.a*iMultiplier;
            };

        // Write the data to the file, and free the buffer.
        pWriter->writeFace( f, sInfo, pData );
        delete pData;
        Kernel()->Interface()->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()->Interface()->ProgressEnd();
};