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( std::
string input, const std::
string& delimiters );
176 void _allocateAttributeArrays(GLuint numVertices, GLuint numIndices);
179 std::
string _filename;
180 CSCI441_INTERNAL::MODEL_TYPE _modelType;
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;
222 loadModelFile( filename );
242inline void CSCI441::ModelLoader::_init() {
243 _hasVertexTexCoords =
false;
244 _hasVertexNormals =
false;
247 _texCoords =
nullptr;
254 glGenVertexArrays( 1, &_vaod );
255 glGenBuffers( 2, _vbods );
260 _filename = std::move(filename);
261 if( _filename.find(
".obj") != std::string::npos ) {
262 result = _loadOBJFile( INFO, ERRORS );
263 _modelType = CSCI441_INTERNAL::MODEL_TYPE::OBJ;
265 else if( _filename.find(
".off") != std::string::npos ) {
266 result = _loadOFFFile( INFO, ERRORS );
267 _modelType = CSCI441_INTERNAL::MODEL_TYPE::OFF;
269 else if( _filename.find(
".ply") != std::string::npos ) {
270 result = _loadPLYFile( INFO, ERRORS );
271 _modelType = CSCI441_INTERNAL::MODEL_TYPE::PLY;
273 else if( _filename.find(
".stl") != std::string::npos ) {
274 result = _loadSTLFile( INFO, ERRORS );
275 _modelType = CSCI441_INTERNAL::MODEL_TYPE::STL;
279 if (ERRORS) fprintf( stderr,
"[ERROR]: Unsupported file format for file: %s\n", _filename.c_str() );
287 glBindVertexArray( _vaod );
288 glBindBuffer( GL_ARRAY_BUFFER, _vbods[0] );
290 glEnableVertexAttribArray( positionLocation );
291 glVertexAttribPointer( positionLocation, 3, GL_FLOAT, GL_FALSE, 0, (
void*)
nullptr );
293 glEnableVertexAttribArray( normalLocation );
294 glVertexAttribPointer( normalLocation, 3, GL_FLOAT, GL_FALSE, 0, (
void*)(
sizeof(glm::vec3) * _uniqueIndex) );
296 glEnableVertexAttribArray( texCoordLocation );
297 glVertexAttribPointer( texCoordLocation, 2, GL_FLOAT, GL_FALSE, 0, (
void*)(
sizeof(glm::vec3) * _uniqueIndex * 2) );
302 GLint matDiffLocation, GLint matSpecLocation, GLint matShinLocation, GLint matAmbLocation,
303 GLenum diffuseTexture )
const {
304 glBindVertexArray( _vaod );
307 if( _modelType == CSCI441_INTERNAL::MODEL_TYPE::OBJ ) {
308 for(
const auto & materialIter : _materialIndexStartStop) {
309 auto materialName = materialIter.first;
310 auto indexStartStop = materialIter.second;
312 CSCI441_INTERNAL::ModelMaterial* material =
nullptr;
313 if( _materials.find( materialName ) != _materials.end() )
314 material = _materials.find( materialName )->second;
316 for(
auto & idxIter : indexStartStop) {
317 auto start = idxIter.first;
318 auto end = idxIter.second;
319 GLsizei length = (GLsizei)(end - start) + 1;
323 if( material !=
nullptr ) {
324 glProgramUniform4fv( shaderProgramHandle, matAmbLocation, 1, &material->ambient[0] );
325 glProgramUniform4fv( shaderProgramHandle, matDiffLocation, 1, &material->diffuse[0] );
326 glProgramUniform4fv( shaderProgramHandle, matSpecLocation, 1, &material->specular[0] );
327 glProgramUniform1f( shaderProgramHandle, matShinLocation, material->shininess );
329 if( material->map_Kd != -1 ) {
330 glActiveTexture( diffuseTexture );
331 glBindTexture( GL_TEXTURE_2D, material->map_Kd );
335 glDrawElements( GL_TRIANGLES, length, GL_UNSIGNED_INT, (
void*)(
sizeof(GLuint)*start) );
339 glDrawElements( GL_TRIANGLES,
static_cast<GLint
>(_numIndices), GL_UNSIGNED_INT, (
void*)
nullptr );
353inline bool CSCI441::ModelLoader::_loadOBJFile(
bool INFO,
bool ERRORS ) {
356 if ( INFO ) fprintf( stdout,
"[.obj]: -=-=-=-=-=-=-=- BEGIN %s Info -=-=-=-=-=-=-=- \n", _filename.c_str() );
361 std::ifstream in( _filename );
362 if( !in.is_open() ) {
363 if (ERRORS) fprintf( stderr,
"[.obj]: [ERROR]: Could not open \"%s\"\n", _filename.c_str() );
364 if ( INFO ) fprintf( stdout,
"[.obj]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=- \n", _filename.c_str() );
368 GLuint numObjects = 0, numGroups = 0;
369 GLuint numVertices = 0, numTexCoords = 0, numNormals = 0;
370 GLuint numFaces = 0, numTriangles = 0;
371 glm::vec3 minDimension = {999999.f, 999999.f, 999999.f};
372 glm::vec3 maxDimension = { -999999.f, -999999.f, -999999.f };
375 std::map<std::string, GLuint> uniqueCounts;
378 int progressCounter = 0;
380 while( getline( in, line ) ) {
381 if( line.length() > 1 && line.at(0) ==
'\t' )
382 line = line.substr( 1 );
383 line.erase( line.find_last_not_of(
" \n\r\t" ) + 1 );
385 std::vector< std::string > tokens = _tokenizeString( line,
" \t" );
386 if( tokens.empty() )
continue;
389 if( tokens[0] ==
"#" || tokens[0].find_first_of(
'#') == 0 ) {
391 }
else if( tokens[0] ==
"o" ) {
393 }
else if( tokens[0] ==
"g" ) {
395 }
else if( tokens[0] ==
"mtllib" ) {
396 _loadMTLFile( tokens[1].c_str(), INFO, ERRORS );
397 }
else if( tokens[0] ==
"v" ) {
400 glm::vec3 pos = { strtof( tokens[1].c_str(),
nullptr ),
401 strtof( tokens[2].c_str(),
nullptr ),
402 strtof( tokens[3].c_str(),
nullptr ) };
404 if( pos.x < minDimension.x ) minDimension.x = pos.x;
405 if( pos.x > maxDimension.x ) maxDimension.x = pos.x;
406 if( pos.y < minDimension.y ) minDimension.y = pos.y;
407 if( pos.y > maxDimension.y ) maxDimension.y = pos.y;
408 if( pos.z < minDimension.z ) minDimension.z = pos.z;
409 if( pos.z > maxDimension.z ) maxDimension.z = pos.z;
410 }
else if( tokens[0] ==
"vn" ) {
412 }
else if( tokens[0] ==
"vt" ) {
414 }
else if( tokens[0] ==
"f" ) {
417 std::vector<std::string> faceTokens = _tokenizeString(line,
" ");
419 for (GLuint i = 1; i < faceTokens.size(); i++) {
421 std::vector<std::string> groupTokens = _tokenizeString(faceTokens[i],
"/");
423 for (
char j: faceTokens[i]) {
424 if (j ==
'/') numSlashes++;
427 std::stringstream currentFaceTokenStream;
429 auto signedVertexIndex = (GLint) strtol(groupTokens[0].c_str(),
nullptr, 10);
430 GLuint vertexIndex = signedVertexIndex;
431 if (signedVertexIndex < 0) vertexIndex = numVertices + signedVertexIndex + 1;
433 currentFaceTokenStream << vertexIndex;
436 if (groupTokens.size() == 2 && numSlashes == 1) {
437 _hasVertexTexCoords =
true;
439 auto signedTexCoordIndex = (GLint) strtol(groupTokens[1].c_str(),
nullptr, 10);
440 GLuint texCoordIndex = signedTexCoordIndex;
441 if (signedTexCoordIndex < 0) texCoordIndex = numTexCoords + signedTexCoordIndex + 1;
443 currentFaceTokenStream <<
"/" << texCoordIndex;
444 }
else if (groupTokens.size() == 2 && numSlashes == 2) {
445 _hasVertexNormals =
true;
447 auto signedNormalIndex = (GLint) strtol(groupTokens[1].c_str(),
nullptr, 10);
448 GLuint normalIndex = signedNormalIndex;
449 if (signedNormalIndex < 0) normalIndex = numNormals + signedNormalIndex + 1;
451 currentFaceTokenStream <<
"//" << normalIndex;
452 }
else if (groupTokens.size() == 3) {
453 _hasVertexTexCoords =
true;
454 _hasVertexNormals =
true;
456 auto signedTexCoordIndex = (GLint) strtol(groupTokens[1].c_str(),
nullptr, 10);
457 GLuint texCoordIndex = signedTexCoordIndex;
458 if (signedTexCoordIndex < 0) texCoordIndex = numTexCoords + signedTexCoordIndex + 1;
460 auto signedNormalIndex = (GLint) strtol(groupTokens[2].c_str(),
nullptr, 10);
461 GLuint normalIndex = signedNormalIndex;
462 if (signedNormalIndex < 0) normalIndex = numNormals + signedNormalIndex + 1;
464 currentFaceTokenStream <<
"/" << texCoordIndex <<
"/" << normalIndex;
465 }
else if (groupTokens.size() != 1) {
466 if (ERRORS) fprintf(stderr,
"[.obj]: [ERROR]: Malformed OBJ file, %s.\n", _filename.c_str());
470 std::string processedFaceToken = currentFaceTokenStream.str();
471 if (uniqueCounts.find(processedFaceToken) == uniqueCounts.end()) {
472 uniqueCounts.insert(std::pair<std::string, long int>(processedFaceToken, _uniqueIndex));
477 numTriangles += (faceTokens.size() - 1 - 3 + 1);
480 }
else if( tokens[0] ==
"usemtl" ) {
483 if (INFO) printf(
"[.obj]: ignoring line: %s\n", line.c_str() );
488 if( progressCounter % 5000 == 0 ) {
490 switch( progressCounter ) {
491 case 5000: printf(
"[.obj]: scanning %s...\\", _filename.c_str());
break;
492 case 10000: printf(
"[.obj]: scanning %s...|", _filename.c_str());
break;
493 case 15000: printf(
"[.obj]: scanning %s.../", _filename.c_str());
break;
494 case 20000: printf(
"[.obj]: scanning %s...-", _filename.c_str());
break;
499 if( progressCounter == 20000 )
506 printf(
"\33[2K\r" );
507 printf(
"[.obj]: scanning %s...done!\n", _filename.c_str() );
508 printf(
"[.obj]: ------------\n" );
509 printf(
"[.obj]: Model Stats:\n" );
510 printf(
"[.obj]: Vertices: \t%u\tNormals: \t%u\tTex Coords:\t%u\n", numVertices, numNormals, numTexCoords );
511 printf(
"[.obj]: Unique Verts:\t%u\n", _uniqueIndex );
512 printf(
"[.obj]: Faces: \t%u\tTriangles:\t%u\n", numFaces, numTriangles );
513 printf(
"[.obj]: Objects: \t%u\tGroups: \t%u\n", numObjects, numGroups );
515 glm::vec3 sizeDimensions = maxDimension - minDimension;
516 printf(
"[.obj]: Dimensions:\t(%f, %f, %f)\n", sizeDimensions.x, sizeDimensions.y, sizeDimensions.z );
519 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
520 if (INFO && !_hasVertexNormals)
521 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" );
522 _allocateAttributeArrays(_uniqueIndex, numTriangles*3);
524 if (INFO) printf(
"[.obj]: No vertex normals exist on model, vertex normals will be autogenerated\n" );
525 _allocateAttributeArrays(numTriangles * 3, numTriangles*3);
528 auto objVertices =
new glm::vec3[numVertices];
529 auto objNormals =
new glm::vec3[numNormals];
530 auto objTexCoords =
new glm::vec2[numTexCoords];
532 std::vector<glm::vec3> verticesTemps;
533 std::vector<glm::vec2> texCoordsTemp;
535 printf(
"[.obj]: ------------\n" );
537 uniqueCounts.clear();
541 in.open( _filename );
543 GLuint verticesSeen = 0, texCoordsSeen = 0, normalsSeen = 0, indicesSeen = 0;
544 GLuint uniqueNumVertices = 0;
546 std::string currentMaterial =
"default";
547 _materialIndexStartStop.insert( std::pair< std::string, std::vector< std::pair< GLuint, GLuint > > >( currentMaterial, std::vector< std::pair< GLuint, GLuint > >(1) ) );
548 _materialIndexStartStop.find( currentMaterial )->second.back().first = indicesSeen;
550 while( getline( in, line ) ) {
551 if( line.length() > 1 && line.at(0) ==
'\t' )
552 line = line.substr( 1 );
553 line.erase( line.find_last_not_of(
" \n\r\t" ) + 1 );
555 auto tokens = _tokenizeString( line,
" \t" );
556 if( tokens.empty() )
continue;
559 if( tokens[0] ==
"#" || tokens[0].find_first_of(
'#') == 0
562 || tokens[0] ==
"mtllib"
566 }
else if( tokens[0] ==
"usemtl" ) {
567 if( currentMaterial ==
"default" && indicesSeen == 0 ) {
568 _materialIndexStartStop.clear();
570 _materialIndexStartStop.find( currentMaterial )->second.back().second = indicesSeen - 1;
572 currentMaterial = tokens[1];
573 if( _materialIndexStartStop.find( currentMaterial ) == _materialIndexStartStop.end() ) {
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 _materialIndexStartStop.find( currentMaterial )->second.emplace_back( indicesSeen, -1 );
579 }
else if( tokens[0] ==
"v" ) {
580 objVertices[verticesSeen++] = glm::vec3(strtof(tokens[1].c_str(),
nullptr ),
581 strtof( tokens[2].c_str(),
nullptr ),
582 strtof( tokens[3].c_str(),
nullptr ) );
583 }
else if( tokens[0] ==
"vn" ) {
584 objNormals[normalsSeen++] = glm::vec3(strtof(tokens[1].c_str(),
nullptr ),
585 strtof( tokens[2].c_str(),
nullptr ),
586 strtof( tokens[3].c_str(),
nullptr ));
587 }
else if( tokens[0] ==
"vt" ) {
588 objTexCoords[texCoordsSeen++] = glm::vec2(strtof(tokens[1].c_str(),
nullptr ),
589 strtof( tokens[2].c_str(),
nullptr ));
590 }
else if( tokens[0] ==
"f" ) {
591 std::vector<std::string> processedFaceTokens;
593 bool faceHasVertexNormals =
false;
594 bool faceHasTextureCoordinates =
false;
596 for(GLuint i = 1; i < tokens.size(); i++) {
598 auto vertexAttributeTokens = _tokenizeString(tokens[i],
"/");
599 int numAttributeSlashes = 0;
600 for(
char j : tokens[i]) {
601 if(j ==
'/') numAttributeSlashes++;
604 GLuint vertexIndex = 0, texCoordIndex = 0, normalIndex = 0;
606 std::stringstream currentFaceTokenStream;
607 auto signedVertexIndex = (GLint)strtol(vertexAttributeTokens[0].c_str(),
nullptr, 10);
608 vertexIndex = signedVertexIndex;
609 if(signedVertexIndex < 0) vertexIndex = verticesSeen + signedVertexIndex + 1;
610 currentFaceTokenStream << vertexIndex;
613 if(vertexAttributeTokens.size() == 2 && numAttributeSlashes == 1) {
615 _hasVertexTexCoords =
true;
616 faceHasTextureCoordinates =
true;
618 auto signedTexCoordIndex = (GLint)strtol(vertexAttributeTokens[1].c_str(),
nullptr, 10);
619 texCoordIndex = signedTexCoordIndex;
620 if(signedTexCoordIndex < 0) texCoordIndex = texCoordsSeen + signedTexCoordIndex + 1;
621 currentFaceTokenStream <<
"/" << texCoordIndex;
622 }
else if(vertexAttributeTokens.size() == 2 && numAttributeSlashes == 2) {
624 _hasVertexNormals =
true;
625 faceHasVertexNormals =
true;
627 auto signedNormalIndex = (GLint)strtol(vertexAttributeTokens[1].c_str(),
nullptr, 10);
628 normalIndex = signedNormalIndex;
629 if(signedNormalIndex < 0) normalIndex = normalsSeen + signedNormalIndex + 1;
630 currentFaceTokenStream <<
"//" << normalIndex;
631 }
else if(vertexAttributeTokens.size() == 3) {
633 _hasVertexTexCoords =
true;
634 faceHasTextureCoordinates =
true;
635 _hasVertexNormals =
true;
636 faceHasVertexNormals =
true;
638 auto signedTexCoordIndex = (GLint)strtol(vertexAttributeTokens[1].c_str(),
nullptr, 10);
639 texCoordIndex = signedTexCoordIndex;
640 if(signedTexCoordIndex < 0) texCoordIndex = texCoordsSeen + signedTexCoordIndex + 1;
642 auto signedNormalIndex = (GLint)strtol(vertexAttributeTokens[2].c_str(),
nullptr, 10);
643 normalIndex = signedNormalIndex;
644 if(signedNormalIndex < 0) normalIndex = normalsSeen + signedNormalIndex + 1;
646 currentFaceTokenStream <<
"/" << texCoordIndex <<
"/" << normalIndex;
647 }
else if(vertexAttributeTokens.size() != 1) {
648 if (ERRORS) fprintf(stderr,
"[.obj]: [ERROR]: Malformed OBJ file, %s.\n", _filename.c_str());
652 auto processedFaceToken = currentFaceTokenStream.str();
653 processedFaceTokens.push_back(processedFaceToken);
656 if( uniqueCounts.find( processedFaceToken ) == uniqueCounts.end() ) {
658 uniqueCounts.insert( std::pair<std::string,GLuint>(processedFaceToken, uniqueNumVertices) );
660 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
661 _vertices[ _uniqueIndex ] = objVertices[ vertexIndex - 1 ];
662 if(faceHasTextureCoordinates && texCoordIndex != 0) { _texCoords[ _uniqueIndex ] = objTexCoords[ texCoordIndex - 1 ]; }
663 if(faceHasVertexNormals && normalIndex != 0) _normals[ _uniqueIndex ] = objNormals[ normalIndex - 1 ];
666 verticesTemps.push_back( objVertices[ vertexIndex - 1 ] );
667 if(faceHasTextureCoordinates && texCoordIndex != 0) { texCoordsTemp.push_back( objTexCoords[ texCoordIndex - 1 ] ); }
669 if( (vertexAttributeTokens.size() == 2 && numAttributeSlashes == 2)
670 || (vertexAttributeTokens.size() == 3) ) {
672 if (ERRORS) fprintf( stderr,
"[.obj]: [ERROR]: no vertex normals were specified, should not be trying to access values\n" );
679 for(GLuint i = 1; i < processedFaceTokens.size()-1; i++) {
680 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
681 _indices[ indicesSeen++ ] = uniqueCounts.find( processedFaceTokens[0] )->second;
682 _indices[ indicesSeen++ ] = uniqueCounts.find( processedFaceTokens[i] )->second;
683 _indices[ indicesSeen++ ] = uniqueCounts.find( processedFaceTokens[i+1] )->second;
687 GLuint aI = uniqueCounts.find( processedFaceTokens[0] )->second;
688 GLuint bI = uniqueCounts.find( processedFaceTokens[i] )->second;
689 GLuint cI = uniqueCounts.find( processedFaceTokens[i+1] )->second;
691 glm::vec3 a = verticesTemps[aI];
692 glm::vec3 b = verticesTemps[bI];
693 glm::vec3 c = verticesTemps[cI];
695 glm::vec3 ab = b - a; glm::vec3 ac = c - a;
696 glm::vec3 ba = a - b; glm::vec3 bc = c - b;
697 glm::vec3 ca = a - c; glm::vec3 cb = b - c;
699 glm::vec3 aN = glm::normalize( glm::cross( ab, ac ) );
700 glm::vec3 bN = glm::normalize( glm::cross( bc, ba ) );
701 glm::vec3 cN = glm::normalize( glm::cross( ca, cb ) );
703 _vertices[ _uniqueIndex ] = a;
704 _normals[ _uniqueIndex ] = aN;
705 if( faceHasTextureCoordinates && _hasVertexTexCoords ) { _texCoords[ _uniqueIndex ] = texCoordsTemp[ aI ]; }
706 _indices[ _numIndices++ ] = _uniqueIndex++;
710 _vertices[ _uniqueIndex ] = b;
711 _normals[ _uniqueIndex ] = bN;
712 if( faceHasTextureCoordinates && _hasVertexTexCoords ) { _texCoords[ _uniqueIndex ] = texCoordsTemp[ bI ]; }
713 _indices[ _numIndices++ ] = _uniqueIndex++;
717 _vertices[ _uniqueIndex ] = c;
718 _normals[ _uniqueIndex ] = cN;
719 if( faceHasTextureCoordinates && _hasVertexTexCoords ) { _texCoords[ _uniqueIndex ] = texCoordsTemp[ cI ]; }
720 _indices[ _numIndices++ ] = _uniqueIndex++;
729 if( progressCounter % 5000 == 0 ) {
731 switch( progressCounter ) {
732 case 5000: printf(
"[.obj]: parsing %s...\\", _filename.c_str());
break;
733 case 10000: printf(
"[.obj]: parsing %s...|", _filename.c_str());
break;
734 case 15000: printf(
"[.obj]: parsing %s.../", _filename.c_str());
break;
735 case 20000: printf(
"[.obj]: parsing %s...-", _filename.c_str());
break;
740 if( progressCounter == 20000 )
748 printf(
"\33[2K\r" );
749 printf(
"[.obj]: parsing %s...done!\n", _filename.c_str() );
752 _materialIndexStartStop.find( currentMaterial )->second.back().second = indicesSeen - 1;
757 double seconds = difftime( end, start );
760 printf(
"[.obj]: Completed in %.3fs\n", seconds );
761 printf(
"[.obj]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=- \n\n", _filename.c_str() );
767inline bool CSCI441::ModelLoader::_loadMTLFile(
const char* mtlFilename,
bool INFO,
bool ERRORS ) {
770 if (INFO) printf(
"[.mtl]: -*-*-*-*-*-*-*- BEGIN %s Info -*-*-*-*-*-*-*-\n", mtlFilename );
774 if( _filename.find(
'/') != std::string::npos ) {
775 path = _filename.substr( 0, _filename.find_last_of(
'/')+1 );
781 in.open( mtlFilename );
782 if( !in.is_open() ) {
783 std::string folderMtlFile = path + mtlFilename;
784 in.open( folderMtlFile.c_str() );
785 if( !in.is_open() ) {
786 if (ERRORS) fprintf( stderr,
"[.mtl]: [ERROR]: could not open material file: %s\n", mtlFilename );
787 if ( INFO ) printf(
"[.mtl]: -*-*-*-*-*-*-*- END %s Info -*-*-*-*-*-*-*-\n", mtlFilename );
792 CSCI441_INTERNAL::ModelMaterial* currentMaterial =
nullptr;
793 std::string materialName;
795 unsigned char *textureData =
nullptr;
796 unsigned char *maskData =
nullptr;
797 unsigned char *fullData;
798 int texWidth, texHeight, textureChannels = 1, maskChannels = 1;
799 GLuint textureHandle = 0;
801 std::map< std::string, GLuint > imageHandles;
803 int numMaterials = 0;
805 while( getline( in, line ) ) {
806 if( line.length() > 1 && line.at(0) ==
'\t' )
807 line = line.substr( 1 );
808 line.erase( line.find_last_not_of(
" \n\r\t" ) + 1 );
810 std::vector< std::string > tokens = _tokenizeString( line,
" /" );
811 if( tokens.empty() )
continue;
814 if( tokens[0] ==
"#" ) {
816 }
else if( tokens[0] ==
"newmtl" ) {
817 if (INFO) printf(
"[.mtl]: Parsing material %s properties\n", tokens[1].c_str() );
818 currentMaterial =
new CSCI441_INTERNAL::ModelMaterial();
819 materialName = tokens[1];
820 _materials.insert( std::pair<std::string, CSCI441_INTERNAL::ModelMaterial*>( materialName, currentMaterial ) );
823 textureData =
nullptr;
829 }
else if( tokens[0] ==
"Ka" ) {
830 currentMaterial->ambient[0] = strtof( tokens[1].c_str(),
nullptr );
831 currentMaterial->ambient[1] = strtof( tokens[2].c_str(),
nullptr );
832 currentMaterial->ambient[2] = strtof( tokens[3].c_str(),
nullptr );
833 }
else if( tokens[0] ==
"Kd" ) {
834 currentMaterial->diffuse[0] = strtof( tokens[1].c_str(),
nullptr );
835 currentMaterial->diffuse[1] = strtof( tokens[2].c_str(),
nullptr );
836 currentMaterial->diffuse[2] = strtof( tokens[3].c_str(),
nullptr );
837 }
else if( tokens[0] ==
"Ks" ) {
838 currentMaterial->specular[0] = strtof( tokens[1].c_str(),
nullptr );
839 currentMaterial->specular[1] = strtof( tokens[2].c_str(),
nullptr );
840 currentMaterial->specular[2] = strtof( tokens[3].c_str(),
nullptr );
841 }
else if( tokens[0] ==
"Ke" ) {
842 currentMaterial->emissive[0] = strtof( tokens[1].c_str(),
nullptr );
843 currentMaterial->emissive[1] = strtof( tokens[2].c_str(),
nullptr );
844 currentMaterial->emissive[2] = strtof( tokens[3].c_str(),
nullptr );
845 }
else if( tokens[0] ==
"Ns" ) {
846 currentMaterial->shininess = strtof( tokens[1].c_str(),
nullptr );
847 }
else if( tokens[0] ==
"Tr"
848 || tokens[0] ==
"d" ) {
849 currentMaterial->ambient[3] = strtof( tokens[1].c_str(),
nullptr );
850 currentMaterial->diffuse[3] = strtof( tokens[1].c_str(),
nullptr );
851 currentMaterial->specular[3] = strtof( tokens[1].c_str(),
nullptr );
852 }
else if( tokens[0] ==
"illum" ) {
854 }
else if( tokens[0] ==
"map_Kd" ) {
855 if( imageHandles.find( tokens[1] ) != imageHandles.end() ) {
857 currentMaterial->map_Kd = imageHandles.find( tokens[1] )->second;
859 stbi_set_flip_vertically_on_load(
true);
860 textureData = stbi_load( tokens[1].c_str(), &texWidth, &texHeight, &textureChannels, 0 );
862 std::string folderName = path + tokens[1];
863 textureData = stbi_load( folderName.c_str(), &texWidth, &texHeight, &textureChannels, 0 );
867 if (ERRORS) fprintf( stderr,
"[.mtl]: [ERROR]: File Not Found: %s\n", tokens[1].c_str() );
869 if (INFO) printf(
"[.mtl]: TextureMap:\t%s\tSize: %dx%d\tColors: %d\n", tokens[1].c_str(), texWidth, texHeight, textureChannels );
871 if( maskData ==
nullptr ) {
872 if( textureHandle == 0 ) {
873 glGenTextures( 1, &textureHandle );
874 imageHandles.insert( std::pair<std::string, GLuint>( tokens[1], textureHandle ) );
877 glBindTexture( GL_TEXTURE_2D, textureHandle );
879 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
880 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
882 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
883 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
885 GLint colorSpace = GL_RGB;
886 if( textureChannels == 4 )
887 colorSpace = GL_RGBA;
888 glTexImage2D( GL_TEXTURE_2D, 0, colorSpace, texWidth, texHeight, 0, colorSpace, GL_UNSIGNED_BYTE, textureData );
890 currentMaterial->map_Kd = textureHandle;
892 fullData = CSCI441_INTERNAL::createTransparentTexture( textureData, maskData, texWidth, texHeight, textureChannels, maskChannels );
894 if( textureHandle == 0 ) {
895 glGenTextures( 1, &textureHandle );
896 imageHandles.insert( std::pair<std::string, GLuint>( tokens[1], textureHandle ) );
899 glBindTexture( GL_TEXTURE_2D, textureHandle );
901 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
902 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
904 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
905 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
907 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, texWidth, texHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, fullData );
911 currentMaterial->map_Kd = textureHandle;
915 }
else if( tokens[0] ==
"map_d" ) {
916 if( imageHandles.find( tokens[1] ) != imageHandles.end() ) {
918 currentMaterial->map_d = imageHandles.find( tokens[1] )->second;
920 stbi_set_flip_vertically_on_load(
true);
921 maskData = stbi_load( tokens[1].c_str(), &texWidth, &texHeight, &textureChannels, 0 );
923 std::string folderName = path + tokens[1];
924 maskData = stbi_load( folderName.c_str(), &texWidth, &texHeight, &textureChannels, 0 );
928 if (ERRORS) fprintf( stderr,
"[.mtl]: [ERROR]: File Not Found: %s\n", tokens[1].c_str() );
930 if (INFO) printf(
"[.mtl]: AlphaMap: \t%s\tSize: %dx%d\tColors: %d\n", tokens[1].c_str(), texWidth, texHeight, maskChannels );
932 if( textureData !=
nullptr ) {
933 fullData = CSCI441_INTERNAL::createTransparentTexture( textureData, maskData, texWidth, texHeight, textureChannels, maskChannels );
935 if( textureHandle == 0 ) {
936 glGenTextures( 1, &textureHandle );
937 imageHandles.insert( std::pair<std::string, GLuint>( tokens[1], textureHandle ) );
940 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
941 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
943 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
944 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
946 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, texWidth, texHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, fullData );
950 currentMaterial->map_Kd = textureHandle;
954 }
else if( tokens[0] ==
"map_Ka" ) {
956 }
else if( tokens[0] ==
"map_Ks" ) {
958 }
else if( tokens[0] ==
"map_Ns" ) {
960 }
else if( tokens[0] ==
"Ni" ) {
962 }
else if( tokens[0] ==
"Tf" ) {
964 }
else if( tokens[0] ==
"bump"
965 || tokens[0] ==
"map_bump" ) {
968 if (INFO) printf(
"[.mtl]: ignoring line: %s\n", line.c_str() );
975 printf(
"[.mtl]: Materials:\t%d\n", numMaterials );
976 printf(
"[.mtl]: -*-*-*-*-*-*-*- END %s Info -*-*-*-*-*-*-*-\n", mtlFilename );
982inline bool CSCI441::ModelLoader::_loadOFFFile(
bool INFO,
bool ERRORS ) {
985 if (INFO ) printf(
"[.off]: -=-=-=-=-=-=-=- BEGIN %s Info -=-=-=-=-=-=-=-\n", _filename.c_str() );
990 std::ifstream in( _filename );
991 if( !in.is_open() ) {
992 if (ERRORS) fprintf( stderr,
"[.off]: [ERROR]: Could not open \"%s\"\n", _filename.c_str() );
993 if ( INFO ) printf(
"[.off]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
997 GLuint numVertices = 0, numFaces = 0, numTriangles = 0;
998 GLfloat minX = 999999.0f, maxX = -999999.0f, minY = 999999.0f, maxY = -999999.0f, minZ = 999999.0f, maxZ = -999999.0f;
1001 enum OFF_FILE_STATE { HEADER, VERTICES, FACES, DONE };
1003 OFF_FILE_STATE fileState = HEADER;
1005 GLuint vSeen = 0, fSeen = 0;
1007 while( getline( in, line ) ) {
1008 if( line.length() > 1 && line.at(0) ==
'\t' )
1009 line = line.substr( 1 );
1010 line.erase( line.find_last_not_of(
" \n\r\t" ) + 1 );
1012 std::vector< std::string > tokens = _tokenizeString( line,
" \t" );
1013 if( tokens.empty() )
continue;
1016 if( tokens[0] ==
"#" || tokens[0].find_first_of(
'#') == 0 ) {
1017 }
else if( fileState == HEADER ) {
1018 if( tokens[0] ==
"OFF" ) {
1020 if( tokens.size() != 3 ) {
1021 if (ERRORS) fprintf( stderr,
"[.off]: [ERROR]: Malformed OFF file. # vertices, faces, edges not properly specified\n" );
1022 if ( INFO ) printf(
"[.off]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1027 numVertices = (GLuint)strtol( tokens[0].c_str(),
nullptr, 10 );
1028 numFaces = (GLuint)strtol( tokens[1].c_str(),
nullptr, 10 );
1033 fileState = VERTICES;
1035 }
else if( fileState == VERTICES ) {
1037 GLfloat x = strtof( tokens[0].c_str(),
nullptr ),
1038 y = strtof( tokens[1].c_str(),
nullptr ),
1039 z = strtof( tokens[2].c_str(),
nullptr );
1041 if( x < minX ) minX = x;
1042 if( x > maxX ) maxX = x;
1043 if( y < minY ) minY = y;
1044 if( y > maxY ) maxY = y;
1045 if( z < minZ ) minZ = z;
1046 if( z > maxZ ) maxZ = z;
1049 if( vSeen == numVertices )
1051 }
else if( fileState == FACES ) {
1052 auto numberOfVerticesInFace = (GLuint)strtol( tokens[0].c_str(),
nullptr, 10 );
1054 numTriangles += numberOfVerticesInFace - 3 + 1;
1056 if( fSeen == numFaces )
1059 if (INFO) printf(
"[.off]: unknown file state: %d\n", fileState );
1065 printf(
"\33[2K\r" );
1066 printf(
"[.off]: scanning %s...done!\n", _filename.c_str() );
1067 printf(
"[.off]: ------------\n" );
1068 printf(
"[.off]: Model Stats:\n" );
1069 printf(
"[.off]: Vertices: \t%u\tNormals: \t%u\tTex Coords:\t%u\n", numVertices, 0, 0 );
1070 printf(
"[.off]: Faces: \t%u\tTriangles: \t%u\n", numFaces, numTriangles );
1071 printf(
"[.off]: Dimensions:\t(%f, %f, %f)\n", (maxX - minX), (maxY - minY), (maxZ - minZ) );
1074 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
1075 if (INFO && !_hasVertexNormals)
1076 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" );
1077 _allocateAttributeArrays(numVertices, numTriangles*3);
1079 if (INFO) printf(
"[.off]: No vertex normals exist on model, vertex normals will be autogenerated\n" );
1080 _allocateAttributeArrays(numTriangles*3, numTriangles*3);
1083 std::vector<glm::vec3> verticesTemp;
1085 if (INFO) printf(
"[.off]: ------------\n" );
1087 in.open( _filename );
1095 int progressCounter = 0;
1097 while( getline( in, line ) ) {
1098 if( line.length() > 1 && line.at(0) ==
'\t' )
1099 line = line.substr( 1 );
1100 line.erase( line.find_last_not_of(
" \n\r\t" ) + 1 );
1102 std::vector< std::string > tokens = _tokenizeString( line,
" \t" );
1103 if( tokens.empty() )
continue;
1106 if( tokens[0] ==
"#" || tokens[0].find_first_of(
'#') == 0 ) {
1108 }
else if( fileState == HEADER ) {
1109 if( tokens[0] ==
"OFF" ) {
1113 fileState = VERTICES;
1115 }
else if( fileState == VERTICES ) {
1117 glm::vec3 pos(strtof( tokens[0].c_str(),
nullptr ),
1118 strtof( tokens[1].c_str(),
nullptr ),
1119 strtof( tokens[2].c_str(),
nullptr ) );
1122 if( tokens.size() == 6 || tokens.size() == 7 ) {
1130 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
1131 _vertices[ _uniqueIndex ] = pos;
1134 verticesTemp.push_back(pos);
1138 if( _uniqueIndex == numVertices || vSeen == numVertices )
1140 }
else if( fileState == FACES ) {
1141 auto numberOfVerticesInFace = (GLuint)strtol( tokens[0].c_str(),
nullptr, 10 );
1144 for(GLuint i = 2; i <= numberOfVerticesInFace - 1; i++) {
1145 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
1146 auto fanRoot = strtol( tokens[1].c_str(),
nullptr, 10 );
1147 auto fanA = strtol( tokens[i].c_str(),
nullptr, 10 );
1148 auto fanB = strtol( tokens[i+1].c_str(),
nullptr, 10 );
1150 if( fanRoot < 0 ) fanRoot = numVertices + fanRoot + 1;
1151 if( fanA < 0 ) fanA = numVertices + fanA + 1;
1152 if( fanB < 0 ) fanB = numVertices + fanB + 1;
1155 _indices[ _numIndices++ ] = fanRoot;
1156 _indices[ _numIndices++ ] = fanA;
1157 _indices[ _numIndices++ ] = fanB;
1159 auto aI = strtol( tokens[1].c_str(),
nullptr, 10 );
1160 auto bI = strtol( tokens[i].c_str(),
nullptr, 10 );
1161 auto cI = strtol( tokens[i+1].c_str(),
nullptr, 10 );
1163 if( aI < 0 ) aI = numVertices + aI + 1;
1164 if( bI < 0 ) bI = numVertices + bI + 1;
1165 if( cI < 0 ) cI = numVertices + cI + 1;
1167 glm::vec3 a = verticesTemp[aI];
1168 glm::vec3 b = verticesTemp[bI];
1169 glm::vec3 c = verticesTemp[cI];
1171 glm::vec3 ab = b - a; glm::vec3 ac = c - a;
1172 glm::vec3 ba = a - b; glm::vec3 bc = c - b;
1173 glm::vec3 ca = a - c; glm::vec3 cb = b - c;
1175 glm::vec3 aN = glm::normalize( glm::cross( ab, ac ) );
1176 glm::vec3 bN = glm::normalize( glm::cross( bc, ba ) );
1177 glm::vec3 cN = glm::normalize( glm::cross( ca, cb ) );
1179 _vertices[ _uniqueIndex ] = a;
1180 _normals[ _uniqueIndex ] = aN;
1181 _indices[ _numIndices++ ] = _uniqueIndex++;
1183 _vertices[ _uniqueIndex ] = b;
1184 _normals[ _uniqueIndex ] = bN;
1185 _indices[ _numIndices++ ] = _uniqueIndex++;
1187 _vertices[ _uniqueIndex ] = c;
1188 _normals[ _uniqueIndex ] = cN;
1189 _indices[ _numIndices++ ] = _uniqueIndex++;
1214 if( progressCounter % 5000 == 0 ) {
1216 switch( progressCounter ) {
1217 case 5000: printf(
"[.off]: parsing %s...\\", _filename.c_str());
break;
1218 case 10000: printf(
"[.off]: parsing %s...|", _filename.c_str());
break;
1219 case 15000: printf(
"[.off]: parsing %s.../", _filename.c_str());
break;
1220 case 20000: printf(
"[.off]: parsing %s...-", _filename.c_str());
break;
1225 if( progressCounter == 20000 )
1226 progressCounter = 0;
1234 double seconds = difftime( end, start );
1237 printf(
"\33[2K\r" );
1238 printf(
"[.off]: parsing %s...done! (Time: %.1fs)\n", _filename.c_str(), seconds );
1239 printf(
"[.off]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1246inline bool CSCI441::ModelLoader::_loadPLYFile(
bool INFO,
bool ERRORS ) {
1249 if (INFO ) printf(
"[.ply]: -=-=-=-=-=-=-=- BEGIN %s Info -=-=-=-=-=-=-=-\n", _filename.c_str() );
1254 std::ifstream in( _filename );
1255 if( !in.is_open() ) {
1256 if (ERRORS) fprintf( stderr,
"[.ply]: [ERROR]: Could not open \"%s\"\n", _filename.c_str() );
1257 if ( INFO ) printf(
"[.ply]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1261 GLuint numVertices = 0, numFaces = 0, numTriangles = 0, numMaterials = 0;
1262 GLfloat minX = 999999.0f, maxX = -999999.0f, minY = 999999.0f, maxY = -999999.0f, minZ = 999999.0f, maxZ = -999999.0f;
1265 enum PLY_FILE_STATE { HEADER, VERTICES, FACES, MATERIALS };
1266 enum PLY_ELEMENT_TYPE { NONE, VERTEX, FACE, MATERIAL };
1268 PLY_FILE_STATE fileState = HEADER;
1269 PLY_ELEMENT_TYPE elemType = NONE;
1271 GLuint progressCounter = 0;
1274 while( getline( in, line ) ) {
1275 if( line.length() > 1 && line.at(0) ==
'\t' )
1276 line = line.substr( 1 );
1277 line.erase( line.find_last_not_of(
" \n\r\t" ) + 1 );
1279 std::vector< std::string > tokens = _tokenizeString( line,
" \t" );
1281 if( tokens.empty() )
continue;
1284 if( tokens[0] ==
"comment" ) {
1285 }
else if( fileState == HEADER ) {
1286 if( tokens[0] ==
"ply" ) {
1287 }
else if( tokens[0] ==
"format" ) {
1288 if( tokens[1] !=
"ascii" ) {
1289 if (ERRORS) fprintf( stderr,
"[.ply]: [ERROR]: File \"%s\" not ASCII format\n", _filename.c_str() );
1290 if ( INFO ) printf(
"[.ply]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1294 }
else if( tokens[0] ==
"element" ) {
1295 if( tokens[1] ==
"vertex" ) {
1296 numVertices = (GLuint)strtol( tokens[2].c_str(),
nullptr, 10 );
1298 }
else if( tokens[1] ==
"face" ) {
1299 numFaces = (GLuint)strtol( tokens[2].c_str(),
nullptr, 10 );
1301 }
else if( tokens[1] ==
"edge" ) {
1303 }
else if( tokens[1] ==
"material" ) {
1304 numMaterials = (GLuint)strtol( tokens[2].c_str(),
nullptr, 10 );
1305 elemType = MATERIAL;
1309 }
else if( tokens[0] ==
"property" ) {
1310 if( elemType == VERTEX ) {
1312 }
else if( elemType == FACE ) {
1314 }
else if( elemType == MATERIAL ) {
1317 }
else if( tokens[0] ==
"end_header" ) {
1318 fileState = VERTICES;
1320 }
else if( fileState == VERTICES ) {
1322 auto x = (GLfloat) strtof( tokens[0].c_str(), nullptr ),
1323 y = (GLfloat) strtof( tokens[1].c_str(),
nullptr ),
1324 z = (GLfloat) strtof( tokens[2].c_str(), nullptr );
1326 if( x < minX ) minX = x;
1327 if( x > maxX ) maxX = x;
1328 if( y < minY ) minY = y;
1329 if( y > maxY ) maxY = y;
1330 if( z < minZ ) minZ = z;
1331 if( z > maxZ ) maxZ = z;
1335 if( vSeen == numVertices )
1337 }
else if( fileState == FACES ) {
1338 auto numberOfVerticesInFace = (GLuint)strtol( tokens[0].c_str(),
nullptr, 10);
1339 numTriangles += numberOfVerticesInFace - 3 + 1;
1341 if (INFO) printf(
"[.ply]: unknown file state: %d\n", fileState );
1346 if( progressCounter % 5000 == 0 ) {
1348 switch( progressCounter ) {
1349 case 5000: printf(
"[.ply]: scanning %s...\\", _filename.c_str());
break;
1350 case 10000: printf(
"[.ply]: scanning %s...|", _filename.c_str());
break;
1351 case 15000: printf(
"[.ply]: scanning %s.../", _filename.c_str());
break;
1352 case 20000: printf(
"[.ply]: scanning %s...-", _filename.c_str());
break;
1357 if( progressCounter == 20000 )
1358 progressCounter = 0;
1364 printf(
"\33[2K\r" );
1365 printf(
"[.ply]: scanning %s...done!\n", _filename.c_str() );
1366 printf(
"[.ply]: ------------\n" );
1367 printf(
"[.ply]: Model Stats:\n" );
1368 printf(
"[.ply]: Vertices: \t%u\tNormals: \t%u\tTex Coords:\t%u\n", numVertices, 0, 0 );
1369 printf(
"[.ply]: Faces: \t%u\tTriangles: \t%u\n", numFaces, numTriangles );
1370 printf(
"[.ply]: Dimensions:\t(%f, %f, %f)\n", (maxX - minX), (maxY - minY), (maxZ - minZ) );
1373 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
1374 if (INFO && !_hasVertexNormals)
1375 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" );
1376 _allocateAttributeArrays(numVertices, numTriangles*3);
1378 if (INFO) printf(
"[.ply]: No vertex normals exist on model, vertex normals will be autogenerated\n" );
1379 _allocateAttributeArrays(numTriangles*3, numTriangles*3);
1382 if (INFO) printf(
"[.ply]: ------------\n" );
1384 std::vector<glm::vec3> verticesTemp;
1386 in.open( _filename );
1394 progressCounter = 0;
1397 while( getline( in, line ) ) {
1398 if( line.length() > 1 && line.at(0) ==
'\t' )
1399 line = line.substr( 1 );
1400 line.erase( line.find_last_not_of(
" \n\r\t" ) + 1 );
1402 std::vector< std::string > tokens = _tokenizeString( line,
" \t" );
1404 if( tokens.empty() )
continue;
1407 if( tokens[0] ==
"comment" ) {
1408 }
else if( fileState == HEADER ) {
1409 if( tokens[0] ==
"ply" ) {
1410 }
else if( tokens[0] ==
"format" ) {
1411 if( tokens[1] !=
"ascii" ) {
1412 if (ERRORS) fprintf( stderr,
"[.ply]: [ERROR]: File \"%s\" not ASCII format\n", _filename.c_str() );
1413 if ( INFO ) printf(
"[.ply]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1417 }
else if( tokens[0] ==
"element" ) {
1418 if( tokens[1] ==
"vertex" ) {
1419 numVertices = (GLuint)strtol( tokens[2].c_str(),
nullptr, 10 );
1421 }
else if( tokens[1] ==
"face" ) {
1422 numFaces = (GLuint)strtol( tokens[2].c_str(),
nullptr, 10 );
1424 }
else if( tokens[1] ==
"edge" ) {
1426 }
else if( tokens[1] ==
"material" ) {
1427 numMaterials = (GLuint)strtol( tokens[2].c_str(),
nullptr, 10 );
1428 elemType = MATERIAL;
1432 }
else if( tokens[0] ==
"property" ) {
1433 if( elemType == VERTEX ) {
1435 }
else if( elemType == FACE ) {
1437 }
else if( elemType == MATERIAL ) {
1440 }
else if( tokens[0] ==
"end_header" ) {
1441 fileState = VERTICES;
1443 }
else if( fileState == VERTICES ) {
1445 glm::vec3 pos(strtof( tokens[0].c_str(),
nullptr ),
1446 strtof( tokens[1].c_str(),
nullptr ),
1447 strtof( tokens[2].c_str(),
nullptr ) );
1449 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
1450 _vertices[ _uniqueIndex ] = pos;
1453 verticesTemp.push_back(pos );
1457 if( _uniqueIndex == numVertices || vSeen == numVertices )
1459 }
else if( fileState == FACES ) {
1460 auto numberOfVerticesInFace = (GLuint)strtol( tokens[0].c_str(),
nullptr, 10 );
1462 for( GLuint i = 2; i <= numberOfVerticesInFace - 1; i++ ) {
1463 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
1464 _indices[ _numIndices++ ] = (GLuint)strtol( tokens[1].c_str(),
nullptr, 10 );
1465 _indices[ _numIndices++ ] = (GLuint)strtol( tokens[i].c_str(),
nullptr, 10 );
1466 _indices[ _numIndices++ ] = (GLuint)strtol( tokens[i+1].c_str(),
nullptr, 10 );
1468 auto aI = (GLuint)strtol( tokens[1].c_str(),
nullptr, 10 );
1469 auto bI = (GLuint)strtol( tokens[i].c_str(),
nullptr, 10 );
1470 auto cI = (GLuint)strtol( tokens[i+1].c_str(),
nullptr, 10 );
1472 glm::vec3 a = verticesTemp[aI];
1473 glm::vec3 b = verticesTemp[bI];
1474 glm::vec3 c = verticesTemp[cI];
1476 glm::vec3 ab = b - a; glm::vec3 ac = c - a;
1477 glm::vec3 ba = a - b; glm::vec3 bc = c - b;
1478 glm::vec3 ca = a - c; glm::vec3 cb = b - c;
1480 glm::vec3 aN = glm::normalize( glm::cross( ab, ac ) );
1481 glm::vec3 bN = glm::normalize( glm::cross( bc, ba ) );
1482 glm::vec3 cN = glm::normalize( glm::cross( ca, cb ) );
1484 _vertices[ _uniqueIndex ] = a;
1485 _normals[ _uniqueIndex ] = aN;
1486 _indices[ _numIndices++ ] = _uniqueIndex++;
1488 _vertices[ _uniqueIndex ] = b;
1489 _normals[ _uniqueIndex ] = bN;
1490 _indices[ _numIndices++ ] = _uniqueIndex++;
1492 _vertices[ _uniqueIndex ] = c;
1493 _normals[ _uniqueIndex ] = cN;
1494 _indices[ _numIndices++ ] = _uniqueIndex++;
1498 if (INFO) printf(
"[.ply]: unknown file state: %d\n", fileState );
1503 if( progressCounter % 5000 == 0 ) {
1505 switch( progressCounter ) {
1506 case 5000: printf(
"[.ply]: parsing %s...\\", _filename.c_str());
break;
1507 case 10000: printf(
"[.ply]: parsing %s...|", _filename.c_str());
break;
1508 case 15000: printf(
"[.ply]: parsing %s.../", _filename.c_str());
break;
1509 case 20000: printf(
"[.ply]: parsing %s...-", _filename.c_str());
break;
1514 if( progressCounter == 20000 )
1515 progressCounter = 0;
1523 double seconds = difftime( end, start );
1526 printf(
"\33[2K\r" );
1527 printf(
"[.ply]: parsing %s...done!\n[.ply]: Time to complete: %.3fs\n", _filename.c_str(), seconds );
1528 printf(
"[.ply]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1534inline bool CSCI441::ModelLoader::_loadSTLFile(
bool INFO,
bool ERRORS ) {
1537 if (INFO) printf(
"[.stl]: -=-=-=-=-=-=-=- BEGIN %s Info -=-=-=-=-=-=-=-\n", _filename.c_str() );
1542 std::ifstream in( _filename );
1543 if( !in.is_open() ) {
1544 if (ERRORS) fprintf(stderr,
"[.stl]: [ERROR]: Could not open \"%s\"\n", _filename.c_str() );
1545 if ( INFO ) printf(
"[.stl]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1549 GLuint numVertices = 0, numNormals = 0, numFaces = 0, numTriangles = 0, numVertsInLoop = 0;
1550 GLfloat minX = 999999.0f, maxX = -999999.0f, minY = 999999.0f, maxY = -999999.0f, minZ = 999999.0f, maxZ = -999999.0f;
1553 int progressCounter = 0;
1554 glm::vec3 normalVector = {0.f,0.f,0.f};
1556 while( getline( in, line ) ) {
1557 if( line.length() > 1 && line.at(0) ==
'\t' )
1558 line = line.substr( 1 );
1559 line.erase( line.find_last_not_of(
" \n\r\t" ) + 1 );
1561 std::vector< std::string > tokens = _tokenizeString( line,
" \t" );
1563 if( tokens.empty() )
continue;
1565 if( tokens[0] ==
"solid" ) {
1567 }
else if( tokens[0] ==
"facet" ) {
1570 }
else if( tokens[0] ==
"outer" && tokens[1] ==
"loop" ) {
1573 }
else if( tokens[0] ==
"vertex" ) {
1574 GLfloat x = strtof( tokens[1].c_str(),
nullptr ),
1575 y = strtof( tokens[2].c_str(),
nullptr ),
1576 z = strtof( tokens[3].c_str(),
nullptr );
1578 if( x < minX ) minX = x;
1579 if( x > maxX ) maxX = x;
1580 if( y < minY ) minY = y;
1581 if( y > maxY ) maxY = y;
1582 if( z < minZ ) minZ = z;
1583 if( z > maxZ ) maxZ = z;
1587 }
else if( tokens[0] ==
"endloop" ) {
1589 numTriangles += numVertsInLoop - 3 + 1;
1590 }
else if( tokens[0] ==
"endfacet" ) {
1592 }
else if( tokens[0] ==
"endsolid" ) {
1596 if( memchr( line.c_str(),
'\0', line.length() ) !=
nullptr ) {
1597 if (ERRORS) fprintf( stderr,
"[.stl]: [ERROR]: Cannot read binary STL file \"%s\"\n", _filename.c_str() );
1598 if ( INFO ) printf(
"[.stl]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1601 }
else if (INFO) printf(
"[.stl]: unknown line: %s\n", line.c_str() );
1606 if( progressCounter % 5000 == 0 ) {
1608 switch( progressCounter ) {
1609 case 5000: printf(
"[.stl]: scanning %s...\\", _filename.c_str());
break;
1610 case 10000: printf(
"[.stl]: scanning %s...|", _filename.c_str());
break;
1611 case 15000: printf(
"[.stl]: scanning %s.../", _filename.c_str());
break;
1612 case 20000: printf(
"[.stl]: scanning %s...-", _filename.c_str());
break;
1617 if( progressCounter == 20000 )
1618 progressCounter = 0;
1624 printf(
"\33[2K\r" );
1625 printf(
"[.stl]: scanning %s...done!\n", _filename.c_str() );
1626 printf(
"[.stl]: ------------\n" );
1627 printf(
"[.stl]: Model Stats:\n" );
1628 printf(
"[.stl]: Vertices: \t%u\tNormals: \t%u\tTex Coords:\t%u\n", numVertices, numNormals, 0 );
1629 printf(
"[.stl]: Faces: \t%u\tTriangles: \t%u\n", numFaces, numTriangles );
1630 printf(
"[.stl]: Dimensions:\t(%f, %f, %f)\n", (maxX - minX), (maxY - minY), (maxZ - minZ) );
1633 _allocateAttributeArrays(numVertices, numTriangles*3);
1635 if (INFO) printf(
"[.stl]: ------------\n" );
1637 in.open( _filename );
1642 while( getline( in, line ) ) {
1643 if( line.length() > 1 && line.at(0) ==
'\t' )
1644 line = line.substr( 1 );
1645 line.erase( line.find_last_not_of(
" \n\r\t" ) + 1 );
1647 std::vector< std::string > tokens = _tokenizeString( line,
" \t" );
1649 if( tokens.empty() )
continue;
1651 if( tokens[0] ==
"solid" ) {
1653 }
else if( tokens[0] ==
"facet" ) {
1655 normalVector = glm::vec3( strtof( tokens[2].c_str(),
nullptr ),
1656 strtof( tokens[3].c_str(),
nullptr ),
1657 strtof( tokens[4].c_str(),
nullptr ) );
1658 }
else if( tokens[0] ==
"outer" && tokens[1] ==
"loop" ) {
1660 }
else if( tokens[0] ==
"vertex" ) {
1661 _vertices[ _uniqueIndex ] = glm::vec3(strtof( tokens[1].c_str(),
nullptr ),
1662 strtof( tokens[2].c_str(),
nullptr ),
1663 strtof( tokens[3].c_str(),
nullptr ) );
1664 _normals[ _uniqueIndex ] = normalVector;
1665 _indices[ _numIndices++ ] = _uniqueIndex++;
1666 }
else if( tokens[0] ==
"endloop" ) {
1668 }
else if( tokens[0] ==
"endfacet" ) {
1670 }
else if( tokens[0] ==
"endsolid" ) {
1679 if( progressCounter % 5000 == 0 ) {
1681 switch( progressCounter ) {
1682 case 5000: printf(
"[.stl]: parsing %s...\\", _filename.c_str());
break;
1683 case 10000: printf(
"[.stl]: parsing %s...|", _filename.c_str());
break;
1684 case 15000: printf(
"[.stl]: parsing %s.../", _filename.c_str());
break;
1685 case 20000: printf(
"[.stl]: parsing %s...-", _filename.c_str());
break;
1690 if( progressCounter == 20000 )
1691 progressCounter = 0;
1699 double seconds = difftime( end, start );
1703 printf(
"[.stl]: parsing %s...done!\n[.stl]: Time to complete: %.3fs\n", _filename.c_str(), seconds);
1704 printf(
"[.stl]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1712 sAUTO_GEN_NORMALS =
true;
1717 sAUTO_GEN_NORMALS =
false;
1720inline void CSCI441::ModelLoader::_allocateAttributeArrays(
const GLuint numVertices,
const GLuint numIndices) {
1721 _vertices =
new glm::vec3[numVertices];
1722 _normals =
new glm::vec3[numVertices];
1723 _texCoords =
new glm::vec2[numVertices];
1724 _indices =
new GLuint[numIndices];
1727inline void CSCI441::ModelLoader::_bufferData() {
1728 glBindVertexArray( _vaod );
1730 glBindBuffer( GL_ARRAY_BUFFER, _vbods[0] );
1731 glBufferData( GL_ARRAY_BUFFER,
static_cast<GLsizeiptr
>((
sizeof(glm::vec3)*2 +
sizeof(glm::vec2)) * _uniqueIndex),
nullptr, GL_STATIC_DRAW );
1732 glBufferSubData( GL_ARRAY_BUFFER, 0,
static_cast<GLsizeiptr
>(
sizeof(glm::vec3) * _uniqueIndex), _vertices );
1733 glBufferSubData( GL_ARRAY_BUFFER,
static_cast<GLintptr
>(
sizeof(glm::vec3) * _uniqueIndex),
static_cast<GLsizeiptr
>(
sizeof(glm::vec3) * _uniqueIndex), _normals );
1734 glBufferSubData( GL_ARRAY_BUFFER,
static_cast<GLintptr
>(
sizeof(glm::vec3) * _uniqueIndex * 2),
static_cast<GLsizeiptr
>(
sizeof(glm::vec2) * _uniqueIndex), _texCoords );
1736 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, _vbods[1] );
1737 glBufferData( GL_ELEMENT_ARRAY_BUFFER,
static_cast<GLsizeiptr
>(
sizeof(GLuint) * _numIndices), _indices, GL_STATIC_DRAW );
1746inline std::vector<std::string> CSCI441::ModelLoader::_tokenizeString(std::string input,
const std::string& delimiters) {
1750 std::vector<std::string> retVec = std::vector<std::string>();
1751 size_t oldR = 0, r = 0;
1754 std::string strippedInput;
1755 GLint lowerValidIndex = 0, upperValidIndex = (GLint)input.size() - 1;
1756 while((GLuint)lowerValidIndex < input.size() && delimiters.find_first_of(input.at(lowerValidIndex), 0) != std::string::npos)
1759 while(upperValidIndex >= 0 && delimiters.find_first_of(input.at(upperValidIndex), 0) != std::string::npos)
1763 if((GLuint)lowerValidIndex >= input.size() || upperValidIndex < 0 || lowerValidIndex > upperValidIndex)
1767 strippedInput = input.substr(lowerValidIndex, upperValidIndex-lowerValidIndex+1);
1771 while((r = strippedInput.find_first_of(delimiters, oldR)) != std::string::npos)
1774 retVec.push_back(strippedInput.substr(oldR, r-oldR));
1778 retVec.push_back(strippedInput.substr(oldR, r-oldR));
1783inline void CSCI441::ModelLoader::_moveFromSrc(ModelLoader& src) {
1784 _hasVertexTexCoords = src._hasVertexTexCoords;
1785 src._hasVertexTexCoords =
false;
1787 _hasVertexNormals = src._hasVertexNormals;
1788 src._hasVertexNormals =
false;
1790 _vertices = src._vertices;
1791 src._vertices =
nullptr;
1793 _texCoords = src._texCoords;
1794 src._texCoords =
nullptr;
1796 _normals = src._normals;
1797 src._normals =
nullptr;
1799 _indices = src._indices;
1800 src._indices =
nullptr;
1802 _uniqueIndex = src._uniqueIndex;
1803 src._uniqueIndex = 0;
1805 _numIndices = src._numIndices;
1806 src._numIndices = 0;
1808 _vbods[0] = src._vbods[0];
1809 _vbods[1] = src._vbods[1];
1816 _filename = std::move(src._filename);
1819 _modelType = src._modelType;
1821 _materials = std::move(src._materials);
1822 _materialIndexStartStop = std::move(src._materialIndexStartStop);
1825inline void CSCI441::ModelLoader::_cleanupSelf() {
1827 _vertices =
nullptr;
1832 delete[] _texCoords;
1833 _texCoords =
nullptr;
1838 glDeleteBuffers( 2, _vbods );
1842 glDeleteVertexArrays( 1, &_vaod );
1845 _hasVertexTexCoords =
false;
1846 _hasVertexNormals =
false;
1851 for(
auto [name, material] : _materials ) {
1856 _materialIndexStartStop.clear();
1859inline unsigned char* CSCI441_INTERNAL::createTransparentTexture(
const unsigned char * imageData,
const unsigned char *imageMask,
int texWidth,
int texHeight,
int texChannels,
int maskChannels ) {
1861 auto *fullData =
new unsigned char[texWidth*texHeight*4];
1863 for(
int j = 0; j < texHeight; j++) {
1864 for(
int i = 0; i < texWidth; i++) {
1866 fullData[(j*texWidth+i)*4+0] = imageData[(j*texWidth+i)*texChannels+0];
1867 fullData[(j*texWidth+i)*4+1] = imageData[(j*texWidth+i)*texChannels+1];
1868 fullData[(j*texWidth+i)*4+2] = imageData[(j*texWidth+i)*texChannels+2];
1870 fullData[(j*texWidth+i)*4+0] = 1;
1871 fullData[(j*texWidth+i)*4+1] = 1;
1872 fullData[(j*texWidth+i)*4+2] = 1;
1876 fullData[(j*texWidth+i)*4+3] = imageMask[(j*texWidth+i)*maskChannels+0];
1878 fullData[(j*texWidth+i)*4+3] = 1;
1886inline void CSCI441_INTERNAL::flipImageY(
int texWidth,
int texHeight,
int textureChannels,
unsigned char *textureData ) {
1887 for(
int j = 0; j < texHeight / 2; j++ ) {
1888 for(
int i = 0; i < texWidth; i++ ) {
1889 for(
int k = 0; k < textureChannels; k++ ) {
1890 int top = (j*texWidth + i)*textureChannels + k;
1891 int bot = ((texHeight-j-1)*texWidth + i)*textureChannels + k;
1893 unsigned char t = textureData[top];
1894 textureData[top] = textureData[bot];
1895 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:1711
void setAttributeLocations(GLint positionLocation, GLint normalLocation=-1, GLint texCoordLocation=-1) const
Enables VBO attribute array locations.
Definition: ModelLoader.hpp:286
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:349
GLfloat * getVertices() const
Return the vertex array that makes up the model mesh.
Definition: ModelLoader.hpp:346
bool loadModelFile(std::string filename, bool INFO=true, bool ERRORS=true)
Loads a model from the given file.
Definition: ModelLoader.hpp:258
GLfloat * getNormals() const
Return the normal array that corresponds to the model mesh.
Definition: ModelLoader.hpp:347
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:301
GLuint * getIndices() const
Return the index array that dictates the order to draw the model mesh.
Definition: ModelLoader.hpp:350
GLfloat * getTexCoords() const
Return the texture coordinates array that corresponds to the model mesh.
Definition: ModelLoader.hpp:348
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:345
static void disableAutoGenerateNormals()
Disable auto-generation of vertex normals.
Definition: ModelLoader.hpp:1716
~ModelLoader()
Frees memory associated with model on both CPU and GPU.
Definition: ModelLoader.hpp:225
ModelLoader()
Creates an empty model.
Definition: ModelLoader.hpp:215
Internal material representation for *.mtl files.
CSCI441 Helper Functions for OpenGL.
Definition: ArcballCam.hpp:17