#pragma warning( disable : 4311 4312 )
#include "displacer.h"
#include "math.h"
#include <QtCore/QFileInfo>
#include <QtCore/QDir>
#include <QtGui/QDialog>
#include <QtGui/QDialogButtonBox>
IMPLEMENT_CLASS( DisplaceOperation, TreeNode, "Displace" );
MB_PLUGIN( "MeshDisplacer", "Displacement operation for meshes", "Autodesk", "http://www.mudbox3d.com", DisplaceOperation::Initializer );
DisplaceOperation::DisplaceOperation( void ) :
Node( "Displacement" ),
m_aTiles( "displaceoperation tilelist" ),
m_pObject( this, "Target Mesh" ),
m_iSubdivisionLevel( this, tr("Displace To Level") ),
m_iMaskChannel( this, tr("Use Mask Channel") ),
m_iUDim( this, tr("URange") ),
m_iFirstTileIndex( this, tr("Initial Tile Number") ),
m_sTileRange( this, tr("Use Tile Range") ),
m_sDisplacementFileMask( this, tr("Map") ),
m_sMaskFileMask( this, tr("Mask Map") ),
m_iFinalFaceCount( this, tr("Final Face Count") ),
m_eExecute( this, tr("Go") ),
m_eDelete( this, tr("Delete this operation") ),
m_iDisplacementChannel( this, tr("Use Displacement Channel") ),
m_bSmoothTC( this, tr("Smooth Texture Coors") ),
m_sNextCommand( this, tr("Next Command") ),
m_eMapSpace( this, tr("Map Space") ),
m_fMidvalue( this, tr("Mid value") ),
m_fMultiplier( this, tr("Multiplier") )
{
m_eMapSpace.AddItem( tr("Normal (regular displacement map)") );
m_eMapSpace.AddItem( tr("Relative Tangent") );
m_eMapSpace.AddItem( tr("Absolute Tangent") );
m_eMapSpace.AddItem( tr("Object") );
m_eMapSpace.AddItem( tr("World") );
m_eMapSpace.SetCategory( "#top" );
m_eMapSpace = 0;
m_eMapSpace.SetVisible( true );
unsigned int i = 1;
QString sName;
bool b;
do
{
b = false;
sName = QString(tr("Operation #%1")).arg(i);
for ( Node *p = First(); p; p = p->Next() )
if ( p->IsKindOf( StaticClass() ) )
{
if ( sName == dynamic_cast<DisplaceOperation *>( p )->Name() )
{
i++;
b = true;
};
};
} while ( b );
SetName( sName );
m_bFloatMap = false;
m_iBaseU = 0;
m_iBaseV = 0;
m_pObject.m_sNullString = tr("- Select a Mesh -");
LoadTemplate();
m_fMidvalue.SetVisible( true );
m_fMultiplier.SetVisible( true );
m_sMaskFileMask.SetVisible( true );
m_sNextCommand.SetVisible( false );
m_sNextCommand.Connect( Kernel()->NextCommand );
};
bool NonPTEXValidator( const Geometry *pTarget )
{
if ( pTarget && pTarget->LowestLevel()->AttributeValue( NTRQ("isptexuvlayout") ) == "true" )
return false;
else
return true;
}
QWidget* DisplaceOperation::CreatePropertiesWindow( QWidget *pParent )
{
m_pObject.SetValidator( NonPTEXValidator );
QString sName = Name();
QWidget* pW = TreeNode::CreatePropertiesWindow(pParent);
SetName(sName);
QHBoxLayout* pButtonsLayout = new QHBoxLayout;
pButtonsLayout->setContentsMargins(10, 0, 10, 10);
HelpButton* pHelpButton = new HelpButton( pW, NTRQ("SculptUsingDisplacementHelp") );
pHelpButton->setFixedWidth( 120 );
QPushButton *pDoneButton = new QPushButton( tr("Done") );
pButtonsLayout->addWidget(pHelpButton, Qt::AlignLeft);
pButtonsLayout->addWidget(pDoneButton);
QWidget *pTmpW = new QWidget;
pTmpW->setLayout(pButtonsLayout);
pW->layout()->addWidget(pTmpW);
pW->show();
MB_VERIFY( pW->connect( pDoneButton, SIGNAL(released()), pW, SLOT(reject()) ) );
pW->setWindowTitle(QString(tr("Sculpt using Map - %1").arg(sName)) );
pW->resize( pW->size().width(), pW->size().height() + 40 );
return pW;
};
void DisplaceOperation::Serialize( Stream &s )
{
s == m_pObject == m_iSubdivisionLevel == m_iFinalFaceCount == m_iUDim == m_iFirstTileIndex == m_sTileRange == m_sDisplacementFileMask == m_sMaskFileMask == m_iMaskChannel;
if( s.IsOlderThan( 300 ) )
{
QString s1;
s == s1;
SetName( s1 );
};
if ( !s.IsOlderThan( 155+1 ) )
s == m_bSmoothTC;
if ( s.IsNewerThan( 340 ) )
TreeNode::Serialize( s );
};
void DisplaceOperation::OnNodeEvent( const Attribute &a, NodeEventType eType )
{
if ( a == m_pObject && eType == etValueChanged )
{
if ( m_pObject )
m_iSubdivisionLevel = m_pObject->LevelCount()-1;
};
if ( (a == m_iSubdivisionLevel || a == m_pObject) && eType == etValueChanged )
{
if ( m_pObject.Value() && m_iSubdivisionLevel < int(m_pObject->LevelCount()-1) )
{
Kernel()->MessageBox( Kernel::msgError, tr("Error"), tr("Subdivision level cannot be less than %1").arg( m_pObject->LevelCount()-1 ) );
m_iSubdivisionLevel = m_pObject->LevelCount()-1;
return;
};
if ( m_pObject != 0 && m_iSubdivisionLevel != -1)
m_iFinalFaceCount = int(m_pObject->LowestLevel()->TotalFaceCount()*pow(float(4),m_iSubdivisionLevel));
else
m_iFinalFaceCount = 0;
};
if ( eType == etValueChanged && a == m_sNextCommand )
{
QString sOp = m_sNextCommand.Value().section( ' ', 0, 0 );
if ( sOp == "meshdisplacement" )
{
QString sName = m_sNextCommand.Value().section( '"', 1, 1 );
if ( sName == Name() )
Do();
};
};
if ( a == m_sDisplacementFileMask && eType == etValueChanged )
{
QString sFN = m_sDisplacementFileMask.Value();
m_sDisplacementFileMask = FilterFileName( sFN );
};
if ( a == m_sMaskFileMask && eType == etValueChanged )
{
QString sFN = m_sMaskFileMask.Value();
m_sMaskFileMask = FilterFileName( sFN );
};
if ( a == m_eExecute && eType == etEventTriggered )
{
Kernel()->RecordCommand( NTRQ("meshdisplacement \"%1\"").arg(Name()) );
Do();
};
if ( a == m_eDelete && eType == etEventTriggered )
{
Kernel()->Scene()->RemoveChild(this);
Kernel()->RefreshUI();
delete this;
}
}
void DisplaceOperation::Do( void )
{
if ( m_pObject == 0 )
throw new Error( tr("You must select a mesh in the scene first") );
AxisAlignedBoundingBox b;
for ( unsigned int i = 0; i < m_pObject->LowestLevel()->TCCount(); i++ )
b.Extend( Vector( m_pObject->LowestLevel()->m_pTCs[i].m_fU, m_pObject->LowestLevel()->m_pTCs[i].m_fV ) );
if ( float(m_iUDim) < ceilf(b.m_vEnd.x) )
m_iUDim = int(ceilf(b.m_vEnd.x));
m_iBaseU = int(floorf(b.m_vStart.x));
m_iBaseV = int(floorf(b.m_vStart.y));
m_aTiles.Clear();
QString sTileRange = m_sTileRange.Value();
if ( sTileRange.size() )
{
int p = 0, i[2] = { 0, 0 }, k = 0;
while ( p < sTileRange.size() )
{
if ( p == sTileRange.size() || sTileRange[p] == ',' )
{
i[1] = Max( i[1], i[0] );
for ( int h = i[0]; h <= i[1]; h++ )
{
Tile t;
t.m_iIndex = h;
t.m_bZero = false;
m_aTiles.Add( t );
};
i[0] = i[1] = 0;
k = 0;
};
QChar c = sTileRange[p];
if ( c.isDigit() )
i[k] = i[k]*10+(c.toAscii()-'0');
else if ( sTileRange[p] == '-' )
k = 1;
p++;
};
i[1] = Max( i[1], i[0] );
for ( int h = i[0]; h <= i[1]; h++ )
{
Tile t;
t.m_iIndex = h;
t.m_bZero = false;
m_aTiles.Add( t );
};
int iWidth = int(ceilf(b.m_vEnd.x)-floorf(b.m_vStart.x));
int iHeight = int(ceilf(b.m_vEnd.y)-floorf(b.m_vStart.y));
int iStart = m_iFirstTileIndex;
if ( iWidth > m_iUDim )
m_iUDim = iWidth;
int iEnd = m_iFirstTileIndex+m_iUDim*iHeight;
for ( int i = iStart; i < iEnd; i++ )
{
unsigned int j = 0;
while ( j < m_aTiles.ItemCount() && m_aTiles[j].m_iIndex != i )
j++;
if ( j < m_aTiles.ItemCount() )
continue;
Tile t;
t.m_iIndex = i;
t.m_bZero = true;
m_aTiles.Add( t );
};
MB_ASSERT( m_aTiles.ItemCount() == iEnd-iStart );
}
else
{
int iWidth = int(ceilf(b.m_vEnd.x)-floorf(b.m_vStart.x));
int iHeight = int(ceilf(b.m_vEnd.y)-floorf(b.m_vStart.y));
int iStart = m_iFirstTileIndex;
if ( iWidth > m_iUDim )
m_iUDim = iWidth;
int iEnd = m_iFirstTileIndex+m_iUDim*iHeight;
for ( int i = iStart; i < iEnd; i++ )
{
Tile t;
t.m_iIndex = i;
t.m_bZero = false;
m_aTiles.Add( t );
};
};
Execute();
Kernel()->Redraw();
};
QString DisplaceOperation::GetFileName( const QString &sMask, unsigned int iTile, unsigned int iUPos, unsigned int iVPos ) const
{
QString sFileName = sMask;
QFileInfo i( sFileName );
QDir d = i.absoluteDir();
QString g;
if ( m_aTiles.ItemCount() == 1 )
g = i.completeBaseName()+"*."+i.suffix();
else
{
int iU = iUPos, iV = iVPos;
if ( iU >= 0 )
iU++;
if ( iV >= 0 )
iV++;
g = i.completeBaseName()+QString("_u%1_v%2*.").arg(iU).arg(iV)+i.suffix();
};
QStringList f;
f << g;
d.setNameFilters( f );
if ( d.count() )
{
if ( d.count() > 1 )
Kernel()->MessageBox( Kernel::msgWarning, tr("Warning"), tr("More than one files beginning with the mask %1 is found. The result of the mesh displacement might be incorrect. Delete the unneeded ones to solve the problem.").arg(g) );
QString f = d[0];
QFileInfo k( f );
return QString(d.filePath( f ));
};
return "";
};
QString DisplaceOperation::FilterFileName( const QString &sFileName ) const
{
m_fMultiplier = 1.0f;
m_fMidvalue = 0.0f;
QString s = sFileName;
QString e;
int k = sFileName.size();
while ( k > 0 )
{
k--;
if ( sFileName[k] == '/' || sFileName[k] == '\\' )
break;
if ( sFileName[k] == '.' )
{
e = s.right( s.size()-k-1 );
s = s.left( k );
break;
};
};
for ( int j = s.size()-1; j >= 0; j-- )
{
if ( s[j] == '_' )
{
if ( j < s.size()-1 && s[j+1] == 'g' )
{
float fGain = s.right( s.size()-2-j ).toFloat();
m_fMultiplier = fGain;
m_fMidvalue = 0.5f;
};
s = s.left( j );
continue;
};
if ( s[j] != 'u' && s[j] != 'v' && s[j] != 'g' && s[j] != 'e' && s[j] != '-' && s[j] != '+' && s[j] != '.' && (s[j] < '0' || s[j] > '9') )
break;
};
return s+"."+e;
};
void DisplaceOperation::Execute( void )
{
Instance<Image> pDisplacement, pMask;
if ( !m_pObject )
throw new Error( tr("No target specified") );
SubdivisionLevel::SetUVCreation( true );
SubdivisionLevel *pM = m_pObject->LowestLevel();
if ( pM->TCCount() == 0 )
throw new Error(tr("Target mesh has no texture coordinates"));
while ( int(m_pObject->LevelCount()-1) < m_iSubdivisionLevel )
{
m_pObject->HighestLevel()->Subdivide();
if ( m_bSmoothTC )
m_pObject->HighestLevel()->SmoothTextureCoordinates( 1.0f );
};
pM = m_pObject->Level( m_iSubdivisionLevel );
if ( pM == 0 )
MB_ERROR( tr("Corrupt subdivision structure") );
m_pObject->SetActiveLevel( pM );
pM->AddFaceComponent( pM->fcTCIndex );
Kernel()->Log( NTRQ("Displacement operation on %1\n").arg(pM->Name()) );
Kernel()->ProgressStart( tr("applying maps..."), m_aTiles.ItemCount()*pM->FaceCount() );
Store<VertexInfo> aVertices( "displaceoperation vertex tmp" );
TangentGenerator *pTG = NULL;
if ( m_eMapSpace == spaceTangent || m_eMapSpace == spaceAbsoluteTangent )
pTG = pM->ChildByClass<TangentGenerator>( true );
for ( unsigned int i = 0; i < m_aTiles.ItemCount(); i++ )
{
unsigned int iTile = m_aTiles[i].m_iIndex;
bool bZero = m_aTiles[i].m_bZero;
int iUPos = (iTile-m_iFirstTileIndex)%m_iUDim+m_iBaseU, iVPos = (iTile-m_iFirstTileIndex)/m_iUDim+m_iBaseV;
Kernel()->Log( NTRQ("Displacing tile %1\n").arg(iTile) );
bool bMask = false;
if ( !bZero )
{
QString sFileName = GetFileName( m_sDisplacementFileMask.Value(), iTile, iUPos, iVPos );
Kernel()->Log( NTRQ("\tLoading %1 for dislacement map").arg(sFileName) );
try
{
if ( m_eMapSpace.Value() )
pDisplacement->Load( sFileName );
else
pDisplacement->LoadChannel( sFileName, m_iDisplacementChannel );
}
catch ( Error *pError )
{
Kernel()->Log( " - map not found, skipping\n" );
pError->Discard();
continue;
};
Kernel()->Log( " - OK\n" );
if ( m_sMaskFileMask == "" )
bMask = false;
else
{
QString sFileName = GetFileName( m_sMaskFileMask.Value(), iTile, iUPos, iVPos );
Kernel()->Log( NTRQ("\tLoading %1 for mask map").arg(sFileName) );
bMask = true;
try { pMask->LoadChannel( sFileName, m_iMaskChannel ); }
catch ( Error *pError ) { pError->Discard(); bMask = false; };
if ( bMask )
Kernel()->Log( " - OK\n" );
else
Kernel()->Log( " - not found\n" );
};
};
unsigned int iBefore = aVertices.ItemCount();
Store<int> aProcessedTC( pM->TCCount(), "displace - processedtc" );
for ( unsigned int j = 0; j < pM->TCCount(); j++ )
aProcessedTC[j] = -1;
float fDisplacementMin = -m_fMidvalue*m_fMultiplier;
float fDisplacementMax = (1-m_fMidvalue)*m_fMultiplier;
Vector vMin( fDisplacementMin, fDisplacementMin, fDisplacementMin );
Vector vMax( fDisplacementMax, fDisplacementMax, fDisplacementMax );
for ( unsigned int v = 0; v < pM->FaceCount(); v++ )
{
Kernel()->ProgressSet( i*pM->FaceCount()+v );
for ( int c = 0; c < pM->SideCount(); c++ )
{
unsigned int iVertexIndex, iTCIndex;
if ( pM->Type() == Mesh::typeTriangular )
{
iVertexIndex = pM->TriangleIndex( v, c );
iTCIndex = pM->TriangleTCI( v, c );
}
else
{
iVertexIndex = pM->QuadIndex( v, c );
iTCIndex = pM->QuadTCI( v, c );
};
MB_ONBUG( iTCIndex >= pM->TCCount() )
continue;
if ( pM->VertexStrokeID( iVertexIndex ) == pM->CollectionID() )
continue;
float fU = pM->VertexTC( iTCIndex ).m_fU-float(iUPos), fV = (pM->VertexTC( iTCIndex ).m_fV-float(iVPos));
if ( fU < 0.0f || fU > 1.0f || fV < 0.0f || fV > 1.0f )
continue;
pM->SetVertexStrokeID( iVertexIndex, pM->CollectionID() );
if ( bZero )
{
if ( aProcessedTC[iTCIndex] != -1 )
aVertices[aProcessedTC[iTCIndex]].Refine( Vector(), 1 );
continue;
};
float fMask = 1;
Vector vDisp;
vDisp.x = pDisplacement->ValueAt( fU, fV, 0 );
if ( m_eMapSpace.Value() )
{
vDisp.y = pDisplacement->ValueAt( fU, fV, 1 );
vDisp.z = pDisplacement->ValueAt( fU, fV, 2 );
};
vDisp = vMin+vDisp*(vMax-vMin);
if ( bMask )
fMask = pMask->ValueAt( fU, fV, 0 );
if ( vDisp )
{
if ( aProcessedTC[iTCIndex] != -1 )
{
aVertices[aProcessedTC[iTCIndex]].Refine( vDisp, fMask );
}
else
{
unsigned int iIndex;
iIndex = aVertices.Add( VertexInfo( iVertexIndex, iVertexIndex, vDisp, fMask, v, c ) );
if ( iIndex == 0xffffffff )
{
Kernel()->Log( "\tOut of memory\n" );
throw &Error::s_cBadAlloc;
};
aProcessedTC[iTCIndex] = iIndex;
};
};
};
};
Kernel()->Log( NTRQ("\tProcessed %1 vertices\n").arg( aVertices.ItemCount()-iBefore ) );
};
pM->IncreaseCollectionID();
LayerMeshData *pL = pL = pM->AddLayer();
if ( pL == 0 )
throw Error( tr("Unable to create new layer") );
pL->SetName( "Displacement" );
pL->SetVertexCount( aVertices.ItemCount() );
aVertices.Sort();
for ( unsigned int k = 0; k < aVertices.ItemCount(); k++ )
{
Vector vDelta;
if ( m_eMapSpace.Value() )
{
vDelta = aVertices[k].m_vDisplacement;
if ( m_eMapSpace == spaceTangent || m_eMapSpace == spaceAbsoluteTangent )
{
MB_SAFELY( pTG )
{
Base b = pTG->LocalBase( aVertices[k].m_iFaceIndex, aVertices[k].m_iCornerIndex );
if ( m_eMapSpace == spaceTangent )
{
float al = b.a.Length();
float bl = b.b.Length();
float cl = b.c.Length();
b.b *= sqrtf(al*cl)/bl;
}
else
{
b.Normalize();
}
vDelta = b.TransformFrom( vDelta );
};
};
if ( m_eMapSpace == spaceWorld )
vDelta = pM->Geometry()->Transformation()->TransformToLocal( vDelta, 0 );
}
else
vDelta = Vector(pM->VertexNormal( aVertices[k].m_iVertexIndex ))*aVertices[k].m_vDisplacement.x;
pL->SetVertexData( k, aVertices[k].m_iGlobalVertexIndex, aVertices[k].m_fMask );
pL->SetVertexDelta( k, aVertices[k].m_iVertexIndex, vDelta );
};
pM->ContentChanged();
pM->RecalculateNormals();
Kernel()->ProgressEnd();
};
void DisplaceOperation::Initializer( void )
{
Kernel()->AddClassMenuItem( Kernel::menuMaps, tr("Sculpt using Map"), StaticClass(), tr("New Operation...") );
};