You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1097 lines
40 KiB
1097 lines
40 KiB
/**************************************************************************** |
|
* VCGLib o o * |
|
* Visual and Computer Graphics Library o o * |
|
* _ O _ * |
|
* Copyright(C) 2004-2016 \/)\/ * |
|
* Visual Computing Lab /\/| * |
|
* ISTI - Italian National Research Council | * |
|
* \ * |
|
* All rights reserved. * |
|
* * |
|
* This program is free software; you can redistribute it and/or modify * |
|
* 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 <vcg/complex/allocate.h> |
|
|
|
#include <wrap/callback.h> |
|
#include <wrap/io_trimesh/io_mask.h> |
|
#include <wrap/io_trimesh/io_material.h> |
|
#include <wrap/io_trimesh/io_fan_tessellator.h> |
|
#ifdef __gl_h_ |
|
#include <wrap/gl/glu_tesselator.h> |
|
#endif |
|
#include <vcg/space/color4.h> |
|
|
|
|
|
#include <fstream> |
|
#include <string> |
|
#include <vector> |
|
|
|
|
|
namespace vcg { |
|
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) |
|
{ |
|
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)) |
|
{ |
|
stream.close(); |
|
return E_BAD_VERT_TEX_INDEX; |
|
} |
|
ff.tInd=materials[currentMaterialIdx].index; |
|
} |
|
|
|
// verifying validity of vertex indices |
|
std::vector<int> tmp = ff.v; |
|
std::sort(tmp.begin(),tmp.end()); |
|
auto it = std::unique(tmp.begin(), tmp.end()); // returns the new end of the vector with unique indices |
|
if(it != tmp.end()) { // if the end is changed, there were duplicated indices... |
|
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)) |
|
{ |
|
stream.close(); |
|
return E_BAD_VERT_NORMAL_INDEX; |
|
} |
|
} |
|
|
|
|
|
if(((oi.mask & vcg::tri::io::Mask::IOM_FACECOLOR) != 0) && HasPerFaceColor(m)) // 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 |
|
{ |
|
#ifdef __gl_h_ |
|
//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); |
|
#else |
|
//qDebug("Warning: using fan tessellation for a polygon of %i verteces",vertexesPerFace); |
|
FanTessellator(polygonVect, indexTriangulatedVect); |
|
#endif |
|
} |
|
extraTriangles+=((indexTriangulatedVect.size()/3) -1); |
|
#ifdef QT_VERSION |
|
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()); |
|
} |
|
#endif |
|
//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) != 0) && HasPerFaceColor(m)) |
|
{ |
|
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) ((1 - 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); |
|
(void)extraTriangles; |
|
(void)numTexCoords; |
|
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) != 0 ) && HasPerVertexTexCoord(m)) |
|
{ |
|
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) != 0) && HasPerWedgeNormal(m)) |
|
{ |
|
m.face[i].WN(j).Import(normals[indexedFaces[i].n[j]]); |
|
} |
|
|
|
if (((oi.mask & vcg::tri::io::Mask::IOM_VERTNORMAL) != 0) && HasPerVertexNormal(m)) |
|
{ |
|
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 = 0; |
|
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; |
|
|
|
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) { |
|
currentMaterial.Ka = Point3fFrom3Tokens(tokens,1); |
|
} |
|
} |
|
else if (header.compare("Kd")==0) { |
|
if (tokens.size() >= 4) { |
|
currentMaterial.Kd = Point3fFrom3Tokens(tokens,1); |
|
} |
|
} |
|
else if (header.compare("Ks")==0) { |
|
if (tokens.size() >= 4) { |
|
currentMaterial.Ks = Point3fFrom3Tokens(tokens,1); |
|
} |
|
} |
|
else if (header.compare("d")==0) { // alpha |
|
if (tokens.size() < 2) { |
|
currentMaterial.Tr = 1 - (float) atof(tokens[1].c_str()); |
|
} |
|
} |
|
else if (header.compare("Tr")==0) { // alpha |
|
if (tokens.size() < 2) { |
|
currentMaterial.Tr = (float) atof(tokens[1].c_str()); |
|
} |
|
} |
|
else if (header.compare("Ns")==0) { // shininess |
|
if (tokens.size() < 2) { |
|
currentMaterial.Ns = float(atoi(tokens[1].c_str())); |
|
} |
|
} |
|
else if (header.compare("illum")==0) { // specular illumination on/off |
|
if (tokens.size() < 2) { |
|
currentMaterial.illum = atoi(tokens[1].c_str()); |
|
} |
|
} |
|
else if(header.compare("map_Kd")==0) { // texture name |
|
std::string textureName; |
|
if (tokens.size() == 2) { |
|
//the tex name is the last one (after any option) |
|
textureName = tokens[tokens.size()-1]; |
|
} |
|
|
|
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 |
|
} // end Namespace vcg |
|
|
|
#endif // ndef __VCGLIB_IMPORT_OBJ
|
|
|