- Main Page
- Related Pages
- Modules
- Namespaces
- Classes
- Files
- Directories
- Examples

# 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();
    };