2012-12-05 18:38:16 +01:00
/****************************************************************************
* VCGLib o o *
* Visual and Computer Graphics Library o o *
* _ O _ *
2016-06-13 07:29:25 +02:00
* Copyright ( C ) 2004 - 2016 \ / ) \ / *
2012-12-05 18:38:16 +01:00
* Visual Computing Lab / \ / | *
* ISTI - Italian National Research Council | *
* \ *
* All rights reserved . *
* *
2013-04-12 11:49:51 +02:00
* This program is free software ; you can redistribute it and / or modify *
2012-12-05 18:38:16 +01:00
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation ; either version 2 of the License , or *
* ( at your option ) any later version . *
* *
* This program is distributed in the hope that it will be useful , *
* but WITHOUT ANY WARRANTY ; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the *
* GNU General Public License ( http : //www.gnu.org/licenses/gpl.txt) *
* for more details . *
* *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# ifndef __VCGLIB_IMPORT_OBJ
# define __VCGLIB_IMPORT_OBJ
# include <wrap/callback.h>
# include <wrap/io_trimesh/io_mask.h>
# include <wrap/io_trimesh/io_material.h>
2013-11-25 13:49:12 +01:00
# include <wrap/io_trimesh/io_fan_tessellator.h>
2012-12-05 18:38:16 +01:00
# ifdef __gl_h_
# include <wrap/gl/glu_tesselator.h>
# endif
# include <vcg/space/color4.h>
# include <fstream>
# include <string>
# include <vector>
namespace vcg {
2018-04-06 10:33:53 +02:00
namespace tri {
namespace io {
/**
This class encapsulate a filter for importing obj ( Alias Wavefront ) meshes .
Warning : this code assume little endian ( PC ) architecture ! ! !
*/
template < class OpenMeshType >
class ImporterOBJ
{
public :
static int & MRGBLineCount ( ) { static int _MRGBLineCount = 0 ; return _MRGBLineCount ; }
typedef typename OpenMeshType : : VertexPointer VertexPointer ;
typedef typename OpenMeshType : : ScalarType ScalarType ;
typedef typename OpenMeshType : : VertexType VertexType ;
typedef typename OpenMeshType : : EdgeType EdgeType ;
typedef typename OpenMeshType : : FaceType FaceType ;
typedef typename OpenMeshType : : VertexIterator VertexIterator ;
typedef typename OpenMeshType : : FaceIterator FaceIterator ;
typedef typename OpenMeshType : : CoordType CoordType ;
class Info
{
public :
Info ( ) { }
/// It returns a bit mask describing the field present in the obj file
int mask = 0 ;
/// a Simple callback that can be used for long obj parsing.
CallBackPos * cb = nullptr ;
int numVertices ;
int numEdges ;
int numFaces ; // Note that numFaces can be different from the final number of triangles
int numTexCoords = 0 ;
int numNormals ;
} ; // end class
struct ObjIndexedFace
{
void set ( const int & num ) { v . resize ( num ) ; n . resize ( num ) ; t . resize ( num ) ; }
std : : vector < int > v ;
std : : vector < int > n ;
std : : vector < int > t ;
int tInd ;
bool edge [ 3 ] ; // useless if the face is a polygon, no need to have variable length array
Color4b c ;
int mInd ;
} ;
struct ObjEdge
{
int v0 ;
int v1 ;
} ;
struct ObjTexCoord
{
float u ;
float v ;
} ;
enum OBJError {
// Successfull opening
E_NOERROR = 0 * 2 + 0 , // A*2+B (A position of correspondig string in the array, B=1 if not critical)
// Non Critical Errors (only odd numbers)
E_NON_CRITICAL_ERROR = 0 * 2 + 1 ,
E_MATERIAL_FILE_NOT_FOUND = 1 * 2 + 1 ,
E_MATERIAL_NOT_FOUND = 2 * 2 + 1 ,
E_TEXTURE_NOT_FOUND = 3 * 2 + 1 ,
E_VERTICES_WITH_SAME_IDX_IN_FACE = 4 * 2 + 1 ,
E_LESS_THAN_3_VERT_IN_FACE = 5 * 2 + 1 ,
// Critical Opening Errors (only even numbers)
E_CANTOPEN = 6 * 2 + 0 ,
E_UNEXPECTED_EOF = 7 * 2 + 0 ,
E_ABORTED = 8 * 2 + 0 ,
E_NO_VERTEX = 9 * 2 + 0 ,
E_NO_FACE = 10 * 2 + 0 ,
E_BAD_VERTEX_STATEMENT = 11 * 2 + 0 ,
E_BAD_VERT_TEX_STATEMENT = 12 * 2 + 0 ,
E_BAD_VERT_NORMAL_STATEMENT = 13 * 2 + 0 ,
E_BAD_VERT_INDEX = 14 * 2 + 0 ,
E_BAD_VERT_TEX_INDEX = 15 * 2 + 0 ,
E_BAD_VERT_NORMAL_INDEX = 16 * 2 + 0 ,
E_LESS_THAN_4_VERT_IN_QUAD = 17 * 2 + 0
} ;
// to check if a given error is critical or not.
static bool ErrorCritical ( int err )
{
if ( err = = 0 ) return false ;
if ( err & 1 ) return false ;
return true ;
}
static const char * ErrorMsg ( int error )
{
const int MAXST = 18 ;
static const char * obj_error_msg [ MAXST ] =
{
/* 0 */ " No errors " ,
/* 1 */ " Material library file wrong or not found, a default white material is used " ,
/* 2 */ " Some materials definitions were not found, a default white material is used where no material was available " ,
/* 3 */ " Texture file not found " ,
/* 4 */ " Identical vertex indices found in the same faces -- faces ignored " ,
/* 5 */ " Faces with fewer than 3 vertices -- faces ignored " ,
/* 6 */ " Can't open file " ,
/* 7 */ " Premature End of File. File truncated? " ,
/* 8 */ " Loading aborted by user " ,
/* 9 */ " No vertex found " ,
/* 10 */ " No face found " ,
/* 11 */ " Vertex statement with fewer than 3 coords " ,
/* 12 */ " Texture coords statement with fewer than 2 coords " ,
/* 13 */ " Vertex normal statement with fewer than 3 coords " ,
/* 14 */ " Bad vertex index in face " ,
/* 15 */ " Bad texture coords index in face " ,
/* 16 */ " Bad vertex normal index in face " ,
/* 17 */ " Quad faces with number of corners different from 4 "
} ;
error > > = 1 ;
if ( ( error > = MAXST ) | | ( error < 0 ) ) return " Unknown error " ;
else return obj_error_msg [ error ] ;
}
// Helper functions that checks the range of indexes
// putting them in the correct range if less than zero (as in the obj style)
static bool GoodObjIndex ( int & index , const int maxVal )
{
if ( index > maxVal ) return false ;
if ( index < 0 )
{
index + = maxVal + 1 ;
if ( index < 0 | | index > maxVal ) return false ;
}
return true ;
}
static int Open ( OpenMeshType & mesh , const char * filename , int & loadmask , CallBackPos * cb = 0 )
{
Info oi ;
oi . mask = 0 ;
oi . cb = cb ;
int ret = Open ( mesh , filename , oi ) ;
loadmask = oi . mask ;
return ret ;
}
/*!
* Opens an object file ( in ascii format ) and populates the mesh passed as first
* accordingly to read data
* \ param m The mesh model to be populated with data stored into the file
* \ param filename The name of the file to be opened
* \ param oi A structure containing infos about the object to be opened
*/
static int Open ( OpenMeshType & m , const char * filename , Info & oi )
{
int result = E_NOERROR ;
m . Clear ( ) ;
CallBackPos * cb = oi . cb ;
// if LoadMask has not been called yet, we call it here
if ( oi . mask = = 0 )
LoadMask ( filename , oi ) ;
const int inputMask = oi . mask ;
Mask : : ClampMask < OpenMeshType > ( m , oi . mask ) ;
if ( oi . numVertices = = 0 )
return E_NO_VERTEX ;
std : : ifstream stream ( filename ) ;
if ( stream . fail ( ) )
{
stream . close ( ) ;
return E_CANTOPEN ;
}
typename OpenMeshType : : template PerMeshAttributeHandle < std : : vector < Material > > materialsHandle =
vcg : : tri : : Allocator < OpenMeshType > : : template GetPerMeshAttribute < std : : vector < Material > > ( m , std : : string ( " materialVector " ) ) ;
typename OpenMeshType : : template PerFaceAttributeHandle < int > mIndHandle =
vcg : : tri : : Allocator < OpenMeshType > : : template GetPerFaceAttribute < int > ( m , std : : string ( " materialIndex " ) ) ;
std : : vector < Material > & materials = materialsHandle ( ) ; // materials vector
std : : vector < ObjTexCoord > texCoords ; // texture coordinates
std : : vector < CoordType > normals ; // vertex normals
std : : vector < ObjIndexedFace > indexedFaces ;
std : : vector < std : : string > tokens ;
std : : string line ;
std : : string header ;
short currentMaterialIdx = 0 ; // index of current material into materials vector
Color4b currentColor = Color4b : : LightGray ; // we declare this outside code block since other
// triangles of this face will share the same color
Material defaultMaterial ; // default material: white
defaultMaterial . index = currentMaterialIdx ;
materials . push_back ( defaultMaterial ) ;
int numVertices = 0 ; // stores the number of vertices been read till now
int numEdges = 0 ; // stores the number of edges read till now
int numTriangles = 0 ; // stores the number of faces been read till now
int numTexCoords = 0 ; // stores the number of texture coordinates been read till now
int numVNormals = 0 ; // stores the number of vertex normals been read till now
int numVerticesPlusFaces = oi . numVertices + oi . numFaces ;
int extraTriangles = 0 ;
// vertices and faces allocation
VertexIterator vi = vcg : : tri : : Allocator < OpenMeshType > : : AddVertices ( m , oi . numVertices ) ;
//FaceIterator fi = Allocator<OpenMeshType>::AddFaces(m,oi.numFaces);
// edges found
std : : vector < ObjEdge > ev ;
std : : vector < Color4b > vertexColorVector ;
ObjIndexedFace ff ;
const char * loadingStr = " Loading " ;
while ( ! stream . eof ( ) )
{
tokens . clear ( ) ;
TokenizeNextLine ( stream , tokens , line , & vertexColorVector ) ;
unsigned int numTokens = static_cast < unsigned int > ( tokens . size ( ) ) ;
if ( numTokens > 0 )
{
header . clear ( ) ;
header = tokens [ 0 ] ;
// callback invocation, abort loading process if the call returns false
if ( ( cb ! = NULL ) & & ( ( ( numTriangles + numVertices ) % 100 ) = = 0 ) & & ! ( * cb ) ( ( 100 * ( numTriangles + numVertices ) ) / numVerticesPlusFaces , loadingStr ) )
{
stream . close ( ) ;
return E_ABORTED ;
}
if ( header . compare ( " v " ) = = 0 ) // vertex
{
loadingStr = " Vertex Loading " ;
if ( numTokens < 4 )
{
stream . close ( ) ;
return E_BAD_VERTEX_STATEMENT ;
}
( * vi ) . P ( ) [ 0 ] = ( ScalarType ) atof ( tokens [ 1 ] . c_str ( ) ) ;
( * vi ) . P ( ) [ 1 ] = ( ScalarType ) atof ( tokens [ 2 ] . c_str ( ) ) ;
( * vi ) . P ( ) [ 2 ] = ( ScalarType ) atof ( tokens [ 3 ] . c_str ( ) ) ;
+ + numVertices ;
// assigning vertex color
// ----------------------
if ( ( ( oi . mask & vcg : : tri : : io : : Mask : : IOM_VERTCOLOR ) ! = 0 ) & & ( HasPerVertexColor ( m ) ) )
{
if ( numTokens > = 7 )
2013-11-25 13:49:12 +01:00
{
2018-04-06 10:33:53 +02:00
ScalarType rf ( atof ( tokens [ 4 ] . c_str ( ) ) ) , gf ( atof ( tokens [ 5 ] . c_str ( ) ) ) , bf ( atof ( tokens [ 6 ] . c_str ( ) ) ) ;
ScalarType scaling = ( rf < = 1 & & gf < = 1 & & bf < = 1 ) ? 255. : 1 ;
unsigned char r = ( unsigned char ) ( ( ScalarType ) atof ( tokens [ 4 ] . c_str ( ) ) * scaling ) ;
unsigned char g = ( unsigned char ) ( ( ScalarType ) atof ( tokens [ 5 ] . c_str ( ) ) * scaling ) ;
unsigned char b = ( unsigned char ) ( ( ScalarType ) atof ( tokens [ 6 ] . c_str ( ) ) * scaling ) ;
unsigned char alpha = ( unsigned char ) ( ( numTokens > = 8 ? ( ScalarType ) atof ( tokens [ 7 ] . c_str ( ) ) : 1 ) * scaling ) ;
( * vi ) . C ( ) = Color4b ( r , g , b , alpha ) ;
}
else
{
( * vi ) . C ( ) = currentColor ;
}
}
+ + vi ; // move to next vertex iterator
}
else if ( header . compare ( " vt " ) = = 0 ) // vertex texture coords
{
loadingStr = " Vertex Texture Loading " ;
if ( numTokens < 3 )
{
stream . close ( ) ;
return E_BAD_VERT_TEX_STATEMENT ;
}
ObjTexCoord t ;
t . u = static_cast < float > ( atof ( tokens [ 1 ] . c_str ( ) ) ) ;
t . v = static_cast < float > ( atof ( tokens [ 2 ] . c_str ( ) ) ) ;
texCoords . push_back ( t ) ;
numTexCoords + + ;
}
else if ( header . compare ( " vn " ) = = 0 ) // vertex normal
{
loadingStr = " Vertex Normal Loading " ;
if ( numTokens ! = 4 )
{
stream . close ( ) ;
return E_BAD_VERT_NORMAL_STATEMENT ;
}
CoordType n ;
n [ 0 ] = ( ScalarType ) atof ( tokens [ 1 ] . c_str ( ) ) ;
n [ 1 ] = ( ScalarType ) atof ( tokens [ 2 ] . c_str ( ) ) ;
n [ 2 ] = ( ScalarType ) atof ( tokens [ 3 ] . c_str ( ) ) ;
normals . push_back ( n ) ;
numVNormals + + ;
}
else if ( header . compare ( " l " ) = = 0 )
{
loadingStr = " Edge Loading " ;
if ( numTokens < 3 )
{
result = E_LESS_THAN_3_VERT_IN_FACE ; // TODO add proper/handling error code
continue ;
}
ObjEdge e = { ( atoi ( tokens [ 1 ] . c_str ( ) ) - 1 ) ,
( atoi ( tokens [ 2 ] . c_str ( ) ) - 1 ) } ;
ev . push_back ( e ) ;
numEdges + + ;
}
else if ( ( header . compare ( " f " ) = = 0 ) | | ( header . compare ( " q " ) = = 0 ) ) // face
{
loadingStr = " Face Loading " ;
int vertexesPerFace = static_cast < int > ( tokens . size ( ) - 1 ) ;
bool QuadFlag = false ; // QOBJ format by Silva et al for simply storing quadrangular meshes.
if ( header . compare ( " q " ) = = 0 ) {
QuadFlag = true ;
if ( vertexesPerFace ! = 4 ) {
stream . close ( ) ;
return E_LESS_THAN_4_VERT_IN_QUAD ;
}
}
if ( vertexesPerFace < 3 ) {
// face with fewer than 3 vertices found: ignore this face
extraTriangles - - ;
result = E_LESS_THAN_3_VERT_IN_FACE ;
continue ;
}
if ( ( vertexesPerFace > 3 ) & & OpenMeshType : : FaceType : : HasPolyInfo ( ) )
{
//_BEGIN___ if you are filling a vcg mesh with GENERIC POLYGON
ff . set ( vertexesPerFace ) ;
for ( int i = 0 ; i < vertexesPerFace ; + + i ) { // remember index starts from 1 instead of 0
SplitToken ( tokens [ i + 1 ] , ff . v [ i ] , ff . n [ i ] , ff . t [ i ] , inputMask ) ;
if ( QuadFlag ) ff . v [ i ] + + ; // NOTE THAT THE STUPID QOBJ FORMAT IS ZERO INDEXED!!!!
}
if ( oi . mask & vcg : : tri : : io : : Mask : : IOM_WEDGTEXCOORD )
{
// verifying validity of texture coords indices
for ( int i = 0 ; i < vertexesPerFace ; i + + )
if ( ! GoodObjIndex ( ff . t [ i ] , oi . numTexCoords ) )
2013-11-25 13:49:12 +01:00
{
2018-04-06 10:33:53 +02:00
stream . close ( ) ;
return E_BAD_VERT_TEX_INDEX ;
2013-11-25 13:49:12 +01:00
}
2018-04-06 10:33:53 +02:00
ff . tInd = materials [ currentMaterialIdx ] . index ;
}
// verifying validity of vertex indices
std : : vector < int > tmp = ff . v ;
std : : sort ( tmp . begin ( ) , tmp . end ( ) ) ;
std : : unique ( tmp . begin ( ) , tmp . end ( ) ) ;
if ( tmp . size ( ) ! = ff . v . size ( ) ) {
result = E_VERTICES_WITH_SAME_IDX_IN_FACE ;
extraTriangles - - ;
continue ;
}
for ( int i = 0 ; i < vertexesPerFace ; i + + )
if ( ! GoodObjIndex ( ff . v [ i ] , numVertices ) )
{
stream . close ( ) ;
return E_BAD_VERT_INDEX ;
}
if ( ( oi . mask & vcg : : tri : : io : : Mask : : IOM_WEDGNORMAL ) | |
( oi . mask & vcg : : tri : : io : : Mask : : IOM_VERTNORMAL ) )
{
// verifying validity of vertex normal indices
for ( int i = 0 ; i < vertexesPerFace ; i + + )
if ( ! GoodObjIndex ( ff . n [ i ] , numVNormals ) )
2013-11-25 13:49:12 +01:00
{
2018-04-06 10:33:53 +02:00
stream . close ( ) ;
return E_BAD_VERT_NORMAL_INDEX ;
2013-11-25 13:49:12 +01:00
}
2018-04-06 10:33:53 +02:00
}
if ( oi . mask & vcg : : tri : : io : : Mask : : IOM_FACECOLOR ) // assigning face color
ff . c = currentColor ;
+ + numTriangles ;
indexedFaces . push_back ( ff ) ;
//_END ___ if you are filling a vcg mesh with GENERIC POLYGON
}
else
{
//_BEGIN___ if you are filling a vcg mesh with TRIANGLES
std : : vector < std : : vector < vcg : : Point3f > > polygonVect ( 1 ) ; // it is a vector of polygon loops
polygonVect [ 0 ] . resize ( vertexesPerFace ) ;
std : : vector < int > indexVVect ( vertexesPerFace ) ;
std : : vector < int > indexNVect ( vertexesPerFace ) ;
std : : vector < int > indexTVect ( vertexesPerFace ) ;
std : : vector < int > indexTriangulatedVect ;
for ( int pi = 0 ; pi < vertexesPerFace ; + + pi )
{
SplitToken ( tokens [ pi + 1 ] , indexVVect [ pi ] , indexNVect [ pi ] , indexTVect [ pi ] , inputMask ) ;
if ( QuadFlag ) indexVVect [ pi ] + + ; // NOTE THAT THE STUPID QOBJ FORMAT IS ZERO INDEXED!!!!
GoodObjIndex ( indexVVect [ pi ] , numVertices ) ;
GoodObjIndex ( indexTVect [ pi ] , oi . numTexCoords ) ;
polygonVect [ 0 ] [ pi ] . Import ( m . vert [ indexVVect [ pi ] ] . cP ( ) ) ;
}
if ( vertexesPerFace > 3 )
oi . mask | = Mask : : IOM_BITPOLYGONAL ;
if ( vertexesPerFace < 5 )
FanTessellator ( polygonVect , indexTriangulatedVect ) ;
else
{
2012-12-05 18:38:16 +01:00
# ifdef __gl_h_
2018-04-06 10:33:53 +02:00
//qDebug("OK: using opengl tessellation for a polygon of %i verteces",vertexesPerFace);
vcg : : glu_tesselator : : tesselate < vcg : : Point3f > ( polygonVect , indexTriangulatedVect ) ;
if ( indexTriangulatedVect . size ( ) = = 0 )
FanTessellator ( polygonVect , indexTriangulatedVect ) ;
2012-12-05 18:38:16 +01:00
# else
2018-04-06 10:33:53 +02:00
//qDebug("Warning: using fan tessellation for a polygon of %i verteces",vertexesPerFace);
FanTessellator ( polygonVect , indexTriangulatedVect ) ;
2012-12-05 18:38:16 +01:00
# endif
2018-04-06 10:33:53 +02:00
}
extraTriangles + = ( ( indexTriangulatedVect . size ( ) / 3 ) - 1 ) ;
2012-12-05 18:38:16 +01:00
# ifdef QT_VERSION
2018-04-06 10:33:53 +02:00
if ( int ( indexTriangulatedVect . size ( ) / 3 ) ! = vertexesPerFace - 2 )
{
qDebug ( " Warning there is a degenerate poligon of %i verteces that was triangulated into %i triangles " , vertexesPerFace , int ( indexTriangulatedVect . size ( ) / 3 ) ) ;
for ( size_t qq = 0 ; qq < polygonVect [ 0 ] . size ( ) ; + + qq )
qDebug ( " (%f %f %f) " , polygonVect [ 0 ] [ qq ] [ 0 ] , polygonVect [ 0 ] [ qq ] [ 1 ] , polygonVect [ 0 ] [ qq ] [ 2 ] ) ;
for ( size_t qq = 0 ; qq < tokens . size ( ) ; + + qq ) qDebug ( " <%s> " , tokens [ qq ] . c_str ( ) ) ;
}
2012-12-05 18:38:16 +01:00
# endif
2018-04-06 10:33:53 +02:00
//qDebug("Triangulated a face of %i vertexes into %i triangles",polygonVect[0].size(),indexTriangulatedVect.size());
for ( size_t pi = 0 ; pi < indexTriangulatedVect . size ( ) ; pi + = 3 )
{
ff . set ( 3 ) ;
int locInd [ 3 ] ;
for ( int iii = 0 ; iii < 3 ; + + iii )
{
locInd [ iii ] = indexTriangulatedVect [ pi + iii ] ;
ff . v [ iii ] = indexVVect [ locInd [ iii ] ] ;
ff . n [ iii ] = indexNVect [ locInd [ iii ] ] ;
ff . t [ iii ] = indexTVect [ locInd [ iii ] ] ;
}
// Setting internal edges: only edges formed by consecutive edges are external.
for ( int iii = 0 ; iii < 3 ; + + iii )
{
if ( ( locInd [ iii ] + 1 ) % vertexesPerFace = = locInd [ ( iii + 1 ) % 3 ] ) ff . edge [ iii ] = false ;
else ff . edge [ iii ] = true ;
}
if ( oi . mask & vcg : : tri : : io : : Mask : : IOM_WEDGTEXCOORD )
{ // verifying validity of texture coords indices
bool invalid = false ;
for ( int i = 0 ; i < 3 ; i + + )
if ( ! GoodObjIndex ( ff . t [ i ] , oi . numTexCoords ) )
{
//return E_BAD_VERT_TEX_INDEX;
invalid = true ;
break ;
}
if ( invalid ) continue ;
ff . tInd = materials [ currentMaterialIdx ] . index ;
}
// verifying validity of vertex indices
if ( ( ff . v [ 0 ] = = ff . v [ 1 ] ) | | ( ff . v [ 0 ] = = ff . v [ 2 ] ) | | ( ff . v [ 1 ] = = ff . v [ 2 ] ) ) {
result = E_VERTICES_WITH_SAME_IDX_IN_FACE ;
extraTriangles - - ;
continue ;
}
{
bool invalid = false ;
for ( int i = 0 ; i < 3 ; i + + )
if ( ! GoodObjIndex ( ff . v [ i ] , numVertices ) )
{
//return E_BAD_VERT_INDEX;
invalid = true ;
break ;
}
if ( invalid ) continue ;
}
// assigning face normal
if ( ( oi . mask & vcg : : tri : : io : : Mask : : IOM_WEDGNORMAL ) | |
( oi . mask & vcg : : tri : : io : : Mask : : IOM_VERTNORMAL ) )
{ // verifying validity of vertex normal indices
bool invalid = false ;
for ( int i = 0 ; i < 3 ; i + + )
if ( ! GoodObjIndex ( ff . n [ i ] , numVNormals ) )
{
//return E_BAD_VERT_NORMAL_INDEX;
invalid = true ;
break ;
}
if ( invalid ) continue ;
}
// assigning face color
if ( oi . mask & vcg : : tri : : io : : Mask : : IOM_FACECOLOR ) ff . c = currentColor ;
ff . mInd = currentMaterialIdx ;
+ + numTriangles ;
indexedFaces . push_back ( ff ) ;
}
} //_END ___ if you are filling a vcg mesh with TRIANGLES
}
else if ( ( header . compare ( " mtllib " ) = = 0 ) & & ( tokens . size ( ) > 1 ) ) // material library
{
// obtain the name of the file containing materials library
std : : string materialFileName ;
if ( tokens . size ( ) = = 2 )
materialFileName = tokens [ 1 ] ; //play it safe
else
materialFileName = line . substr ( 7 ) ; //get everything after "mtllib "
if ( ! LoadMaterials ( materialFileName . c_str ( ) , materials , m . textures ) )
result = E_MATERIAL_FILE_NOT_FOUND ;
}
else if ( ( header . compare ( " usemtl " ) = = 0 ) & & ( tokens . size ( ) > 1 ) ) // material usage
{
// emergency check. If there are no materials, the material library failed to load or was not specified
// but there are tools that save the material library with the same name of the file, but do not add the
// "mtllib" definition in the header. So, we can try to see if this is the case
if ( ( materials . size ( ) = = 1 ) & & ( materials [ 0 ] . materialName = = " " ) ) {
std : : string materialFileName ( filename ) ;
materialFileName . replace ( materialFileName . end ( ) - 4 , materialFileName . end ( ) , " .mtl " ) ;
LoadMaterials ( materialFileName . c_str ( ) , materials , m . textures ) ;
}
std : : string materialName ;
if ( tokens . size ( ) = = 2 )
materialName = tokens [ 1 ] ; //play it safe
else
materialName = line . substr ( 7 ) ; //get everything after "usemtl "
bool found = false ;
unsigned i = 0 ;
while ( ! found & & ( i < materials . size ( ) ) )
{
std : : string currentMaterialName = materials [ i ] . materialName ;
if ( currentMaterialName = = materialName )
{
currentMaterialIdx = i ;
Material & material = materials [ currentMaterialIdx ] ;
Point3f diffuseColor = material . Kd ;
unsigned char r = ( unsigned char ) ( diffuseColor [ 0 ] * 255.0 ) ;
unsigned char g = ( unsigned char ) ( diffuseColor [ 1 ] * 255.0 ) ;
unsigned char b = ( unsigned char ) ( diffuseColor [ 2 ] * 255.0 ) ;
unsigned char alpha = ( unsigned char ) ( material . Tr * 255.0 ) ;
currentColor = Color4b ( r , g , b , alpha ) ;
found = true ;
}
+ + i ;
}
if ( ! found )
{
currentMaterialIdx = 0 ;
result = E_MATERIAL_NOT_FOUND ;
}
}
// we simply ignore other situations
} // end for each line...
} // end while stream not eof
assert ( ( numTriangles + numVertices ) = = numVerticesPlusFaces + extraTriangles ) ;
vcg : : tri : : Allocator < OpenMeshType > : : AddFaces ( m , numTriangles ) ;
// Add found edges
if ( numEdges > 0 )
{
vcg : : tri : : Allocator < OpenMeshType > : : AddEdges ( m , numEdges ) ;
assert ( m . edge . size ( ) = = size_t ( m . en ) ) ;
for ( int i = 0 ; i < numEdges ; + + i )
{
ObjEdge & e = ev [ i ] ;
assert ( e . v0 > = 0 & & size_t ( e . v0 ) < m . vert . size ( ) & &
e . v1 > = 0 & & size_t ( e . v1 ) < m . vert . size ( ) ) ;
// TODO add proper handling of bad indices
m . edge [ i ] . V ( 0 ) = & ( m . vert [ e . v0 ] ) ;
m . edge [ i ] . V ( 1 ) = & ( m . vert [ e . v1 ] ) ;
}
}
//-------------------------------------------------------------------------------
// Now the final passes:
// First Pass to convert indexes into pointers for face to vert/norm/tex references
for ( int i = 0 ; i < numTriangles ; + + i )
{
assert ( m . face . size ( ) = = size_t ( m . fn ) ) ;
m . face [ i ] . Alloc ( indexedFaces [ i ] . v . size ( ) ) ; // it does not do anything if it is a trimesh
for ( unsigned int j = 0 ; j < indexedFaces [ i ] . v . size ( ) ; + + j )
{
int vertInd = indexedFaces [ i ] . v [ j ] ;
assert ( vertInd > = 0 & & vertInd < m . vn ) ; ( void ) vertInd ;
m . face [ i ] . V ( j ) = & ( m . vert [ indexedFaces [ i ] . v [ j ] ] ) ;
if ( ( ( oi . mask & vcg : : tri : : io : : Mask : : IOM_WEDGTEXCOORD ) ! = 0 ) & & ( HasPerWedgeTexCoord ( m ) ) )
{
ObjTexCoord t = texCoords [ indexedFaces [ i ] . t [ j ] ] ;
m . face [ i ] . WT ( j ) . u ( ) = t . u ;
m . face [ i ] . WT ( j ) . v ( ) = t . v ;
m . face [ i ] . WT ( j ) . n ( ) = indexedFaces [ i ] . tInd ;
}
if ( oi . mask & vcg : : tri : : io : : Mask : : IOM_VERTTEXCOORD ) {
ObjTexCoord t = texCoords [ indexedFaces [ i ] . t [ j ] ] ;
m . face [ i ] . V ( j ) - > T ( ) . u ( ) = t . u ;
m . face [ i ] . V ( j ) - > T ( ) . v ( ) = t . v ;
m . face [ i ] . V ( j ) - > T ( ) . n ( ) = indexedFaces [ i ] . tInd ;
}
if ( oi . mask & vcg : : tri : : io : : Mask : : IOM_WEDGNORMAL )
{
m . face [ i ] . WN ( j ) . Import ( normals [ indexedFaces [ i ] . n [ j ] ] ) ;
}
if ( oi . mask & vcg : : tri : : io : : Mask : : IOM_VERTNORMAL )
{
m . face [ i ] . V ( j ) - > N ( ) . Import ( normals [ indexedFaces [ i ] . n [ j ] ] ) ;
}
// set faux edge flags according to internals faces
if ( indexedFaces [ i ] . edge [ j ] )
m . face [ i ] . SetF ( j ) ;
else
m . face [ i ] . ClearF ( j ) ;
}
if ( HasPerFaceNormal ( m ) )
{
if ( ( ( oi . mask & vcg : : tri : : io : : Mask : : IOM_FACECOLOR ) ! = 0 ) & & ( HasPerFaceColor ( m ) ) )
{
m . face [ i ] . C ( ) = indexedFaces [ i ] . c ;
mIndHandle [ i ] = indexedFaces [ i ] . mInd ;
}
if ( ( ( oi . mask & vcg : : tri : : io : : Mask : : IOM_WEDGNORMAL ) ! = 0 ) & & ( HasPerWedgeNormal ( m ) ) )
{
// face normal is computed as an average of wedge normals
m . face [ i ] . N ( ) . Import ( m . face [ i ] . WN ( 0 ) + m . face [ i ] . WN ( 1 ) + m . face [ i ] . WN ( 2 ) ) ;
}
else
{
m . face [ i ] . N ( ) . Import ( TriangleNormal ( m . face [ i ] ) . Normalize ( ) ) ;
}
}
}
// final pass to manage the ZBrush PerVertex Color that are managed into comments
if ( vertexColorVector . size ( ) > 0 )
{
// if(vertexColorVector.size()!=m.vn){
// qDebug("Warning Read %i vertices and %i vertex colors",m.vn,vertexColorVector.size());
// qDebug("line count %i x 64 = %i",MRGBLineCount(), MRGBLineCount()*64);
// }
for ( int i = 0 ; i < m . vn ; + + i )
{
m . vert [ i ] . C ( ) = vertexColorVector [ i ] ;
}
}
stream . close ( ) ;
return result ;
} // end of Open
/*!
* Read the next valid line and parses it into " tokens " ( e . g . groups like 234 / 234 / 234 ) , allowing
* the tokens to be read one at a time . It read multiple lines concatenating them if they end with ' \ '
* \ param stream The object providing the input stream
* \ param tokens The " tokens " in the next line
*/
inline static void TokenizeNextLine ( std : : ifstream & stream , std : : vector < std : : string > & tokens , std : : string & line , std : : vector < Color4b > * colVec )
{
if ( stream . eof ( ) ) return ;
do
{
std : : getline ( stream , line ) ;
// We have to manage backspace terminated lines,
// joining them together before parsing them
if ( ! line . empty ( ) & & line . back ( ) = = 13 ) line . pop_back ( ) ;
while ( ! line . empty ( ) & & line . back ( ) = = ' \\ ' ) {
std : : string tmpLine ;
std : : getline ( stream , tmpLine ) ;
if ( tmpLine . back ( ) = = 13 ) line . pop_back ( ) ;
line . pop_back ( ) ;
line . append ( tmpLine ) ;
}
const size_t len = line . length ( ) ;
if ( ( len > 0 ) & & colVec & & line [ 0 ] = = ' # ' )
{
// The following MRGB block contains ZBrush Vertex Color (Polypaint)
// and masking output as 4 hexadecimal values per vertex. The vertex color format is MMRRGGBB with up to 64 entries per MRGB line.
if ( ( len > = 5 ) & & line [ 1 ] = = ' M ' & & line [ 2 ] = = ' R ' & & line [ 3 ] = = ' G ' & & line [ 4 ] = = ' B ' )
{ // Parsing the polycolor of ZBrush
MRGBLineCount ( ) + + ;
char buf [ 3 ] = " 00 " ;
Color4b cc ( Color4b : : Black ) ;
for ( size_t i = 6 ; ( i + 7 ) < len ; i + = 8 )
{
for ( size_t j = 1 ; j < 4 ; j + + )
{
buf [ 0 ] = line [ i + j * 2 + 0 ] ;
buf [ 1 ] = line [ i + j * 2 + 1 ] ;
buf [ 2 ] = 0 ;
char * p ;
int val = strtoul ( buf , & p , 16 ) ;
cc [ j - 1 ] = val ;
}
colVec - > push_back ( cc ) ;
}
}
}
}
while ( ( line . length ( ) = = 0 | | line [ 0 ] = = ' # ' ) & & ! stream . eof ( ) ) ; // skip comments and empty lines
if ( ( line . length ( ) = = 0 ) | | ( line [ 0 ] = = ' # ' ) ) // can be true only on last line of file
return ;
size_t from = 0 ;
size_t to = 0 ;
size_t length = line . size ( ) ;
tokens . clear ( ) ;
do
{
while ( from ! = length & & ( line [ from ] = = ' ' | | line [ from ] = = ' \t ' | | line [ from ] = = ' \r ' ) )
from + + ;
if ( from ! = length )
{
to = from + 1 ;
while ( to ! = length & & line [ to ] ! = ' ' & & line [ to ] ! = ' \t ' & & line [ to ] ! = ' \r ' )
to + + ;
tokens . push_back ( line . substr ( from , to - from ) . c_str ( ) ) ;
from = to ;
}
}
while ( from < length ) ;
} // end TokenizeNextLine
// This function takes a token and, according to the mask, it returns the indexes of the involved vertex, normal and texcoord indexes.
// Example. if the obj file has vertex texcoord (e.g. lines 'vt 0.444 0.5555')
// when parsing a line like
// f 46/303 619/325 624/326 623/327
// if in the mask you have specified to read wedge tex coord
// for the first token it will return inside vId and tId the corresponding indexes 46 and 303 )
inline static void SplitToken ( const std : : string & token , int & vId , int & nId , int & tId , int mask )
{
static const char delimiter = ' / ' ;
vId = nId = tId = 0 ;
if ( token . empty ( ) ) return ;
size_t firstSep = token . find_first_of ( delimiter ) ;
size_t secondSep = ( firstSep = = std : : string : : npos ) ? ( std : : string : : npos ) : ( token . find_first_of ( delimiter , firstSep + 1 ) ) ;
const bool hasPosition = true ;
const bool hasTexcoord = ( firstSep ! = std : : string : : npos ) & & ( ( firstSep + 1 ) < secondSep ) ;
const bool hasNormal = ( secondSep ! = std : : string : : npos ) | | ( mask & Mask : : IOM_WEDGNORMAL ) | | ( mask & Mask : : IOM_VERTNORMAL ) ;
if ( hasPosition ) vId = atoi ( token . substr ( 0 , firstSep ) . c_str ( ) ) - 1 ;
if ( hasTexcoord ) tId = atoi ( token . substr ( firstSep + 1 , secondSep - firstSep - 1 ) . c_str ( ) ) - 1 ;
if ( hasNormal )
nId = atoi ( token . substr ( secondSep + 1 ) . c_str ( ) ) - 1 ;
}
/** returns a Point3f done from (tokens[pos],tokens[pos+1],tokens[pos+2])
*/
static Point3f Point3fFrom3Tokens ( std : : vector < std : : string > & tokens , int pos )
{
float r = ( float ) atof ( tokens [ pos + 0 ] . c_str ( ) ) ;
float g = ( float ) atof ( tokens [ pos + 1 ] . c_str ( ) ) ;
float b = ( float ) atof ( tokens [ pos + 2 ] . c_str ( ) ) ;
return Point3f ( r , g , b ) ;
}
/*!
* Retrieves infos about kind of data stored into the file and fills a mask appropriately
* \ param filename The name of the file to open
* \ param mask A mask which will be filled according to type of data found in the object
* \ param oi A structure which will be filled with infos about the object to be opened
*/
static bool LoadMask ( const char * filename , Info & oi )
{
std : : ifstream stream ( filename ) ;
if ( stream . fail ( ) )
{
stream . close ( ) ;
return false ;
}
// obtain length of file:
stream . seekg ( 0 , std : : ios : : end ) ;
int length = stream . tellg ( ) ;
stream . seekg ( 0 , std : : ios : : beg ) ;
if ( length = = 0 ) return false ;
bool bHasPerFaceColor = false ;
bool bHasNormals = false ;
bool bHasPerVertexColor = false ;
oi . numVertices = 0 ;
oi . numEdges = 0 ;
oi . numFaces = 0 ;
oi . numTexCoords = 0 ;
oi . numNormals = 0 ;
int lineCount = 0 ;
int totRead = 0 ;
bool firstV = true ;
std : : string line ;
while ( ! stream . eof ( ) )
{
lineCount + + ;
std : : getline ( stream , line ) ;
totRead + = line . size ( ) ;
if ( oi . cb & & ( lineCount % 1000 ) = = 0 )
( * oi . cb ) ( ( int ) ( 100.0 * ( float ( totRead ) ) / float ( length ) ) , " Loading mask... " ) ;
if ( line . size ( ) > 2 )
{
if ( line [ 0 ] = = ' v ' )
{
if ( ( line [ 1 ] = = ' ' ) | | ( line [ 1 ] = = ' \t ' ) )
{
oi . numVertices + + ;
if ( firstV )
{
int sepN = 0 ;
for ( size_t lit = 0 ; lit < line . size ( ) ; lit + + ) {
if ( ( line [ lit ] = = ' ' ) | | ( line [ lit ] = = ' \t ' ) )
sepN + + ;
}
if ( sepN > = 6 )
bHasPerVertexColor = true ;
firstV = false ;
}
}
if ( line [ 1 ] = = ' t ' ) oi . numTexCoords + + ;
if ( line [ 1 ] = = ' n ' ) {
oi . numNormals + + ;
bHasNormals = true ;
}
}
else {
if ( ( line [ 0 ] = = ' f ' ) | | ( line [ 0 ] = = ' q ' ) ) oi . numFaces + + ;
else
if ( line [ 0 ] = = ' l ' ) oi . numEdges + + ;
else
if ( line [ 0 ] = = ' u ' & & line [ 1 ] = = ' s ' ) bHasPerFaceColor = true ; // there is a usematerial so add per face color
}
}
}
oi . mask = 0 ;
if ( oi . numTexCoords )
{
if ( oi . numTexCoords = = oi . numVertices )
oi . mask | = vcg : : tri : : io : : Mask : : IOM_VERTTEXCOORD ;
oi . mask | = vcg : : tri : : io : : Mask : : IOM_WEDGTEXCOORD ;
// Usually if you have tex coords you also have materials
oi . mask | = vcg : : tri : : io : : Mask : : IOM_FACECOLOR ;
}
if ( bHasPerFaceColor ) oi . mask | = vcg : : tri : : io : : Mask : : IOM_FACECOLOR ;
if ( bHasPerVertexColor ) oi . mask | = vcg : : tri : : io : : Mask : : IOM_VERTCOLOR ;
if ( bHasNormals ) {
if ( oi . numNormals = = oi . numVertices )
oi . mask | = vcg : : tri : : io : : Mask : : IOM_VERTNORMAL ;
else
oi . mask | = vcg : : tri : : io : : Mask : : IOM_WEDGNORMAL ;
}
if ( oi . numEdges )
oi . mask | = vcg : : tri : : io : : Mask : : IOM_EDGEINDEX ;
stream . close ( ) ;
return true ;
}
static bool LoadMask ( const char * filename , int & mask )
{
Info oi ;
bool ret = LoadMask ( filename , oi ) ;
mask = oi . mask ;
return ret ;
}
static bool LoadMaterials ( const char * filename , std : : vector < Material > & materials , std : : vector < std : : string > & textures )
{
// assumes we are in the right directory
std : : ifstream stream ( filename ) ;
if ( stream . fail ( ) )
return false ;
std : : vector < std : : string > tokens ;
std : : string line ;
std : : string header ;
materials . clear ( ) ;
Material currentMaterial ;
// Fill in some default values for the material
currentMaterial . index = ( unsigned int ) ( - 1 ) ;
currentMaterial . Ka = Point3f ( 0.2 , 0.2 , 0.2 ) ;
currentMaterial . Kd = Point3f ( 1 , 1 , 1 ) ;
currentMaterial . Ks = Point3f ( 1 , 1 , 1 ) ;
currentMaterial . Tr = 1 ;
currentMaterial . Ns = 0 ;
currentMaterial . illum = 2 ;
bool first = true ;
while ( ! stream . eof ( ) )
{
tokens . clear ( ) ;
TokenizeNextLine ( stream , tokens , line , 0 ) ;
if ( tokens . size ( ) > 0 )
{
header . clear ( ) ;
header = tokens [ 0 ] ;
if ( header . compare ( " newmtl " ) = = 0 )
{
if ( ! first )
{
materials . push_back ( currentMaterial ) ;
currentMaterial = Material ( ) ;
currentMaterial . index = ( unsigned int ) ( - 1 ) ;
}
else
first = false ;
//strcpy(currentMaterial.name, tokens[1].c_str());
if ( tokens . size ( ) < 2 )
return false ;
else if ( tokens . size ( ) = = 2 )
currentMaterial . materialName = tokens [ 1 ] ; //play it safe
else
currentMaterial . materialName = line . substr ( 7 ) ; //space in the name, get everything after "newmtl "
}
else if ( header . compare ( " Ka " ) = = 0 )
{
if ( tokens . size ( ) < 4 ) return false ;
currentMaterial . Ka = Point3fFrom3Tokens ( tokens , 1 ) ;
}
else if ( header . compare ( " Kd " ) = = 0 )
{
if ( tokens . size ( ) < 4 ) return false ;
currentMaterial . Kd = Point3fFrom3Tokens ( tokens , 1 ) ;
}
else if ( header . compare ( " Ks " ) = = 0 )
{
if ( tokens . size ( ) < 4 ) return false ;
currentMaterial . Ks = Point3fFrom3Tokens ( tokens , 1 ) ;
}
else if ( ( header . compare ( " d " ) = = 0 ) | |
( header . compare ( " Tr " ) = = 0 ) ) // alpha
{
if ( tokens . size ( ) < 2 ) return false ;
currentMaterial . Tr = ( float ) atof ( tokens [ 1 ] . c_str ( ) ) ;
}
else if ( header . compare ( " Ns " ) = = 0 ) // shininess
{
if ( tokens . size ( ) < 2 ) return false ;
currentMaterial . Ns = float ( atoi ( tokens [ 1 ] . c_str ( ) ) ) ;
}
else if ( header . compare ( " illum " ) = = 0 ) // specular illumination on/off
{
if ( tokens . size ( ) < 2 ) return false ;
currentMaterial . illum = atoi ( tokens [ 1 ] . c_str ( ) ) ; ;
}
else if ( header . compare ( " map_Kd " ) = = 0 ) // texture name
{
std : : string textureName ;
if ( tokens . size ( ) < 2 )
return false ;
else if ( tokens . size ( ) = = 2 )
textureName = tokens [ 1 ] ; //play it safe
else
textureName = line . substr ( 7 ) ; //get everything after "map_Kd "
currentMaterial . map_Kd = textureName ;
// adding texture name into textures vector (if not already present)
// avoid adding the same name twice
auto it = std : : find ( textures . begin ( ) , textures . end ( ) , textureName ) ;
if ( it = = textures . end ( ) ) {
currentMaterial . index = textures . size ( ) ;
textures . push_back ( textureName ) ;
} else {
currentMaterial . index = std : : distance ( textures . begin ( ) , it ) ;
}
}
// we simply ignore other situations
}
}
materials . push_back ( currentMaterial ) ; // add last read material
stream . close ( ) ;
// Sometimes some materials have texture and no texture
// in this case for sake of uniformity we just use the first texture.
if ( ! textures . empty ( ) )
{
for ( size_t i = 0 ; i < materials . size ( ) ; + + i )
{
if ( materials [ i ] . map_Kd . empty ( ) )
{
materials [ i ] . map_Kd = textures [ 0 ] ;
materials [ i ] . index = 0 ;
}
}
}
return true ;
}
} ; // end class
} // end Namespace tri
} // end Namespace io
2012-12-05 18:38:16 +01:00
} // end Namespace vcg
# endif // ndef __VCGLIB_IMPORT_OBJ