17#ifndef CSCI441_MODEL_LOADER_HPP
18#define CSCI441_MODEL_LOADER_HPP
23#ifdef CSCI441_USE_GLEW
61 [[maybe_unused]]
explicit ModelLoader(
const char* filename );
93 bool loadModelFile( std::
string filename,
bool INFO = true,
bool ERRORS = true );
101 [[maybe_unused]]
void setAttributeLocations(GLint positionLocation, GLint normalLocation = -1, GLint texCoordLocation = -1) const;
113 [[maybe_unused]]
bool draw( GLuint shaderProgramHandle,
114 GLint matDiffLocation = -1, GLint matSpecLocation = -1, GLint matShinLocation = -1, GLint matAmbLocation = -1,
115 GLenum diffuseTexture = GL_TEXTURE0 ) const;
121 [[maybe_unused]] [[nodiscard]] GLuint getNumberOfVertices() const;
127 [[maybe_unused]] [[nodiscard]] GLfloat* getVertices() const;
133 [[maybe_unused]] [[nodiscard]] GLfloat* getNormals() const;
139 [[maybe_unused]] [[nodiscard]] GLfloat* getTexCoords() const;
144 [[maybe_unused]] [[nodiscard]] GLuint getNumberOfIndices() const;
150 [[maybe_unused]] [[nodiscard]] GLuint* getIndices() const;
159 [[maybe_unused]] static
void enableAutoGenerateNormals();
166 [[maybe_unused]] static
void disableAutoGenerateNormals();
170 bool _loadMTLFile( const
char *mtlFilename,
bool INFO,
bool ERRORS );
171 bool _loadOBJFile(
bool INFO,
bool ERRORS );
172 bool _loadOFFFile(
bool INFO,
bool ERRORS );
173 bool _loadPLYFile(
bool INFO,
bool ERRORS );
174 bool _loadSTLFile(
bool INFO,
bool ERRORS );
175 static std::vector<std::
string> _tokenizeString( const std::
string& input, const std::
string& delimiters );
176 void _allocateAttributeArrays(GLuint numVertices, GLuint numIndices);
177 void _bufferData() const;
179 std::
string _filename;
180 CSCI441_INTERNAL::MODEL_TYPE _modelType;
183 GLuint _vbods[2] = {0};
185 glm::vec3* _vertices;
187 glm::vec2* _texCoords;
192 std::map< std::string, CSCI441_INTERNAL::ModelMaterial* > _materials;
193 std::map< std::string, std::vector< std::pair< GLuint, GLuint > > > _materialIndexStartStop;
195 bool _hasVertexTexCoords;
196 bool _hasVertexNormals;
201 static bool sAUTO_GEN_NORMALS;
208namespace CSCI441_INTERNAL {
209 unsigned char* createTransparentTexture(
const unsigned char *imageData,
const unsigned char *imageMask,
int texWidth,
int texHeight,
int texChannels,
int maskChannels );
210 [[maybe_unused]]
void flipImageY(
int texWidth,
int texHeight,
int textureChannels,
unsigned char *textureData );
213inline bool CSCI441::ModelLoader::sAUTO_GEN_NORMALS =
false;
216 _modelType(CSCI441_INTERNAL::MODEL_TYPE::UNKNOWN),
224 _hasVertexTexCoords(false),
225 _hasVertexNormals(false)
232 _modelType(CSCI441_INTERNAL::MODEL_TYPE::UNKNOWN),
240 _hasVertexTexCoords(false),
241 _hasVertexNormals(false)
252 _modelType(CSCI441_INTERNAL::MODEL_TYPE::UNKNOWN),
260 _hasVertexTexCoords(
false),
261 _hasVertexNormals(
false)
274inline void CSCI441::ModelLoader::_init() {
275 glGenVertexArrays( 1, &_vaod );
276 glGenBuffers( 2, _vbods );
281 _filename = std::move(filename);
282 if( _filename.find(
".obj") != std::string::npos ) {
283 result = _loadOBJFile( INFO, ERRORS );
284 _modelType = CSCI441_INTERNAL::MODEL_TYPE::OBJ;
286 else if( _filename.find(
".off") != std::string::npos ) {
287 result = _loadOFFFile( INFO, ERRORS );
288 _modelType = CSCI441_INTERNAL::MODEL_TYPE::OFF;
290 else if( _filename.find(
".ply") != std::string::npos ) {
291 result = _loadPLYFile( INFO, ERRORS );
292 _modelType = CSCI441_INTERNAL::MODEL_TYPE::PLY;
294 else if( _filename.find(
".stl") != std::string::npos ) {
295 result = _loadSTLFile( INFO, ERRORS );
296 _modelType = CSCI441_INTERNAL::MODEL_TYPE::STL;
300 if (ERRORS) fprintf( stderr,
"[ERROR]: Unsupported file format for file: %s\n", _filename.c_str() );
308 glBindVertexArray( _vaod );
309 glBindBuffer( GL_ARRAY_BUFFER, _vbods[0] );
311 glEnableVertexAttribArray( positionLocation );
312 glVertexAttribPointer( positionLocation, 3, GL_FLOAT, GL_FALSE, 0, (
void*)
nullptr );
314 glEnableVertexAttribArray( normalLocation );
315 glVertexAttribPointer( normalLocation, 3, GL_FLOAT, GL_FALSE, 0, (
void*)(
sizeof(glm::vec3) * _uniqueIndex) );
317 glEnableVertexAttribArray( texCoordLocation );
318 glVertexAttribPointer( texCoordLocation, 2, GL_FLOAT, GL_FALSE, 0, (
void*)(
sizeof(glm::vec3) * _uniqueIndex * 2) );
323 const GLint matDiffLocation,
const GLint matSpecLocation,
const GLint matShinLocation,
const GLint matAmbLocation,
324 const GLenum diffuseTexture )
const {
325 glBindVertexArray( _vaod );
328 if( _modelType == CSCI441_INTERNAL::MODEL_TYPE::OBJ ) {
329 for(
const auto & materialIter : _materialIndexStartStop) {
330 auto materialName = materialIter.first;
331 auto indexStartStop = materialIter.second;
333 CSCI441_INTERNAL::ModelMaterial* material =
nullptr;
334 if( _materials.find( materialName ) != _materials.end() )
335 material = _materials.find( materialName )->second;
337 for(
const auto &[start, end] : indexStartStop) {
338 const GLsizei length =
static_cast<GLsizei
>(end - start) + 1;
342 if( material !=
nullptr ) {
343 glProgramUniform4fv( shaderProgramHandle, matAmbLocation, 1, &material->ambient[0] );
344 glProgramUniform4fv( shaderProgramHandle, matDiffLocation, 1, &material->diffuse[0] );
345 glProgramUniform4fv( shaderProgramHandle, matSpecLocation, 1, &material->specular[0] );
346 glProgramUniform1f( shaderProgramHandle, matShinLocation, material->shininess );
348 if( material->map_Kd != -1 ) {
349 glActiveTexture( diffuseTexture );
350 glBindTexture( GL_TEXTURE_2D, material->map_Kd );
354 glDrawElements( GL_TRIANGLES, length, GL_UNSIGNED_INT, (
void*)(
sizeof(GLuint)*start) );
358 glDrawElements( GL_TRIANGLES,
static_cast<GLint
>(_numIndices), GL_UNSIGNED_INT, (
void*)
nullptr );
372inline bool CSCI441::ModelLoader::_loadOBJFile(
const bool INFO,
const bool ERRORS ) {
376 if( _filename.find(
'/') != std::string::npos ) {
377 path = _filename.substr( 0, _filename.find_last_of(
'/')+1 );
382 if ( INFO ) fprintf( stdout,
"[.obj]: -=-=-=-=-=-=-=- BEGIN %s Info -=-=-=-=-=-=-=- \n", _filename.c_str() );
387 std::ifstream in( _filename );
388 if( !in.is_open() ) {
389 if (ERRORS) fprintf( stderr,
"[.obj]: [ERROR]: Could not open \"%s\"\n", _filename.c_str() );
390 if ( INFO ) fprintf( stdout,
"[.obj]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=- \n", _filename.c_str() );
394 GLuint numObjects = 0, numGroups = 0;
395 GLuint numVertices = 0, numTexCoords = 0, numNormals = 0;
396 GLuint numFaces = 0, numTriangles = 0;
397 glm::vec3 minDimension = {999999.f, 999999.f, 999999.f};
398 glm::vec3 maxDimension = { -999999.f, -999999.f, -999999.f };
401 std::map<std::string, GLuint> uniqueCounts;
404 int progressCounter = 0;
406 while( getline( in, line ) ) {
407 if( line.length() > 1 && line.at(0) ==
'\t' )
408 line = line.substr( 1 );
409 line.erase( line.find_last_not_of(
" \n\r\t" ) + 1 );
411 std::vector< std::string > tokens = _tokenizeString( line,
" \t" );
412 if( tokens.empty() )
continue;
415 if( tokens[0] ==
"#" || tokens[0].find_first_of(
'#') == 0 ) {
417 }
else if( tokens[0] ==
"o" ) {
419 }
else if( tokens[0] ==
"g" ) {
421 }
else if( tokens[0] ==
"mtllib" ) {
422 std::string mtlFilename = path + tokens[1];
423 _loadMTLFile( mtlFilename.c_str(), INFO, ERRORS );
424 }
else if( tokens[0] ==
"v" ) {
427 glm::vec3 pos = { strtof( tokens[1].c_str(),
nullptr ),
428 strtof( tokens[2].c_str(),
nullptr ),
429 strtof( tokens[3].c_str(),
nullptr ) };
431 if( pos.x < minDimension.x ) minDimension.x = pos.x;
432 if( pos.x > maxDimension.x ) maxDimension.x = pos.x;
433 if( pos.y < minDimension.y ) minDimension.y = pos.y;
434 if( pos.y > maxDimension.y ) maxDimension.y = pos.y;
435 if( pos.z < minDimension.z ) minDimension.z = pos.z;
436 if( pos.z > maxDimension.z ) maxDimension.z = pos.z;
437 }
else if( tokens[0] ==
"vn" ) {
439 }
else if( tokens[0] ==
"vt" ) {
441 }
else if( tokens[0] ==
"f" ) {
444 std::vector<std::string> faceTokens = _tokenizeString(line,
" ");
446 for (GLuint i = 1; i < faceTokens.size(); i++) {
448 std::vector<std::string> groupTokens = _tokenizeString(faceTokens[i],
"/");
450 for (
char j: faceTokens[i]) {
451 if (j ==
'/') numSlashes++;
454 std::stringstream currentFaceTokenStream;
456 auto signedVertexIndex =
static_cast<GLint
>(strtol(groupTokens[0].c_str(),
nullptr, 10));
457 GLuint vertexIndex = signedVertexIndex;
458 if (signedVertexIndex < 0) vertexIndex = numVertices + signedVertexIndex + 1;
460 currentFaceTokenStream << vertexIndex;
463 if (groupTokens.size() == 2 && numSlashes == 1) {
464 _hasVertexTexCoords =
true;
466 auto signedTexCoordIndex =
static_cast<GLint
>(strtol(groupTokens[1].c_str(),
nullptr, 10));
467 GLuint texCoordIndex = signedTexCoordIndex;
468 if (signedTexCoordIndex < 0) texCoordIndex = numTexCoords + signedTexCoordIndex + 1;
470 currentFaceTokenStream <<
"/" << texCoordIndex;
471 }
else if (groupTokens.size() == 2 && numSlashes == 2) {
472 _hasVertexNormals =
true;
474 auto signedNormalIndex =
static_cast<GLint
>(strtol(groupTokens[1].c_str(),
nullptr, 10));
475 GLuint normalIndex = signedNormalIndex;
476 if (signedNormalIndex < 0) normalIndex = numNormals + signedNormalIndex + 1;
478 currentFaceTokenStream <<
"//" << normalIndex;
479 }
else if (groupTokens.size() == 3) {
480 _hasVertexTexCoords =
true;
481 _hasVertexNormals =
true;
483 auto signedTexCoordIndex =
static_cast<GLint
>(strtol(groupTokens[1].c_str(),
nullptr, 10));
484 GLuint texCoordIndex = signedTexCoordIndex;
485 if (signedTexCoordIndex < 0) texCoordIndex = numTexCoords + signedTexCoordIndex + 1;
487 auto signedNormalIndex =
static_cast<GLint
>(strtol(groupTokens[2].c_str(),
nullptr, 10));
488 GLuint normalIndex = signedNormalIndex;
489 if (signedNormalIndex < 0) normalIndex = numNormals + signedNormalIndex + 1;
491 currentFaceTokenStream <<
"/" << texCoordIndex <<
"/" << normalIndex;
492 }
else if (groupTokens.size() != 1) {
493 if (ERRORS) fprintf(stderr,
"[.obj]: [ERROR]: Malformed OBJ file, %s.\n", _filename.c_str());
497 std::string processedFaceToken = currentFaceTokenStream.str();
498 if (uniqueCounts.find(processedFaceToken) == uniqueCounts.end()) {
499 uniqueCounts.insert(std::pair<std::string, long int>(processedFaceToken, _uniqueIndex));
504 numTriangles += (faceTokens.size() - 1 - 3 + 1);
507 }
else if( tokens[0] ==
"usemtl" ) {
510 if (INFO) printf(
"[.obj]: ignoring line: %s\n", line.c_str() );
515 if( progressCounter % 5000 == 0 ) {
517 switch( progressCounter ) {
518 case 5000: printf(
"[.obj]: scanning %s...\\", _filename.c_str());
break;
519 case 10000: printf(
"[.obj]: scanning %s...|", _filename.c_str());
break;
520 case 15000: printf(
"[.obj]: scanning %s.../", _filename.c_str());
break;
521 case 20000: printf(
"[.obj]: scanning %s...-", _filename.c_str());
break;
526 if( progressCounter == 20000 )
533 printf(
"\33[2K\r" );
534 printf(
"[.obj]: scanning %s...done!\n", _filename.c_str() );
535 printf(
"[.obj]: ------------\n" );
536 printf(
"[.obj]: Model Stats:\n" );
537 printf(
"[.obj]: Vertices: \t%u\tNormals: \t%u\tTex Coords:\t%u\n", numVertices, numNormals, numTexCoords );
538 printf(
"[.obj]: Unique Verts:\t%u\n", _uniqueIndex );
539 printf(
"[.obj]: Faces: \t%u\tTriangles:\t%u\n", numFaces, numTriangles );
540 printf(
"[.obj]: Objects: \t%u\tGroups: \t%u\n", numObjects, numGroups );
542 glm::vec3 sizeDimensions = maxDimension - minDimension;
543 printf(
"[.obj]: Dimensions:\t(%f, %f, %f)\n", sizeDimensions.x, sizeDimensions.y, sizeDimensions.z );
546 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
547 if (INFO && !_hasVertexNormals)
548 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" );
549 _allocateAttributeArrays(_uniqueIndex, numTriangles*3);
551 if (INFO) printf(
"[.obj]: No vertex normals exist on model, vertex normals will be autogenerated\n" );
552 _allocateAttributeArrays(numTriangles * 3, numTriangles*3);
555 auto objVertices =
new glm::vec3[numVertices];
556 auto objNormals =
new glm::vec3[numNormals];
557 auto objTexCoords =
new glm::vec2[numTexCoords];
559 std::vector<glm::vec3> verticesTemps;
560 std::vector<glm::vec2> texCoordsTemp;
562 printf(
"[.obj]: ------------\n" );
564 uniqueCounts.clear();
568 in.open( _filename );
570 GLuint verticesSeen = 0, texCoordsSeen = 0, normalsSeen = 0, indicesSeen = 0;
571 GLuint uniqueNumVertices = 0;
573 std::string currentMaterial =
"default";
574 _materialIndexStartStop.insert( std::pair< std::string, std::vector< std::pair< GLuint, GLuint > > >( currentMaterial, std::vector< std::pair< GLuint, GLuint > >(1) ) );
575 _materialIndexStartStop.find( currentMaterial )->second.back().first = indicesSeen;
577 while( getline( in, line ) ) {
578 if( line.length() > 1 && line.at(0) ==
'\t' )
579 line = line.substr( 1 );
580 line.erase( line.find_last_not_of(
" \n\r\t" ) + 1 );
582 auto tokens = _tokenizeString( line,
" \t" );
583 if( tokens.empty() )
continue;
586 if( tokens[0] ==
"#" || tokens[0].find_first_of(
'#') == 0
589 || tokens[0] ==
"mtllib"
593 }
else if( tokens[0] ==
"usemtl" ) {
594 if( currentMaterial ==
"default" && indicesSeen == 0 ) {
595 _materialIndexStartStop.clear();
597 _materialIndexStartStop.find( currentMaterial )->second.back().second = indicesSeen - 1;
599 currentMaterial = tokens[1];
600 if( _materialIndexStartStop.find( currentMaterial ) == _materialIndexStartStop.end() ) {
601 _materialIndexStartStop.insert( std::pair< std::string, std::vector< std::pair< GLuint, GLuint > > >( currentMaterial, std::vector< std::pair< GLuint, GLuint > >(1) ) );
602 _materialIndexStartStop.find( currentMaterial )->second.back().first = indicesSeen;
604 _materialIndexStartStop.find( currentMaterial )->second.emplace_back( indicesSeen, -1 );
606 }
else if( tokens[0] ==
"v" ) {
607 objVertices[verticesSeen++] = glm::vec3(strtof(tokens[1].c_str(),
nullptr ),
608 strtof( tokens[2].c_str(),
nullptr ),
609 strtof( tokens[3].c_str(),
nullptr ) );
610 }
else if( tokens[0] ==
"vn" ) {
611 objNormals[normalsSeen++] = glm::vec3(strtof(tokens[1].c_str(),
nullptr ),
612 strtof( tokens[2].c_str(),
nullptr ),
613 strtof( tokens[3].c_str(),
nullptr ));
614 }
else if( tokens[0] ==
"vt" ) {
615 objTexCoords[texCoordsSeen++] = glm::vec2(strtof(tokens[1].c_str(),
nullptr ),
616 strtof( tokens[2].c_str(),
nullptr ));
617 }
else if( tokens[0] ==
"f" ) {
618 std::vector<std::string> processedFaceTokens;
620 bool faceHasVertexNormals =
false;
621 bool faceHasTextureCoordinates =
false;
623 for(GLuint i = 1; i < tokens.size(); i++) {
625 auto vertexAttributeTokens = _tokenizeString(tokens[i],
"/");
626 int numAttributeSlashes = 0;
627 for(
char j : tokens[i]) {
628 if(j ==
'/') numAttributeSlashes++;
631 std::stringstream currentFaceTokenStream;
633 auto signedVertexIndex =
static_cast<GLint
>(strtol(vertexAttributeTokens[0].c_str(),
nullptr, 10));
634 GLuint vertexIndex = signedVertexIndex;
635 if(signedVertexIndex < 0) vertexIndex = verticesSeen + signedVertexIndex + 1;
636 currentFaceTokenStream << vertexIndex;
638 GLuint texCoordIndex = 0, normalIndex = 0;
641 if(vertexAttributeTokens.size() == 2 && numAttributeSlashes == 1) {
643 _hasVertexTexCoords =
true;
644 faceHasTextureCoordinates =
true;
646 auto signedTexCoordIndex =
static_cast<GLint
>(strtol(vertexAttributeTokens[1].c_str(),
nullptr, 10));
647 texCoordIndex = signedTexCoordIndex;
648 if(signedTexCoordIndex < 0) texCoordIndex = texCoordsSeen + signedTexCoordIndex + 1;
649 currentFaceTokenStream <<
"/" << texCoordIndex;
650 }
else if(vertexAttributeTokens.size() == 2 && numAttributeSlashes == 2) {
652 _hasVertexNormals =
true;
653 faceHasVertexNormals =
true;
655 auto signedNormalIndex =
static_cast<GLint
>(strtol(vertexAttributeTokens[1].c_str(),
nullptr, 10));
656 normalIndex = signedNormalIndex;
657 if(signedNormalIndex < 0) normalIndex = normalsSeen + signedNormalIndex + 1;
658 currentFaceTokenStream <<
"//" << normalIndex;
659 }
else if(vertexAttributeTokens.size() == 3) {
661 _hasVertexTexCoords =
true;
662 faceHasTextureCoordinates =
true;
663 _hasVertexNormals =
true;
664 faceHasVertexNormals =
true;
666 auto signedTexCoordIndex =
static_cast<GLint
>(strtol(vertexAttributeTokens[1].c_str(),
nullptr, 10));
667 texCoordIndex = signedTexCoordIndex;
668 if(signedTexCoordIndex < 0) texCoordIndex = texCoordsSeen + signedTexCoordIndex + 1;
670 auto signedNormalIndex =
static_cast<GLint
>(strtol(vertexAttributeTokens[2].c_str(),
nullptr, 10));
671 normalIndex = signedNormalIndex;
672 if(signedNormalIndex < 0) normalIndex = normalsSeen + signedNormalIndex + 1;
674 currentFaceTokenStream <<
"/" << texCoordIndex <<
"/" << normalIndex;
675 }
else if(vertexAttributeTokens.size() != 1) {
676 if (ERRORS) fprintf(stderr,
"[.obj]: [ERROR]: Malformed OBJ file, %s.\n", _filename.c_str());
680 auto processedFaceToken = currentFaceTokenStream.str();
681 processedFaceTokens.push_back(processedFaceToken);
684 if( uniqueCounts.find( processedFaceToken ) == uniqueCounts.end() ) {
686 uniqueCounts.insert( std::pair<std::string,GLuint>(processedFaceToken, uniqueNumVertices) );
688 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
689 _vertices[ _uniqueIndex ] = objVertices[ vertexIndex - 1 ];
690 if(faceHasTextureCoordinates && texCoordIndex != 0) { _texCoords[ _uniqueIndex ] = objTexCoords[ texCoordIndex - 1 ]; }
691 if(faceHasVertexNormals && normalIndex != 0) _normals[ _uniqueIndex ] = objNormals[ normalIndex - 1 ];
694 verticesTemps.push_back( objVertices[ vertexIndex - 1 ] );
695 if(faceHasTextureCoordinates && texCoordIndex != 0) { texCoordsTemp.push_back( objTexCoords[ texCoordIndex - 1 ] ); }
697 if( (vertexAttributeTokens.size() == 2 && numAttributeSlashes == 2)
698 || (vertexAttributeTokens.size() == 3) ) {
700 if (ERRORS) fprintf( stderr,
"[.obj]: [ERROR]: no vertex normals were specified, should not be trying to access values\n" );
707 for(GLuint i = 1; i < processedFaceTokens.size()-1; i++) {
708 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
709 _indices[ indicesSeen++ ] = uniqueCounts.find( processedFaceTokens[0] )->second;
710 _indices[ indicesSeen++ ] = uniqueCounts.find( processedFaceTokens[i] )->second;
711 _indices[ indicesSeen++ ] = uniqueCounts.find( processedFaceTokens[i+1] )->second;
715 GLuint aI = uniqueCounts.find( processedFaceTokens[0] )->second;
716 GLuint bI = uniqueCounts.find( processedFaceTokens[i] )->second;
717 GLuint cI = uniqueCounts.find( processedFaceTokens[i+1] )->second;
719 glm::vec3 a = verticesTemps[aI];
720 glm::vec3 b = verticesTemps[bI];
721 glm::vec3 c = verticesTemps[cI];
723 glm::vec3 ab = b - a; glm::vec3 ac = c - a;
724 glm::vec3 ba = a - b; glm::vec3 bc = c - b;
725 glm::vec3 ca = a - c; glm::vec3 cb = b - c;
727 glm::vec3 aN = glm::normalize( glm::cross( ab, ac ) );
728 glm::vec3 bN = glm::normalize( glm::cross( bc, ba ) );
729 glm::vec3 cN = glm::normalize( glm::cross( ca, cb ) );
731 _vertices[ _uniqueIndex ] = a;
732 _normals[ _uniqueIndex ] = aN;
733 if( faceHasTextureCoordinates && _hasVertexTexCoords ) { _texCoords[ _uniqueIndex ] = texCoordsTemp[ aI ]; }
734 _indices[ _numIndices++ ] = _uniqueIndex++;
738 _vertices[ _uniqueIndex ] = b;
739 _normals[ _uniqueIndex ] = bN;
740 if( faceHasTextureCoordinates && _hasVertexTexCoords ) { _texCoords[ _uniqueIndex ] = texCoordsTemp[ bI ]; }
741 _indices[ _numIndices++ ] = _uniqueIndex++;
745 _vertices[ _uniqueIndex ] = c;
746 _normals[ _uniqueIndex ] = cN;
747 if( faceHasTextureCoordinates && _hasVertexTexCoords ) { _texCoords[ _uniqueIndex ] = texCoordsTemp[ cI ]; }
748 _indices[ _numIndices++ ] = _uniqueIndex++;
757 if( progressCounter % 5000 == 0 ) {
759 switch( progressCounter ) {
760 case 5000: printf(
"[.obj]: parsing %s...\\", _filename.c_str());
break;
761 case 10000: printf(
"[.obj]: parsing %s...|", _filename.c_str());
break;
762 case 15000: printf(
"[.obj]: parsing %s.../", _filename.c_str());
break;
763 case 20000: printf(
"[.obj]: parsing %s...-", _filename.c_str());
break;
768 if( progressCounter == 20000 )
776 printf(
"\33[2K\r" );
777 printf(
"[.obj]: parsing %s...done!\n", _filename.c_str() );
780 _materialIndexStartStop.find( currentMaterial )->second.back().second = indicesSeen - 1;
785 double seconds = difftime( end, start );
788 printf(
"[.obj]: Completed in %.3fs\n", seconds );
789 printf(
"[.obj]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=- \n\n", _filename.c_str() );
795inline bool CSCI441::ModelLoader::_loadMTLFile(
const char* mtlFilename,
const bool INFO,
const bool ERRORS ) {
798 if (INFO) printf(
"[.mtl]: -*-*-*-*-*-*-*- BEGIN %s Info -*-*-*-*-*-*-*-\n", mtlFilename );
802 if( _filename.find(
'/') != std::string::npos ) {
803 path = _filename.substr( 0, _filename.find_last_of(
'/')+1 );
809 in.open( mtlFilename );
810 if( !in.is_open() ) {
811 in.open( mtlFilename );
812 if( !in.is_open() ) {
813 if (ERRORS) fprintf( stderr,
"[.mtl]: [ERROR]: could not open material file: %s\n", mtlFilename );
814 if ( INFO ) printf(
"[.mtl]: -*-*-*-*-*-*-*- END %s Info -*-*-*-*-*-*-*-\n", mtlFilename );
819 CSCI441_INTERNAL::ModelMaterial* currentMaterial =
nullptr;
820 std::string materialName;
822 unsigned char *textureData =
nullptr;
823 unsigned char *maskData =
nullptr;
824 unsigned char *fullData;
825 int texWidth, texHeight, textureChannels = 1, maskChannels = 1;
826 GLuint textureHandle = 0;
828 std::map< std::string, GLuint > imageHandles;
830 int numMaterials = 0;
832 while( getline( in, line ) ) {
833 if( line.length() > 1 && line.at(0) ==
'\t' )
834 line = line.substr( 1 );
835 line.erase( line.find_last_not_of(
" \n\r\t" ) + 1 );
837 std::vector< std::string > tokens = _tokenizeString( line,
" /" );
838 if( tokens.empty() )
continue;
841 if( tokens[0] ==
"#" ) {
843 }
else if( tokens[0] ==
"newmtl" ) {
844 if (INFO) printf(
"[.mtl]: Parsing material %s properties\n", tokens[1].c_str() );
845 currentMaterial =
new CSCI441_INTERNAL::ModelMaterial();
846 materialName = tokens[1];
847 _materials.insert( std::pair<std::string, CSCI441_INTERNAL::ModelMaterial*>( materialName, currentMaterial ) );
850 textureData =
nullptr;
856 }
else if( tokens[0] ==
"Ka" ) {
857 currentMaterial->ambient[0] = strtof( tokens[1].c_str(),
nullptr );
858 currentMaterial->ambient[1] = strtof( tokens[2].c_str(),
nullptr );
859 currentMaterial->ambient[2] = strtof( tokens[3].c_str(),
nullptr );
860 }
else if( tokens[0] ==
"Kd" ) {
861 currentMaterial->diffuse[0] = strtof( tokens[1].c_str(),
nullptr );
862 currentMaterial->diffuse[1] = strtof( tokens[2].c_str(),
nullptr );
863 currentMaterial->diffuse[2] = strtof( tokens[3].c_str(),
nullptr );
864 }
else if( tokens[0] ==
"Ks" ) {
865 currentMaterial->specular[0] = strtof( tokens[1].c_str(),
nullptr );
866 currentMaterial->specular[1] = strtof( tokens[2].c_str(),
nullptr );
867 currentMaterial->specular[2] = strtof( tokens[3].c_str(),
nullptr );
868 }
else if( tokens[0] ==
"Ke" ) {
869 currentMaterial->emissive[0] = strtof( tokens[1].c_str(),
nullptr );
870 currentMaterial->emissive[1] = strtof( tokens[2].c_str(),
nullptr );
871 currentMaterial->emissive[2] = strtof( tokens[3].c_str(),
nullptr );
872 }
else if( tokens[0] ==
"Ns" ) {
873 currentMaterial->shininess = strtof( tokens[1].c_str(),
nullptr );
874 }
else if( tokens[0] ==
"Tr"
875 || tokens[0] ==
"d" ) {
876 currentMaterial->ambient[3] = strtof( tokens[1].c_str(),
nullptr );
877 currentMaterial->diffuse[3] = strtof( tokens[1].c_str(),
nullptr );
878 currentMaterial->specular[3] = strtof( tokens[1].c_str(),
nullptr );
879 }
else if( tokens[0] ==
"illum" ) {
881 }
else if( tokens[0] ==
"map_Kd" ) {
882 if( imageHandles.find( tokens[1] ) != imageHandles.end() ) {
884 currentMaterial->map_Kd = imageHandles.find( tokens[1] )->second;
886 stbi_set_flip_vertically_on_load(
true);
887 textureData = stbi_load( tokens[1].c_str(), &texWidth, &texHeight, &textureChannels, 0 );
889 std::string folderName = path + tokens[1];
890 textureData = stbi_load( folderName.c_str(), &texWidth, &texHeight, &textureChannels, 0 );
894 if (ERRORS) fprintf( stderr,
"[.mtl]: [ERROR]: File Not Found: %s\n", tokens[1].c_str() );
896 if (INFO) printf(
"[.mtl]: TextureMap:\t%s\tSize: %dx%d\tColors: %d\n", tokens[1].c_str(), texWidth, texHeight, textureChannels );
898 if( maskData ==
nullptr ) {
899 if( textureHandle == 0 ) {
900 glGenTextures( 1, &textureHandle );
901 imageHandles.insert( std::pair<std::string, GLuint>( tokens[1], textureHandle ) );
904 glBindTexture( GL_TEXTURE_2D, textureHandle );
906 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
907 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
909 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
910 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
912 GLint colorSpace = GL_RGB;
913 if( textureChannels == 4 )
914 colorSpace = GL_RGBA;
915 glTexImage2D( GL_TEXTURE_2D, 0, colorSpace, texWidth, texHeight, 0, colorSpace, GL_UNSIGNED_BYTE, textureData );
917 currentMaterial->map_Kd = textureHandle;
919 fullData = CSCI441_INTERNAL::createTransparentTexture( textureData, maskData, texWidth, texHeight, textureChannels, maskChannels );
921 if( textureHandle == 0 ) {
922 glGenTextures( 1, &textureHandle );
923 imageHandles.insert( std::pair<std::string, GLuint>( tokens[1], textureHandle ) );
926 glBindTexture( GL_TEXTURE_2D, textureHandle );
928 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
929 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
931 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
932 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
934 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, texWidth, texHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, fullData );
938 currentMaterial->map_Kd = textureHandle;
942 }
else if( tokens[0] ==
"map_d" ) {
943 if( imageHandles.find( tokens[1] ) != imageHandles.end() ) {
945 currentMaterial->map_d = imageHandles.find( tokens[1] )->second;
947 stbi_set_flip_vertically_on_load(
true);
948 maskData = stbi_load( tokens[1].c_str(), &texWidth, &texHeight, &textureChannels, 0 );
950 std::string folderName = path + tokens[1];
951 maskData = stbi_load( folderName.c_str(), &texWidth, &texHeight, &textureChannels, 0 );
955 if (ERRORS) fprintf( stderr,
"[.mtl]: [ERROR]: File Not Found: %s\n", tokens[1].c_str() );
957 if (INFO) printf(
"[.mtl]: AlphaMap: \t%s\tSize: %dx%d\tColors: %d\n", tokens[1].c_str(), texWidth, texHeight, maskChannels );
959 if( textureData !=
nullptr ) {
960 fullData = CSCI441_INTERNAL::createTransparentTexture( textureData, maskData, texWidth, texHeight, textureChannels, maskChannels );
962 if( textureHandle == 0 ) {
963 glGenTextures( 1, &textureHandle );
964 imageHandles.insert( std::pair<std::string, GLuint>( tokens[1], textureHandle ) );
967 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
968 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
970 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
971 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
973 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, texWidth, texHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, fullData );
977 currentMaterial->map_Kd = textureHandle;
981 }
else if( tokens[0] ==
"map_Ka" ) {
983 }
else if( tokens[0] ==
"map_Ks" ) {
985 }
else if( tokens[0] ==
"map_Ns" ) {
987 }
else if( tokens[0] ==
"Ni" ) {
989 }
else if( tokens[0] ==
"Tf" ) {
991 }
else if( tokens[0] ==
"bump"
992 || tokens[0] ==
"map_bump" ) {
995 if (INFO) printf(
"[.mtl]: ignoring line: %s\n", line.c_str() );
1002 printf(
"[.mtl]: Materials:\t%d\n", numMaterials );
1003 printf(
"[.mtl]: -*-*-*-*-*-*-*- END %s Info -*-*-*-*-*-*-*-\n", mtlFilename );
1009inline bool CSCI441::ModelLoader::_loadOFFFile(
const bool INFO,
const bool ERRORS ) {
1012 if (INFO ) printf(
"[.off]: -=-=-=-=-=-=-=- BEGIN %s Info -=-=-=-=-=-=-=-\n", _filename.c_str() );
1017 std::ifstream in( _filename );
1018 if( !in.is_open() ) {
1019 if (ERRORS) fprintf( stderr,
"[.off]: [ERROR]: Could not open \"%s\"\n", _filename.c_str() );
1020 if ( INFO ) printf(
"[.off]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1024 GLuint numVertices = 0, numFaces = 0, numTriangles = 0;
1025 GLfloat minX = 999999.0f, maxX = -999999.0f, minY = 999999.0f, maxY = -999999.0f, minZ = 999999.0f, maxZ = -999999.0f;
1028 enum OFF_FILE_STATE { HEADER, VERTICES, FACES, DONE };
1030 OFF_FILE_STATE fileState = HEADER;
1032 GLuint vSeen = 0, fSeen = 0;
1034 while( getline( in, line ) ) {
1035 if( line.length() > 1 && line.at(0) ==
'\t' )
1036 line = line.substr( 1 );
1037 line.erase( line.find_last_not_of(
" \n\r\t" ) + 1 );
1039 std::vector< std::string > tokens = _tokenizeString( line,
" \t" );
1040 if( tokens.empty() )
continue;
1043 if( tokens[0] ==
"#" || tokens[0].find_first_of(
'#') == 0 ) {
1044 }
else if( fileState == HEADER ) {
1045 if( tokens[0] ==
"OFF" ) {
1047 if( tokens.size() != 3 ) {
1048 if (ERRORS) fprintf( stderr,
"[.off]: [ERROR]: Malformed OFF file. # vertices, faces, edges not properly specified\n" );
1049 if ( INFO ) printf(
"[.off]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1054 numVertices =
static_cast<GLuint
>(strtol(tokens[0].c_str(),
nullptr, 10));
1055 numFaces =
static_cast<GLuint
>(strtol(tokens[1].c_str(),
nullptr, 10));
1060 fileState = VERTICES;
1062 }
else if( fileState == VERTICES ) {
1064 GLfloat x = strtof( tokens[0].c_str(),
nullptr ),
1065 y = strtof( tokens[1].c_str(),
nullptr ),
1066 z = strtof( tokens[2].c_str(),
nullptr );
1068 if( x < minX ) minX = x;
1069 if( x > maxX ) maxX = x;
1070 if( y < minY ) minY = y;
1071 if( y > maxY ) maxY = y;
1072 if( z < minZ ) minZ = z;
1073 if( z > maxZ ) maxZ = z;
1076 if( vSeen == numVertices )
1078 }
else if( fileState == FACES ) {
1079 auto numberOfVerticesInFace =
static_cast<GLuint
>(strtol(tokens[0].c_str(),
nullptr, 10));
1081 numTriangles += numberOfVerticesInFace - 3 + 1;
1083 if( fSeen == numFaces )
1086 if (INFO) printf(
"[.off]: unknown file state: %d\n", fileState );
1092 printf(
"\33[2K\r" );
1093 printf(
"[.off]: scanning %s...done!\n", _filename.c_str() );
1094 printf(
"[.off]: ------------\n" );
1095 printf(
"[.off]: Model Stats:\n" );
1096 printf(
"[.off]: Vertices: \t%u\tNormals: \t%u\tTex Coords:\t%u\n", numVertices, 0, 0 );
1097 printf(
"[.off]: Faces: \t%u\tTriangles: \t%u\n", numFaces, numTriangles );
1098 printf(
"[.off]: Dimensions:\t(%f, %f, %f)\n", (maxX - minX), (maxY - minY), (maxZ - minZ) );
1101 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
1102 if (INFO && !_hasVertexNormals)
1103 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" );
1104 _allocateAttributeArrays(numVertices, numTriangles*3);
1106 if (INFO) printf(
"[.off]: No vertex normals exist on model, vertex normals will be autogenerated\n" );
1107 _allocateAttributeArrays(numTriangles*3, numTriangles*3);
1110 std::vector<glm::vec3> verticesTemp;
1112 if (INFO) printf(
"[.off]: ------------\n" );
1114 in.open( _filename );
1122 int progressCounter = 0;
1124 while( getline( in, line ) ) {
1125 if( line.length() > 1 && line.at(0) ==
'\t' )
1126 line = line.substr( 1 );
1127 line.erase( line.find_last_not_of(
" \n\r\t" ) + 1 );
1129 std::vector< std::string > tokens = _tokenizeString( line,
" \t" );
1130 if( tokens.empty() )
continue;
1133 if( tokens[0] ==
"#" || tokens[0].find_first_of(
'#') == 0 ) {
1135 }
else if( fileState == HEADER ) {
1136 if( tokens[0] ==
"OFF" ) {
1140 fileState = VERTICES;
1142 }
else if( fileState == VERTICES ) {
1144 glm::vec3 pos(strtof( tokens[0].c_str(),
nullptr ),
1145 strtof( tokens[1].c_str(),
nullptr ),
1146 strtof( tokens[2].c_str(),
nullptr ) );
1149 if( tokens.size() == 6 || tokens.size() == 7 ) {
1157 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
1158 _vertices[ _uniqueIndex ] = pos;
1161 verticesTemp.push_back(pos);
1165 if( _uniqueIndex == numVertices || vSeen == numVertices )
1167 }
else if( fileState == FACES ) {
1168 auto numberOfVerticesInFace =
static_cast<GLuint
>(strtol(tokens[0].c_str(),
nullptr, 10));
1171 for(GLuint i = 2; i <= numberOfVerticesInFace - 1; i++) {
1172 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
1173 auto fanRoot = strtol( tokens[1].c_str(),
nullptr, 10 );
1174 auto fanA = strtol( tokens[i].c_str(),
nullptr, 10 );
1175 auto fanB = strtol( tokens[i+1].c_str(),
nullptr, 10 );
1177 if( fanRoot < 0 ) fanRoot = numVertices + fanRoot + 1;
1178 if( fanA < 0 ) fanA = numVertices + fanA + 1;
1179 if( fanB < 0 ) fanB = numVertices + fanB + 1;
1182 _indices[ _numIndices++ ] = fanRoot;
1183 _indices[ _numIndices++ ] = fanA;
1184 _indices[ _numIndices++ ] = fanB;
1186 auto aI = strtol( tokens[1].c_str(),
nullptr, 10 );
1187 auto bI = strtol( tokens[i].c_str(),
nullptr, 10 );
1188 auto cI = strtol( tokens[i+1].c_str(),
nullptr, 10 );
1190 if( aI < 0 ) aI = numVertices + aI + 1;
1191 if( bI < 0 ) bI = numVertices + bI + 1;
1192 if( cI < 0 ) cI = numVertices + cI + 1;
1194 glm::vec3 a = verticesTemp[aI];
1195 glm::vec3 b = verticesTemp[bI];
1196 glm::vec3 c = verticesTemp[cI];
1198 glm::vec3 ab = b - a; glm::vec3 ac = c - a;
1199 glm::vec3 ba = a - b; glm::vec3 bc = c - b;
1200 glm::vec3 ca = a - c; glm::vec3 cb = b - c;
1202 glm::vec3 aN = glm::normalize( glm::cross( ab, ac ) );
1203 glm::vec3 bN = glm::normalize( glm::cross( bc, ba ) );
1204 glm::vec3 cN = glm::normalize( glm::cross( ca, cb ) );
1206 _vertices[ _uniqueIndex ] = a;
1207 _normals[ _uniqueIndex ] = aN;
1208 _indices[ _numIndices++ ] = _uniqueIndex++;
1210 _vertices[ _uniqueIndex ] = b;
1211 _normals[ _uniqueIndex ] = bN;
1212 _indices[ _numIndices++ ] = _uniqueIndex++;
1214 _vertices[ _uniqueIndex ] = c;
1215 _normals[ _uniqueIndex ] = cN;
1216 _indices[ _numIndices++ ] = _uniqueIndex++;
1241 if( progressCounter % 5000 == 0 ) {
1243 switch( progressCounter ) {
1244 case 5000: printf(
"[.off]: parsing %s...\\", _filename.c_str());
break;
1245 case 10000: printf(
"[.off]: parsing %s...|", _filename.c_str());
break;
1246 case 15000: printf(
"[.off]: parsing %s.../", _filename.c_str());
break;
1247 case 20000: printf(
"[.off]: parsing %s...-", _filename.c_str());
break;
1252 if( progressCounter == 20000 )
1253 progressCounter = 0;
1261 double seconds = difftime( end, start );
1264 printf(
"\33[2K\r" );
1265 printf(
"[.off]: parsing %s...done! (Time: %.1fs)\n", _filename.c_str(), seconds );
1266 printf(
"[.off]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1273inline bool CSCI441::ModelLoader::_loadPLYFile(
const bool INFO,
const bool ERRORS ) {
1276 if (INFO ) printf(
"[.ply]: -=-=-=-=-=-=-=- BEGIN %s Info -=-=-=-=-=-=-=-\n", _filename.c_str() );
1281 std::ifstream in( _filename );
1282 if( !in.is_open() ) {
1283 if (ERRORS) fprintf( stderr,
"[.ply]: [ERROR]: Could not open \"%s\"\n", _filename.c_str() );
1284 if ( INFO ) printf(
"[.ply]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1288 GLuint numVertices = 0, numFaces = 0, numTriangles = 0, numMaterials = 0;
1289 GLfloat minX = 999999.0f, maxX = -999999.0f, minY = 999999.0f, maxY = -999999.0f, minZ = 999999.0f, maxZ = -999999.0f;
1292 enum PLY_FILE_STATE { HEADER, VERTICES, FACES, MATERIALS };
1293 enum PLY_ELEMENT_TYPE { NONE, VERTEX, FACE, MATERIAL };
1295 PLY_FILE_STATE fileState = HEADER;
1296 PLY_ELEMENT_TYPE elemType = NONE;
1298 GLuint progressCounter = 0;
1301 while( getline( in, line ) ) {
1302 if( line.length() > 1 && line.at(0) ==
'\t' )
1303 line = line.substr( 1 );
1304 line.erase( line.find_last_not_of(
" \n\r\t" ) + 1 );
1306 std::vector< std::string > tokens = _tokenizeString( line,
" \t" );
1308 if( tokens.empty() )
continue;
1311 if( tokens[0] ==
"comment" ) {
1312 }
else if( fileState == HEADER ) {
1313 if( tokens[0] ==
"ply" ) {
1314 }
else if( tokens[0] ==
"format" ) {
1315 if( tokens[1] !=
"ascii" ) {
1316 if (ERRORS) fprintf( stderr,
"[.ply]: [ERROR]: File \"%s\" not ASCII format\n", _filename.c_str() );
1317 if ( INFO ) printf(
"[.ply]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1321 }
else if( tokens[0] ==
"element" ) {
1322 if( tokens[1] ==
"vertex" ) {
1323 numVertices =
static_cast<GLuint
>(strtol(tokens[2].c_str(),
nullptr, 10));
1325 }
else if( tokens[1] ==
"face" ) {
1326 numFaces =
static_cast<GLuint
>(strtol(tokens[2].c_str(),
nullptr, 10));
1328 }
else if( tokens[1] ==
"edge" ) {
1330 }
else if( tokens[1] ==
"material" ) {
1331 numMaterials =
static_cast<GLuint
>(strtol(tokens[2].c_str(),
nullptr, 10));
1332 elemType = MATERIAL;
1336 }
else if( tokens[0] ==
"property" ) {
1337 if( elemType == VERTEX ) {
1339 }
else if( elemType == FACE ) {
1341 }
else if( elemType == MATERIAL ) {
1344 }
else if( tokens[0] ==
"end_header" ) {
1345 fileState = VERTICES;
1347 }
else if( fileState == VERTICES ) {
1349 auto x = (GLfloat) strtof( tokens[0].c_str(), nullptr ),
1350 y = (GLfloat) strtof( tokens[1].c_str(),
nullptr ),
1351 z = (GLfloat) strtof( tokens[2].c_str(), nullptr );
1353 if( x < minX ) minX = x;
1354 if( x > maxX ) maxX = x;
1355 if( y < minY ) minY = y;
1356 if( y > maxY ) maxY = y;
1357 if( z < minZ ) minZ = z;
1358 if( z > maxZ ) maxZ = z;
1362 if( vSeen == numVertices )
1364 }
else if( fileState == FACES ) {
1365 auto numberOfVerticesInFace =
static_cast<GLuint
>(strtol(tokens[0].c_str(),
nullptr, 10));
1366 numTriangles += numberOfVerticesInFace - 3 + 1;
1368 if (INFO) printf(
"[.ply]: unknown file state: %d\n", fileState );
1373 if( progressCounter % 5000 == 0 ) {
1375 switch( progressCounter ) {
1376 case 5000: printf(
"[.ply]: scanning %s...\\", _filename.c_str());
break;
1377 case 10000: printf(
"[.ply]: scanning %s...|", _filename.c_str());
break;
1378 case 15000: printf(
"[.ply]: scanning %s.../", _filename.c_str());
break;
1379 case 20000: printf(
"[.ply]: scanning %s...-", _filename.c_str());
break;
1384 if( progressCounter == 20000 )
1385 progressCounter = 0;
1391 printf(
"\33[2K\r" );
1392 printf(
"[.ply]: scanning %s...done!\n", _filename.c_str() );
1393 printf(
"[.ply]: ------------\n" );
1394 printf(
"[.ply]: Model Stats:\n" );
1395 printf(
"[.ply]: Vertices: \t%u\tNormals: \t%u\tTex Coords:\t%u\n", numVertices, 0, 0 );
1396 printf(
"[.ply]: Faces: \t%u\tTriangles: \t%u\n", numFaces, numTriangles );
1397 printf(
"[.ply]: Dimensions:\t(%f, %f, %f)\n", (maxX - minX), (maxY - minY), (maxZ - minZ) );
1400 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
1401 if (INFO && !_hasVertexNormals)
1402 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" );
1403 _allocateAttributeArrays(numVertices, numTriangles*3);
1405 if (INFO) printf(
"[.ply]: No vertex normals exist on model, vertex normals will be autogenerated\n" );
1406 _allocateAttributeArrays(numTriangles*3, numTriangles*3);
1409 if (INFO) printf(
"[.ply]: ------------\n" );
1411 std::vector<glm::vec3> verticesTemp;
1413 in.open( _filename );
1421 progressCounter = 0;
1424 while( getline( in, line ) ) {
1425 if( line.length() > 1 && line.at(0) ==
'\t' )
1426 line = line.substr( 1 );
1427 line.erase( line.find_last_not_of(
" \n\r\t" ) + 1 );
1429 std::vector< std::string > tokens = _tokenizeString( line,
" \t" );
1431 if( tokens.empty() )
continue;
1434 if( tokens[0] ==
"comment" ) {
1435 }
else if( fileState == HEADER ) {
1436 if( tokens[0] ==
"ply" ) {
1437 }
else if( tokens[0] ==
"format" ) {
1438 if( tokens[1] !=
"ascii" ) {
1439 if (ERRORS) fprintf( stderr,
"[.ply]: [ERROR]: File \"%s\" not ASCII format\n", _filename.c_str() );
1440 if ( INFO ) printf(
"[.ply]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1444 }
else if( tokens[0] ==
"element" ) {
1445 if( tokens[1] ==
"vertex" ) {
1446 numVertices =
static_cast<GLuint
>(strtol(tokens[2].c_str(),
nullptr, 10));
1448 }
else if( tokens[1] ==
"face" ) {
1449 numFaces =
static_cast<GLuint
>(strtol(tokens[2].c_str(),
nullptr, 10));
1451 }
else if( tokens[1] ==
"edge" ) {
1453 }
else if( tokens[1] ==
"material" ) {
1454 numMaterials =
static_cast<GLuint
>(strtol(tokens[2].c_str(),
nullptr, 10));
1455 elemType = MATERIAL;
1459 }
else if( tokens[0] ==
"property" ) {
1460 if( elemType == VERTEX ) {
1462 }
else if( elemType == FACE ) {
1464 }
else if( elemType == MATERIAL ) {
1467 }
else if( tokens[0] ==
"end_header" ) {
1468 fileState = VERTICES;
1470 }
else if( fileState == VERTICES ) {
1472 glm::vec3 pos(strtof( tokens[0].c_str(),
nullptr ),
1473 strtof( tokens[1].c_str(),
nullptr ),
1474 strtof( tokens[2].c_str(),
nullptr ) );
1476 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
1477 _vertices[ _uniqueIndex ] = pos;
1480 verticesTemp.push_back(pos );
1484 if( _uniqueIndex == numVertices || vSeen == numVertices )
1486 }
else if( fileState == FACES ) {
1487 auto numberOfVerticesInFace =
static_cast<GLuint
>(strtol(tokens[0].c_str(),
nullptr, 10));
1489 for( GLuint i = 2; i <= numberOfVerticesInFace - 1; i++ ) {
1490 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
1491 _indices[ _numIndices++ ] =
static_cast<GLuint
>(strtol(tokens[1].c_str(),
nullptr, 10));
1492 _indices[ _numIndices++ ] =
static_cast<GLuint
>(strtol(tokens[i].c_str(),
nullptr, 10));
1493 _indices[ _numIndices++ ] =
static_cast<GLuint
>(strtol(tokens[i + 1].c_str(),
nullptr, 10));
1495 auto aI =
static_cast<GLuint
>(strtol(tokens[1].c_str(),
nullptr, 10));
1496 auto bI =
static_cast<GLuint
>(strtol(tokens[i].c_str(),
nullptr, 10));
1497 auto cI =
static_cast<GLuint
>(strtol(tokens[i + 1].c_str(),
nullptr, 10));
1499 glm::vec3 a = verticesTemp[aI];
1500 glm::vec3 b = verticesTemp[bI];
1501 glm::vec3 c = verticesTemp[cI];
1503 glm::vec3 ab = b - a; glm::vec3 ac = c - a;
1504 glm::vec3 ba = a - b; glm::vec3 bc = c - b;
1505 glm::vec3 ca = a - c; glm::vec3 cb = b - c;
1507 glm::vec3 aN = glm::normalize( glm::cross( ab, ac ) );
1508 glm::vec3 bN = glm::normalize( glm::cross( bc, ba ) );
1509 glm::vec3 cN = glm::normalize( glm::cross( ca, cb ) );
1511 _vertices[ _uniqueIndex ] = a;
1512 _normals[ _uniqueIndex ] = aN;
1513 _indices[ _numIndices++ ] = _uniqueIndex++;
1515 _vertices[ _uniqueIndex ] = b;
1516 _normals[ _uniqueIndex ] = bN;
1517 _indices[ _numIndices++ ] = _uniqueIndex++;
1519 _vertices[ _uniqueIndex ] = c;
1520 _normals[ _uniqueIndex ] = cN;
1521 _indices[ _numIndices++ ] = _uniqueIndex++;
1525 if (INFO) printf(
"[.ply]: unknown file state: %d\n", fileState );
1530 if( progressCounter % 5000 == 0 ) {
1532 switch( progressCounter ) {
1533 case 5000: printf(
"[.ply]: parsing %s...\\", _filename.c_str());
break;
1534 case 10000: printf(
"[.ply]: parsing %s...|", _filename.c_str());
break;
1535 case 15000: printf(
"[.ply]: parsing %s.../", _filename.c_str());
break;
1536 case 20000: printf(
"[.ply]: parsing %s...-", _filename.c_str());
break;
1541 if( progressCounter == 20000 )
1542 progressCounter = 0;
1550 double seconds = difftime( end, start );
1553 printf(
"\33[2K\r" );
1554 printf(
"[.ply]: parsing %s...done!\n[.ply]: Time to complete: %.3fs\n", _filename.c_str(), seconds );
1555 printf(
"[.ply]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1561inline bool CSCI441::ModelLoader::_loadSTLFile(
const bool INFO,
const bool ERRORS ) {
1564 if (INFO) printf(
"[.stl]: -=-=-=-=-=-=-=- BEGIN %s Info -=-=-=-=-=-=-=-\n", _filename.c_str() );
1569 std::ifstream in( _filename );
1570 if( !in.is_open() ) {
1571 if (ERRORS) fprintf(stderr,
"[.stl]: [ERROR]: Could not open \"%s\"\n", _filename.c_str() );
1572 if ( INFO ) printf(
"[.stl]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1576 GLuint numVertices = 0, numNormals = 0, numFaces = 0, numTriangles = 0, numVertsInLoop = 0;
1577 GLfloat minX = 999999.0f, maxX = -999999.0f, minY = 999999.0f, maxY = -999999.0f, minZ = 999999.0f, maxZ = -999999.0f;
1580 int progressCounter = 0;
1581 glm::vec3 normalVector = {0.f,0.f,0.f};
1583 while( getline( in, line ) ) {
1584 if( line.length() > 1 && line.at(0) ==
'\t' )
1585 line = line.substr( 1 );
1586 line.erase( line.find_last_not_of(
" \n\r\t" ) + 1 );
1588 std::vector< std::string > tokens = _tokenizeString( line,
" \t" );
1590 if( tokens.empty() )
continue;
1592 if( tokens[0] ==
"solid" ) {
1594 }
else if( tokens[0] ==
"facet" ) {
1597 }
else if( tokens[0] ==
"outer" && tokens[1] ==
"loop" ) {
1600 }
else if( tokens[0] ==
"vertex" ) {
1601 GLfloat x = strtof( tokens[1].c_str(),
nullptr ),
1602 y = strtof( tokens[2].c_str(),
nullptr ),
1603 z = strtof( tokens[3].c_str(),
nullptr );
1605 if( x < minX ) minX = x;
1606 if( x > maxX ) maxX = x;
1607 if( y < minY ) minY = y;
1608 if( y > maxY ) maxY = y;
1609 if( z < minZ ) minZ = z;
1610 if( z > maxZ ) maxZ = z;
1614 }
else if( tokens[0] ==
"endloop" ) {
1616 numTriangles += numVertsInLoop - 3 + 1;
1617 }
else if( tokens[0] ==
"endfacet" ) {
1619 }
else if( tokens[0] ==
"endsolid" ) {
1623 if( memchr( line.c_str(),
'\0', line.length() ) !=
nullptr ) {
1624 if (ERRORS) fprintf( stderr,
"[.stl]: [ERROR]: Cannot read binary STL file \"%s\"\n", _filename.c_str() );
1625 if ( INFO ) printf(
"[.stl]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1628 }
else if (INFO) printf(
"[.stl]: unknown line: %s\n", line.c_str() );
1633 if( progressCounter % 5000 == 0 ) {
1635 switch( progressCounter ) {
1636 case 5000: printf(
"[.stl]: scanning %s...\\", _filename.c_str());
break;
1637 case 10000: printf(
"[.stl]: scanning %s...|", _filename.c_str());
break;
1638 case 15000: printf(
"[.stl]: scanning %s.../", _filename.c_str());
break;
1639 case 20000: printf(
"[.stl]: scanning %s...-", _filename.c_str());
break;
1644 if( progressCounter == 20000 )
1645 progressCounter = 0;
1651 printf(
"\33[2K\r" );
1652 printf(
"[.stl]: scanning %s...done!\n", _filename.c_str() );
1653 printf(
"[.stl]: ------------\n" );
1654 printf(
"[.stl]: Model Stats:\n" );
1655 printf(
"[.stl]: Vertices: \t%u\tNormals: \t%u\tTex Coords:\t%u\n", numVertices, numNormals, 0 );
1656 printf(
"[.stl]: Faces: \t%u\tTriangles: \t%u\n", numFaces, numTriangles );
1657 printf(
"[.stl]: Dimensions:\t(%f, %f, %f)\n", (maxX - minX), (maxY - minY), (maxZ - minZ) );
1660 _allocateAttributeArrays(numVertices, numTriangles*3);
1662 if (INFO) printf(
"[.stl]: ------------\n" );
1664 in.open( _filename );
1669 while( getline( in, line ) ) {
1670 if( line.length() > 1 && line.at(0) ==
'\t' )
1671 line = line.substr( 1 );
1672 line.erase( line.find_last_not_of(
" \n\r\t" ) + 1 );
1674 std::vector< std::string > tokens = _tokenizeString( line,
" \t" );
1676 if( tokens.empty() )
continue;
1678 if( tokens[0] ==
"solid" ) {
1680 }
else if( tokens[0] ==
"facet" ) {
1682 normalVector = glm::vec3( strtof( tokens[2].c_str(),
nullptr ),
1683 strtof( tokens[3].c_str(),
nullptr ),
1684 strtof( tokens[4].c_str(),
nullptr ) );
1685 }
else if( tokens[0] ==
"outer" && tokens[1] ==
"loop" ) {
1687 }
else if( tokens[0] ==
"vertex" ) {
1688 _vertices[ _uniqueIndex ] = glm::vec3(strtof( tokens[1].c_str(),
nullptr ),
1689 strtof( tokens[2].c_str(),
nullptr ),
1690 strtof( tokens[3].c_str(),
nullptr ) );
1691 _normals[ _uniqueIndex ] = normalVector;
1692 _indices[ _numIndices++ ] = _uniqueIndex++;
1693 }
else if( tokens[0] ==
"endloop" ) {
1695 }
else if( tokens[0] ==
"endfacet" ) {
1697 }
else if( tokens[0] ==
"endsolid" ) {
1706 if( progressCounter % 5000 == 0 ) {
1708 switch( progressCounter ) {
1709 case 5000: printf(
"[.stl]: parsing %s...\\", _filename.c_str());
break;
1710 case 10000: printf(
"[.stl]: parsing %s...|", _filename.c_str());
break;
1711 case 15000: printf(
"[.stl]: parsing %s.../", _filename.c_str());
break;
1712 case 20000: printf(
"[.stl]: parsing %s...-", _filename.c_str());
break;
1717 if( progressCounter == 20000 )
1718 progressCounter = 0;
1726 double seconds = difftime( end, start );
1730 printf(
"[.stl]: parsing %s...done!\n[.stl]: Time to complete: %.3fs\n", _filename.c_str(), seconds);
1731 printf(
"[.stl]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1739 sAUTO_GEN_NORMALS =
true;
1744 sAUTO_GEN_NORMALS =
false;
1747inline void CSCI441::ModelLoader::_allocateAttributeArrays(
const GLuint numVertices,
const GLuint numIndices) {
1748 _vertices =
new glm::vec3[numVertices];
1749 _normals =
new glm::vec3[numVertices];
1750 _texCoords =
new glm::vec2[numVertices];
1751 _indices =
new GLuint[numIndices];
1754inline void CSCI441::ModelLoader::_bufferData()
const {
1755 glBindVertexArray( _vaod );
1757 glBindBuffer( GL_ARRAY_BUFFER, _vbods[0] );
1758 glBufferData( GL_ARRAY_BUFFER,
static_cast<GLsizeiptr
>((
sizeof(glm::vec3)*2 +
sizeof(glm::vec2)) * _uniqueIndex),
nullptr, GL_STATIC_DRAW );
1759 glBufferSubData( GL_ARRAY_BUFFER, 0,
static_cast<GLsizeiptr
>(
sizeof(glm::vec3) * _uniqueIndex), _vertices );
1760 glBufferSubData( GL_ARRAY_BUFFER,
static_cast<GLintptr
>(
sizeof(glm::vec3) * _uniqueIndex),
static_cast<GLsizeiptr
>(
sizeof(glm::vec3) * _uniqueIndex), _normals );
1761 glBufferSubData( GL_ARRAY_BUFFER,
static_cast<GLintptr
>(
sizeof(glm::vec3) * _uniqueIndex * 2),
static_cast<GLsizeiptr
>(
sizeof(glm::vec2) * _uniqueIndex), _texCoords );
1763 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, _vbods[1] );
1764 glBufferData( GL_ELEMENT_ARRAY_BUFFER,
static_cast<GLsizeiptr
>(
sizeof(GLuint) * _numIndices), _indices, GL_STATIC_DRAW );
1773inline std::vector<std::string> CSCI441::ModelLoader::_tokenizeString(
const std::string& input,
const std::string& delimiters) {
1777 auto retVec = std::vector<std::string>();
1778 size_t oldR = 0, r = 0;
1781 GLint lowerValidIndex = 0, upperValidIndex =
static_cast<GLint
>(input.size()) - 1;
1782 while(
static_cast<GLuint
>(lowerValidIndex) < input.size() && delimiters.find_first_of(input.at(lowerValidIndex), 0) != std::string::npos)
1785 while(upperValidIndex >= 0 && delimiters.find_first_of(input.at(upperValidIndex), 0) != std::string::npos)
1789 if(
static_cast<GLuint
>(lowerValidIndex) >= input.size() || upperValidIndex < 0 || lowerValidIndex > upperValidIndex)
1793 const std::string strippedInput = input.substr(lowerValidIndex, upperValidIndex-lowerValidIndex+1);
1797 while((r = strippedInput.find_first_of(delimiters, oldR)) != std::string::npos) {
1800 retVec.push_back(strippedInput.substr(oldR, r-oldR));
1805 retVec.push_back(strippedInput.substr(oldR, r-oldR));
1811inline void CSCI441::ModelLoader::_moveFromSrc(ModelLoader& src) {
1812 _hasVertexTexCoords = src._hasVertexTexCoords;
1813 src._hasVertexTexCoords =
false;
1815 _hasVertexNormals = src._hasVertexNormals;
1816 src._hasVertexNormals =
false;
1818 _vertices = src._vertices;
1819 src._vertices =
nullptr;
1821 _texCoords = src._texCoords;
1822 src._texCoords =
nullptr;
1824 _normals = src._normals;
1825 src._normals =
nullptr;
1827 _indices = src._indices;
1828 src._indices =
nullptr;
1830 _uniqueIndex = src._uniqueIndex;
1831 src._uniqueIndex = 0;
1833 _numIndices = src._numIndices;
1834 src._numIndices = 0;
1836 _vbods[0] = src._vbods[0];
1837 _vbods[1] = src._vbods[1];
1844 _filename = std::move(src._filename);
1847 _modelType = src._modelType;
1849 _materials = std::move(src._materials);
1850 _materialIndexStartStop = std::move(src._materialIndexStartStop);
1853inline void CSCI441::ModelLoader::_cleanupSelf() {
1855 _vertices =
nullptr;
1860 delete[] _texCoords;
1861 _texCoords =
nullptr;
1866 glDeleteBuffers( 2, _vbods );
1870 glDeleteVertexArrays( 1, &_vaod );
1873 _hasVertexTexCoords =
false;
1874 _hasVertexNormals =
false;
1879 for(
const auto& [name, material] : _materials ) {
1884 _materialIndexStartStop.clear();
1887inline unsigned char* CSCI441_INTERNAL::createTransparentTexture(
const unsigned char * imageData,
const unsigned char *imageMask,
const int texWidth,
const int texHeight,
const int texChannels,
const int maskChannels ) {
1889 auto *fullData =
new unsigned char[texWidth*texHeight*4];
1891 for(
int j = 0; j < texHeight; j++) {
1892 for(
int i = 0; i < texWidth; i++) {
1894 fullData[(j*texWidth+i)*4+0] = imageData[(j*texWidth+i)*texChannels+0];
1895 fullData[(j*texWidth+i)*4+1] = imageData[(j*texWidth+i)*texChannels+1];
1896 fullData[(j*texWidth+i)*4+2] = imageData[(j*texWidth+i)*texChannels+2];
1898 fullData[(j*texWidth+i)*4+0] = 1;
1899 fullData[(j*texWidth+i)*4+1] = 1;
1900 fullData[(j*texWidth+i)*4+2] = 1;
1904 fullData[(j*texWidth+i)*4+3] = imageMask[(j*texWidth+i)*maskChannels+0];
1906 fullData[(j*texWidth+i)*4+3] = 1;
1914inline void CSCI441_INTERNAL::flipImageY(
const int texWidth,
const int texHeight,
const int textureChannels,
unsigned char *textureData ) {
1915 for(
int j = 0; j < texHeight / 2; j++ ) {
1916 for(
int i = 0; i < texWidth; i++ ) {
1917 for(
int k = 0; k < textureChannels; k++ ) {
1918 const int top = (j*texWidth + i)*textureChannels + k;
1919 const int bot = ((texHeight-j-1)*texWidth + i)*textureChannels + k;
1921 const unsigned char t = textureData[top];
1922 textureData[top] = textureData[bot];
1923 textureData[bot] = t;
Loads object models from file and renders using VBOs/VAOs.
Definition: ModelLoader.hpp:51
ModelLoader(const ModelLoader &)=delete
do not allow models to be copied
static void enableAutoGenerateNormals()
Enable auto-generation of vertex normals.
Definition: ModelLoader.hpp:1738
void setAttributeLocations(GLint positionLocation, GLint normalLocation=-1, GLint texCoordLocation=-1) const
Enables VBO attribute array locations.
Definition: ModelLoader.hpp:307
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:368
GLfloat * getVertices() const
Return the vertex array that makes up the model mesh.
Definition: ModelLoader.hpp:365
bool loadModelFile(std::string filename, bool INFO=true, bool ERRORS=true)
Loads a model from the given file.
Definition: ModelLoader.hpp:279
GLfloat * getNormals() const
Return the normal array that corresponds to the model mesh.
Definition: ModelLoader.hpp:366
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:322
GLuint * getIndices() const
Return the index array that dictates the order to draw the model mesh.
Definition: ModelLoader.hpp:369
GLfloat * getTexCoords() const
Return the texture coordinates array that corresponds to the model mesh.
Definition: ModelLoader.hpp:367
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:364
static void disableAutoGenerateNormals()
Disable auto-generation of vertex normals.
Definition: ModelLoader.hpp:1743
~ModelLoader()
Frees memory associated with model on both CPU and GPU.
Definition: ModelLoader.hpp:247
ModelLoader()
Creates an empty model.
Definition: ModelLoader.hpp:215
Internal material representation for *.mtl files.
CSCI441 Helper Functions for OpenGL.
Definition: ArcballCam.hpp:17