17#ifndef CSCI441_MODEL_LOADER_HPP
18#define CSCI441_MODEL_LOADER_HPP
22#ifdef CSCI441_USE_GLEW
60 [[maybe_unused]]
explicit ModelLoader(
const char* filename );
82 bool loadModelFile( std::string filename,
bool INFO =
true,
bool ERRORS =
true );
90 [[maybe_unused]]
void setAttributeLocations(GLint positionLocation, GLint normalLocation = -1, GLint texCoordLocation = -1)
const;
102 [[maybe_unused]]
bool draw( GLuint shaderProgramHandle,
103 GLint matDiffLocation = -1, GLint matSpecLocation = -1, GLint matShinLocation = -1, GLint matAmbLocation = -1,
104 GLenum diffuseTexture = GL_TEXTURE0 )
const;
110 [[maybe_unused]] [[nodiscard]] GLuint getNumberOfVertices()
const;
116 [[maybe_unused]] [[nodiscard]] GLfloat* getVertices()
const;
122 [[maybe_unused]] [[nodiscard]] GLfloat* getNormals()
const;
128 [[maybe_unused]] [[nodiscard]] GLfloat* getTexCoords()
const;
133 [[maybe_unused]] [[nodiscard]] GLuint getNumberOfIndices()
const;
139 [[maybe_unused]] [[nodiscard]] GLuint* getIndices()
const;
148 [[maybe_unused]]
static void enableAutoGenerateNormals();
155 [[maybe_unused]]
static void disableAutoGenerateNormals();
159 bool _loadMTLFile(
const char *mtlFilename,
bool INFO,
bool ERRORS );
160 bool _loadOBJFile(
bool INFO,
bool ERRORS );
161 bool _loadOFFFile(
bool INFO,
bool ERRORS );
162 bool _loadPLYFile(
bool INFO,
bool ERRORS );
163 bool _loadSTLFile(
bool INFO,
bool ERRORS );
164 static std::vector<std::string> _tokenizeString( std::string input,
const std::string& delimiters );
165 void _allocateAttributeArrays(GLuint numVertices, GLuint numIndices);
168 std::string _filename;
169 CSCI441_INTERNAL::MODEL_TYPE _modelType;
174 glm::vec3* _vertices;
176 glm::vec2* _texCoords;
181 std::map< std::string, CSCI441_INTERNAL::ModelMaterial* > _materials;
182 std::map< std::string, std::vector< std::pair< GLuint, GLuint > > > _materialIndexStartStop;
184 bool _hasVertexTexCoords;
185 bool _hasVertexNormals;
187 static bool sAUTO_GEN_NORMALS;
194namespace CSCI441_INTERNAL {
195 unsigned char* createTransparentTexture(
const unsigned char *imageData,
const unsigned char *imageMask,
int texWidth,
int texHeight,
int texChannels,
int maskChannels );
196 [[maybe_unused]]
void flipImageY(
int texWidth,
int texHeight,
int textureChannels,
unsigned char *textureData );
199inline bool CSCI441::ModelLoader::sAUTO_GEN_NORMALS =
false;
208 loadModelFile( filename );
217 glDeleteBuffers( 2, _vbods );
218 glDeleteVertexArrays( 1, &_vaod );
221inline void CSCI441::ModelLoader::_init() {
222 _hasVertexTexCoords =
false;
223 _hasVertexNormals =
false;
226 _texCoords =
nullptr;
233 glGenVertexArrays( 1, &_vaod );
234 glGenBuffers( 2, _vbods );
239 _filename = std::move(filename);
240 if( _filename.find(
".obj") != std::string::npos ) {
241 result = _loadOBJFile( INFO, ERRORS );
242 _modelType = CSCI441_INTERNAL::MODEL_TYPE::OBJ;
244 else if( _filename.find(
".off") != std::string::npos ) {
245 result = _loadOFFFile( INFO, ERRORS );
246 _modelType = CSCI441_INTERNAL::MODEL_TYPE::OFF;
248 else if( _filename.find(
".ply") != std::string::npos ) {
249 result = _loadPLYFile( INFO, ERRORS );
250 _modelType = CSCI441_INTERNAL::MODEL_TYPE::PLY;
252 else if( _filename.find(
".stl") != std::string::npos ) {
253 result = _loadSTLFile( INFO, ERRORS );
254 _modelType = CSCI441_INTERNAL::MODEL_TYPE::STL;
258 if (ERRORS) fprintf( stderr,
"[ERROR]: Unsupported file format for file: %s\n", _filename.c_str() );
266 glBindVertexArray( _vaod );
267 glBindBuffer( GL_ARRAY_BUFFER, _vbods[0] );
269 glEnableVertexAttribArray( positionLocation );
270 glVertexAttribPointer( positionLocation, 3, GL_FLOAT, GL_FALSE, 0, (
void*)
nullptr );
272 glEnableVertexAttribArray( normalLocation );
273 glVertexAttribPointer( normalLocation, 3, GL_FLOAT, GL_FALSE, 0, (
void*)(
sizeof(glm::vec3) * _uniqueIndex) );
275 glEnableVertexAttribArray( texCoordLocation );
276 glVertexAttribPointer( texCoordLocation, 2, GL_FLOAT, GL_FALSE, 0, (
void*)(
sizeof(glm::vec3) * _uniqueIndex * 2) );
281 GLint matDiffLocation, GLint matSpecLocation, GLint matShinLocation, GLint matAmbLocation,
282 GLenum diffuseTexture )
const {
283 glBindVertexArray( _vaod );
286 if( _modelType == CSCI441_INTERNAL::MODEL_TYPE::OBJ ) {
287 for(
const auto & materialIter : _materialIndexStartStop) {
288 auto materialName = materialIter.first;
289 auto indexStartStop = materialIter.second;
291 CSCI441_INTERNAL::ModelMaterial* material =
nullptr;
292 if( _materials.find( materialName ) != _materials.end() )
293 material = _materials.find( materialName )->second;
295 for(
auto & idxIter : indexStartStop) {
296 auto start = idxIter.first;
297 auto end = idxIter.second;
298 GLsizei length = (GLsizei)(end - start) + 1;
302 if( material !=
nullptr ) {
303 glProgramUniform4fv( shaderProgramHandle, matAmbLocation, 1, &material->ambient[0] );
304 glProgramUniform4fv( shaderProgramHandle, matDiffLocation, 1, &material->diffuse[0] );
305 glProgramUniform4fv( shaderProgramHandle, matSpecLocation, 1, &material->specular[0] );
306 glProgramUniform1f( shaderProgramHandle, matShinLocation, material->shininess );
308 if( material->map_Kd != -1 ) {
309 glActiveTexture( diffuseTexture );
310 glBindTexture( GL_TEXTURE_2D, material->map_Kd );
314 glDrawElements( GL_TRIANGLES, length, GL_UNSIGNED_INT, (
void*)(
sizeof(GLuint)*start) );
318 glDrawElements( GL_TRIANGLES,
static_cast<GLint
>(_numIndices), GL_UNSIGNED_INT, (
void*)
nullptr );
332inline bool CSCI441::ModelLoader::_loadOBJFile(
bool INFO,
bool ERRORS ) {
335 if ( INFO ) fprintf( stdout,
"[.obj]: -=-=-=-=-=-=-=- BEGIN %s Info -=-=-=-=-=-=-=- \n", _filename.c_str() );
340 std::ifstream in( _filename );
341 if( !in.is_open() ) {
342 if (ERRORS) fprintf( stderr,
"[.obj]: [ERROR]: Could not open \"%s\"\n", _filename.c_str() );
343 if ( INFO ) fprintf( stdout,
"[.obj]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=- \n", _filename.c_str() );
347 GLuint numObjects = 0, numGroups = 0;
348 GLuint numVertices = 0, numTexCoords = 0, numNormals = 0;
349 GLuint numFaces = 0, numTriangles = 0;
350 glm::vec3 minDimension = {999999.f, 999999.f, 999999.f};
351 glm::vec3 maxDimension = { -999999.f, -999999.f, -999999.f };
354 std::map<std::string, GLuint> uniqueCounts;
357 int progressCounter = 0;
359 while( getline( in, line ) ) {
360 if( line.length() > 1 && line.at(0) ==
'\t' )
361 line = line.substr( 1 );
362 line.erase( line.find_last_not_of(
" \n\r\t" ) + 1 );
364 std::vector< std::string > tokens = _tokenizeString( line,
" \t" );
365 if( tokens.empty() )
continue;
368 if( tokens[0] ==
"#" || tokens[0].find_first_of(
'#') == 0 ) {
370 }
else if( tokens[0] ==
"o" ) {
372 }
else if( tokens[0] ==
"g" ) {
374 }
else if( tokens[0] ==
"mtllib" ) {
375 _loadMTLFile( tokens[1].c_str(), INFO, ERRORS );
376 }
else if( tokens[0] ==
"v" ) {
379 glm::vec3 pos = { strtof( tokens[1].c_str(),
nullptr ),
380 strtof( tokens[2].c_str(),
nullptr ),
381 strtof( tokens[3].c_str(),
nullptr ) };
383 if( pos.x < minDimension.x ) minDimension.x = pos.x;
384 if( pos.x > maxDimension.x ) maxDimension.x = pos.x;
385 if( pos.y < minDimension.y ) minDimension.y = pos.y;
386 if( pos.y > maxDimension.y ) maxDimension.y = pos.y;
387 if( pos.z < minDimension.z ) minDimension.z = pos.z;
388 if( pos.z > maxDimension.z ) maxDimension.z = pos.z;
389 }
else if( tokens[0] ==
"vn" ) {
391 }
else if( tokens[0] ==
"vt" ) {
393 }
else if( tokens[0] ==
"f" ) {
396 std::vector<std::string> faceTokens = _tokenizeString(line,
" ");
398 for (GLuint i = 1; i < faceTokens.size(); i++) {
400 std::vector<std::string> groupTokens = _tokenizeString(faceTokens[i],
"/");
402 for (
char j: faceTokens[i]) {
403 if (j ==
'/') numSlashes++;
406 std::stringstream currentFaceTokenStream;
408 auto signedVertexIndex = (GLint) strtol(groupTokens[0].c_str(),
nullptr, 10);
409 GLuint vertexIndex = signedVertexIndex;
410 if (signedVertexIndex < 0) vertexIndex = numVertices + signedVertexIndex + 1;
412 currentFaceTokenStream << vertexIndex;
415 if (groupTokens.size() == 2 && numSlashes == 1) {
416 _hasVertexTexCoords =
true;
418 auto signedTexCoordIndex = (GLint) strtol(groupTokens[1].c_str(),
nullptr, 10);
419 GLuint texCoordIndex = signedTexCoordIndex;
420 if (signedTexCoordIndex < 0) texCoordIndex = numTexCoords + signedTexCoordIndex + 1;
422 currentFaceTokenStream <<
"/" << texCoordIndex;
423 }
else if (groupTokens.size() == 2 && numSlashes == 2) {
424 _hasVertexNormals =
true;
426 auto signedNormalIndex = (GLint) strtol(groupTokens[1].c_str(),
nullptr, 10);
427 GLuint normalIndex = signedNormalIndex;
428 if (signedNormalIndex < 0) normalIndex = numNormals + signedNormalIndex + 1;
430 currentFaceTokenStream <<
"//" << normalIndex;
431 }
else if (groupTokens.size() == 3) {
432 _hasVertexTexCoords =
true;
433 _hasVertexNormals =
true;
435 auto signedTexCoordIndex = (GLint) strtol(groupTokens[1].c_str(),
nullptr, 10);
436 GLuint texCoordIndex = signedTexCoordIndex;
437 if (signedTexCoordIndex < 0) texCoordIndex = numTexCoords + signedTexCoordIndex + 1;
439 auto signedNormalIndex = (GLint) strtol(groupTokens[2].c_str(),
nullptr, 10);
440 GLuint normalIndex = signedNormalIndex;
441 if (signedNormalIndex < 0) normalIndex = numNormals + signedNormalIndex + 1;
443 currentFaceTokenStream <<
"/" << texCoordIndex <<
"/" << normalIndex;
444 }
else if (groupTokens.size() != 1) {
445 if (ERRORS) fprintf(stderr,
"[.obj]: [ERROR]: Malformed OBJ file, %s.\n", _filename.c_str());
449 std::string processedFaceToken = currentFaceTokenStream.str();
450 if (uniqueCounts.find(processedFaceToken) == uniqueCounts.end()) {
451 uniqueCounts.insert(std::pair<std::string, long int>(processedFaceToken, _uniqueIndex));
456 numTriangles += (faceTokens.size() - 1 - 3 + 1);
459 }
else if( tokens[0] ==
"usemtl" ) {
462 if (INFO) printf(
"[.obj]: ignoring line: %s\n", line.c_str() );
467 if( progressCounter % 5000 == 0 ) {
469 switch( progressCounter ) {
470 case 5000: printf(
"[.obj]: scanning %s...\\", _filename.c_str());
break;
471 case 10000: printf(
"[.obj]: scanning %s...|", _filename.c_str());
break;
472 case 15000: printf(
"[.obj]: scanning %s.../", _filename.c_str());
break;
473 case 20000: printf(
"[.obj]: scanning %s...-", _filename.c_str());
break;
478 if( progressCounter == 20000 )
485 printf(
"\33[2K\r" );
486 printf(
"[.obj]: scanning %s...done!\n", _filename.c_str() );
487 printf(
"[.obj]: ------------\n" );
488 printf(
"[.obj]: Model Stats:\n" );
489 printf(
"[.obj]: Vertices: \t%u\tNormals: \t%u\tTex Coords:\t%u\n", numVertices, numNormals, numTexCoords );
490 printf(
"[.obj]: Unique Verts:\t%u\n", _uniqueIndex );
491 printf(
"[.obj]: Faces: \t%u\tTriangles:\t%u\n", numFaces, numTriangles );
492 printf(
"[.obj]: Objects: \t%u\tGroups: \t%u\n", numObjects, numGroups );
494 glm::vec3 sizeDimensions = maxDimension - minDimension;
495 printf(
"[.obj]: Dimensions:\t(%f, %f, %f)\n", sizeDimensions.x, sizeDimensions.y, sizeDimensions.z );
498 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
499 if (INFO && !_hasVertexNormals)
500 printf(
"[.obj]: [WARN]: No vertex normals exist on model. To autogenerate vertex\n\tnormals, call CSCI441::ModelLoader::enableAutoGenerateNormals()\n\tprior to loading the model file.\n" );
501 _allocateAttributeArrays(_uniqueIndex, numTriangles*3);
503 if (INFO) printf(
"[.obj]: No vertex normals exist on model, vertex normals will be autogenerated\n" );
504 _allocateAttributeArrays(numTriangles * 3, numTriangles*3);
507 auto objVertices =
new glm::vec3[numVertices];
508 auto objNormals =
new glm::vec3[numNormals];
509 auto objTexCoords =
new glm::vec2[numTexCoords];
511 std::vector<glm::vec3> verticesTemps;
512 std::vector<glm::vec2> texCoordsTemp;
514 printf(
"[.obj]: ------------\n" );
516 uniqueCounts.clear();
520 in.open( _filename );
522 GLuint verticesSeen = 0, texCoordsSeen = 0, normalsSeen = 0, indicesSeen = 0;
523 GLuint uniqueNumVertices = 0;
525 std::string currentMaterial =
"default";
526 _materialIndexStartStop.insert( std::pair< std::string, std::vector< std::pair< GLuint, GLuint > > >( currentMaterial, std::vector< std::pair< GLuint, GLuint > >(1) ) );
527 _materialIndexStartStop.find( currentMaterial )->second.back().first = indicesSeen;
529 while( getline( in, line ) ) {
530 if( line.length() > 1 && line.at(0) ==
'\t' )
531 line = line.substr( 1 );
532 line.erase( line.find_last_not_of(
" \n\r\t" ) + 1 );
534 auto tokens = _tokenizeString( line,
" \t" );
535 if( tokens.empty() )
continue;
538 if( tokens[0] ==
"#" || tokens[0].find_first_of(
'#') == 0
541 || tokens[0] ==
"mtllib"
545 }
else if( tokens[0] ==
"usemtl" ) {
546 if( currentMaterial ==
"default" && indicesSeen == 0 ) {
547 _materialIndexStartStop.clear();
549 _materialIndexStartStop.find( currentMaterial )->second.back().second = indicesSeen - 1;
551 currentMaterial = tokens[1];
552 if( _materialIndexStartStop.find( currentMaterial ) == _materialIndexStartStop.end() ) {
553 _materialIndexStartStop.insert( std::pair< std::string, std::vector< std::pair< GLuint, GLuint > > >( currentMaterial, std::vector< std::pair< GLuint, GLuint > >(1) ) );
554 _materialIndexStartStop.find( currentMaterial )->second.back().first = indicesSeen;
556 _materialIndexStartStop.find( currentMaterial )->second.emplace_back( indicesSeen, -1 );
558 }
else if( tokens[0] ==
"v" ) {
559 objVertices[verticesSeen++] = glm::vec3(strtof(tokens[1].c_str(),
nullptr ),
560 strtof( tokens[2].c_str(),
nullptr ),
561 strtof( tokens[3].c_str(),
nullptr ) );
562 }
else if( tokens[0] ==
"vn" ) {
563 objNormals[normalsSeen++] = glm::vec3(strtof(tokens[1].c_str(),
nullptr ),
564 strtof( tokens[2].c_str(),
nullptr ),
565 strtof( tokens[3].c_str(),
nullptr ));
566 }
else if( tokens[0] ==
"vt" ) {
567 objTexCoords[texCoordsSeen++] = glm::vec2(strtof(tokens[1].c_str(),
nullptr ),
568 strtof( tokens[2].c_str(),
nullptr ));
569 }
else if( tokens[0] ==
"f" ) {
570 std::vector<std::string> processedFaceTokens;
572 bool faceHasVertexNormals =
false;
573 bool faceHasTextureCoordinates =
false;
575 for(GLuint i = 1; i < tokens.size(); i++) {
577 auto vertexAttributeTokens = _tokenizeString(tokens[i],
"/");
578 int numAttributeSlashes = 0;
579 for(
char j : tokens[i]) {
580 if(j ==
'/') numAttributeSlashes++;
583 GLuint vertexIndex = 0, texCoordIndex = 0, normalIndex = 0;
585 std::stringstream currentFaceTokenStream;
586 auto signedVertexIndex = (GLint)strtol(vertexAttributeTokens[0].c_str(),
nullptr, 10);
587 vertexIndex = signedVertexIndex;
588 if(signedVertexIndex < 0) vertexIndex = verticesSeen + signedVertexIndex + 1;
589 currentFaceTokenStream << vertexIndex;
592 if(vertexAttributeTokens.size() == 2 && numAttributeSlashes == 1) {
594 _hasVertexTexCoords =
true;
595 faceHasTextureCoordinates =
true;
597 auto signedTexCoordIndex = (GLint)strtol(vertexAttributeTokens[1].c_str(),
nullptr, 10);
598 texCoordIndex = signedTexCoordIndex;
599 if(signedTexCoordIndex < 0) texCoordIndex = texCoordsSeen + signedTexCoordIndex + 1;
600 currentFaceTokenStream <<
"/" << texCoordIndex;
601 }
else if(vertexAttributeTokens.size() == 2 && numAttributeSlashes == 2) {
603 _hasVertexNormals =
true;
604 faceHasVertexNormals =
true;
606 auto signedNormalIndex = (GLint)strtol(vertexAttributeTokens[1].c_str(),
nullptr, 10);
607 normalIndex = signedNormalIndex;
608 if(signedNormalIndex < 0) normalIndex = normalsSeen + signedNormalIndex + 1;
609 currentFaceTokenStream <<
"//" << normalIndex;
610 }
else if(vertexAttributeTokens.size() == 3) {
612 _hasVertexTexCoords =
true;
613 faceHasTextureCoordinates =
true;
614 _hasVertexNormals =
true;
615 faceHasVertexNormals =
true;
617 auto signedTexCoordIndex = (GLint)strtol(vertexAttributeTokens[1].c_str(),
nullptr, 10);
618 texCoordIndex = signedTexCoordIndex;
619 if(signedTexCoordIndex < 0) texCoordIndex = texCoordsSeen + signedTexCoordIndex + 1;
621 auto signedNormalIndex = (GLint)strtol(vertexAttributeTokens[2].c_str(),
nullptr, 10);
622 normalIndex = signedNormalIndex;
623 if(signedNormalIndex < 0) normalIndex = normalsSeen + signedNormalIndex + 1;
625 currentFaceTokenStream <<
"/" << texCoordIndex <<
"/" << normalIndex;
626 }
else if(vertexAttributeTokens.size() != 1) {
627 if (ERRORS) fprintf(stderr,
"[.obj]: [ERROR]: Malformed OBJ file, %s.\n", _filename.c_str());
631 auto processedFaceToken = currentFaceTokenStream.str();
632 processedFaceTokens.push_back(processedFaceToken);
635 if( uniqueCounts.find( processedFaceToken ) == uniqueCounts.end() ) {
637 uniqueCounts.insert( std::pair<std::string,GLuint>(processedFaceToken, uniqueNumVertices) );
639 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
640 _vertices[ _uniqueIndex ] = objVertices[ vertexIndex - 1 ];
641 if(faceHasTextureCoordinates && texCoordIndex != 0) { _texCoords[ _uniqueIndex ] = objTexCoords[ texCoordIndex - 1 ]; }
642 if(faceHasVertexNormals && normalIndex != 0) _normals[ _uniqueIndex ] = objNormals[ normalIndex - 1 ];
645 verticesTemps.push_back( objVertices[ vertexIndex - 1 ] );
646 if(faceHasTextureCoordinates && texCoordIndex != 0) { texCoordsTemp.push_back( objTexCoords[ texCoordIndex - 1 ] ); }
648 if( (vertexAttributeTokens.size() == 2 && numAttributeSlashes == 2)
649 || (vertexAttributeTokens.size() == 3) ) {
651 if (ERRORS) fprintf( stderr,
"[.obj]: [ERROR]: no vertex normals were specified, should not be trying to access values\n" );
658 for(GLuint i = 1; i < processedFaceTokens.size()-1; i++) {
659 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
660 _indices[ indicesSeen++ ] = uniqueCounts.find( processedFaceTokens[0] )->second;
661 _indices[ indicesSeen++ ] = uniqueCounts.find( processedFaceTokens[i] )->second;
662 _indices[ indicesSeen++ ] = uniqueCounts.find( processedFaceTokens[i+1] )->second;
666 GLuint aI = uniqueCounts.find( processedFaceTokens[0] )->second;
667 GLuint bI = uniqueCounts.find( processedFaceTokens[i] )->second;
668 GLuint cI = uniqueCounts.find( processedFaceTokens[i+1] )->second;
670 glm::vec3 a = verticesTemps[aI];
671 glm::vec3 b = verticesTemps[bI];
672 glm::vec3 c = verticesTemps[cI];
674 glm::vec3 ab = b - a; glm::vec3 ac = c - a;
675 glm::vec3 ba = a - b; glm::vec3 bc = c - b;
676 glm::vec3 ca = a - c; glm::vec3 cb = b - c;
678 glm::vec3 aN = glm::normalize( glm::cross( ab, ac ) );
679 glm::vec3 bN = glm::normalize( glm::cross( bc, ba ) );
680 glm::vec3 cN = glm::normalize( glm::cross( ca, cb ) );
682 _vertices[ _uniqueIndex ] = a;
683 _normals[ _uniqueIndex ] = aN;
684 if( faceHasTextureCoordinates && _hasVertexTexCoords ) { _texCoords[ _uniqueIndex ] = texCoordsTemp[ aI ]; }
685 _indices[ _numIndices++ ] = _uniqueIndex++;
689 _vertices[ _uniqueIndex ] = b;
690 _normals[ _uniqueIndex ] = bN;
691 if( faceHasTextureCoordinates && _hasVertexTexCoords ) { _texCoords[ _uniqueIndex ] = texCoordsTemp[ bI ]; }
692 _indices[ _numIndices++ ] = _uniqueIndex++;
696 _vertices[ _uniqueIndex ] = c;
697 _normals[ _uniqueIndex ] = cN;
698 if( faceHasTextureCoordinates && _hasVertexTexCoords ) { _texCoords[ _uniqueIndex ] = texCoordsTemp[ cI ]; }
699 _indices[ _numIndices++ ] = _uniqueIndex++;
708 if( progressCounter % 5000 == 0 ) {
710 switch( progressCounter ) {
711 case 5000: printf(
"[.obj]: parsing %s...\\", _filename.c_str());
break;
712 case 10000: printf(
"[.obj]: parsing %s...|", _filename.c_str());
break;
713 case 15000: printf(
"[.obj]: parsing %s.../", _filename.c_str());
break;
714 case 20000: printf(
"[.obj]: parsing %s...-", _filename.c_str());
break;
719 if( progressCounter == 20000 )
727 printf(
"\33[2K\r" );
728 printf(
"[.obj]: parsing %s...done!\n", _filename.c_str() );
731 _materialIndexStartStop.find( currentMaterial )->second.back().second = indicesSeen - 1;
736 double seconds = difftime( end, start );
739 printf(
"[.obj]: Completed in %.3fs\n", seconds );
740 printf(
"[.obj]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=- \n\n", _filename.c_str() );
746inline bool CSCI441::ModelLoader::_loadMTLFile(
const char* mtlFilename,
bool INFO,
bool ERRORS ) {
749 if (INFO) printf(
"[.mtl]: -*-*-*-*-*-*-*- BEGIN %s Info -*-*-*-*-*-*-*-\n", mtlFilename );
753 if( _filename.find(
'/') != std::string::npos ) {
754 path = _filename.substr( 0, _filename.find_last_of(
'/')+1 );
760 in.open( mtlFilename );
761 if( !in.is_open() ) {
762 std::string folderMtlFile = path + mtlFilename;
763 in.open( folderMtlFile.c_str() );
764 if( !in.is_open() ) {
765 if (ERRORS) fprintf( stderr,
"[.mtl]: [ERROR]: could not open material file: %s\n", mtlFilename );
766 if ( INFO ) printf(
"[.mtl]: -*-*-*-*-*-*-*- END %s Info -*-*-*-*-*-*-*-\n", mtlFilename );
771 CSCI441_INTERNAL::ModelMaterial* currentMaterial =
nullptr;
772 std::string materialName;
774 unsigned char *textureData =
nullptr;
775 unsigned char *maskData =
nullptr;
776 unsigned char *fullData;
777 int texWidth, texHeight, textureChannels = 1, maskChannels = 1;
778 GLuint textureHandle = 0;
780 std::map< std::string, GLuint > imageHandles;
782 int numMaterials = 0;
784 while( getline( in, line ) ) {
785 if( line.length() > 1 && line.at(0) ==
'\t' )
786 line = line.substr( 1 );
787 line.erase( line.find_last_not_of(
" \n\r\t" ) + 1 );
789 std::vector< std::string > tokens = _tokenizeString( line,
" /" );
790 if( tokens.empty() )
continue;
793 if( tokens[0] ==
"#" ) {
795 }
else if( tokens[0] ==
"newmtl" ) {
796 if (INFO) printf(
"[.mtl]: Parsing material %s properties\n", tokens[1].c_str() );
797 currentMaterial =
new CSCI441_INTERNAL::ModelMaterial();
798 materialName = tokens[1];
799 _materials.insert( std::pair<std::string, CSCI441_INTERNAL::ModelMaterial*>( materialName, currentMaterial ) );
802 textureData =
nullptr;
808 }
else if( tokens[0] ==
"Ka" ) {
809 currentMaterial->ambient[0] = strtof( tokens[1].c_str(),
nullptr );
810 currentMaterial->ambient[1] = strtof( tokens[2].c_str(),
nullptr );
811 currentMaterial->ambient[2] = strtof( tokens[3].c_str(),
nullptr );
812 }
else if( tokens[0] ==
"Kd" ) {
813 currentMaterial->diffuse[0] = strtof( tokens[1].c_str(),
nullptr );
814 currentMaterial->diffuse[1] = strtof( tokens[2].c_str(),
nullptr );
815 currentMaterial->diffuse[2] = strtof( tokens[3].c_str(),
nullptr );
816 }
else if( tokens[0] ==
"Ks" ) {
817 currentMaterial->specular[0] = strtof( tokens[1].c_str(),
nullptr );
818 currentMaterial->specular[1] = strtof( tokens[2].c_str(),
nullptr );
819 currentMaterial->specular[2] = strtof( tokens[3].c_str(),
nullptr );
820 }
else if( tokens[0] ==
"Ke" ) {
821 currentMaterial->emissive[0] = strtof( tokens[1].c_str(),
nullptr );
822 currentMaterial->emissive[1] = strtof( tokens[2].c_str(),
nullptr );
823 currentMaterial->emissive[2] = strtof( tokens[3].c_str(),
nullptr );
824 }
else if( tokens[0] ==
"Ns" ) {
825 currentMaterial->shininess = strtof( tokens[1].c_str(),
nullptr );
826 }
else if( tokens[0] ==
"Tr"
827 || tokens[0] ==
"d" ) {
828 currentMaterial->ambient[3] = strtof( tokens[1].c_str(),
nullptr );
829 currentMaterial->diffuse[3] = strtof( tokens[1].c_str(),
nullptr );
830 currentMaterial->specular[3] = strtof( tokens[1].c_str(),
nullptr );
831 }
else if( tokens[0] ==
"illum" ) {
833 }
else if( tokens[0] ==
"map_Kd" ) {
834 if( imageHandles.find( tokens[1] ) != imageHandles.end() ) {
836 currentMaterial->map_Kd = imageHandles.find( tokens[1] )->second;
838 stbi_set_flip_vertically_on_load(
true);
839 textureData = stbi_load( tokens[1].c_str(), &texWidth, &texHeight, &textureChannels, 0 );
841 std::string folderName = path + tokens[1];
842 textureData = stbi_load( folderName.c_str(), &texWidth, &texHeight, &textureChannels, 0 );
846 if (ERRORS) fprintf( stderr,
"[.mtl]: [ERROR]: File Not Found: %s\n", tokens[1].c_str() );
848 if (INFO) printf(
"[.mtl]: TextureMap:\t%s\tSize: %dx%d\tColors: %d\n", tokens[1].c_str(), texWidth, texHeight, textureChannels );
850 if( maskData ==
nullptr ) {
851 if( textureHandle == 0 ) {
852 glGenTextures( 1, &textureHandle );
853 imageHandles.insert( std::pair<std::string, GLuint>( tokens[1], textureHandle ) );
856 glBindTexture( GL_TEXTURE_2D, textureHandle );
858 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
859 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
861 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
862 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
864 GLint colorSpace = GL_RGB;
865 if( textureChannels == 4 )
866 colorSpace = GL_RGBA;
867 glTexImage2D( GL_TEXTURE_2D, 0, colorSpace, texWidth, texHeight, 0, colorSpace, GL_UNSIGNED_BYTE, textureData );
869 currentMaterial->map_Kd = textureHandle;
871 fullData = CSCI441_INTERNAL::createTransparentTexture( textureData, maskData, texWidth, texHeight, textureChannels, maskChannels );
873 if( textureHandle == 0 ) {
874 glGenTextures( 1, &textureHandle );
875 imageHandles.insert( std::pair<std::string, GLuint>( tokens[1], textureHandle ) );
878 glBindTexture( GL_TEXTURE_2D, textureHandle );
880 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
881 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
883 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
884 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
886 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, texWidth, texHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, fullData );
890 currentMaterial->map_Kd = textureHandle;
894 }
else if( tokens[0] ==
"map_d" ) {
895 if( imageHandles.find( tokens[1] ) != imageHandles.end() ) {
897 currentMaterial->map_d = imageHandles.find( tokens[1] )->second;
899 stbi_set_flip_vertically_on_load(
true);
900 maskData = stbi_load( tokens[1].c_str(), &texWidth, &texHeight, &textureChannels, 0 );
902 std::string folderName = path + tokens[1];
903 maskData = stbi_load( folderName.c_str(), &texWidth, &texHeight, &textureChannels, 0 );
907 if (ERRORS) fprintf( stderr,
"[.mtl]: [ERROR]: File Not Found: %s\n", tokens[1].c_str() );
909 if (INFO) printf(
"[.mtl]: AlphaMap: \t%s\tSize: %dx%d\tColors: %d\n", tokens[1].c_str(), texWidth, texHeight, maskChannels );
911 if( textureData !=
nullptr ) {
912 fullData = CSCI441_INTERNAL::createTransparentTexture( textureData, maskData, texWidth, texHeight, textureChannels, maskChannels );
914 if( textureHandle == 0 ) {
915 glGenTextures( 1, &textureHandle );
916 imageHandles.insert( std::pair<std::string, GLuint>( tokens[1], textureHandle ) );
919 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
920 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
922 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
923 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
925 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, texWidth, texHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, fullData );
929 currentMaterial->map_Kd = textureHandle;
933 }
else if( tokens[0] ==
"map_Ka" ) {
935 }
else if( tokens[0] ==
"map_Ks" ) {
937 }
else if( tokens[0] ==
"map_Ns" ) {
939 }
else if( tokens[0] ==
"Ni" ) {
941 }
else if( tokens[0] ==
"Tf" ) {
943 }
else if( tokens[0] ==
"bump"
944 || tokens[0] ==
"map_bump" ) {
947 if (INFO) printf(
"[.mtl]: ignoring line: %s\n", line.c_str() );
954 printf(
"[.mtl]: Materials:\t%d\n", numMaterials );
955 printf(
"[.mtl]: -*-*-*-*-*-*-*- END %s Info -*-*-*-*-*-*-*-\n", mtlFilename );
961inline bool CSCI441::ModelLoader::_loadOFFFile(
bool INFO,
bool ERRORS ) {
964 if (INFO ) printf(
"[.off]: -=-=-=-=-=-=-=- BEGIN %s Info -=-=-=-=-=-=-=-\n", _filename.c_str() );
969 std::ifstream in( _filename );
970 if( !in.is_open() ) {
971 if (ERRORS) fprintf( stderr,
"[.off]: [ERROR]: Could not open \"%s\"\n", _filename.c_str() );
972 if ( INFO ) printf(
"[.off]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
976 GLuint numVertices = 0, numFaces = 0, numTriangles = 0;
977 GLfloat minX = 999999.0f, maxX = -999999.0f, minY = 999999.0f, maxY = -999999.0f, minZ = 999999.0f, maxZ = -999999.0f;
980 enum OFF_FILE_STATE { HEADER, VERTICES, FACES, DONE };
982 OFF_FILE_STATE fileState = HEADER;
984 GLuint vSeen = 0, fSeen = 0;
986 while( getline( in, line ) ) {
987 if( line.length() > 1 && line.at(0) ==
'\t' )
988 line = line.substr( 1 );
989 line.erase( line.find_last_not_of(
" \n\r\t" ) + 1 );
991 std::vector< std::string > tokens = _tokenizeString( line,
" \t" );
992 if( tokens.empty() )
continue;
995 if( tokens[0] ==
"#" || tokens[0].find_first_of(
'#') == 0 ) {
996 }
else if( fileState == HEADER ) {
997 if( tokens[0] ==
"OFF" ) {
999 if( tokens.size() != 3 ) {
1000 if (ERRORS) fprintf( stderr,
"[.off]: [ERROR]: Malformed OFF file. # vertices, faces, edges not properly specified\n" );
1001 if ( INFO ) printf(
"[.off]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1006 numVertices = (GLuint)strtol( tokens[0].c_str(),
nullptr, 10 );
1007 numFaces = (GLuint)strtol( tokens[1].c_str(),
nullptr, 10 );
1012 fileState = VERTICES;
1014 }
else if( fileState == VERTICES ) {
1016 GLfloat x = strtof( tokens[0].c_str(),
nullptr ),
1017 y = strtof( tokens[1].c_str(),
nullptr ),
1018 z = strtof( tokens[2].c_str(),
nullptr );
1020 if( x < minX ) minX = x;
1021 if( x > maxX ) maxX = x;
1022 if( y < minY ) minY = y;
1023 if( y > maxY ) maxY = y;
1024 if( z < minZ ) minZ = z;
1025 if( z > maxZ ) maxZ = z;
1028 if( vSeen == numVertices )
1030 }
else if( fileState == FACES ) {
1031 auto numberOfVerticesInFace = (GLuint)strtol( tokens[0].c_str(),
nullptr, 10 );
1033 numTriangles += numberOfVerticesInFace - 3 + 1;
1035 if( fSeen == numFaces )
1038 if (INFO) printf(
"[.off]: unknown file state: %d\n", fileState );
1044 printf(
"\33[2K\r" );
1045 printf(
"[.off]: scanning %s...done!\n", _filename.c_str() );
1046 printf(
"[.off]: ------------\n" );
1047 printf(
"[.off]: Model Stats:\n" );
1048 printf(
"[.off]: Vertices: \t%u\tNormals: \t%u\tTex Coords:\t%u\n", numVertices, 0, 0 );
1049 printf(
"[.off]: Faces: \t%u\tTriangles: \t%u\n", numFaces, numTriangles );
1050 printf(
"[.off]: Dimensions:\t(%f, %f, %f)\n", (maxX - minX), (maxY - minY), (maxZ - minZ) );
1053 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
1054 if (INFO && !_hasVertexNormals)
1055 printf(
"[.off]: [WARN]: No vertex normals exist on model. To autogenerate vertex\n\tnormals, call CSCI441::ModelLoader::enableAutoGenerateNormals()\n\tprior to loading the model file.\n" );
1056 _allocateAttributeArrays(numVertices, numTriangles*3);
1058 if (INFO) printf(
"[.off]: No vertex normals exist on model, vertex normals will be autogenerated\n" );
1059 _allocateAttributeArrays(numTriangles*3, numTriangles*3);
1062 std::vector<glm::vec3> verticesTemp;
1064 if (INFO) printf(
"[.off]: ------------\n" );
1066 in.open( _filename );
1074 int progressCounter = 0;
1076 while( getline( in, line ) ) {
1077 if( line.length() > 1 && line.at(0) ==
'\t' )
1078 line = line.substr( 1 );
1079 line.erase( line.find_last_not_of(
" \n\r\t" ) + 1 );
1081 std::vector< std::string > tokens = _tokenizeString( line,
" \t" );
1082 if( tokens.empty() )
continue;
1085 if( tokens[0] ==
"#" || tokens[0].find_first_of(
'#') == 0 ) {
1087 }
else if( fileState == HEADER ) {
1088 if( tokens[0] ==
"OFF" ) {
1092 fileState = VERTICES;
1094 }
else if( fileState == VERTICES ) {
1096 glm::vec3 pos(strtof( tokens[0].c_str(),
nullptr ),
1097 strtof( tokens[1].c_str(),
nullptr ),
1098 strtof( tokens[2].c_str(),
nullptr ) );
1101 if( tokens.size() == 6 || tokens.size() == 7 ) {
1109 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
1110 _vertices[ _uniqueIndex ] = pos;
1113 verticesTemp.push_back(pos);
1117 if( _uniqueIndex == numVertices || vSeen == numVertices )
1119 }
else if( fileState == FACES ) {
1120 auto numberOfVerticesInFace = (GLuint)strtol( tokens[0].c_str(),
nullptr, 10 );
1123 for(GLuint i = 2; i <= numberOfVerticesInFace - 1; i++) {
1124 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
1125 auto fanRoot = strtol( tokens[1].c_str(),
nullptr, 10 );
1126 auto fanA = strtol( tokens[i].c_str(),
nullptr, 10 );
1127 auto fanB = strtol( tokens[i+1].c_str(),
nullptr, 10 );
1129 if( fanRoot < 0 ) fanRoot = numVertices + fanRoot + 1;
1130 if( fanA < 0 ) fanA = numVertices + fanA + 1;
1131 if( fanB < 0 ) fanB = numVertices + fanB + 1;
1134 _indices[ _numIndices++ ] = fanRoot;
1135 _indices[ _numIndices++ ] = fanA;
1136 _indices[ _numIndices++ ] = fanB;
1138 auto aI = strtol( tokens[1].c_str(),
nullptr, 10 );
1139 auto bI = strtol( tokens[i].c_str(),
nullptr, 10 );
1140 auto cI = strtol( tokens[i+1].c_str(),
nullptr, 10 );
1142 if( aI < 0 ) aI = numVertices + aI + 1;
1143 if( bI < 0 ) bI = numVertices + bI + 1;
1144 if( cI < 0 ) cI = numVertices + cI + 1;
1146 glm::vec3 a = verticesTemp[aI];
1147 glm::vec3 b = verticesTemp[bI];
1148 glm::vec3 c = verticesTemp[cI];
1150 glm::vec3 ab = b - a; glm::vec3 ac = c - a;
1151 glm::vec3 ba = a - b; glm::vec3 bc = c - b;
1152 glm::vec3 ca = a - c; glm::vec3 cb = b - c;
1154 glm::vec3 aN = glm::normalize( glm::cross( ab, ac ) );
1155 glm::vec3 bN = glm::normalize( glm::cross( bc, ba ) );
1156 glm::vec3 cN = glm::normalize( glm::cross( ca, cb ) );
1158 _vertices[ _uniqueIndex ] = a;
1159 _normals[ _uniqueIndex ] = aN;
1160 _indices[ _numIndices++ ] = _uniqueIndex++;
1162 _vertices[ _uniqueIndex ] = b;
1163 _normals[ _uniqueIndex ] = bN;
1164 _indices[ _numIndices++ ] = _uniqueIndex++;
1166 _vertices[ _uniqueIndex ] = c;
1167 _normals[ _uniqueIndex ] = cN;
1168 _indices[ _numIndices++ ] = _uniqueIndex++;
1193 if( progressCounter % 5000 == 0 ) {
1195 switch( progressCounter ) {
1196 case 5000: printf(
"[.off]: parsing %s...\\", _filename.c_str());
break;
1197 case 10000: printf(
"[.off]: parsing %s...|", _filename.c_str());
break;
1198 case 15000: printf(
"[.off]: parsing %s.../", _filename.c_str());
break;
1199 case 20000: printf(
"[.off]: parsing %s...-", _filename.c_str());
break;
1204 if( progressCounter == 20000 )
1205 progressCounter = 0;
1213 double seconds = difftime( end, start );
1216 printf(
"\33[2K\r" );
1217 printf(
"[.off]: parsing %s...done! (Time: %.1fs)\n", _filename.c_str(), seconds );
1218 printf(
"[.off]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1225inline bool CSCI441::ModelLoader::_loadPLYFile(
bool INFO,
bool ERRORS ) {
1228 if (INFO ) printf(
"[.ply]: -=-=-=-=-=-=-=- BEGIN %s Info -=-=-=-=-=-=-=-\n", _filename.c_str() );
1233 std::ifstream in( _filename );
1234 if( !in.is_open() ) {
1235 if (ERRORS) fprintf( stderr,
"[.ply]: [ERROR]: Could not open \"%s\"\n", _filename.c_str() );
1236 if ( INFO ) printf(
"[.ply]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1240 GLuint numVertices = 0, numFaces = 0, numTriangles = 0, numMaterials = 0;
1241 GLfloat minX = 999999.0f, maxX = -999999.0f, minY = 999999.0f, maxY = -999999.0f, minZ = 999999.0f, maxZ = -999999.0f;
1244 enum PLY_FILE_STATE { HEADER, VERTICES, FACES, MATERIALS };
1245 enum PLY_ELEMENT_TYPE { NONE, VERTEX, FACE, MATERIAL };
1247 PLY_FILE_STATE fileState = HEADER;
1248 PLY_ELEMENT_TYPE elemType = NONE;
1250 GLuint progressCounter = 0;
1253 while( getline( in, line ) ) {
1254 if( line.length() > 1 && line.at(0) ==
'\t' )
1255 line = line.substr( 1 );
1256 line.erase( line.find_last_not_of(
" \n\r\t" ) + 1 );
1258 std::vector< std::string > tokens = _tokenizeString( line,
" \t" );
1260 if( tokens.empty() )
continue;
1263 if( tokens[0] ==
"comment" ) {
1264 }
else if( fileState == HEADER ) {
1265 if( tokens[0] ==
"ply" ) {
1266 }
else if( tokens[0] ==
"format" ) {
1267 if( tokens[1] !=
"ascii" ) {
1268 if (ERRORS) fprintf( stderr,
"[.ply]: [ERROR]: File \"%s\" not ASCII format\n", _filename.c_str() );
1269 if ( INFO ) printf(
"[.ply]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1273 }
else if( tokens[0] ==
"element" ) {
1274 if( tokens[1] ==
"vertex" ) {
1275 numVertices = (GLuint)strtol( tokens[2].c_str(),
nullptr, 10 );
1277 }
else if( tokens[1] ==
"face" ) {
1278 numFaces = (GLuint)strtol( tokens[2].c_str(),
nullptr, 10 );
1280 }
else if( tokens[1] ==
"edge" ) {
1282 }
else if( tokens[1] ==
"material" ) {
1283 numMaterials = (GLuint)strtol( tokens[2].c_str(),
nullptr, 10 );
1284 elemType = MATERIAL;
1288 }
else if( tokens[0] ==
"property" ) {
1289 if( elemType == VERTEX ) {
1291 }
else if( elemType == FACE ) {
1293 }
else if( elemType == MATERIAL ) {
1296 }
else if( tokens[0] ==
"end_header" ) {
1297 fileState = VERTICES;
1299 }
else if( fileState == VERTICES ) {
1301 auto x = (GLfloat) strtof( tokens[0].c_str(), nullptr ),
1302 y = (GLfloat) strtof( tokens[1].c_str(),
nullptr ),
1303 z = (GLfloat) strtof( tokens[2].c_str(), nullptr );
1305 if( x < minX ) minX = x;
1306 if( x > maxX ) maxX = x;
1307 if( y < minY ) minY = y;
1308 if( y > maxY ) maxY = y;
1309 if( z < minZ ) minZ = z;
1310 if( z > maxZ ) maxZ = z;
1314 if( vSeen == numVertices )
1316 }
else if( fileState == FACES ) {
1317 auto numberOfVerticesInFace = (GLuint)strtol( tokens[0].c_str(),
nullptr, 10);
1318 numTriangles += numberOfVerticesInFace - 3 + 1;
1320 if (INFO) printf(
"[.ply]: unknown file state: %d\n", fileState );
1325 if( progressCounter % 5000 == 0 ) {
1327 switch( progressCounter ) {
1328 case 5000: printf(
"[.ply]: scanning %s...\\", _filename.c_str());
break;
1329 case 10000: printf(
"[.ply]: scanning %s...|", _filename.c_str());
break;
1330 case 15000: printf(
"[.ply]: scanning %s.../", _filename.c_str());
break;
1331 case 20000: printf(
"[.ply]: scanning %s...-", _filename.c_str());
break;
1336 if( progressCounter == 20000 )
1337 progressCounter = 0;
1343 printf(
"\33[2K\r" );
1344 printf(
"[.ply]: scanning %s...done!\n", _filename.c_str() );
1345 printf(
"[.ply]: ------------\n" );
1346 printf(
"[.ply]: Model Stats:\n" );
1347 printf(
"[.ply]: Vertices: \t%u\tNormals: \t%u\tTex Coords:\t%u\n", numVertices, 0, 0 );
1348 printf(
"[.ply]: Faces: \t%u\tTriangles: \t%u\n", numFaces, numTriangles );
1349 printf(
"[.ply]: Dimensions:\t(%f, %f, %f)\n", (maxX - minX), (maxY - minY), (maxZ - minZ) );
1352 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
1353 if (INFO && !_hasVertexNormals)
1354 printf(
"[.ply]: [WARN]: No vertex normals exist on model. To autogenerate vertex\n\tnormals, call CSCI441::ModelLoader::enableAutoGenerateNormals()\n\tprior to loading the model file.\n" );
1355 _allocateAttributeArrays(numVertices, numTriangles*3);
1357 if (INFO) printf(
"[.ply]: No vertex normals exist on model, vertex normals will be autogenerated\n" );
1358 _allocateAttributeArrays(numTriangles*3, numTriangles*3);
1361 if (INFO) printf(
"[.ply]: ------------\n" );
1363 std::vector<glm::vec3> verticesTemp;
1365 in.open( _filename );
1373 progressCounter = 0;
1376 while( getline( in, line ) ) {
1377 if( line.length() > 1 && line.at(0) ==
'\t' )
1378 line = line.substr( 1 );
1379 line.erase( line.find_last_not_of(
" \n\r\t" ) + 1 );
1381 std::vector< std::string > tokens = _tokenizeString( line,
" \t" );
1383 if( tokens.empty() )
continue;
1386 if( tokens[0] ==
"comment" ) {
1387 }
else if( fileState == HEADER ) {
1388 if( tokens[0] ==
"ply" ) {
1389 }
else if( tokens[0] ==
"format" ) {
1390 if( tokens[1] !=
"ascii" ) {
1391 if (ERRORS) fprintf( stderr,
"[.ply]: [ERROR]: File \"%s\" not ASCII format\n", _filename.c_str() );
1392 if ( INFO ) printf(
"[.ply]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1396 }
else if( tokens[0] ==
"element" ) {
1397 if( tokens[1] ==
"vertex" ) {
1398 numVertices = (GLuint)strtol( tokens[2].c_str(),
nullptr, 10 );
1400 }
else if( tokens[1] ==
"face" ) {
1401 numFaces = (GLuint)strtol( tokens[2].c_str(),
nullptr, 10 );
1403 }
else if( tokens[1] ==
"edge" ) {
1405 }
else if( tokens[1] ==
"material" ) {
1406 numMaterials = (GLuint)strtol( tokens[2].c_str(),
nullptr, 10 );
1407 elemType = MATERIAL;
1411 }
else if( tokens[0] ==
"property" ) {
1412 if( elemType == VERTEX ) {
1414 }
else if( elemType == FACE ) {
1416 }
else if( elemType == MATERIAL ) {
1419 }
else if( tokens[0] ==
"end_header" ) {
1420 fileState = VERTICES;
1422 }
else if( fileState == VERTICES ) {
1424 glm::vec3 pos(strtof( tokens[0].c_str(),
nullptr ),
1425 strtof( tokens[1].c_str(),
nullptr ),
1426 strtof( tokens[2].c_str(),
nullptr ) );
1428 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
1429 _vertices[ _uniqueIndex ] = pos;
1432 verticesTemp.push_back(pos );
1436 if( _uniqueIndex == numVertices || vSeen == numVertices )
1438 }
else if( fileState == FACES ) {
1439 auto numberOfVerticesInFace = (GLuint)strtol( tokens[0].c_str(),
nullptr, 10 );
1441 for( GLuint i = 2; i <= numberOfVerticesInFace - 1; i++ ) {
1442 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
1443 _indices[ _numIndices++ ] = (GLuint)strtol( tokens[1].c_str(),
nullptr, 10 );
1444 _indices[ _numIndices++ ] = (GLuint)strtol( tokens[i].c_str(),
nullptr, 10 );
1445 _indices[ _numIndices++ ] = (GLuint)strtol( tokens[i+1].c_str(),
nullptr, 10 );
1447 auto aI = (GLuint)strtol( tokens[1].c_str(),
nullptr, 10 );
1448 auto bI = (GLuint)strtol( tokens[i].c_str(),
nullptr, 10 );
1449 auto cI = (GLuint)strtol( tokens[i+1].c_str(),
nullptr, 10 );
1451 glm::vec3 a = verticesTemp[aI];
1452 glm::vec3 b = verticesTemp[bI];
1453 glm::vec3 c = verticesTemp[cI];
1455 glm::vec3 ab = b - a; glm::vec3 ac = c - a;
1456 glm::vec3 ba = a - b; glm::vec3 bc = c - b;
1457 glm::vec3 ca = a - c; glm::vec3 cb = b - c;
1459 glm::vec3 aN = glm::normalize( glm::cross( ab, ac ) );
1460 glm::vec3 bN = glm::normalize( glm::cross( bc, ba ) );
1461 glm::vec3 cN = glm::normalize( glm::cross( ca, cb ) );
1463 _vertices[ _uniqueIndex ] = a;
1464 _normals[ _uniqueIndex ] = aN;
1465 _indices[ _numIndices++ ] = _uniqueIndex++;
1467 _vertices[ _uniqueIndex ] = b;
1468 _normals[ _uniqueIndex ] = bN;
1469 _indices[ _numIndices++ ] = _uniqueIndex++;
1471 _vertices[ _uniqueIndex ] = c;
1472 _normals[ _uniqueIndex ] = cN;
1473 _indices[ _numIndices++ ] = _uniqueIndex++;
1477 if (INFO) printf(
"[.ply]: unknown file state: %d\n", fileState );
1482 if( progressCounter % 5000 == 0 ) {
1484 switch( progressCounter ) {
1485 case 5000: printf(
"[.ply]: parsing %s...\\", _filename.c_str());
break;
1486 case 10000: printf(
"[.ply]: parsing %s...|", _filename.c_str());
break;
1487 case 15000: printf(
"[.ply]: parsing %s.../", _filename.c_str());
break;
1488 case 20000: printf(
"[.ply]: parsing %s...-", _filename.c_str());
break;
1493 if( progressCounter == 20000 )
1494 progressCounter = 0;
1502 double seconds = difftime( end, start );
1505 printf(
"\33[2K\r" );
1506 printf(
"[.ply]: parsing %s...done!\n[.ply]: Time to complete: %.3fs\n", _filename.c_str(), seconds );
1507 printf(
"[.ply]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1513inline bool CSCI441::ModelLoader::_loadSTLFile(
bool INFO,
bool ERRORS ) {
1516 if (INFO) printf(
"[.stl]: -=-=-=-=-=-=-=- BEGIN %s Info -=-=-=-=-=-=-=-\n", _filename.c_str() );
1521 std::ifstream in( _filename );
1522 if( !in.is_open() ) {
1523 if (ERRORS) fprintf(stderr,
"[.stl]: [ERROR]: Could not open \"%s\"\n", _filename.c_str() );
1524 if ( INFO ) printf(
"[.stl]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1528 GLuint numVertices = 0, numNormals = 0, numFaces = 0, numTriangles = 0, numVertsInLoop = 0;
1529 GLfloat minX = 999999.0f, maxX = -999999.0f, minY = 999999.0f, maxY = -999999.0f, minZ = 999999.0f, maxZ = -999999.0f;
1532 int progressCounter = 0;
1533 glm::vec3 normalVector = {0.f,0.f,0.f};
1535 while( getline( in, line ) ) {
1536 if( line.length() > 1 && line.at(0) ==
'\t' )
1537 line = line.substr( 1 );
1538 line.erase( line.find_last_not_of(
" \n\r\t" ) + 1 );
1540 std::vector< std::string > tokens = _tokenizeString( line,
" \t" );
1542 if( tokens.empty() )
continue;
1544 if( tokens[0] ==
"solid" ) {
1546 }
else if( tokens[0] ==
"facet" ) {
1549 }
else if( tokens[0] ==
"outer" && tokens[1] ==
"loop" ) {
1552 }
else if( tokens[0] ==
"vertex" ) {
1553 GLfloat x = strtof( tokens[1].c_str(),
nullptr ),
1554 y = strtof( tokens[2].c_str(),
nullptr ),
1555 z = strtof( tokens[3].c_str(),
nullptr );
1557 if( x < minX ) minX = x;
1558 if( x > maxX ) maxX = x;
1559 if( y < minY ) minY = y;
1560 if( y > maxY ) maxY = y;
1561 if( z < minZ ) minZ = z;
1562 if( z > maxZ ) maxZ = z;
1566 }
else if( tokens[0] ==
"endloop" ) {
1568 numTriangles += numVertsInLoop - 3 + 1;
1569 }
else if( tokens[0] ==
"endfacet" ) {
1571 }
else if( tokens[0] ==
"endsolid" ) {
1575 if( memchr( line.c_str(),
'\0', line.length() ) !=
nullptr ) {
1576 if (ERRORS) fprintf( stderr,
"[.stl]: [ERROR]: Cannot read binary STL file \"%s\"\n", _filename.c_str() );
1577 if ( INFO ) printf(
"[.stl]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1580 }
else if (INFO) printf(
"[.stl]: unknown line: %s\n", line.c_str() );
1585 if( progressCounter % 5000 == 0 ) {
1587 switch( progressCounter ) {
1588 case 5000: printf(
"[.stl]: scanning %s...\\", _filename.c_str());
break;
1589 case 10000: printf(
"[.stl]: scanning %s...|", _filename.c_str());
break;
1590 case 15000: printf(
"[.stl]: scanning %s.../", _filename.c_str());
break;
1591 case 20000: printf(
"[.stl]: scanning %s...-", _filename.c_str());
break;
1596 if( progressCounter == 20000 )
1597 progressCounter = 0;
1603 printf(
"\33[2K\r" );
1604 printf(
"[.stl]: scanning %s...done!\n", _filename.c_str() );
1605 printf(
"[.stl]: ------------\n" );
1606 printf(
"[.stl]: Model Stats:\n" );
1607 printf(
"[.stl]: Vertices: \t%u\tNormals: \t%u\tTex Coords:\t%u\n", numVertices, numNormals, 0 );
1608 printf(
"[.stl]: Faces: \t%u\tTriangles: \t%u\n", numFaces, numTriangles );
1609 printf(
"[.stl]: Dimensions:\t(%f, %f, %f)\n", (maxX - minX), (maxY - minY), (maxZ - minZ) );
1612 _allocateAttributeArrays(numVertices, numTriangles*3);
1614 if (INFO) printf(
"[.stl]: ------------\n" );
1616 in.open( _filename );
1621 while( getline( in, line ) ) {
1622 if( line.length() > 1 && line.at(0) ==
'\t' )
1623 line = line.substr( 1 );
1624 line.erase( line.find_last_not_of(
" \n\r\t" ) + 1 );
1626 std::vector< std::string > tokens = _tokenizeString( line,
" \t" );
1628 if( tokens.empty() )
continue;
1630 if( tokens[0] ==
"solid" ) {
1632 }
else if( tokens[0] ==
"facet" ) {
1634 normalVector = glm::vec3( strtof( tokens[2].c_str(),
nullptr ),
1635 strtof( tokens[3].c_str(),
nullptr ),
1636 strtof( tokens[4].c_str(),
nullptr ) );
1637 }
else if( tokens[0] ==
"outer" && tokens[1] ==
"loop" ) {
1639 }
else if( tokens[0] ==
"vertex" ) {
1640 _vertices[ _uniqueIndex ] = glm::vec3(strtof( tokens[1].c_str(),
nullptr ),
1641 strtof( tokens[2].c_str(),
nullptr ),
1642 strtof( tokens[3].c_str(),
nullptr ) );
1643 _normals[ _uniqueIndex ] = normalVector;
1644 _indices[ _numIndices++ ] = _uniqueIndex++;
1645 }
else if( tokens[0] ==
"endloop" ) {
1647 }
else if( tokens[0] ==
"endfacet" ) {
1649 }
else if( tokens[0] ==
"endsolid" ) {
1658 if( progressCounter % 5000 == 0 ) {
1660 switch( progressCounter ) {
1661 case 5000: printf(
"[.stl]: parsing %s...\\", _filename.c_str());
break;
1662 case 10000: printf(
"[.stl]: parsing %s...|", _filename.c_str());
break;
1663 case 15000: printf(
"[.stl]: parsing %s.../", _filename.c_str());
break;
1664 case 20000: printf(
"[.stl]: parsing %s...-", _filename.c_str());
break;
1669 if( progressCounter == 20000 )
1670 progressCounter = 0;
1678 double seconds = difftime( end, start );
1682 printf(
"[.stl]: parsing %s...done!\n[.stl]: Time to complete: %.3fs\n", _filename.c_str(), seconds);
1683 printf(
"[.stl]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1691 sAUTO_GEN_NORMALS =
true;
1696 sAUTO_GEN_NORMALS =
false;
1699inline void CSCI441::ModelLoader::_allocateAttributeArrays(
const GLuint numVertices,
const GLuint numIndices) {
1700 _vertices =
new glm::vec3[numVertices];
1701 _normals =
new glm::vec3[numVertices];
1702 _texCoords =
new glm::vec2[numVertices];
1703 _indices =
new GLuint[numIndices];
1706inline void CSCI441::ModelLoader::_bufferData() {
1707 glBindVertexArray( _vaod );
1709 glBindBuffer( GL_ARRAY_BUFFER, _vbods[0] );
1710 glBufferData( GL_ARRAY_BUFFER,
static_cast<GLsizeiptr
>((
sizeof(glm::vec3)*2 +
sizeof(glm::vec2)) * _uniqueIndex),
nullptr, GL_STATIC_DRAW );
1711 glBufferSubData( GL_ARRAY_BUFFER, 0,
static_cast<GLsizeiptr
>(
sizeof(glm::vec3) * _uniqueIndex), _vertices );
1712 glBufferSubData( GL_ARRAY_BUFFER,
static_cast<GLintptr
>(
sizeof(glm::vec3) * _uniqueIndex),
static_cast<GLsizeiptr
>(
sizeof(glm::vec3) * _uniqueIndex), _normals );
1713 glBufferSubData( GL_ARRAY_BUFFER,
static_cast<GLintptr
>(
sizeof(glm::vec3) * _uniqueIndex * 2),
static_cast<GLsizeiptr
>(
sizeof(glm::vec2) * _uniqueIndex), _texCoords );
1715 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, _vbods[1] );
1716 glBufferData( GL_ELEMENT_ARRAY_BUFFER,
static_cast<GLsizeiptr
>(
sizeof(GLuint) * _numIndices), _indices, GL_STATIC_DRAW );
1725inline std::vector<std::string> CSCI441::ModelLoader::_tokenizeString(std::string input,
const std::string& delimiters) {
1729 std::vector<std::string> retVec = std::vector<std::string>();
1730 size_t oldR = 0, r = 0;
1733 std::string strippedInput;
1734 GLint lowerValidIndex = 0, upperValidIndex = (GLint)input.size() - 1;
1735 while((GLuint)lowerValidIndex < input.size() && delimiters.find_first_of(input.at(lowerValidIndex), 0) != std::string::npos)
1738 while(upperValidIndex >= 0 && delimiters.find_first_of(input.at(upperValidIndex), 0) != std::string::npos)
1742 if((GLuint)lowerValidIndex >= input.size() || upperValidIndex < 0 || lowerValidIndex > upperValidIndex)
1746 strippedInput = input.substr(lowerValidIndex, upperValidIndex-lowerValidIndex+1);
1750 while((r = strippedInput.find_first_of(delimiters, oldR)) != std::string::npos)
1753 retVec.push_back(strippedInput.substr(oldR, r-oldR));
1757 retVec.push_back(strippedInput.substr(oldR, r-oldR));
1762inline unsigned char* CSCI441_INTERNAL::createTransparentTexture(
const unsigned char * imageData,
const unsigned char *imageMask,
int texWidth,
int texHeight,
int texChannels,
int maskChannels ) {
1764 auto *fullData =
new unsigned char[texWidth*texHeight*4];
1766 for(
int j = 0; j < texHeight; j++) {
1767 for(
int i = 0; i < texWidth; i++) {
1769 fullData[(j*texWidth+i)*4+0] = imageData[(j*texWidth+i)*texChannels+0];
1770 fullData[(j*texWidth+i)*4+1] = imageData[(j*texWidth+i)*texChannels+1];
1771 fullData[(j*texWidth+i)*4+2] = imageData[(j*texWidth+i)*texChannels+2];
1773 fullData[(j*texWidth+i)*4+0] = 1;
1774 fullData[(j*texWidth+i)*4+1] = 1;
1775 fullData[(j*texWidth+i)*4+2] = 1;
1779 fullData[(j*texWidth+i)*4+3] = imageMask[(j*texWidth+i)*maskChannels+0];
1781 fullData[(j*texWidth+i)*4+3] = 1;
1789inline void CSCI441_INTERNAL::flipImageY(
int texWidth,
int texHeight,
int textureChannels,
unsigned char *textureData ) {
1790 for(
int j = 0; j < texHeight / 2; j++ ) {
1791 for(
int i = 0; i < texWidth; i++ ) {
1792 for(
int k = 0; k < textureChannels; k++ ) {
1793 int top = (j*texWidth + i)*textureChannels + k;
1794 int bot = ((texHeight-j-1)*texWidth + i)*textureChannels + k;
1796 unsigned char t = textureData[top];
1797 textureData[top] = textureData[bot];
1798 textureData[bot] = t;
Loads object models from file and renders using VBOs/VAOs.
Definition: ModelLoader.hpp:50
ModelLoader(const ModelLoader &)=delete
do not allow models to be copied
static void enableAutoGenerateNormals()
Enable auto-generation of vertex normals.
Definition: ModelLoader.hpp:1690
void setAttributeLocations(GLint positionLocation, GLint normalLocation=-1, GLint texCoordLocation=-1) const
Enables VBO attribute array locations.
Definition: ModelLoader.hpp:265
ModelLoader & operator=(const ModelLoader &)=delete
do not allow models to be copied
GLuint getNumberOfIndices() const
Return the number of indices to draw the model. This value corresponds to the size of the Indices arr...
Definition: ModelLoader.hpp:328
GLfloat * getVertices() const
Return the vertex array that makes up the model mesh.
Definition: ModelLoader.hpp:325
bool loadModelFile(std::string filename, bool INFO=true, bool ERRORS=true)
Loads a model from the given file.
Definition: ModelLoader.hpp:237
GLfloat * getNormals() const
Return the normal array that corresponds to the model mesh.
Definition: ModelLoader.hpp:326
bool draw(GLuint shaderProgramHandle, GLint matDiffLocation=-1, GLint matSpecLocation=-1, GLint matShinLocation=-1, GLint matAmbLocation=-1, GLenum diffuseTexture=GL_TEXTURE0) const
Renders a model.
Definition: ModelLoader.hpp:280
GLuint * getIndices() const
Return the index array that dictates the order to draw the model mesh.
Definition: ModelLoader.hpp:329
GLfloat * getTexCoords() const
Return the texture coordinates array that corresponds to the model mesh.
Definition: ModelLoader.hpp:327
GLuint getNumberOfVertices() const
Return the number of vertices the model is made up of. This value corresponds to the size of the Vert...
Definition: ModelLoader.hpp:324
static void disableAutoGenerateNormals()
Disable auto-generation of vertex normals.
Definition: ModelLoader.hpp:1695
~ModelLoader()
Frees memory associated with model on both CPU and GPU.
Definition: ModelLoader.hpp:211
ModelLoader()
Creates an empty model.
Definition: ModelLoader.hpp:201
Internal material representation for *.mtl files.
CSCI441 Helper Functions for OpenGL.
Definition: ArcballCam.hpp:17