#ifndef ASSIMP_BUILD_NO_OBJ_IMPORTER
#include "ObjFileParser.h"
#include "ObjFileMtlImporter.h"
#include "ObjTools.h"
#include "ObjFileData.h"
#include "ParsingUtils.h"
#include "BaseImporter.h"
#include <assimp/DefaultIOSystem.h>
#include <assimp/DefaultLogger.hpp>
#include <assimp/material.h>
#include <assimp/Importer.hpp>
#include <cstdlib>
namespace Assimp {
const std::string ObjFileParser::DEFAULT_MATERIAL = AI_DEFAULT_MATERIAL_NAME;
ObjFileParser::ObjFileParser()
: m_DataIt()
, m_DataItEnd()
, m_pModel( NULL )
, m_uiLine( 0 )
, m_pIO( nullptr )
, m_progress( nullptr )
, m_originalObjFileName() {
}
ObjFileParser::ObjFileParser( IOStreamBuffer<char> &streamBuffer, const std::string &modelName,
IOSystem *io, ProgressHandler* progress,
const std::string &originalObjFileName) :
m_DataIt(),
m_DataItEnd(),
m_pModel(NULL),
m_uiLine(0),
m_pIO( io ),
m_progress(progress),
m_originalObjFileName(originalObjFileName)
{
std::fill_n(m_buffer,Buffersize,0);
m_pModel = new ObjFile::Model();
m_pModel->m_ModelName = modelName;
m_pModel->m_pDefaultMaterial = new ObjFile::Material;
m_pModel->m_pDefaultMaterial->MaterialName.Set( DEFAULT_MATERIAL );
m_pModel->m_MaterialLib.push_back( DEFAULT_MATERIAL );
m_pModel->m_MaterialMap[ DEFAULT_MATERIAL ] = m_pModel->m_pDefaultMaterial;
parseFile( streamBuffer );
}
ObjFileParser::~ObjFileParser() {
delete m_pModel;
m_pModel = NULL;
}
void ObjFileParser::setBuffer( std::vector<char> &buffer ) {
m_DataIt = buffer.begin();
m_DataItEnd = buffer.end();
}
ObjFile::Model *ObjFileParser::GetModel() const {
return m_pModel;
}
void ObjFileParser::parseFile( IOStreamBuffer<char> &streamBuffer ) {
unsigned int progressCounter = 0;
const unsigned int bytesToProcess = static_cast<unsigned int>(streamBuffer.size());
const unsigned int progressTotal = 3 * bytesToProcess;
const unsigned int progressOffset = bytesToProcess;
unsigned int processed = 0;
size_t lastFilePos( 0 );
std::vector<char> buffer;
while ( streamBuffer.getNextDataLine( buffer, '\\' ) ) {
m_DataIt = buffer.begin();
m_DataItEnd = buffer.end();
const size_t filePos( streamBuffer.getFilePos() );
if ( lastFilePos < filePos ) {
processed += static_cast<unsigned int>(filePos);
lastFilePos = filePos;
progressCounter++;
m_progress->UpdateFileRead( progressOffset + processed * 2, progressTotal );
}
switch (*m_DataIt) {
case 'v': {
++m_DataIt;
if (*m_DataIt == ' ' || *m_DataIt == '\t') {
size_t numComponents = getNumComponentsInDataDefinition();
if (numComponents == 3) {
getVector3(m_pModel->m_Vertices);
} else if (numComponents == 4) {
getHomogeneousVector3(m_pModel->m_Vertices);
} else if (numComponents == 6) {
getTwoVectors3(m_pModel->m_Vertices, m_pModel->m_VertexColors);
}
} else if (*m_DataIt == 't') {
++m_DataIt;
getVector( m_pModel->m_TextureCoord );
} else if (*m_DataIt == 'n') {
++m_DataIt;
getVector3( m_pModel->m_Normals );
}
}
break;
case 'p': case 'l':
case 'f':
{
getFace(*m_DataIt == 'f' ? aiPrimitiveType_POLYGON : (*m_DataIt == 'l'
? aiPrimitiveType_LINE : aiPrimitiveType_POINT));
}
break;
case '#': {
getComment();
}
break;
case 'u': {
std::string name;
getNameNoSpace(m_DataIt, m_DataItEnd, name);
size_t nextSpace = name.find(" ");
if (nextSpace != std::string::npos)
name = name.substr(0, nextSpace);
if(name == "usemtl")
{
getMaterialDesc();
}
}
break;
case 'm': {
std::string name;
getNameNoSpace(m_DataIt, m_DataItEnd, name);
size_t nextSpace = name.find(" ");
if (nextSpace != std::string::npos)
name = name.substr(0, nextSpace);
if (name == "mg")
getGroupNumberAndResolution();
else if(name == "mtllib")
getMaterialLib();
else
goto pf_skip_line;
}
break;
case 'g': {
getGroupName();
}
break;
case 's': {
getGroupNumber();
}
break;
case 'o': {
getObjectName();
}
break;
default:
{
pf_skip_line:
m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
}
break;
}
}
}
void ObjFileParser::copyNextWord(char *pBuffer, size_t length) {
size_t index = 0;
m_DataIt = getNextWord<DataArrayIt>(m_DataIt, m_DataItEnd);
if ( *m_DataIt == '\\' ) {
m_DataIt++;
m_DataIt++;
m_DataIt = getNextWord<DataArrayIt>( m_DataIt, m_DataItEnd );
}
while( m_DataIt != m_DataItEnd && !IsSpaceOrNewLine( *m_DataIt ) ) {
pBuffer[index] = *m_DataIt;
index++;
if( index == length - 1 ) {
break;
}
++m_DataIt;
}
ai_assert(index < length);
pBuffer[index] = '\0';
}
static bool isDataDefinitionEnd( const char *tmp ) {
if ( *tmp == '\\' ) {
tmp++;
if ( IsLineEnd( *tmp ) ) {
tmp++;
return true;
}
}
return false;
}
size_t ObjFileParser::getNumComponentsInDataDefinition() {
size_t numComponents( 0 );
const char* tmp( &m_DataIt[0] );
bool end_of_definition = false;
while ( !end_of_definition ) {
if ( isDataDefinitionEnd( tmp ) ) {
tmp += 2;
} else if ( IsLineEnd( *tmp ) ) {
end_of_definition = true;
}
if ( !SkipSpaces( &tmp ) ) {
break;
}
const bool isNum( IsNumeric( *tmp ) );
SkipToken( tmp );
if ( isNum ) {
++numComponents;
}
if ( !SkipSpaces( &tmp ) ) {
break;
}
}
return numComponents;
}
void ObjFileParser::getVector( std::vector<aiVector3D> &point3d_array ) {
size_t numComponents = getNumComponentsInDataDefinition();
ai_real x, y, z;
if( 2 == numComponents ) {
copyNextWord( m_buffer, Buffersize );
x = ( ai_real ) fast_atof( m_buffer );
copyNextWord( m_buffer, Buffersize );
y = ( ai_real ) fast_atof( m_buffer );
z = 0.0;
} else if( 3 == numComponents ) {
copyNextWord( m_buffer, Buffersize );
x = ( ai_real ) fast_atof( m_buffer );
copyNextWord( m_buffer, Buffersize );
y = ( ai_real ) fast_atof( m_buffer );
copyNextWord( m_buffer, Buffersize );
z = ( ai_real ) fast_atof( m_buffer );
} else {
throw DeadlyImportError( "OBJ: Invalid number of components" );
}
point3d_array.push_back( aiVector3D( x, y, z ) );
m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
}
void ObjFileParser::getVector3( std::vector<aiVector3D> &point3d_array ) {
ai_real x, y, z;
copyNextWord(m_buffer, Buffersize);
x = (ai_real) fast_atof(m_buffer);
copyNextWord(m_buffer, Buffersize);
y = (ai_real) fast_atof(m_buffer);
copyNextWord( m_buffer, Buffersize );
z = ( ai_real ) fast_atof( m_buffer );
point3d_array.push_back( aiVector3D( x, y, z ) );
m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
}
void ObjFileParser::getHomogeneousVector3( std::vector<aiVector3D> &point3d_array ) {
ai_real x, y, z, w;
copyNextWord(m_buffer, Buffersize);
x = (ai_real) fast_atof(m_buffer);
copyNextWord(m_buffer, Buffersize);
y = (ai_real) fast_atof(m_buffer);
copyNextWord( m_buffer, Buffersize );
z = ( ai_real ) fast_atof( m_buffer );
copyNextWord( m_buffer, Buffersize );
w = ( ai_real ) fast_atof( m_buffer );
ai_assert( w != 0 );
point3d_array.push_back( aiVector3D( x/w, y/w, z/w ) );
m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
}
void ObjFileParser::getTwoVectors3( std::vector<aiVector3D> &point3d_array_a, std::vector<aiVector3D> &point3d_array_b ) {
ai_real x, y, z;
copyNextWord(m_buffer, Buffersize);
x = (ai_real) fast_atof(m_buffer);
copyNextWord(m_buffer, Buffersize);
y = (ai_real) fast_atof(m_buffer);
copyNextWord( m_buffer, Buffersize );
z = ( ai_real ) fast_atof( m_buffer );
point3d_array_a.push_back( aiVector3D( x, y, z ) );
copyNextWord(m_buffer, Buffersize);
x = (ai_real) fast_atof(m_buffer);
copyNextWord(m_buffer, Buffersize);
y = (ai_real) fast_atof(m_buffer);
copyNextWord( m_buffer, Buffersize );
z = ( ai_real ) fast_atof( m_buffer );
point3d_array_b.push_back( aiVector3D( x, y, z ) );
m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
}
void ObjFileParser::getVector2( std::vector<aiVector2D> &point2d_array ) {
ai_real x, y;
copyNextWord(m_buffer, Buffersize);
x = (ai_real) fast_atof(m_buffer);
copyNextWord(m_buffer, Buffersize);
y = (ai_real) fast_atof(m_buffer);
point2d_array.push_back(aiVector2D(x, y));
m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
}
static const std::string DefaultObjName = "defaultobject";
void ObjFileParser::getFace( aiPrimitiveType type ) {
m_DataIt = getNextToken<DataArrayIt>( m_DataIt, m_DataItEnd );
if ( m_DataIt == m_DataItEnd || *m_DataIt == '\0' ) {
return;
}
ObjFile::Face *face = new ObjFile::Face( type );
bool hasNormal = false;
const int vSize = static_cast<unsigned int>(m_pModel->m_Vertices.size());
const int vtSize = static_cast<unsigned int>(m_pModel->m_TextureCoord.size());
const int vnSize = static_cast<unsigned int>(m_pModel->m_Normals.size());
const bool vt = (!m_pModel->m_TextureCoord.empty());
const bool vn = (!m_pModel->m_Normals.empty());
int iStep = 0, iPos = 0;
while ( m_DataIt != m_DataItEnd ) {
iStep = 1;
if ( IsLineEnd( *m_DataIt ) ) {
break;
}
if ( *m_DataIt =='/' ) {
if (type == aiPrimitiveType_POINT) {
DefaultLogger::get()->error("Obj: Separator unexpected in point statement");
}
if (iPos == 0) {
if (!vt && vn) {
iPos = 1;
iStep++;
}
}
iPos++;
} else if( IsSpaceOrNewLine( *m_DataIt ) ) {
iPos = 0;
} else {
const int iVal( ::atoi( & ( *m_DataIt ) ) );
int tmp = iVal;
if ( iVal < 0 ) {
++iStep;
}
while ( ( tmp = tmp / 10 ) != 0 ) {
++iStep;
}
if ( iVal > 0 ) {
if ( 0 == iPos ) {
face->m_vertices.push_back( iVal - 1 );
} else if ( 1 == iPos ) {
face->m_texturCoords.push_back( iVal - 1 );
} else if ( 2 == iPos ) {
face->m_normals.push_back( iVal - 1 );
hasNormal = true;
} else {
reportErrorTokenInFace();
}
} else if ( iVal < 0 ) {
if ( 0 == iPos ) {
face->m_vertices.push_back( vSize + iVal );
} else if ( 1 == iPos ) {
face->m_texturCoords.push_back( vtSize + iVal );
} else if ( 2 == iPos ) {
face->m_normals.push_back( vnSize + iVal );
hasNormal = true;
} else {
reportErrorTokenInFace();
}
}
}
m_DataIt += iStep;
}
if ( face->m_vertices.empty() ) {
DefaultLogger::get()->error("Obj: Ignoring empty face");
m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
delete face;
return;
}
if( NULL != m_pModel->m_pCurrentMaterial ) {
face->m_pMaterial = m_pModel->m_pCurrentMaterial;
} else {
face->m_pMaterial = m_pModel->m_pDefaultMaterial;
}
if( NULL == m_pModel->m_pCurrent ) {
createObject( DefaultObjName );
}
if ( NULL == m_pModel->m_pCurrentMesh ) {
createMesh( DefaultObjName );
}
m_pModel->m_pCurrentMesh->m_Faces.push_back( face );
m_pModel->m_pCurrentMesh->m_uiNumIndices += (unsigned int) face->m_vertices.size();
m_pModel->m_pCurrentMesh->m_uiUVCoordinates[ 0 ] += (unsigned int) face->m_texturCoords.size();
if( !m_pModel->m_pCurrentMesh->m_hasNormals && hasNormal ) {
m_pModel->m_pCurrentMesh->m_hasNormals = true;
}
m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
}
void ObjFileParser::getMaterialDesc() {
m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
if (m_DataIt == m_DataItEnd) {
return;
}
char *pStart = &(*m_DataIt);
while( m_DataIt != m_DataItEnd && !IsLineEnd( *m_DataIt ) ) {
++m_DataIt;
}
bool skip = false;
std::string strName(pStart, &(*m_DataIt));
strName = trim_whitespaces(strName);
if (strName.empty())
skip = true;
if ( m_pModel->m_pCurrentMaterial && m_pModel->m_pCurrentMaterial->MaterialName == aiString( strName ) ) {
skip = true;
}
if (!skip) {
std::map<std::string, ObjFile::Material*>::iterator it = m_pModel->m_MaterialMap.find(strName);
if (it == m_pModel->m_MaterialMap.end()) {
DefaultLogger::get()->error("OBJ: failed to locate material " + strName + ", creating new material");
m_pModel->m_pCurrentMaterial = new ObjFile::Material();
m_pModel->m_pCurrentMaterial->MaterialName.Set(strName);
m_pModel->m_MaterialLib.push_back(strName);
m_pModel->m_MaterialMap[strName] = m_pModel->m_pCurrentMaterial;
} else {
m_pModel->m_pCurrentMaterial = (*it).second;
}
if ( needsNewMesh( strName ) ) {
createMesh( strName );
}
m_pModel->m_pCurrentMesh->m_uiMaterialIndex = getMaterialIndex(strName);
}
m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
}
void ObjFileParser::getComment() {
m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
}
void ObjFileParser::getMaterialLib() {
m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
if( m_DataIt == m_DataItEnd ) {
return;
}
char *pStart = &(*m_DataIt);
while( m_DataIt != m_DataItEnd && !IsLineEnd( *m_DataIt ) ) {
++m_DataIt;
}
const std::string strMatName(pStart, &(*m_DataIt));
std::string absName;
if ( 0 == strMatName.length() ) {
DefaultLogger::get()->warn( "OBJ: no name for material library specified." );
return;
}
if ( m_pIO->StackSize() > 0 ) {
std::string path = m_pIO->CurrentDirectory();
if ( '/' != *path.rbegin() ) {
path += '/';
}
absName = path + strMatName;
} else {
absName = strMatName;
}
IOStream *pFile = m_pIO->Open( absName );
if (!pFile ) {
DefaultLogger::get()->error("OBJ: Unable to locate material file " + strMatName);
std::string strMatFallbackName = m_originalObjFileName.substr(0, m_originalObjFileName.length() - 3) + "mtl";
DefaultLogger::get()->info("OBJ: Opening fallback material file " + strMatFallbackName);
pFile = m_pIO->Open(strMatFallbackName);
if (!pFile) {
DefaultLogger::get()->error("OBJ: Unable to locate fallback material file " + strMatFallbackName);
m_DataIt = skipLine<DataArrayIt>(m_DataIt, m_DataItEnd, m_uiLine);
return;
}
}
std::vector<char> buffer;
BaseImporter::TextFileToBuffer( pFile, buffer, BaseImporter::ALLOW_EMPTY );
m_pIO->Close( pFile );
ObjFileMtlImporter mtlImporter( buffer, strMatName, m_pModel );
}
void ObjFileParser::getNewMaterial() {
m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
m_DataIt = getNextWord<DataArrayIt>(m_DataIt, m_DataItEnd);
if( m_DataIt == m_DataItEnd ) {
return;
}
char *pStart = &(*m_DataIt);
std::string strMat( pStart, *m_DataIt );
while( m_DataIt != m_DataItEnd && IsSpaceOrNewLine( *m_DataIt ) ) {
++m_DataIt;
}
std::map<std::string, ObjFile::Material*>::iterator it = m_pModel->m_MaterialMap.find( strMat );
if ( it == m_pModel->m_MaterialMap.end() ) {
DefaultLogger::get()->warn("OBJ: Unsupported material requested: " + strMat);
m_pModel->m_pCurrentMaterial = m_pModel->m_pDefaultMaterial;
} else {
if ( needsNewMesh( strMat ) ) {
createMesh( strMat );
}
m_pModel->m_pCurrentMesh->m_uiMaterialIndex = getMaterialIndex( strMat );
}
m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
}
int ObjFileParser::getMaterialIndex( const std::string &strMaterialName )
{
int mat_index = -1;
if( strMaterialName.empty() ) {
return mat_index;
}
for (size_t index = 0; index < m_pModel->m_MaterialLib.size(); ++index)
{
if ( strMaterialName == m_pModel->m_MaterialLib[ index ])
{
mat_index = (int)index;
break;
}
}
return mat_index;
}
void ObjFileParser::getGroupName() {
std::string groupName;
m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
m_DataIt = getName<DataArrayIt>(m_DataIt, m_DataItEnd, groupName);
if( isEndOfBuffer( m_DataIt, m_DataItEnd ) ) {
return;
}
if ( m_pModel->m_strActiveGroup != groupName ) {
ObjFile::Model::ConstGroupMapIt it = m_pModel->m_Groups.find(groupName);
createObject( groupName );
if (it == m_pModel->m_Groups.end())
{
std::vector<unsigned int> *pFaceIDArray = new std::vector<unsigned int>;
m_pModel->m_Groups[ groupName ] = pFaceIDArray;
m_pModel->m_pGroupFaceIDs = (pFaceIDArray);
}
else
{
m_pModel->m_pGroupFaceIDs = (*it).second;
}
m_pModel->m_strActiveGroup = groupName;
}
m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
}
void ObjFileParser::getGroupNumber()
{
m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
}
void ObjFileParser::getGroupNumberAndResolution()
{
m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
}
void ObjFileParser::getObjectName()
{
m_DataIt = getNextToken<DataArrayIt>(m_DataIt, m_DataItEnd);
if( m_DataIt == m_DataItEnd ) {
return;
}
char *pStart = &(*m_DataIt);
while( m_DataIt != m_DataItEnd && !IsSpaceOrNewLine( *m_DataIt ) ) {
++m_DataIt;
}
std::string strObjectName(pStart, &(*m_DataIt));
if (!strObjectName.empty())
{
m_pModel->m_pCurrent = NULL;
for (std::vector<ObjFile::Object*>::const_iterator it = m_pModel->m_Objects.begin();
it != m_pModel->m_Objects.end();
++it)
{
if ((*it)->m_strObjName == strObjectName)
{
m_pModel->m_pCurrent = *it;
break;
}
}
if( NULL == m_pModel->m_pCurrent ) {
createObject( strObjectName );
}
}
m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
}
void ObjFileParser::createObject(const std::string &objName)
{
ai_assert( NULL != m_pModel );
m_pModel->m_pCurrent = new ObjFile::Object;
m_pModel->m_pCurrent->m_strObjName = objName;
m_pModel->m_Objects.push_back( m_pModel->m_pCurrent );
createMesh( objName );
if( m_pModel->m_pCurrentMaterial )
{
m_pModel->m_pCurrentMesh->m_uiMaterialIndex =
getMaterialIndex( m_pModel->m_pCurrentMaterial->MaterialName.data );
m_pModel->m_pCurrentMesh->m_pMaterial = m_pModel->m_pCurrentMaterial;
}
}
void ObjFileParser::createMesh( const std::string &meshName )
{
ai_assert( NULL != m_pModel );
m_pModel->m_pCurrentMesh = new ObjFile::Mesh( meshName );
m_pModel->m_Meshes.push_back( m_pModel->m_pCurrentMesh );
unsigned int meshId = static_cast<unsigned int>(m_pModel->m_Meshes.size()-1);
if ( NULL != m_pModel->m_pCurrent )
{
m_pModel->m_pCurrent->m_Meshes.push_back( meshId );
}
else
{
DefaultLogger::get()->error("OBJ: No object detected to attach a new mesh instance.");
}
}
bool ObjFileParser::needsNewMesh( const std::string &materialName )
{
if(m_pModel->m_pCurrentMesh == 0)
{
return true;
}
bool newMat = false;
int matIdx = getMaterialIndex( materialName );
int curMatIdx = m_pModel->m_pCurrentMesh->m_uiMaterialIndex;
if ( curMatIdx != int(ObjFile::Mesh::NoMaterial)
&& curMatIdx != matIdx
&& m_pModel->m_pCurrentMesh->m_Faces.size() > 0 )
{
newMat = true;
}
return newMat;
}
void ObjFileParser::reportErrorTokenInFace()
{
m_DataIt = skipLine<DataArrayIt>( m_DataIt, m_DataItEnd, m_uiLine );
DefaultLogger::get()->error("OBJ: Not supported token in face description detected");
}
}
#endif