PtexImporter/PtexPaintLayerImporter.cpp

PtexImporter/PtexPaintLayerImporter.cpp
//**************************************************************************/
// Copyright (c) 2011, 2012 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: PTEX texture importer
// CREATED: January 2011
//**************************************************************************/
#if defined(__APPLE__)
#include <OpenGL/OpenGL.h>
#include <OpenGL/Glu.h>
#else
#if defined(JAMBUILD)
#include <GLee.h>
#else
#include <GL/GLee.h>
#include <gl/glu.h>
#endif
#endif
#include <Mudbox/mudbox.h>
#include <math.h>
#include <QtOpenGL/QGLWidget>
#include <QtOpenGL/QGLFormat>
#include <QtOpenGL/QGLContext>
#include "PtexPaintLayerImporter.h"
#include <QtGui/QMessageBox>
#include <Cg/cgGL.h>
#include <QtOpenGL/qgl.h>
#if defined(JAMBUILD)
#include <MudboxIntern/CgFile.h>
#else
#include "CgFile.h"
#endif
IMPLEMENT_CLASS( PtexPaintLayerImporter, PaintLayerImporter, "pteximporter" );
#define MIN_TRI_UV_SIZE 64
//------------------------------------------------------------------------------
// Simple float 2D coordinate ...
class fCoord {
public: float x, y;
fCoord(float a, float b) : x(a), y(b) {}
fCoord() : x(0), y(0) {}
};
//------------------------------------------------------------------------------
// This now works for quads and quads composed of 2 triangles.
inline bool isUVRightHanded(const Mesh & mesh, unsigned face)
{
if ((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.0f;
}
else
{
if (face >= (mesh.FaceCount() - 1)) // boundary condition
return false;
TC o = mesh.TriangleVertexTC(face, 0); // 1 +---+ 2 (1 in face + 1)
TC a = mesh.TriangleVertexTC(face, 1) - o; // | /|
TC b = mesh.TriangleVertexTC(face+1, 2) - o; // | / |
float zOfCrossProduct = a.u*b.v - b.u*a.v; // |/ |
return zOfCrossProduct > 0.0f; // 0 +---+ 3 (2 in face + 1)
}
}
//------------------------------------------------------------------------------
bool PtexPaintLayerImporter::Prepare( const QString &sFileName, Mesh *pMesh,
bool bAsMask, bool bSilentMode )
{
if ( !m_Importer )
{
m_Importer = CreateInstance<PtexImporter>();
if ( !m_Importer )
return false;
m_Importer->CreateMeshFromPtex(sFileName, false, bSilentMode);
}
pMesh = pMesh->Geometry()->LowestLevel();
if (!m_Importer->m_FaceMap)
m_Importer->BuildMapsFromBaseMesh(pMesh);
Ptex::String s;
QByteArray a = QFile::encodeName( sFileName );
PtexTexture *pT = PtexTexture::open( a.constData(), s );
if (!pT) {
if ( !bSilentMode )
Kernel()->Interface()->HUDMessageShow( tr("Unable to open PTEX file. Mudbox cannot import it."),
Kernel()->Log( QString( "Unable to open PTEX file. Mudbox cannot import it.\n" ));
return false;
}
if (pT->meshType() != Ptex::mt_quad) {
if ( !bSilentMode )
Kernel()->Interface()->HUDMessageShow( tr("This PTEX file is not quadrangular. "
"Mudbox cannot import it."),
Kernel()->Log( QString( "This PTEX file is not quadrangular. Mudbox cannot import it.\n" ));
pT->release();
return false;
}
// Step1: Check if the file is compatible with our mesh
// first my making sure the face counts are consistent
if ( pT->numFaces() != m_Importer->m_iSubFaceCount )
{
if ( !bSilentMode )
Kernel()->Interface()->HUDMessageShow( tr("This PTEX file has different number of faces than your mesh. "
"Mudbox cannot import it."),
Kernel()->Log( QString( "PTEX Import failed: Your mesh: %1 faces, file: %2 faces.\n" )
.arg( pMesh->FaceCount() ).arg( pT->numFaces() ) );
pT->release();
return false;
}
if ( pMesh->FaceCount() != m_Importer->m_iTotalTesselatedFaceCount ) {
if ( !bSilentMode )
Kernel()->Interface()->HUDMessageShow( tr("This PTEX file has different number of faces than your mesh. "
"Mudbox cannot import it."),
Kernel()->Log( QString( "PTEX Import failed: Your mesh: %1 faces, file: %2 faces.\n" )
.arg( pMesh->FaceCount() ).arg( m_Importer->m_iTotalTesselatedFaceCount ) );
pT->release();
return false;
}
// if its a quad mesh, ensure that the topology and adjacency is the same
if ((pMesh->Type() == Topology::typeQuadric))
{
if ( m_Importer && !m_Importer->m_allQuads ) // make sure the ptex is all quads too
{
if ( !bSilentMode )
Kernel()->Interface()->HUDMessageShow( tr("This PTEX file is not compatible with your mesh. "
"Mudbox cannot import it. (1)"),
Kernel()->Log( QString( "PTEX Import failed: Ptex is mixed quads and tris/n-gons, your mesh is all quads.\n" ));
pT->release();
return false;
}
for ( unsigned int f = 0; f < pMesh->FaceCount(); f++ )
{
const Ptex::FaceInfo &i = pT->getFaceInfo( f );
for ( int c = 0; c < 4; c++ )
{
int a = i.adjface( c ), b = i.adjedge( c ), d = pMesh->QuadAdjacency( f, c );
if ( (i.adjface( c ) == -1 && pMesh->QuadAdjacency( f, c ) != 0xffffffff) ||
(i.adjface( c ) != -1 && pMesh->QuadAdjacency( f, c ) != i.adjface( c )*4+i.adjedge( c )) )
{
if ( !bSilentMode )
Kernel()->Interface()->HUDMessageShow( tr("This PTEX file is not compatible with your mesh. "
"Mudbox cannot import it. (2)"),
Kernel()->Log( QString( "PTEX Import failed: Ptex adjacency does not match mudbox mesh adjacency.\n" ));
pT->release();
return false;
}
}
}
}
else
{ // mesh is triangular, check that the ptex is not all quads...
if ( m_Importer->m_allQuads )
{
if ( !bSilentMode )
Kernel()->Interface()->HUDMessageShow( tr("This PTEX file is not compatible with your mesh. "
"Mudbox cannot import it. (3)"),
Kernel()->Log( QString( "PTEX Import failed: Ptex is all quads, mudbox mesh is mixed quads and tris/n-gons.\n" ));
pT->release();
return false;
}
// could check for consistent adjacency here....
}
// Step 2: Auto generate UV.
if ( pG )
{
// the sizes of the quads in uv space no longer have to perfectly match,
// as this importer will resize them if needed.
unsigned int iMaxSize = 1;
while ( iMaxSize*2 < pG->UVTileSize() )
iMaxSize *= 2;
for ( int f = 0; f < pT->numFaces(); f++ )
{ // ths loops through the ptex faces & subfaces....
const Ptex::FaceInfo &i = pT->getFaceInfo( f );
if ( (1u << i.res.ulog2) > iMaxSize || (1u << i.res.vlog2) > iMaxSize )
{
if ( !bSilentMode )
Kernel()->Interface()->HUDMessageShow( tr("This PTEX contains faces with too big resolution. "
"Mudbox cannot import it."),
Kernel()->Log( QString( "PTEX Import failed: Ptex contains faces bigger than the tile size.\n" ));
pT->release();
return false;
}
}
}
else
{ // No UV generator, set up for ptex and make new uvs...
bool bConvertToPtex = false;
if ( pMesh->UVlessPaintingStatus() == 0 )
{
if ( (Kernel()->Scene()->ActivePaintLayer() || Kernel()->Scene()->ActivePaintLayerGroup() ) && !bAsMask )
{
if ( !bSilentMode )
{
QMessageBox::StandardButton b = QMessageBox::question( Kernel()->Interface()->MainWindow(),
tr("PTEX paint layer import"),
tr("The mesh %1 has UV paint layers. "
"Do you want to convert them to PTEX Layers?").
arg(pMesh->Geometry()->Name()),
if ( b == QMessageBox::Cancel ) {
Kernel()->Log( QString( "PTEX Import cancelled. (1)\n" ));
pT->release();
return false;
}
bConvertToPtex = b == QMessageBox::Yes;
}
else
bConvertToPtex = true;
}
else
{
if ( bAsMask )
bConvertToPtex = true;
if ( !bSilentMode )
{
QMessageBox msgBox(Kernel()->Interface()->MainWindow());
msgBox.setWindowTitle( tr("PTEX paint layer import") );
msgBox.setText(QString( tr("To import a PTEX paint layer, the model %1 must first be set up for PTEX.") ).
arg(pMesh->Geometry()->Name()));
QPushButton* YesBtn = msgBox.addButton(tr("Setup for PTEX"), QMessageBox::AcceptRole);
QPushButton* CancelBtn = msgBox.addButton(QMessageBox::Cancel);
msgBox.exec();
if ( msgBox.clickedButton() == CancelBtn ) {
Kernel()->Log( QString( "PTEX Import cancelled. (2)\n" ));
pT->release();
return false;
}
}
}
}
pG = pMesh->ChildByClass<UVGeneratorNode>( true );
unsigned int iMaxSize = 1;
while ( (iMaxSize * 2) < pG->UVTileSize() )
iMaxSize *= 2;
for ( int f = 0; f < pT->numFaces(); f++ )
{ // ths loops through the ptex faces & subfaces....
const Ptex::FaceInfo &i = pT->getFaceInfo( f );
if ( (1u << i.res.ulog2) > iMaxSize || (1u << i.res.vlog2) > iMaxSize )
{
if ( !bSilentMode )
Kernel()->Interface()->HUDMessageShow( tr("This PTEX contains faces with too big resolution. "
"Mudbox cannot import it."),
Kernel()->Log( QString( "PTEX Import failed: Ptex contains faces bigger than the tile size.\n" ));
pT->release();
return false;
}
}
for ( unsigned int mf = 0; mf < pMesh->FaceCount(); mf++ )
{ // loop through the mudbox faces...
const int pf = m_Importer->m_ReverseFaceMap[mf]; // lookup the ptex poly id from the mudbox face ID
unsigned int ppf = m_Importer->m_FaceMap[pf].m_PTexSubfaceID; // xlate from ptex polyID to PTex subFaceID
const Ptex::FaceInfo &i = pT->getFaceInfo( ppf );
int maxU = i.res.ulog2;
int maxV = i.res.vlog2;
unsigned int boost = 0;
if ( m_Importer->m_FaceMap[pf].m_NumEdges != 4)
{
// if it's an ngon or triangle, look at the max uv size of each
// subface for that n-gon, *and* all the neighbors of those subfaces.
for (int j = 0; j < m_Importer->m_FaceMap[pf].m_NumEdges; ++j)
{
const Ptex::FaceInfo &ii = pT->getFaceInfo( ppf );
if (ii.res.ulog2 > maxU) maxU = ii.res.ulog2;
if (ii.res.vlog2 > maxV) maxV = ii.res.vlog2;
for (int k = 0; k < 4; ++k) // look through the neighbors
{ // and take this into account.
const Ptex::FaceInfo &jj = pT->getFaceInfo( ii.adjface(k) );
if (jj.res.ulog2 > maxU) maxU = jj.res.ulog2;
if (jj.res.vlog2 > maxV) maxV = jj.res.vlog2;
}
ppf++;
}
// Since in ptex each triangle and n-gon is subdivided into n smaller
// subfaces, lets make sure the n-gon is big enough in the mudbox
// internal uv space so that any losses due to resampling are minimized
// by ensureing that we magnify, and not minify texture subfaces on import.
if ( m_Importer->m_FaceMap[pf].m_NumEdges != 4)
boost = 1;
}
unsigned int nu = 1u << (maxU + boost);
unsigned int nv = 1u << (maxV + boost);
if (nu > iMaxSize) nu = iMaxSize;
if (nv > iMaxSize) nv = iMaxSize;
// back that up with enforcing some minimum uv sizes for triangles and n-gons
if ( m_Importer->m_FaceMap[pf].m_NumEdges == 3)
{
if (nu < MIN_TRI_UV_SIZE) nu = MIN_TRI_UV_SIZE;
if (nv < MIN_TRI_UV_SIZE) nv = MIN_TRI_UV_SIZE;
}
else if ( m_Importer->m_FaceMap[pf].m_NumEdges > 4)
{
if (nu < (MIN_TRI_UV_SIZE * 2)) nu = MIN_TRI_UV_SIZE * 2;
if (nv < (MIN_TRI_UV_SIZE * 2)) nv = MIN_TRI_UV_SIZE * 2;
}
pG->SetFaceSize( mf, nu, nv );
} // endfor mesh faces
pT->release();
// this next three lines causes the UVs to be generated.
QVector<Mesh*> aMeshes;
aMeshes.push_back( pMesh );
PtexPainting::Setup( aMeshes, bConvertToPtex );
}
return true;
}
//------------------------------------------------------------------------------
bool PtexPaintLayerImporter::Import( const QString &sFileName,
int iFileTypeIndex,
Mesh *pMesh,
TexturePool *pTargetPool )
{
pMesh = pMesh->Geometry()->LowestLevel();
if ( !pG )
{
pG = pMesh->ChildByClass<UVGeneratorNode>( true );
}
MB_ONBUG( pG == 0 )
return false;
// It looks like someone is calling "Prepare" on a different instance than
// what they call Import on -- this used to work, but now we have state
// we carry forward. If this is happening, we need to rebuild this state.
// that is carried in the m_Importer - specificaly the face maps
if ( !m_Importer ) {
m_Importer = CreateInstance<PtexImporter>();
if ( !m_Importer )
return false;
m_Importer->BuildMapsFromBaseMesh(pMesh);
}
if (!m_Importer->m_FaceMap) {
m_Importer->BuildMapsFromBaseMesh(pMesh);
}
// one last sanity check for the importer and the face maps.
// if they aren't there, we can't proceed without crashing
if ( !m_Importer ||
(m_Importer && !m_Importer->m_FaceMap) ||
(m_Importer && !m_Importer->m_ReverseFaceMap) ) {
return false;
}
MB_ASSERT( iFileTypeIndex == 0 );
pMesh = pMesh->Geometry()->LowestLevel(); // redundant?
if ( !pMesh->HasExpandedTCs() )
pMesh->CreateExpandedTCs();
// Copy the color data to the textures.
Material *pMat = pMesh->Geometry()->Material();
if ( pTargetPool == 0 )
{
for ( unsigned int j = 0; j < pMat->TextureCount(); j++ )
{
TexturePool *pTP = pMat->Texture( j );
if ( pTP->Name() == NTR("Diffuse") )
{
LayerContainer *pC = dynamic_cast<LayerContainer *>( pTP );
if ( pC )
{
Layer *pL = pC->CreateLayer();
pTargetPool = dynamic_cast<TexturePool *>( pL );
}
pTP->SetRenderMode( TexturePool::renderModeTexture );
}
}
}
MB_SAFELY( pTargetPool && pG )
{
Kernel()->Interface()->ProgressStart( tr("Reading Ptex Texture..."),
m_Importer->m_iSubFaceCount );
QFileInfo i( sFileName );
pTargetPool->Node::SetName( i.baseName() );
QByteArray a = QFile::encodeName( sFileName );
Ptex::String s;
PtexTexture *pT = PtexTexture::open( a.constData(), s );
if (!pT) {
Kernel()->Interface()->HUDMessageShow( tr("Unable to open PTEX file. "
"Mudbox cannot import it."),
Kernel()->Log( QString( "Unable to open PTEX file. Mudbox cannot import it.\n" ));
if ( m_Importer )
{
if (m_Importer->m_FaceMap != 0) {
delete [] m_Importer->m_FaceMap;
m_Importer->m_FaceMap = 0;
}
if (m_Importer->m_ReverseFaceMap != 0) {
delete [] m_Importer->m_ReverseFaceMap;
m_Importer->m_ReverseFaceMap = 0;
}
}
return false;
}
if (pT->meshType() != Ptex::mt_quad) {
Kernel()->Interface()->HUDMessageShow( tr("This PTEX file is not quadrangular. "
"Mudbox cannot import it."),
Kernel()->Log( QString( "This PTEX file is not quadrangular. Mudbox cannot import it.\n" ));
pT->release();
return false;
}
LoadPtexTexture( pT, pTargetPool, pMesh, pG );
pT->release();
Kernel()->Interface()->ProgressEnd();
// Step 4: Fill the UV bleeding area.
EdgeBleeding::FillBleedingAreas( pMesh, pTargetPool, true );
}
if ( m_Importer )
{
if (m_Importer->m_FaceMap != 0) {
delete [] m_Importer->m_FaceMap;
m_Importer->m_FaceMap = 0;
}
if (m_Importer->m_ReverseFaceMap != 0) {
delete [] m_Importer->m_ReverseFaceMap;
m_Importer->m_ReverseFaceMap = 0;
}
}
return true;
}
//------------------------------------------------------------------------------
// fcn to draw a textured quad given the 4 corners.
static void drawQuad(float x0, float y0, float x1, float y1,
float x2, float y2, float x3, float y3)
{
glBegin( GL_QUADS );
glColor4f( 1, 1, 1, 1 );
glTexCoord2f( x0, y0 ); glVertex2f( x0, y0 ); // 0
glTexCoord2f( x1, y1 ); glVertex2f( x1, y1 ); // 1
glTexCoord2f( x2, y2 ); glVertex2f( x2, y2 ); // 2
glTexCoord2f( x3, y3 ); glVertex2f( x3, y3 ); // 3
glEnd();
}
//------------------------------------------------------------------------------
// Load the texture data from the ptex file to an existing paint layer
void PtexPaintLayerImporter::LoadPtexTexture( PtexTexture *pT,
TexturePool *pTP,
const Mesh *pMesh,
{
bool bDescriptorInitialized = false;
// map from the ptex formats to mudbox formats.
static const enum Image::Format aPtexFormats[4] = { Image::e8integer, Image::e16integer,
Image::e16float, Image::e32float };
const unsigned int iTileSize = pG->UVTileSize(); // This is usually 2048
// bad things happen if we have gotten here and there are no texture coords on the mesh.
if (!pMesh->m_pTCs) {
Kernel()->Log( QString( "This mesh has no PTEX UVs!\n" ));
Kernel()->Interface()->HUDMessageShow( tr("This mesh has no PTEX UVs!"),
return;
}
if (!pG->getMesh()->m_pTCs) {
Kernel()->Log( QString( "This mesh has no PTEX UVs! (2)\n" ));
Kernel()->Interface()->HUDMessageShow( tr("This mesh has no PTEX UVs (2)!"),
return;
}
// just for curiosity, keep track of how much resampling we have done
int unresampledQuads = 0;
int resampledQuads = 0;
int resampledSubFaces = 0;
// Find out how many texture tiles we have to process.
unsigned int iUS = 0, iUE = 0, iVS = 0, iVE = 0;
for ( unsigned int mf = 0; mf < pMesh->FaceCount(); mf++ )
{
UVGeneratorNode::DimData2 d = pG->FaceUVArea( mf );
iUS = Min( iUS, d[0] );
iUE = Max( iUE, d[0] + 1 );
iVS = Min( iVS, d[1] );
iVE = Max( iVE, d[1] + 1 );
}
bool * swapUV = new bool[pMesh->FaceCount()];
for (unsigned int mf = 0; mf < pMesh->FaceCount(); mf++)
swapUV[mf] = !isUVRightHanded(*pMesh, mf);
// Loop through the 2d grid of texture tiles.
for ( unsigned int u = iUS; u < iUE; u++ ) {
for ( unsigned int v = iVS; v < iVE; v++ )
{
bool quadResampling = false;
if ( !bDescriptorInitialized )
{
bDescriptorInitialized = true;
pTP->SetDefaultTileDescriptor( TexturePool::TileDescriptor( iTileSize, iTileSize, aPtexFormats[pT->dataType()], 4, NTR("ptx"), Color::transparent ) );
};
// Create an image with the same format as the ptex file to avoid quality loss.
Instance<Image> i;
i->Create( iTileSize, iTileSize, 4, aPtexFormats[pT->dataType()], true );
// loop through the quads first....
// Loop through all the faces of the mesh.
// loop through the ptex geometry faces, and handle the quads first...
// this loop will process only those quads where no resampling is needed.
for ( int pf = 0; pf < m_Importer->m_iFaceCount; pf++ )
{
if ( m_Importer->m_FaceMap[pf].m_NumEdges == 4 )
{
const unsigned int mf = m_Importer->m_FaceMap[pf].m_MBFaceID; // xlate from ptex faceID to Mudbox FaceID
const unsigned int ppf = m_Importer->m_FaceMap[pf].m_PTexSubfaceID; // xlate from ptex faceID to PTex subFaceID
// Check which tile the face belongs to, and if it is something else than the current one, skip it.
if ( pG->FaceUVArea( mf )[0] != u || pG->FaceUVArea( mf )[1] != v )
continue;
// if the exponents are different, leave it for the second pass
// which will handle triangles, n-gons and quads that need resampling
if ( (pG->FaceSizeExponent( mf )[swapUV[mf] ? 1 : 0] != pT->getFaceInfo( ppf ).res.ulog2) ||
(pG->FaceSizeExponent( mf )[swapUV[mf] ? 0 : 1] != pT->getFaceInfo( ppf ).res.vlog2 ) ) {
quadResampling = true;
continue;
}
MB_ASSERT( pG->FaceSizeExponent( mf )[swapUV[mf] ? 1 : 0] == pT->getFaceInfo( ppf ).res.ulog2 );
MB_ASSERT( pG->FaceSizeExponent( mf )[swapUV[mf] ? 0 : 1] == pT->getFaceInfo( ppf ).res.vlog2 );
++unresampledQuads;
UVGeneratorNode::DimData2 d = pG->FaceUVPosition( mf );
// Prepare the surface data in a buffer.
const char iOrientation = pG->FaceOrientation( mf );
void *p = NULL;
switch ( pT->dataType() )
{
case Ptex::dt_uint8:
p = PrepareFaceBuffer<unsigned char, 255>( pT, ppf, iOrientation, swapUV[mf] );
break;
case Ptex::dt_uint16:
p = PrepareFaceBuffer<unsigned short, 65535>( pT, ppf, iOrientation, swapUV[mf] );
break;
case Ptex::dt_half:
p = PrepareFaceBuffer<half_, 1>( pT, ppf, iOrientation, swapUV[mf] );
break;
case Ptex::dt_float:
p = PrepareFaceBuffer<float, 1>( pT, ppf, iOrientation, swapUV[mf] );
break;
}
// Copy the buffer onto the image, and delete the buffer.
i->setTile( d[0]%iTileSize, d[1]%iTileSize, pG->FaceUVSize( mf )[0], pG->FaceUVSize( mf )[1], p );
free( p );
Kernel()->Interface()->ProgressAdd();
} // endif -- handle unresampled quads
} // endfor all the polys
// Fill the texture tile using the image we prepared.
Texture *pO = pTP->Tile( AxisAlignedBoundingBox( Vector( u, v, 0.5 ), Vector( u+1, v+1, 0.5 ) ) );
pO->CopyFrom( i );
// now handle polys where the texture has to be resampled -- non quads, and
// quads where the u or v size does not match that in the mudbox mesh
if (!m_Importer->m_allQuads || quadResampling)
{
// make sure proxying does not interfere.
unsigned char proxyLevel = pO->getProxyLevel();
if (proxyLevel != 0)
pO->setProxyLevel(0);
// make the texture tile the current render target
pO->SetAsRenderTarget();
glFlush();
// Set the viewport to fill this FBO completely with the texture 1:1
glPushAttrib( GL_ENABLE_BIT | GL_TEXTURE_BIT );
glBlendFunc( GL_ONE, GL_ZERO );
glDisable( GL_BLEND );
glDisable( GL_DEPTH_TEST );
glDisable( GL_CULL_FACE );
glDisable( GL_SCISSOR_TEST );
glDisable( GL_LIGHTING );
glEnable( GL_TEXTURE_2D );
// set up the gl matrices ---
glMatrixMode( GL_TEXTURE );
glPushMatrix();
glLoadIdentity();
// make 1 = 1 texel in the render target.
glMatrixMode( GL_PROJECTION );
glPushMatrix();
glLoadIdentity();
glViewport( 0, 0, pO->Width(), pO->Height() );
gluOrtho2D(0, pO->Width(), 0, pO->Height() );
glMatrixMode( GL_MODELVIEW );
glPushMatrix();
glLoadIdentity();
bool dirty = false;
CGcontext cgContext = cgCreateContext();
#if defined(JAMBUILD)
CgFile f( NTR("Shaders/TexturePaint/quad_linear.bin") );
#else
CgFile f( NTR("Shaders/Simple.cg") );
#endif
CGprogram cgProgram = cgCreateProgram(cgContext, CG_SOURCE, &f.Buffer()[0], cgProfile, NULL, NULL );
MB_ASSERT( cgProgram );
cgGLLoadProgram( cgProgram );
CGparameter cgV0, cgV1, cgV2, cgV3;
cgV0 = cgGetNamedParameter( cgProgram, NTR("V0") );
cgV1 = cgGetNamedParameter( cgProgram, NTR("V1") );
cgV2 = cgGetNamedParameter( cgProgram, NTR("V3") );
cgV3 = cgGetNamedParameter( cgProgram, NTR("V2") );
cgGLBindProgram( cgProgram );
cgGLEnableProfile( cgProfile );
// loop through the ptex faces, and handle the triangles and n-gons and
// (TODO) potentially any quads that need to be resampled.
for ( int pf = 0; pf < m_Importer->m_iFaceCount; pf++ )
{
const unsigned int mf = m_Importer->m_FaceMap[pf].m_MBFaceID; // xlate from ptex faceID to Mudbox FaceID
const unsigned int ppf = m_Importer->m_FaceMap[pf].m_PTexSubfaceID; // xlate from ptex faceID to PTex subFaceID
// Check which tile the face belongs to, and if it is something else than the current one, skip it.
if ( pG->FaceUVArea( mf )[0] != u || pG->FaceUVArea( mf )[1] != v )
continue;
const int numEdges = m_Importer->m_FaceMap[pf].m_NumEdges;
// Handle triangles and n-gons where n < MAX_EDGE_COUNT....
if ( numEdges != 4 &&
numEdges > 2 &&
numEdges < MAX_EDGE_COUNT) {
static const float jitterOffsets [3] = { -1.0f, 0.0f, 1.0f };
// collect the texture coordinates of the poly vertices
TC TCs[MAX_EDGE_COUNT+1];
TCs[0] = pMesh->TriangleVertexTC(mf, 0); // get the first triangle
TCs[1] = pMesh->TriangleVertexTC(mf, 1); // of the n-gon
TCs[2] = pMesh->TriangleVertexTC(mf, 2);
// Remember; on import to Mudbox, all n-gons are tessellated
// into fans of n-2 triangles
for (int j = 3; j < numEdges; ++j) { // and the last vert of each subsequent triangle
TCs[j] = pMesh->TriangleVertexTC(mf + j - 2, 2);
}
// Convert them all to texel coordinates in the current texture tile
for (int j = 0; j < numEdges; ++j) {
float dd = 0.0f;
TCs[j].u = modff(TCs[j].u, &dd) * iTileSize;
TCs[j].v = modff(TCs[j].v, &dd) * iTileSize;
}
// draw the poly jitered in uv by +-1.
// this is to ensure thet ehe pixels around the edge of the
// ngon are filled with colors from just inside the ngon --
// ensuring no "seams" in the texture display.
for (int ii = 0; ii < 3; ++ii) {
for (int jj = 0; jj < 3; ++jj) {
TC tempTCs[MAX_EDGE_COUNT+1];
memcpy(tempTCs, TCs, sizeof(TCs));
for (int kk = 0; kk < numEdges; ++kk) {
tempTCs[kk].u += jitterOffsets[jj];
tempTCs[kk].v += jitterOffsets[ii];
}
// Compute the center of the poly, and the center of each edge.
// which will give us the quads of the subfaces in target uv
// coordinates.
// Recall that the winding order in ptex is ccw.
// See http://ptex.us/adjdata.html for a nice diagram.
fCoord polyCenter(0, 0);
for (int j = 0; j < numEdges; ++j) {
polyCenter.x += TCs[j].u;
polyCenter.y += TCs[j].v;
}
polyCenter.x /= (float) numEdges;
polyCenter.y /= (float) numEdges;
tempTCs[numEdges] = tempTCs[0]; // Replicate the first vertex at the end...
// now compute the edge centers
fCoord edgeCenters[MAX_EDGE_COUNT];
for (int j = 0; j < numEdges; ++j) {
edgeCenters[j].x = (tempTCs[j].u + tempTCs[j + 1].u) * 0.5f;
edgeCenters[j].y = (tempTCs[j].v + tempTCs[j + 1].v) * 0.5f;
}
// remember; in ptex, each triangle/n-gon is catmull / clarke
// subdivided into n quads -- with ccw winding. These are
// called sub-faces in ptex nomenclature.
// loop through the sub-faces, getting the texture from the
// ptex file for each sub-face and drawing it as a textured
// quad into the target texture tile.
for (int subFace = 0; subFace < numEdges; ++subFace) {
const int fId = ppf + subFace;
Ptex::FaceInfo fInfo = pT->getFaceInfo(fId);
// Prepare the surface data from the ptex file in a buffer.
const char iOrientation = 0;
void *p = NULL;
switch ( pT->dataType() )
{
case Ptex::dt_uint8:
p = PrepareFaceBuffer<unsigned char, 255>( pT, fId, iOrientation, false );
break;
case Ptex::dt_uint16:
p = PrepareFaceBuffer<unsigned short, 65535>( pT, fId, iOrientation, false );
break;
case Ptex::dt_half:
p = PrepareFaceBuffer<half_, 1>( pT, fId, iOrientation, false );
break;
case Ptex::dt_float:
p = PrepareFaceBuffer<float, 1>( pT, fId, iOrientation, false );
break;
}
const unsigned int iUsize = 1u << fInfo.res.ulog2;
const unsigned int iVsize = 1u << fInfo.res.vlog2;
Instance<Image> i;
i->Create( iUsize, iVsize, 4, aPtexFormats[pT->dataType()], true );
// Copy the buffer onto the image, and delete the buffer.
i->setTile( 0, 0, iUsize, iVsize, p );
free( p );
// put it into a texture (this will be the source)
Instance<Texture> t;
t->Create(iUsize, iVsize, 4, aPtexFormats[pT->dataType()]);
t->CopyFrom(i);
glEnable( GL_TEXTURE_2D );
t->Activate();
// make sure the texture filtering is correct.
const int lastIndex = (subFace == 0) ? numEdges-1 : subFace-1;
cgGLSetParameter2fv( cgV0, tempTCs[subFace] );
cgGLSetParameter2fv( cgV1, (float *)&edgeCenters[subFace] );
cgGLSetParameter2fv( cgV2, (float *)&polyCenter );
cgGLSetParameter2fv( cgV3, (float *)&edgeCenters[lastIndex] );
// draw the textured quad (representing the subface) into the target texture.
drawQuad(tempTCs[subFace].u, tempTCs[subFace].v, // start with the corner of the poly
edgeCenters[subFace].x, edgeCenters[subFace].y, // then the center of the edge ccw
polyCenter.x, polyCenter.y, // then the center of the poly
edgeCenters[lastIndex].x, edgeCenters[lastIndex].y); // finally the center of the edge cw
} // end for each subface
}
}
// after drawing the jitered polys, draw it in precisely the right location.
// Compute the center of the poly, and the center of each edge.
// which will give us the quads of the subfaces in target uv
// coordinates.
// Recall that the winding order in ptex is ccw.
// See http://ptex.us/adjdata.html for a nice diagram.
fCoord polyCenter(0, 0);
for (int j = 0; j < numEdges; ++j) {
polyCenter.x += TCs[j].u;
polyCenter.y += TCs[j].v;
}
polyCenter.x /= (float) numEdges;
polyCenter.y /= (float) numEdges;
TCs[numEdges] = TCs[0]; // Replicate the first vertex at the end...
// now compute the edge centers
fCoord edgeCenters[MAX_EDGE_COUNT];
for (int j = 0; j < numEdges; ++j) {
edgeCenters[j].x = (TCs[j].u + TCs[j + 1].u) * 0.5f;
edgeCenters[j].y = (TCs[j].v + TCs[j + 1].v) * 0.5f;
}
// remember; in ptex, each triangle/n-gon is catmull / clarke
// subdivided into n quads -- with ccw winding. These are
// called sub-faces in ptex nomenclature.
// loop through the sub-faces, getting the texture from the
// ptex file for each sub-face and drawing it as a textured
// quad into the target texture tile.
for (int subFace = 0; subFace < numEdges; ++subFace) {
const int fId = ppf + subFace;
Ptex::FaceInfo fInfo = pT->getFaceInfo(fId);
// Prepare the surface data from the ptex file in a buffer.
const char iOrientation = 0;
void *p = NULL;
switch ( pT->dataType() )
{
case Ptex::dt_uint8:
p = PrepareFaceBuffer<unsigned char, 255>( pT, fId, iOrientation, false );
break;
case Ptex::dt_uint16:
p = PrepareFaceBuffer<unsigned short, 65535>( pT, fId, iOrientation, false );
break;
case Ptex::dt_half:
p = PrepareFaceBuffer<half_, 1>( pT, fId, iOrientation, false );
break;
case Ptex::dt_float:
p = PrepareFaceBuffer<float, 1>( pT, fId, iOrientation, false );
break;
}
const unsigned int iUsize = 1u << fInfo.res.ulog2;
const unsigned int iVsize = 1u << fInfo.res.vlog2;
Instance<Image> i;
i->Create( iUsize, iVsize, 4, aPtexFormats[pT->dataType()], true );
// Copy the buffer onto the image, and delete the buffer.
i->setTile( 0, 0, iUsize, iVsize, p );
free( p );
// put it into a texture (this will be the source)
Instance<Texture> t;
t->Create(iUsize, iVsize, 4, aPtexFormats[pT->dataType()]);
t->CopyFrom(i);
glEnable( GL_TEXTURE_2D );
t->Activate();
// make sure the texture filtering is correct.
const int lastIndex = (subFace == 0) ? numEdges-1 : subFace-1;
cgGLSetParameter2fv( cgV0, TCs[subFace] );
cgGLSetParameter2fv( cgV1, (float *)&edgeCenters[subFace] );
cgGLSetParameter2fv( cgV2, (float *)&polyCenter );
cgGLSetParameter2fv( cgV3, (float *)&edgeCenters[lastIndex] );
// draw the textured quad (representing the subface) into the target texture.
drawQuad(TCs[subFace].u, TCs[subFace].v, // start with the corner of the poly
edgeCenters[subFace].x, edgeCenters[subFace].y, // then the center of the edge ccw
polyCenter.x, polyCenter.y, // then the center of the poly
edgeCenters[lastIndex].x, edgeCenters[lastIndex].y); // finally the center of the edge cw
dirty = true;
++resampledSubFaces;
Kernel()->Interface()->ProgressAdd();
} // end for each subface
} else // endif non quad poly.
if ( numEdges == 4 && quadResampling ) {
// if the exponents are the same, it was done in the first pass above
// which handles quads that do not require resampling, so in that case
// just skip this one -- it should have been processed above.
if ( (pG->FaceSizeExponent( mf )[swapUV[mf] ? 1 : 0] == pT->getFaceInfo( ppf ).res.ulog2) &&
(pG->FaceSizeExponent( mf )[swapUV[mf] ? 0 : 1] == pT->getFaceInfo( ppf ).res.vlog2 ) ) {
continue;
}
TC TCs[4];
if (m_Importer->m_allQuads) {
TCs[0] = pMesh->QuadVertexTC(mf, 0);
TCs[1] = pMesh->QuadVertexTC(mf, 1);
TCs[2] = pMesh->QuadVertexTC(mf, 2);
TCs[3] = pMesh->QuadVertexTC(mf, 3);
} else {
// collect the texture coordinates of the poly vertices
TCs[0] = pMesh->TriangleVertexTC(mf, 0); // get the first triangle
TCs[1] = pMesh->TriangleVertexTC(mf, 1); // of the quad
TCs[2] = pMesh->TriangleVertexTC(mf, 2);
TCs[3] = pMesh->TriangleVertexTC(mf+1, 2); // and the last vertex of the second
}
// Convert them all to texel coordinates in the current texture tile
for (int j = 0; j < 4; ++j) {
float dd = 0.0f;
TCs[j].u = modff(TCs[j].u, &dd) * iTileSize;
TCs[j].v = modff(TCs[j].v, &dd) * iTileSize;
}
const int fId = ppf;
Ptex::FaceInfo fInfo = pT->getFaceInfo(fId);
// Prepare the surface data from the ptex file in a buffer.
const char iOrientation = 0;
void *p = NULL;
switch ( pT->dataType() )
{
case Ptex::dt_uint8:
p = PrepareFaceBuffer<unsigned char, 255>( pT, fId, iOrientation, false );
break;
case Ptex::dt_uint16:
p = PrepareFaceBuffer<unsigned short, 65535>( pT, fId, iOrientation, false );
break;
case Ptex::dt_half:
p = PrepareFaceBuffer<half_, 1>( pT, fId, iOrientation, false );
break;
case Ptex::dt_float:
p = PrepareFaceBuffer<float, 1>( pT, fId, iOrientation, false );
break;
}
const unsigned int iUsize = 1u << fInfo.res.ulog2;
const unsigned int iVsize = 1u << fInfo.res.vlog2;
Instance<Image> i;
i->Create( iUsize, iVsize, 4, aPtexFormats[pT->dataType()], true );
// Copy the buffer onto the image, and delete the buffer.
i->setTile( 0, 0, iUsize, iVsize, p );
free( p );
// put it in a texture
Instance<Texture> t;
t->Create(iUsize, iVsize, 4, aPtexFormats[pT->dataType()]);
t->CopyFrom(i);
glEnable( GL_TEXTURE_2D );
t->Activate();
// make sure the texture filtering is correct.
// draw the textured quad (representing the subface) into the target texture.
cgGLSetParameter2fv( cgV0, TCs[0] );
cgGLSetParameter2fv( cgV1, TCs[1] );
cgGLSetParameter2fv( cgV2, TCs[2] );
cgGLSetParameter2fv( cgV3, TCs[3] );
drawQuad(TCs[0].u, TCs[0].v, TCs[1].u, TCs[1].v,
TCs[2].u, TCs[2].v, TCs[3].u, TCs[3].v);
dirty = true;
++resampledQuads; // keep track of how many we did....
Kernel()->Interface()->ProgressAdd();
} // endif quad.
} // endfor all the polys
glFlush();
cgGLDisableProfile( cgProfile );
cgDestroyContext( cgContext );
// restore the matrices
glMatrixMode( GL_MODELVIEW );
glPopMatrix();
glMatrixMode( GL_PROJECTION );
glPopMatrix();
glMatrixMode( GL_TEXTURE );
glPopMatrix();
glMatrixMode( GL_MODELVIEW );
glPopAttrib();
pO->RestoreRenderTarget();
// it is very important to tell Mudbox's textureing system if
// you have modified a texture when it is resident on the GPU.
// Otherwise, those changes may be lost.
if (dirty)
pO->AddDirtyTile(ImgTile(0, 0, iTileSize, iTileSize));
if (proxyLevel != 0)
pO->setProxyLevel(proxyLevel);
} // endif handle resampled polys
} // end-For looping through uv tiles -- V component
} // end-For looping through uv tiles -- U component
delete [] swapUV;
Kernel()->Log( QString( "PTEX Import processed %1 quad faces with NO resampling. \n").arg(unresampledQuads));
Kernel()->Log( QString( "PTEX Import processed %1 resampled / resized quad faces.\n").arg(resampledQuads));
Kernel()->Log( QString( "PTEX Import processed %1 resampled / resized sub-faces.\n\n").arg(resampledSubFaces));
}
//------------------------------------------------------------------------------
// The following function reads the color data from the ptex file, and prepares
// a buffer. During the preparation it rotates the data to the desired direction,
// and extends it (if necessary) to four channels.
template <typename eFormat, const int iMax>
void *PtexPaintLayerImporter::PrepareFaceBuffer( PtexTexture *pTexture,
unsigned int pTexFaceIndex, // ptex subface index
char iDirection,
bool bSwapUV )
{
if (bSwapUV && iDirection % 2)
iDirection = 4 - iDirection;
static int NoPremultiply = -1;
if (NoPremultiply == -1) {
NoPremultiply = (int)(::getenv("MUDBOX_NO_PTEX_PREMULTIPLY_ON_IMPORT") != 0);
}
const unsigned int iChannelCount = (unsigned int)pTexture->numChannels();
const unsigned int iU = 1u << pTexture->getFaceInfo( pTexFaceIndex ).res.ulog2;
const unsigned int iV = 1u << pTexture->getFaceInfo( pTexFaceIndex ).res.vlog2;
eFormat *pTmp = new eFormat[iU*iV*iChannelCount];
// First read the data from the ptex file into a temporary buffer.
pTexture->getData( pTexFaceIndex, pTmp, iU*iChannelCount*sizeof(eFormat) );
eFormat *pBuffer = (eFormat *)malloc(sizeof(eFormat) * 4 * iU * iV);
const float fMax = (float)iMax;
const float invMax = 1.0f / fMax;
// Copy the content of the temporary buffer into the final one, and during
// the copy, extend it to four channels, and rotate if necessary.
for ( unsigned int y = 0; y < iV; y++ ) {
for ( unsigned int x = 0; x < iU; x++ )
{
// Calculate the position where the data should be written to,
// this depends on the orientation of the face.
unsigned int d = 0;
switch ( iDirection )
{
case 0:
d = bSwapUV ? 4*(y+x*iV) : 4*(x+y*iU);
break;
case 1:
d = bSwapUV ? 4*(x+(iV-y-1)*iU) : 4*(y+(iU-x-1)*iV);
break;
case 2:
d = bSwapUV ? 4*((iV-y-1)+(iU-x-1)*iV) : 4*((iU-x-1)+(iV-y-1)*iU);
break;
case 3:
d = bSwapUV ? 4*((iU-x-1)+y*iU) : 4*((iV-y-1)+x*iV);
break;
}
// Copy the data which exist in the ptex file.
unsigned int c = 0;
while ( c < iChannelCount )
{
pBuffer[d+c] = pTmp[iChannelCount*(x+y*iU)+c];
c++;
}
if ((NoPremultiply == false) && (iChannelCount == 4)) {
// image/texture data in a ptex file is stored unpremultiplied,
// but Mudbox always wants image and texture data premultiplied,
// so we multiply the alpha into the rgb channels here.
float alpha = (float)pBuffer[d+3];
if (iMax != 1) {
pBuffer[d+0] = (eFormat)((((float)pBuffer[d+0] * invMax) * (alpha * invMax)) * fMax);
pBuffer[d+1] = (eFormat)((((float)pBuffer[d+1] * invMax) * (alpha * invMax)) * fMax);
pBuffer[d+2] = (eFormat)((((float)pBuffer[d+2] * invMax) * (alpha * invMax)) * fMax);
} else {
pBuffer[d+0] = pBuffer[d+0] * alpha;
pBuffer[d+1] = pBuffer[d+1] * alpha;
pBuffer[d+2] = pBuffer[d+2] * alpha;
}
} else
{
// Fill the rest using default data (1 for the alpha channel,
// red channel for others).
while ( c < 4 )
{
if ( c == 3 )
pBuffer[d+c] = iMax;
else
pBuffer[d+c] = pBuffer[d];
c++;
}
}
}
}
delete [] pTmp;
return pBuffer;
}
//------------------------------------------------------------------------------