CSCI441 OpenGL Library 6.0.1.1
CS@Mines CSCI441 Computer Graphics Course Library
Loading...
Searching...
No Matches
ModelLoader.hpp
Go to the documentation of this file.
1
17#ifndef CSCI441_MODEL_LOADER_HPP
18#define CSCI441_MODEL_LOADER_HPP
19
20#include "constants.h"
21#include "modelMaterial.hpp"
22
23#ifdef CSCI441_USE_GLEW
24 #include <GL/glew.h>
25#else
26 #include <glad/gl.h>
27#endif
28
29#include <glm/glm.hpp>
30
31#ifndef CSCI441_TEXTURE_UTILS_HPP
32 #include <stb_image.h>
33#endif
34
35#include <cstdio>
36#include <cstdlib>
37#include <cstring>
38#include <ctime>
39#include <fstream>
40#include <sstream>
41#include <map>
42#include <string>
43#include <utility>
44#include <vector>
45
47
48namespace CSCI441 {
49
54 class [[maybe_unused]] ModelLoader final {
55 public:
64 [[maybe_unused]] explicit ModelLoader( const char* filename );
69
73 ModelLoader(const ModelLoader&) = delete;
78
82 ModelLoader(ModelLoader&&) noexcept;
87 ModelLoader& operator=(ModelLoader&&) noexcept;
88
96 bool loadModelFile( std::string filename, bool INFO = true, bool ERRORS = true );
97
105 [[maybe_unused]] void setAttributeLocations(GLint positionLocation, GLint normalLocation = -1, GLint texCoordLocation = -1, GLint tangentLocation = -1) const;
106
117 [[maybe_unused]] bool draw( GLuint shaderProgramHandle,
118 GLint matDiffLocation = -1, GLint matSpecLocation = -1, GLint matShinLocation = -1, GLint matAmbLocation = -1,
119 GLenum diffuseTexture = GL_TEXTURE0 ) const;
120
125 [[maybe_unused]] [[nodiscard]] GLuint getNumberOfVertices() const;
131 [[maybe_unused]] [[nodiscard]] GLfloat* getVertices() const;
137 [[maybe_unused]] [[nodiscard]] GLfloat* getNormals() const;
143 [[maybe_unused]] [[nodiscard]] GLfloat* getTangents() const;
149 [[maybe_unused]] [[nodiscard]] GLfloat* getTexCoords() const;
154 [[maybe_unused]] [[nodiscard]] GLuint getNumberOfIndices() const;
160 [[maybe_unused]] [[nodiscard]] GLuint* getIndices() const;
161
169 [[maybe_unused]] static void enableAutoGenerateNormals();
176 [[maybe_unused]] static void disableAutoGenerateNormals();
177
184 [[maybe_unused]] static void enableAutoGenerateTangents();
191 [[maybe_unused]] static void disableAutoGenerateTangents();
192
193 private:
194 void _init();
195 bool _loadMTLFile( const char *mtlFilename, bool INFO, bool ERRORS );
196 bool _loadOBJFile( bool INFO, bool ERRORS );
197 bool _loadOFFFile( bool INFO, bool ERRORS );
198 bool _loadPLYFile( bool INFO, bool ERRORS );
199 bool _loadSTLFile( bool INFO, bool ERRORS );
200 static std::vector<std::string> _tokenizeString( const std::string& input, const std::string& delimiters );
201 void _allocateAttributeArrays(GLuint numVertices, GLuint numIndices);
202 void _bufferData() const;
203
204 std::string _filename;
205 CSCI441_INTERNAL::MODEL_TYPE _modelType;
206
207 GLuint _vaod;
208 GLuint _vbods[2] = {0};
209
210 glm::vec3* _vertices;
211 glm::vec3* _normals;
212 glm::vec4* _tangents;
213 glm::vec2* _texCoords;
214 GLuint* _indices;
215 GLuint _uniqueIndex;
216 GLuint _numIndices;
217
218 std::map< std::string, CSCI441_INTERNAL::ModelMaterial* > _materials;
219 std::map< std::string, std::vector< std::pair< GLuint, GLuint > > > _materialIndexStartStop;
220
221 bool _hasVertexTexCoords;
222 bool _hasVertexNormals;
223
224 void _moveFromSrc(ModelLoader&);
225 void _cleanupSelf();
226
227 static bool sAUTO_GEN_NORMALS;
228 static bool sAUTO_GEN_TANGENTS;
229 };
230}
231
234
235namespace CSCI441_INTERNAL {
236 unsigned char* createTransparentTexture( const unsigned char *imageData, const unsigned char *imageMask, int texWidth, int texHeight, int texChannels, int maskChannels );
237 [[maybe_unused]] void flipImageY( int texWidth, int texHeight, int textureChannels, unsigned char *textureData );
238}
239
240inline bool CSCI441::ModelLoader::sAUTO_GEN_NORMALS = false;
241inline bool CSCI441::ModelLoader::sAUTO_GEN_TANGENTS = false;
242
244 _modelType(CSCI441_INTERNAL::MODEL_TYPE::UNKNOWN),
245 _vaod(0),
246 _vertices(nullptr),
247 _normals(nullptr),
248 _tangents(nullptr),
249 _texCoords(nullptr),
250 _indices(nullptr),
251 _uniqueIndex(0),
252 _numIndices(0),
253 _hasVertexTexCoords(false),
254 _hasVertexNormals(false)
255{
256 _init();
257}
258
259[[maybe_unused]]
260inline CSCI441::ModelLoader::ModelLoader( const char* filename ) :
261 _modelType(CSCI441_INTERNAL::MODEL_TYPE::UNKNOWN),
262 _vaod(0),
263 _vertices(nullptr),
264 _normals(nullptr),
265 _tangents(nullptr),
266 _texCoords(nullptr),
267 _indices(nullptr),
268 _uniqueIndex(0),
269 _numIndices(0),
270 _hasVertexTexCoords(false),
271 _hasVertexNormals(false)
272{
273 _init();
274 loadModelFile( filename );
275}
276
278 _cleanupSelf();
279}
280
282 _modelType(CSCI441_INTERNAL::MODEL_TYPE::UNKNOWN),
283 _vaod(0),
284 _vertices(nullptr),
285 _normals(nullptr),
286 _tangents(nullptr),
287 _texCoords(nullptr),
288 _indices(nullptr),
289 _uniqueIndex(0),
290 _numIndices(0),
291 _hasVertexTexCoords(false),
292 _hasVertexNormals(false)
293{
294 _moveFromSrc(src);
295}
296
298 if (this != &src) { // guard against self move
299 _cleanupSelf(); // empty self
300 _moveFromSrc(src); // move from source
301 }
302 return *this;
303}
304
305inline void CSCI441::ModelLoader::_init() {
306 glGenVertexArrays( 1, &_vaod );
307 glGenBuffers( 2, _vbods );
308}
309
310inline bool CSCI441::ModelLoader::loadModelFile( std::string filename, bool const INFO, const bool ERRORS ) {
311 bool result = true;
312 _filename = std::move(filename);
313 if( _filename.find(".obj") != std::string::npos ) {
314 result = _loadOBJFile( INFO, ERRORS );
315 _modelType = CSCI441_INTERNAL::MODEL_TYPE::OBJ;
316 }
317 else if( _filename.find(".off") != std::string::npos ) {
318 result = _loadOFFFile( INFO, ERRORS );
319 _modelType = CSCI441_INTERNAL::MODEL_TYPE::OFF;
320 }
321 else if( _filename.find(".ply") != std::string::npos ) {
322 result = _loadPLYFile( INFO, ERRORS );
323 _modelType = CSCI441_INTERNAL::MODEL_TYPE::PLY;
324 }
325 else if( _filename.find(".stl") != std::string::npos ) {
326 result = _loadSTLFile( INFO, ERRORS );
327 _modelType = CSCI441_INTERNAL::MODEL_TYPE::STL;
328 }
329 else {
330 result = false;
331 if (ERRORS) fprintf( stderr, "[ERROR]: Unsupported file format for file: %s\n", _filename.c_str() );
332 }
333
334 return result;
335}
336
337[[maybe_unused]]
338inline void CSCI441::ModelLoader::setAttributeLocations(const GLint positionLocation, const GLint normalLocation, const GLint texCoordLocation, const GLint tangentLocation) const {
339 glBindVertexArray( _vaod );
340 glBindBuffer( GL_ARRAY_BUFFER, _vbods[0] );
341
342 glEnableVertexAttribArray( positionLocation );
343 glVertexAttribPointer( positionLocation, 3, GL_FLOAT, GL_FALSE, 0, (void*)nullptr );
344
345 glEnableVertexAttribArray( normalLocation );
346 glVertexAttribPointer( normalLocation, 3, GL_FLOAT, GL_FALSE, 0, (void*)(sizeof(glm::vec3) * _uniqueIndex) );
347
348 glEnableVertexAttribArray( texCoordLocation );
349 glVertexAttribPointer( texCoordLocation, 2, GL_FLOAT, GL_FALSE, 0, (void*)(sizeof(glm::vec3) * _uniqueIndex * 2) );
350
351 glEnableVertexAttribArray( tangentLocation );
352 glVertexAttribPointer( tangentLocation, 4, GL_FLOAT, GL_FALSE, 0, (void*)(sizeof(glm::vec3) * _uniqueIndex * 2 + sizeof(glm::vec2) * _uniqueIndex) );
353}
354
355[[maybe_unused]]
356inline bool CSCI441::ModelLoader::draw( const GLuint shaderProgramHandle,
357 const GLint matDiffLocation, const GLint matSpecLocation, const GLint matShinLocation, const GLint matAmbLocation,
358 const GLenum diffuseTexture ) const {
359 glBindVertexArray( _vaod );
360
361 bool result = true;
362 if( _modelType == CSCI441_INTERNAL::MODEL_TYPE::OBJ ) {
363 for(const auto & materialIter : _materialIndexStartStop) {
364 auto materialName = materialIter.first;
365 auto indexStartStop = materialIter.second;
366
367 CSCI441_INTERNAL::ModelMaterial* material = nullptr;
368 if( _materials.find( materialName ) != _materials.end() )
369 material = _materials.find( materialName )->second;
370
371 for(const auto &[start, end] : indexStartStop) {
372 const GLsizei length = static_cast<GLsizei>(end - start) + 1;
373
374// printf( "rendering material %s (%u, %u) = %u\n", materialName.c_str(), start, end, length );
375
376 if( material != nullptr ) {
377 glProgramUniform4fv( shaderProgramHandle, matAmbLocation, 1, &material->ambient[0] );
378 glProgramUniform4fv( shaderProgramHandle, matDiffLocation, 1, &material->diffuse[0] );
379 glProgramUniform4fv( shaderProgramHandle, matSpecLocation, 1, &material->specular[0] );
380 glProgramUniform1f( shaderProgramHandle, matShinLocation, material->shininess );
381
382 if( material->map_Kd != -1 ) {
383 glActiveTexture( diffuseTexture );
384 glBindTexture( GL_TEXTURE_2D, material->map_Kd );
385 }
386 }
387
388 glDrawElements( GL_TRIANGLES, length, GL_UNSIGNED_INT, (void*)(sizeof(GLuint)*start) );
389 }
390 }
391 } else {
392 glDrawElements( GL_TRIANGLES, static_cast<GLint>(_numIndices), GL_UNSIGNED_INT, (void*)nullptr );
393 }
394
395 return result;
396}
397
398[[maybe_unused]] inline GLuint CSCI441::ModelLoader::getNumberOfVertices() const { return _uniqueIndex; }
399[[maybe_unused]] inline GLfloat* CSCI441::ModelLoader::getVertices() const { return (_vertices != nullptr ? reinterpret_cast<GLfloat *>(&_vertices[0]) : nullptr); }
400[[maybe_unused]] inline GLfloat* CSCI441::ModelLoader::getNormals() const { return (_normals != nullptr ? reinterpret_cast<GLfloat *>(&_normals[0]) : nullptr); }
401[[maybe_unused]] inline GLfloat* CSCI441::ModelLoader::getTangents() const { return (_tangents != nullptr ? reinterpret_cast<GLfloat *>(&_tangents[0]) : nullptr); }
402[[maybe_unused]] inline GLfloat* CSCI441::ModelLoader::getTexCoords() const { return (_texCoords != nullptr ? reinterpret_cast<GLfloat *>(&_texCoords[0]) : nullptr); }
403[[maybe_unused]] inline GLuint CSCI441::ModelLoader::getNumberOfIndices() const { return _numIndices; }
404[[maybe_unused]] inline GLuint* CSCI441::ModelLoader::getIndices() const { return _indices; }
405
406// Read in a WaveFront *.obj File
407inline bool CSCI441::ModelLoader::_loadOBJFile( const bool INFO, const bool ERRORS ) {
408 bool result = true;
409
410 std::string path;
411 if( _filename.find('/') != std::string::npos ) {
412 path = _filename.substr( 0, _filename.find_last_of('/')+1 );
413 } else {
414 path = "./";
415 }
416
417 if ( INFO ) fprintf( stdout, "[.obj]: -=-=-=-=-=-=-=- BEGIN %s Info -=-=-=-=-=-=-=- \n", _filename.c_str() );
418
419 time_t start, end;
420 time(&start);
421
422 std::ifstream in( _filename );
423 if( !in.is_open() ) {
424 if (ERRORS) fprintf( stderr, "[.obj]: [ERROR]: Could not open \"%s\"\n", _filename.c_str() );
425 if ( INFO ) fprintf( stdout, "[.obj]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=- \n", _filename.c_str() );
426 return false;
427 }
428
429 GLuint numObjects = 0, numGroups = 0;
430 GLuint numVertices = 0, numTexCoords = 0, numNormals = 0;
431 GLuint numFaces = 0, numTriangles = 0;
432 glm::vec3 minDimension = {999999.f, 999999.f, 999999.f};
433 glm::vec3 maxDimension = { -999999.f, -999999.f, -999999.f };
434 std::string line;
435
436 std::map<std::string, GLuint> uniqueCounts;
437 _uniqueIndex = 0;
438
439 int progressCounter = 0;
440
441 while( getline( in, line ) ) {
442 if( line.length() > 1 && line.at(0) == '\t' )
443 line = line.substr( 1 );
444 line.erase( line.find_last_not_of( " \n\r\t" ) + 1 );
445
446 std::vector< std::string > tokens = _tokenizeString( line, " \t" );
447 if( tokens.empty() ) continue;
448
449 //the line should have a single character that lets us know if it's a...
450 if( tokens[0] == "#" || tokens[0].find_first_of('#') == 0 ) {
451 // comment ignore
452 } else if( tokens[0] == "o" ) { // object name ignore
453 numObjects++;
454 } else if( tokens[0] == "g" ) { // polygon group name ignore
455 numGroups++;
456 } else if( tokens[0] == "mtllib" ) { // material library
457 std::string mtlFilename = path + tokens[1];
458 _loadMTLFile( mtlFilename.c_str(), INFO, ERRORS );
459 } else if( tokens[0] == "v" ) { //vertex
460 numVertices++;
461
462 glm::vec3 pos = { strtof( tokens[1].c_str(), nullptr ),
463 strtof( tokens[2].c_str(), nullptr ),
464 strtof( tokens[3].c_str(), nullptr ) };
465
466 if( pos.x < minDimension.x ) minDimension.x = pos.x;
467 if( pos.x > maxDimension.x ) maxDimension.x = pos.x;
468 if( pos.y < minDimension.y ) minDimension.y = pos.y;
469 if( pos.y > maxDimension.y ) maxDimension.y = pos.y;
470 if( pos.z < minDimension.z ) minDimension.z = pos.z;
471 if( pos.z > maxDimension.z ) maxDimension.z = pos.z;
472 } else if( tokens[0] == "vn" ) { //vertex normal
473 numNormals++;
474 } else if( tokens[0] == "vt" ) { //vertex tex coord
475 numTexCoords++;
476 } else if( tokens[0] == "f" ) { //face!
477 //now, faces can be either quads or triangles (or maybe more?)
478 //split the string on spaces to get the number of vertices+attributes.
479 std::vector<std::string> faceTokens = _tokenizeString(line, " ");
480
481 for (GLuint i = 1; i < faceTokens.size(); i++) {
482 //need to use both the tokens and number of slashes to determine what info is there.
483 std::vector<std::string> groupTokens = _tokenizeString(faceTokens[i], "/");
484 int numSlashes = 0;
485 for (char j: faceTokens[i]) {
486 if (j == '/') numSlashes++;
487 }
488
489 std::stringstream currentFaceTokenStream;
490
491 auto signedVertexIndex = static_cast<GLint>(strtol(groupTokens[0].c_str(), nullptr, 10));
492 GLuint vertexIndex = signedVertexIndex;
493 if (signedVertexIndex < 0) vertexIndex = numVertices + signedVertexIndex + 1;
494
495 currentFaceTokenStream << vertexIndex;
496
497 //based on combination of number of tokens and slashes, we can determine what we have.
498 if (groupTokens.size() == 2 && numSlashes == 1) {
499 _hasVertexTexCoords = true;
500
501 auto signedTexCoordIndex = static_cast<GLint>(strtol(groupTokens[1].c_str(), nullptr, 10));
502 GLuint texCoordIndex = signedTexCoordIndex;
503 if (signedTexCoordIndex < 0) texCoordIndex = numTexCoords + signedTexCoordIndex + 1;
504
505 currentFaceTokenStream << "/" << texCoordIndex;
506 } else if (groupTokens.size() == 2 && numSlashes == 2) {
507 _hasVertexNormals = true;
508
509 auto signedNormalIndex = static_cast<GLint>(strtol(groupTokens[1].c_str(), nullptr, 10));
510 GLuint normalIndex = signedNormalIndex;
511 if (signedNormalIndex < 0) normalIndex = numNormals + signedNormalIndex + 1;
512
513 currentFaceTokenStream << "//" << normalIndex;
514 } else if (groupTokens.size() == 3) {
515 _hasVertexTexCoords = true;
516 _hasVertexNormals = true;
517
518 auto signedTexCoordIndex = static_cast<GLint>(strtol(groupTokens[1].c_str(), nullptr, 10));
519 GLuint texCoordIndex = signedTexCoordIndex;
520 if (signedTexCoordIndex < 0) texCoordIndex = numTexCoords + signedTexCoordIndex + 1;
521
522 auto signedNormalIndex = static_cast<GLint>(strtol(groupTokens[2].c_str(), nullptr, 10));
523 GLuint normalIndex = signedNormalIndex;
524 if (signedNormalIndex < 0) normalIndex = numNormals + signedNormalIndex + 1;
525
526 currentFaceTokenStream << "/" << texCoordIndex << "/" << normalIndex;
527 } else if (groupTokens.size() != 1) {
528 if (ERRORS) fprintf(stderr, "[.obj]: [ERROR]: Malformed OBJ file, %s.\n", _filename.c_str());
529 return false;
530 }
531
532 std::string processedFaceToken = currentFaceTokenStream.str();
533 if (uniqueCounts.find(processedFaceToken) == uniqueCounts.end()) {
534 uniqueCounts.insert(std::pair<std::string, long int>(processedFaceToken, _uniqueIndex));
535 _uniqueIndex++;
536 }
537 }
538
539 numTriangles += (faceTokens.size() - 1 - 3 + 1);
540
541 numFaces++;
542 } else if( tokens[0] == "usemtl" ) { // use material library
543
544 } else {
545 if (INFO) printf( "[.obj]: ignoring line: %s\n", line.c_str() );
546 }
547
548 if (INFO) {
549 progressCounter++;
550 if( progressCounter % 5000 == 0 ) {
551 printf("\33[2K\r");
552 switch( progressCounter ) {
553 case 5000: printf("[.obj]: scanning %s...\\", _filename.c_str()); break;
554 case 10000: printf("[.obj]: scanning %s...|", _filename.c_str()); break;
555 case 15000: printf("[.obj]: scanning %s.../", _filename.c_str()); break;
556 case 20000: printf("[.obj]: scanning %s...-", _filename.c_str()); break;
557 default: break;
558 }
559 fflush(stdout);
560 }
561 if( progressCounter == 20000 )
562 progressCounter = 0;
563 }
564 }
565 in.close();
566
567 if (INFO) {
568 printf( "\33[2K\r" );
569 printf( "[.obj]: scanning %s...done!\n", _filename.c_str() );
570 printf( "[.obj]: ------------\n" );
571 printf( "[.obj]: Model Stats:\n" );
572 printf( "[.obj]: Vertices: \t%u\tNormals: \t%u\tTex Coords:\t%u\n", numVertices, numNormals, numTexCoords );
573 printf( "[.obj]: Unique Verts:\t%u\n", _uniqueIndex );
574 printf( "[.obj]: Faces: \t%u\tTriangles:\t%u\n", numFaces, numTriangles );
575 printf( "[.obj]: Objects: \t%u\tGroups: \t%u\n", numObjects, numGroups );
576
577 glm::vec3 sizeDimensions = maxDimension - minDimension;
578 printf( "[.obj]: Dimensions:\t(%f, %f, %f)\n", sizeDimensions.x, sizeDimensions.y, sizeDimensions.z );
579 }
580
581 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
582 if (INFO && !_hasVertexNormals)
583 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" );
584 if (INFO && sAUTO_GEN_TANGENTS) fprintf( stdout, "[.obj]: Vertex tangents will be autogenerated\n" );
585 _allocateAttributeArrays(_uniqueIndex, numTriangles*3);
586 } else {
587 if (INFO) printf( "[.obj]: No vertex normals exist on model, vertex normals will be autogenerated\n" );
588 if (INFO && sAUTO_GEN_TANGENTS) fprintf( stdout, "[.obj]: Vertex tangents will be autogenerated\n" );
589 _allocateAttributeArrays(numTriangles * 3, numTriangles*3);
590 }
591
592 auto objVertices = new glm::vec3[numVertices];
593 auto objNormals = new glm::vec3[numNormals];
594 auto objTexCoords = new glm::vec2[numTexCoords];
595
596 std::vector<glm::vec3> verticesTemps;
597 std::vector<glm::vec2> texCoordsTemp;
598
599 printf( "[.obj]: ------------\n" );
600
601 uniqueCounts.clear();
602 _uniqueIndex = 0;
603 _numIndices = 0;
604
605 in.open( _filename );
606
607 GLuint verticesSeen = 0, texCoordsSeen = 0, normalsSeen = 0, indicesSeen = 0;
608 GLuint uniqueNumVertices = 0;
609
610 std::string currentMaterial = "default";
611 _materialIndexStartStop.insert( std::pair< std::string, std::vector< std::pair< GLuint, GLuint > > >( currentMaterial, std::vector< std::pair< GLuint, GLuint > >(1) ) );
612 _materialIndexStartStop.find( currentMaterial )->second.back().first = indicesSeen;
613
614 while( getline( in, line ) ) {
615 if( line.length() > 1 && line.at(0) == '\t' )
616 line = line.substr( 1 );
617 line.erase( line.find_last_not_of( " \n\r\t" ) + 1 );
618
619 auto tokens = _tokenizeString( line, " \t" );
620 if( tokens.empty() ) continue;
621
622 //the line should have a single character that lets us know if it's a...
623 if( tokens[0] == "#" || tokens[0].find_first_of('#') == 0 // comment ignore
624 || tokens[0] == "o" // object name ignore
625 || tokens[0] == "g" // polygon group name ignore
626 || tokens[0] == "mtllib" // material library
627 || tokens[0] == "s" // smooth shading
628 ) {
629
630 } else if( tokens[0] == "usemtl" ) { // use material library
631 if( currentMaterial == "default" && indicesSeen == 0 ) {
632 _materialIndexStartStop.clear();
633 } else {
634 _materialIndexStartStop.find( currentMaterial )->second.back().second = indicesSeen - 1;
635 }
636 currentMaterial = tokens[1];
637 if( _materialIndexStartStop.find( currentMaterial ) == _materialIndexStartStop.end() ) {
638 _materialIndexStartStop.insert( std::pair< std::string, std::vector< std::pair< GLuint, GLuint > > >( currentMaterial, std::vector< std::pair< GLuint, GLuint > >(1) ) );
639 _materialIndexStartStop.find( currentMaterial )->second.back().first = indicesSeen;
640 } else {
641 _materialIndexStartStop.find( currentMaterial )->second.emplace_back( indicesSeen, -1 );
642 }
643 } else if( tokens[0] == "v" ) { //vertex
644 objVertices[verticesSeen++] = glm::vec3(strtof(tokens[1].c_str(), nullptr ),
645 strtof( tokens[2].c_str(), nullptr ),
646 strtof( tokens[3].c_str(), nullptr ) );
647 } else if( tokens[0] == "vn" ) { //vertex normal
648 objNormals[normalsSeen++] = glm::vec3(strtof(tokens[1].c_str(), nullptr ),
649 strtof( tokens[2].c_str(), nullptr ),
650 strtof( tokens[3].c_str(), nullptr ));
651 } else if( tokens[0] == "vt" ) { //vertex tex coord
652 objTexCoords[texCoordsSeen++] = glm::vec2(strtof(tokens[1].c_str(), nullptr ),
653 strtof( tokens[2].c_str(), nullptr ));
654 } else if( tokens[0] == "f" ) { //face!
655 std::vector<std::string> processedFaceTokens;
656
657 bool faceHasVertexNormals = false;
658 bool faceHasTextureCoordinates = false;
659
660 for(GLuint i = 1; i < tokens.size(); i++) {
661 //need to use both the tokens and number of slashes to determine what info is there.
662 auto vertexAttributeTokens = _tokenizeString(tokens[i], "/");
663 int numAttributeSlashes = 0;
664 for(char j : tokens[i]) {
665 if(j == '/') numAttributeSlashes++;
666 }
667
668 std::stringstream currentFaceTokenStream;
669
670 auto signedVertexIndex = static_cast<GLint>(strtol(vertexAttributeTokens[0].c_str(), nullptr, 10));
671 GLuint vertexIndex = signedVertexIndex;
672 if(signedVertexIndex < 0) vertexIndex = verticesSeen + signedVertexIndex + 1;
673 currentFaceTokenStream << vertexIndex;
674
675 GLuint texCoordIndex = 0, normalIndex = 0;
676
677 //based on combination of number of tokens and slashes, we can determine what we have.
678 if(vertexAttributeTokens.size() == 2 && numAttributeSlashes == 1) {
679 // v/t
680 _hasVertexTexCoords = true;
681 faceHasTextureCoordinates = true;
682
683 auto signedTexCoordIndex = static_cast<GLint>(strtol(vertexAttributeTokens[1].c_str(), nullptr, 10));
684 texCoordIndex = signedTexCoordIndex;
685 if(signedTexCoordIndex < 0) texCoordIndex = texCoordsSeen + signedTexCoordIndex + 1;
686 currentFaceTokenStream << "/" << texCoordIndex;
687 } else if(vertexAttributeTokens.size() == 2 && numAttributeSlashes == 2) {
688 // v//n
689 _hasVertexNormals = true;
690 faceHasVertexNormals = true;
691
692 auto signedNormalIndex = static_cast<GLint>(strtol(vertexAttributeTokens[1].c_str(), nullptr, 10));
693 normalIndex = signedNormalIndex;
694 if(signedNormalIndex < 0) normalIndex = normalsSeen + signedNormalIndex + 1;
695 currentFaceTokenStream << "//" << normalIndex;
696 } else if(vertexAttributeTokens.size() == 3) {
697 // v/t/n
698 _hasVertexTexCoords = true;
699 faceHasTextureCoordinates = true;
700 _hasVertexNormals = true;
701 faceHasVertexNormals = true;
702
703 auto signedTexCoordIndex = static_cast<GLint>(strtol(vertexAttributeTokens[1].c_str(), nullptr, 10));
704 texCoordIndex = signedTexCoordIndex;
705 if(signedTexCoordIndex < 0) texCoordIndex = texCoordsSeen + signedTexCoordIndex + 1;
706
707 auto signedNormalIndex = static_cast<GLint>(strtol(vertexAttributeTokens[2].c_str(), nullptr, 10));
708 normalIndex = signedNormalIndex;
709 if(signedNormalIndex < 0) normalIndex = normalsSeen + signedNormalIndex + 1;
710
711 currentFaceTokenStream << "/" << texCoordIndex << "/" << normalIndex;
712 } else if(vertexAttributeTokens.size() != 1) {
713 if (ERRORS) fprintf(stderr, "[.obj]: [ERROR]: Malformed OBJ file, %s.\n", _filename.c_str());
714 return false;
715 }
716
717 auto processedFaceToken = currentFaceTokenStream.str();
718 processedFaceTokens.push_back(processedFaceToken);
719
720 // check if we've seen this attribute combination before
721 if( uniqueCounts.find( processedFaceToken ) == uniqueCounts.end() ) {
722 // if not, add it to the list
723 uniqueCounts.insert( std::pair<std::string,GLuint>(processedFaceToken, uniqueNumVertices) );
724
725 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
726 _vertices[ _uniqueIndex ] = objVertices[ vertexIndex - 1 ];
727 if(faceHasTextureCoordinates && texCoordIndex != 0) { _texCoords[ _uniqueIndex ] = objTexCoords[ texCoordIndex - 1 ]; }
728 if(faceHasVertexNormals && normalIndex != 0) _normals[ _uniqueIndex ] = objNormals[ normalIndex - 1 ];
729 _uniqueIndex++;
730 } else {
731 verticesTemps.push_back( objVertices[ vertexIndex - 1 ] );
732 if(faceHasTextureCoordinates && texCoordIndex != 0) { texCoordsTemp.push_back( objTexCoords[ texCoordIndex - 1 ] ); }
733
734 if( (vertexAttributeTokens.size() == 2 && numAttributeSlashes == 2)
735 || (vertexAttributeTokens.size() == 3) ) {
736 // should not occur if no normals
737 if (ERRORS) fprintf( stderr, "[.obj]: [ERROR]: no vertex normals were specified, should not be trying to access values\n" );
738 }
739 }
740 uniqueNumVertices++;
741 }
742 }
743
744 for(GLuint i = 1; i < processedFaceTokens.size()-1; i++) {
745 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
746 _indices[ indicesSeen++ ] = uniqueCounts.find( processedFaceTokens[0] )->second;
747 _indices[ indicesSeen++ ] = uniqueCounts.find( processedFaceTokens[i] )->second;
748 _indices[ indicesSeen++ ] = uniqueCounts.find( processedFaceTokens[i+1] )->second;
749
750 if (sAUTO_GEN_TANGENTS) {
751 glm::vec3 a = _vertices[ _indices[ indicesSeen - 3 ] ];
752 glm::vec3 b = _vertices[ _indices[ indicesSeen - 2 ] ];
753 glm::vec3 c = _vertices[ _indices[ indicesSeen - 1 ] ];
754
755 glm::vec3 ab = b - a;
756 glm::vec3 bc = c - b;
757 glm::vec3 ca = a - c;
758
759 _tangents[ _indices[ indicesSeen - 3 ] ] = glm::vec4(ab, 1.0f);
760 _tangents[ _indices[ indicesSeen - 2 ] ] = glm::vec4(bc, 1.0f);
761 _tangents[ _indices[ indicesSeen - 1 ] ] = glm::vec4(ca, 1.0f);
762 }
763
764 _numIndices += 3;
765 } else {
766 GLuint aI = uniqueCounts.find( processedFaceTokens[0] )->second;
767 GLuint bI = uniqueCounts.find( processedFaceTokens[i] )->second;
768 GLuint cI = uniqueCounts.find( processedFaceTokens[i+1] )->second;
769
770 glm::vec3 a = verticesTemps[aI];
771 glm::vec3 b = verticesTemps[bI];
772 glm::vec3 c = verticesTemps[cI];
773
774 glm::vec3 ab = b - a; glm::vec3 ac = c - a;
775 glm::vec3 ba = a - b; glm::vec3 bc = c - b;
776 glm::vec3 ca = a - c; glm::vec3 cb = b - c;
777
778 glm::vec3 aN = glm::normalize( glm::cross( ab, ac ) );
779 glm::vec3 bN = glm::normalize( glm::cross( bc, ba ) );
780 glm::vec3 cN = glm::normalize( glm::cross( ca, cb ) );
781
782 _vertices[ _uniqueIndex ] = a;
783 _normals[ _uniqueIndex ] = aN;
784 if (sAUTO_GEN_TANGENTS) _tangents[ _uniqueIndex ] = glm::vec4(ab, 1.0f);
785 if( faceHasTextureCoordinates && _hasVertexTexCoords ) { _texCoords[ _uniqueIndex ] = texCoordsTemp[ aI ]; }
786 _indices[ _numIndices++ ] = _uniqueIndex++;
787
788 indicesSeen++;
789
790 _vertices[ _uniqueIndex ] = b;
791 _normals[ _uniqueIndex ] = bN;
792 if (sAUTO_GEN_TANGENTS) _tangents[ _uniqueIndex ] = glm::vec4(bc, 1.0f);
793 if( faceHasTextureCoordinates && _hasVertexTexCoords ) { _texCoords[ _uniqueIndex ] = texCoordsTemp[ bI ]; }
794 _indices[ _numIndices++ ] = _uniqueIndex++;
795
796 indicesSeen++;
797
798 _vertices[ _uniqueIndex ] = c;
799 _normals[ _uniqueIndex ] = cN;
800 if (sAUTO_GEN_TANGENTS) _tangents[ _uniqueIndex ] = glm::vec4(ca, 1.0f);
801 if( faceHasTextureCoordinates && _hasVertexTexCoords ) { _texCoords[ _uniqueIndex ] = texCoordsTemp[ cI ]; }
802 _indices[ _numIndices++ ] = _uniqueIndex++;
803
804 indicesSeen++;
805 }
806 }
807 }
808
809 if (INFO) {
810 progressCounter++;
811 if( progressCounter % 5000 == 0 ) {
812 printf("\33[2K\r");
813 switch( progressCounter ) {
814 case 5000: printf("[.obj]: parsing %s...\\", _filename.c_str()); break;
815 case 10000: printf("[.obj]: parsing %s...|", _filename.c_str()); break;
816 case 15000: printf("[.obj]: parsing %s.../", _filename.c_str()); break;
817 case 20000: printf("[.obj]: parsing %s...-", _filename.c_str()); break;
818 default: break;
819 }
820 fflush(stdout);
821 }
822 if( progressCounter == 20000 )
823 progressCounter = 0;
824 }
825 }
826
827 in.close();
828
829 if (INFO) {
830 printf( "\33[2K\r" );
831 printf( "[.obj]: parsing %s...done!\n", _filename.c_str() );
832 }
833
834 _materialIndexStartStop.find( currentMaterial )->second.back().second = indicesSeen - 1;
835
836 _bufferData();
837
838 time(&end);
839 double seconds = difftime( end, start );
840
841 if (INFO) {
842 printf( "[.obj]: Completed in %.3fs\n", seconds );
843 printf( "[.obj]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=- \n\n", _filename.c_str() );
844 }
845
846 return result;
847}
848
849inline bool CSCI441::ModelLoader::_loadMTLFile( const char* mtlFilename, const bool INFO, const bool ERRORS ) {
850 bool result = true;
851
852 if (INFO) printf( "[.mtl]: -*-*-*-*-*-*-*- BEGIN %s Info -*-*-*-*-*-*-*-\n", mtlFilename );
853
854 std::string line;
855 std::string path;
856 if( _filename.find('/') != std::string::npos ) {
857 path = _filename.substr( 0, _filename.find_last_of('/')+1 );
858 } else {
859 path = "./";
860 }
861
862 std::ifstream in;
863 in.open( mtlFilename );
864 if( !in.is_open() ) {
865 in.open( mtlFilename );
866 if( !in.is_open() ) {
867 if (ERRORS) fprintf( stderr, "[.mtl]: [ERROR]: could not open material file: %s\n", mtlFilename );
868 if ( INFO ) printf( "[.mtl]: -*-*-*-*-*-*-*- END %s Info -*-*-*-*-*-*-*-\n", mtlFilename );
869 return false;
870 }
871 }
872
873 CSCI441_INTERNAL::ModelMaterial* currentMaterial = nullptr;
874 std::string materialName;
875
876 unsigned char *textureData = nullptr;
877 unsigned char *maskData = nullptr;
878 unsigned char *fullData;
879 int texWidth, texHeight, textureChannels = 1, maskChannels = 1;
880 GLuint textureHandle = 0;
881
882 std::map< std::string, GLuint > imageHandles;
883
884 int numMaterials = 0;
885
886 while( getline( in, line ) ) {
887 if( line.length() > 1 && line.at(0) == '\t' )
888 line = line.substr( 1 );
889 line.erase( line.find_last_not_of( " \n\r\t" ) + 1 );
890
891 std::vector< std::string > tokens = _tokenizeString( line, " /" );
892 if( tokens.empty() ) continue;
893
894 //the line should have a single character that lets us know if it's a...
895 if( tokens[0] == "#" ) { // comment
896 // ignore
897 } else if( tokens[0] == "newmtl" ) { //new material
898 if (INFO) printf( "[.mtl]: Parsing material %s properties\n", tokens[1].c_str() );
899 currentMaterial = new CSCI441_INTERNAL::ModelMaterial();
900 materialName = tokens[1];
901 _materials.insert( std::pair<std::string, CSCI441_INTERNAL::ModelMaterial*>( materialName, currentMaterial ) );
902
903 textureHandle = 0;
904 textureData = nullptr;
905 maskData = nullptr;
906 textureChannels = 1;
907 maskChannels = 1;
908
909 numMaterials++;
910 } else if( tokens[0] == "Ka" ) { // ambient component
911 currentMaterial->ambient[0] = strtof( tokens[1].c_str(), nullptr );
912 currentMaterial->ambient[1] = strtof( tokens[2].c_str(), nullptr );
913 currentMaterial->ambient[2] = strtof( tokens[3].c_str(), nullptr );
914 } else if( tokens[0] == "Kd" ) { // diffuse component
915 currentMaterial->diffuse[0] = strtof( tokens[1].c_str(), nullptr );
916 currentMaterial->diffuse[1] = strtof( tokens[2].c_str(), nullptr );
917 currentMaterial->diffuse[2] = strtof( tokens[3].c_str(), nullptr );
918 } else if( tokens[0] == "Ks" ) { // specular component
919 currentMaterial->specular[0] = strtof( tokens[1].c_str(), nullptr );
920 currentMaterial->specular[1] = strtof( tokens[2].c_str(), nullptr );
921 currentMaterial->specular[2] = strtof( tokens[3].c_str(), nullptr );
922 } else if( tokens[0] == "Ke" ) { // emissive component
923 currentMaterial->emissive[0] = strtof( tokens[1].c_str(), nullptr );
924 currentMaterial->emissive[1] = strtof( tokens[2].c_str(), nullptr );
925 currentMaterial->emissive[2] = strtof( tokens[3].c_str(), nullptr );
926 } else if( tokens[0] == "Ns" ) { // shininess component
927 currentMaterial->shininess = strtof( tokens[1].c_str(), nullptr );
928 } else if( tokens[0] == "Tr"
929 || tokens[0] == "d" ) { // transparency component - Tr or d can be used depending on the format
930 currentMaterial->ambient[3] = strtof( tokens[1].c_str(), nullptr );
931 currentMaterial->diffuse[3] = strtof( tokens[1].c_str(), nullptr );
932 currentMaterial->specular[3] = strtof( tokens[1].c_str(), nullptr );
933 } else if( tokens[0] == "illum" ) { // illumination type component
934 // TODO illumination type?
935 } else if( tokens[0] == "map_Kd" ) { // diffuse color texture map
936 if( imageHandles.find( tokens[1] ) != imageHandles.end() ) {
937 // _textureHandles->insert( pair< string, GLuint >( materialName, imageHandles.find( tokens[1] )->second ) );
938 currentMaterial->map_Kd = imageHandles.find( tokens[1] )->second;
939 } else {
940 stbi_set_flip_vertically_on_load(true);
941 textureData = stbi_load( tokens[1].c_str(), &texWidth, &texHeight, &textureChannels, 0 );
942 if( !textureData ) {
943 std::string folderName = path + tokens[1];
944 textureData = stbi_load( folderName.c_str(), &texWidth, &texHeight, &textureChannels, 0 );
945 }
946
947 if( !textureData ) {
948 if (ERRORS) fprintf( stderr, "[.mtl]: [ERROR]: File Not Found: %s\n", tokens[1].c_str() );
949 } else {
950 if (INFO) printf( "[.mtl]: TextureMap:\t%s\tSize: %dx%d\tColors: %d\n", tokens[1].c_str(), texWidth, texHeight, textureChannels );
951
952 if( maskData == nullptr ) {
953 if( textureHandle == 0 ) {
954 glGenTextures( 1, &textureHandle );
955 imageHandles.insert( std::pair<std::string, GLuint>( tokens[1], textureHandle ) );
956 }
957
958 glBindTexture( GL_TEXTURE_2D, textureHandle );
959
960 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
961 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
962
963 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
964 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
965
966 GLint colorSpace = GL_RGB;
967 if( textureChannels == 4 )
968 colorSpace = GL_RGBA;
969 glTexImage2D( GL_TEXTURE_2D, 0, colorSpace, texWidth, texHeight, 0, colorSpace, GL_UNSIGNED_BYTE, textureData );
970
971 currentMaterial->map_Kd = textureHandle;
972 } else {
973 fullData = CSCI441_INTERNAL::createTransparentTexture( textureData, maskData, texWidth, texHeight, textureChannels, maskChannels );
974
975 if( textureHandle == 0 ) {
976 glGenTextures( 1, &textureHandle );
977 imageHandles.insert( std::pair<std::string, GLuint>( tokens[1], textureHandle ) );
978 }
979
980 glBindTexture( GL_TEXTURE_2D, textureHandle );
981
982 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
983 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
984
985 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
986 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
987
988 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, texWidth, texHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, fullData );
989
990 delete[] fullData;
991
992 currentMaterial->map_Kd = textureHandle;
993 }
994 }
995 }
996 } else if( tokens[0] == "map_d" ) { // alpha texture map
997 if( imageHandles.find( tokens[1] ) != imageHandles.end() ) {
998 // _textureHandles->insert( pair< string, GLuint >( materialName, imageHandles.find( tokens[1] )->second ) );
999 currentMaterial->map_d = imageHandles.find( tokens[1] )->second;
1000 } else {
1001 stbi_set_flip_vertically_on_load(true);
1002 maskData = stbi_load( tokens[1].c_str(), &texWidth, &texHeight, &textureChannels, 0 );
1003 if( !textureData ) {
1004 std::string folderName = path + tokens[1];
1005 maskData = stbi_load( folderName.c_str(), &texWidth, &texHeight, &textureChannels, 0 );
1006 }
1007
1008 if( !maskData ) {
1009 if (ERRORS) fprintf( stderr, "[.mtl]: [ERROR]: File Not Found: %s\n", tokens[1].c_str() );
1010 } else {
1011 if (INFO) printf( "[.mtl]: AlphaMap: \t%s\tSize: %dx%d\tColors: %d\n", tokens[1].c_str(), texWidth, texHeight, maskChannels );
1012
1013 if( textureData != nullptr ) {
1014 fullData = CSCI441_INTERNAL::createTransparentTexture( textureData, maskData, texWidth, texHeight, textureChannels, maskChannels );
1015
1016 if( textureHandle == 0 ) {
1017 glGenTextures( 1, &textureHandle );
1018 imageHandles.insert( std::pair<std::string, GLuint>( tokens[1], textureHandle ) );
1019 }
1020
1021 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
1022 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
1023
1024 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
1025 glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
1026
1027 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, texWidth, texHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, fullData );
1028
1029 delete[] fullData;
1030
1031 currentMaterial->map_Kd = textureHandle;
1032 }
1033 }
1034 }
1035 } else if( tokens[0] == "map_Ka" ) { // ambient color texture map
1036 // TODO ambient color map?
1037 } else if( tokens[0] == "map_Ks" ) { // specular color texture map
1038 // TODO specular color map?
1039 } else if( tokens[0] == "map_Ns" ) { // specular highlight map (shininess map)
1040 // TODO specular highlight map?
1041 } else if( tokens[0] == "Ni" ) { // optical density / index of refraction
1042 // TODO index of refraction?
1043 } else if( tokens[0] == "Tf" ) { // transmission filter
1044 // TODO transmission filter?
1045 } else if( tokens[0] == "bump"
1046 || tokens[0] == "map_bump" ) { // bump map
1047 // TODO bump map?
1048 } else {
1049 if (INFO) printf( "[.mtl]: ignoring line: %s\n", line.c_str() );
1050 }
1051 }
1052
1053 in.close();
1054
1055 if ( INFO ) {
1056 printf( "[.mtl]: Materials:\t%d\n", numMaterials );
1057 printf( "[.mtl]: -*-*-*-*-*-*-*- END %s Info -*-*-*-*-*-*-*-\n", mtlFilename );
1058 }
1059
1060 return result;
1061}
1062
1063inline bool CSCI441::ModelLoader::_loadOFFFile( const bool INFO, const bool ERRORS ) {
1064 bool result = true;
1065
1066 if (INFO ) printf( "[.off]: -=-=-=-=-=-=-=- BEGIN %s Info -=-=-=-=-=-=-=-\n", _filename.c_str() );
1067
1068 time_t start, end;
1069 time(&start);
1070
1071 std::ifstream in( _filename );
1072 if( !in.is_open() ) {
1073 if (ERRORS) fprintf( stderr, "[.off]: [ERROR]: Could not open \"%s\"\n", _filename.c_str() );
1074 if ( INFO ) printf( "[.off]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1075 return false;
1076 }
1077
1078 GLuint numVertices = 0, numFaces = 0, numTriangles = 0;
1079 GLfloat minX = 999999.0f, maxX = -999999.0f, minY = 999999.0f, maxY = -999999.0f, minZ = 999999.0f, maxZ = -999999.0f;
1080 std::string line;
1081
1082 enum OFF_FILE_STATE { HEADER, VERTICES, FACES, DONE };
1083
1084 OFF_FILE_STATE fileState = HEADER;
1085
1086 GLuint vSeen = 0, fSeen = 0;
1087
1088 while( getline( in, line ) ) {
1089 if( line.length() > 1 && line.at(0) == '\t' )
1090 line = line.substr( 1 );
1091 line.erase( line.find_last_not_of( " \n\r\t" ) + 1 );
1092
1093 std::vector< std::string > tokens = _tokenizeString( line, " \t" );
1094 if( tokens.empty() ) continue;
1095
1096 //the line should have a single character that lets us know if it's a...
1097 if( tokens[0] == "#" || tokens[0].find_first_of('#') == 0 ) { // comment ignore
1098 } else if( fileState == HEADER ) {
1099 if( tokens[0] == "OFF" ) { // denotes OFF File type
1100 } else {
1101 if( tokens.size() != 3 ) {
1102 if (ERRORS) fprintf( stderr, "[.off]: [ERROR]: Malformed OFF file. # vertices, faces, edges not properly specified\n" );
1103 if ( INFO ) printf( "[.off]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1104 in.close();
1105 return false;
1106 }
1107 // read in number of expected vertices, faces, and edges
1108 numVertices = static_cast<GLuint>(strtol(tokens[0].c_str(), nullptr, 10));
1109 numFaces = static_cast<GLuint>(strtol(tokens[1].c_str(), nullptr, 10));
1110
1111 // ignore tokens[2] - number of edges -- unnecessary information
1112 // numEdges = (GLuint)strtol( tokens[2].c_str(), nullptr, 10 );
1113
1114 fileState = VERTICES;
1115 }
1116 } else if( fileState == VERTICES ) {
1117 // read in x y z vertex location
1118 GLfloat x = strtof( tokens[0].c_str(), nullptr ),
1119 y = strtof( tokens[1].c_str(), nullptr ),
1120 z = strtof( tokens[2].c_str(), nullptr );
1121
1122 if( x < minX ) minX = x;
1123 if( x > maxX ) maxX = x;
1124 if( y < minY ) minY = y;
1125 if( y > maxY ) maxY = y;
1126 if( z < minZ ) minZ = z;
1127 if( z > maxZ ) maxZ = z;
1128
1129 vSeen++;
1130 if( vSeen == numVertices )
1131 fileState = FACES;
1132 } else if( fileState == FACES ) {
1133 auto numberOfVerticesInFace = static_cast<GLuint>(strtol(tokens[0].c_str(), nullptr, 10));
1134
1135 numTriangles += numberOfVerticesInFace - 3 + 1;
1136
1137 if( fSeen == numFaces )
1138 fileState = DONE;
1139 } else {
1140 if (INFO) printf( "[.off]: unknown file state: %d\n", fileState );
1141 }
1142 }
1143 in.close();
1144
1145 if (INFO) {
1146 printf( "\33[2K\r" );
1147 printf( "[.off]: scanning %s...done!\n", _filename.c_str() );
1148 printf( "[.off]: ------------\n" );
1149 printf( "[.off]: Model Stats:\n" );
1150 printf( "[.off]: Vertices: \t%u\tNormals: \t%u\tTex Coords:\t%u\n", numVertices, 0, 0 );
1151 printf( "[.off]: Faces: \t%u\tTriangles: \t%u\n", numFaces, numTriangles );
1152 printf( "[.off]: Dimensions:\t(%f, %f, %f)\n", (maxX - minX), (maxY - minY), (maxZ - minZ) );
1153 }
1154
1155 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
1156 if (INFO && !_hasVertexNormals)
1157 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" );
1158 _allocateAttributeArrays(numVertices, numTriangles*3);
1159 } else {
1160 if (INFO) printf( "[.off]: No vertex normals exist on model, vertex normals will be autogenerated\n" );
1161 _allocateAttributeArrays(numTriangles*3, numTriangles*3);
1162 }
1163
1164 std::vector<glm::vec3> verticesTemp;
1165
1166 if (INFO) printf( "[.off]: ------------\n" );
1167
1168 in.open( _filename );
1169
1170 _uniqueIndex = 0;
1171 _numIndices = 0;
1172 vSeen = 0;
1173
1174 fileState = HEADER;
1175
1176 int progressCounter = 0;
1177
1178 while( getline( in, line ) ) {
1179 if( line.length() > 1 && line.at(0) == '\t' )
1180 line = line.substr( 1 );
1181 line.erase( line.find_last_not_of( " \n\r\t" ) + 1 );
1182
1183 std::vector< std::string > tokens = _tokenizeString( line, " \t" );
1184 if( tokens.empty() ) continue;
1185
1186 //the line should have a single character that lets us know if it's a...
1187 if( tokens[0] == "#" || tokens[0].find_first_of('#') == 0 ) {
1188 // comment ignore
1189 } else if( fileState == HEADER ) {
1190 if( tokens[0] == "OFF" ) {
1191 // denotes OFF File type
1192 } else {
1193 // end of OFF Header reached
1194 fileState = VERTICES;
1195 }
1196 } else if( fileState == VERTICES ) {
1197 // read in x y z vertex location
1198 glm::vec3 pos(strtof( tokens[0].c_str(), nullptr ),
1199 strtof( tokens[1].c_str(), nullptr ),
1200 strtof( tokens[2].c_str(), nullptr ) );
1201
1202 // check if RGB(A) color information is associated with vertex
1203 if( tokens.size() == 6 || tokens.size() == 7 ) {
1204 // TODO: handle RGBA color info
1205// glm::vec4 color(strtof( tokens[3].c_str(), nullptr ),
1206// strtof( tokens[4].c_str(), nullptr ),
1207// strtof( tokens[5].c_str(), nullptr ),
1208// (tokens.size() == 7 ? strtof( tokens[6].c_str(), nullptr ) : 1.0f) );
1209 }
1210
1211 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
1212 _vertices[ _uniqueIndex ] = pos;
1213 _uniqueIndex++;
1214 } else {
1215 verticesTemp.push_back(pos);
1216 vSeen++;
1217 }
1218 // if all vertices have been read in, move on to faces
1219 if( _uniqueIndex == numVertices || vSeen == numVertices )
1220 fileState = FACES;
1221 } else if( fileState == FACES ) {
1222 auto numberOfVerticesInFace = static_cast<GLuint>(strtol(tokens[0].c_str(), nullptr, 10));
1223
1224 // read in each vertex index of the face
1225 for(GLuint i = 2; i <= numberOfVerticesInFace - 1; i++) {
1226 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
1227 auto fanRoot = strtol( tokens[1].c_str(), nullptr, 10 );
1228 auto fanA = strtol( tokens[i].c_str(), nullptr, 10 );
1229 auto fanB = strtol( tokens[i+1].c_str(), nullptr, 10 );
1230
1231 if( fanRoot < 0 ) fanRoot = numVertices + fanRoot + 1;
1232 if( fanA < 0 ) fanA = numVertices + fanA + 1;
1233 if( fanB < 0 ) fanB = numVertices + fanB + 1;
1234
1235 //regardless, we always get a vertex index.
1236 _indices[ _numIndices++ ] = fanRoot;
1237 _indices[ _numIndices++ ] = fanA;
1238 _indices[ _numIndices++ ] = fanB;
1239 } else {
1240 auto aI = strtol( tokens[1].c_str(), nullptr, 10 );
1241 auto bI = strtol( tokens[i].c_str(), nullptr, 10 );
1242 auto cI = strtol( tokens[i+1].c_str(), nullptr, 10 );
1243
1244 if( aI < 0 ) aI = numVertices + aI + 1;
1245 if( bI < 0 ) bI = numVertices + bI + 1;
1246 if( cI < 0 ) cI = numVertices + cI + 1;
1247
1248 glm::vec3 a = verticesTemp[aI];
1249 glm::vec3 b = verticesTemp[bI];
1250 glm::vec3 c = verticesTemp[cI];
1251
1252 glm::vec3 ab = b - a; glm::vec3 ac = c - a;
1253 glm::vec3 ba = a - b; glm::vec3 bc = c - b;
1254 glm::vec3 ca = a - c; glm::vec3 cb = b - c;
1255
1256 glm::vec3 aN = glm::normalize( glm::cross( ab, ac ) );
1257 glm::vec3 bN = glm::normalize( glm::cross( bc, ba ) );
1258 glm::vec3 cN = glm::normalize( glm::cross( ca, cb ) );
1259
1260 _vertices[ _uniqueIndex ] = a;
1261 _normals[ _uniqueIndex ] = aN;
1262 _indices[ _numIndices++ ] = _uniqueIndex++;
1263
1264 _vertices[ _uniqueIndex ] = b;
1265 _normals[ _uniqueIndex ] = bN;
1266 _indices[ _numIndices++ ] = _uniqueIndex++;
1267
1268 _vertices[ _uniqueIndex ] = c;
1269 _normals[ _uniqueIndex ] = cN;
1270 _indices[ _numIndices++ ] = _uniqueIndex++;
1271 }
1272 }
1273
1274 // check if RGB(A) color information is associated with face
1275 // TODO: handle color info
1276 //some local variables to hold the vertex+attribute indices we read in.
1277 //we do it this way because we'll have to split quads into triangles ourselves.
1278 // GLfloat color[4] = {-1,-1,-1,1};
1279 // if( tokens.size() == numberOfVerticesInFace + 4 || tokens.size() == numberOfVerticesInFace + 5 ) {
1280 // color[0] = atof( tokens[numberOfVerticesInFace + 1].c_str() );
1281 // color[1] = atof( tokens[numberOfVerticesInFace + 2].c_str() );
1282 // color[2] = atof( tokens[numberOfVerticesInFace + 3].c_str() );
1283 // color[3] = 1;
1284 //
1285 // if( tokens.size() == numberOfVerticesInFace + 5 )
1286 // color[3] = atof( tokens[numberOfVerticesInFace + 4].c_str() );
1287 // }
1288
1289 } else {
1290
1291 }
1292
1293 if (INFO) {
1294 progressCounter++;
1295 if( progressCounter % 5000 == 0 ) {
1296 printf("\33[2K\r");
1297 switch( progressCounter ) {
1298 case 5000: printf("[.off]: parsing %s...\\", _filename.c_str()); break;
1299 case 10000: printf("[.off]: parsing %s...|", _filename.c_str()); break;
1300 case 15000: printf("[.off]: parsing %s.../", _filename.c_str()); break;
1301 case 20000: printf("[.off]: parsing %s...-", _filename.c_str()); break;
1302 default: break;
1303 }
1304 fflush(stdout);
1305 }
1306 if( progressCounter == 20000 )
1307 progressCounter = 0;
1308 }
1309 }
1310 in.close();
1311
1312 _bufferData();
1313
1314 time(&end);
1315 double seconds = difftime( end, start );
1316
1317 if (INFO) {
1318 printf( "\33[2K\r" );
1319 printf( "[.off]: parsing %s...done! (Time: %.1fs)\n", _filename.c_str(), seconds );
1320 printf( "[.off]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1321 }
1322
1323 return result;
1324}
1325
1326// notes on PLY format: http://paulbourke.net/dataformats/ply/
1327inline bool CSCI441::ModelLoader::_loadPLYFile( const bool INFO, const bool ERRORS ) {
1328 bool result = true;
1329
1330 if (INFO ) printf( "[.ply]: -=-=-=-=-=-=-=- BEGIN %s Info -=-=-=-=-=-=-=-\n", _filename.c_str() );
1331
1332 time_t start, end;
1333 time(&start);
1334
1335 std::ifstream in( _filename );
1336 if( !in.is_open() ) {
1337 if (ERRORS) fprintf( stderr, "[.ply]: [ERROR]: Could not open \"%s\"\n", _filename.c_str() );
1338 if ( INFO ) printf( "[.ply]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1339 return false;
1340 }
1341
1342 GLuint numVertices = 0, numFaces = 0, numTriangles = 0, numMaterials = 0;
1343 GLfloat minX = 999999.0f, maxX = -999999.0f, minY = 999999.0f, maxY = -999999.0f, minZ = 999999.0f, maxZ = -999999.0f;
1344 std::string line;
1345
1346 enum PLY_FILE_STATE { HEADER, VERTICES, FACES, MATERIALS };
1347 enum PLY_ELEMENT_TYPE { NONE, VERTEX, FACE, MATERIAL };
1348
1349 PLY_FILE_STATE fileState = HEADER;
1350 PLY_ELEMENT_TYPE elemType = NONE;
1351
1352 GLuint progressCounter = 0;
1353 GLuint vSeen = 0;
1354
1355 while( getline( in, line ) ) {
1356 if( line.length() > 1 && line.at(0) == '\t' )
1357 line = line.substr( 1 );
1358 line.erase( line.find_last_not_of( " \n\r\t" ) + 1 );
1359
1360 std::vector< std::string > tokens = _tokenizeString( line, " \t" );
1361
1362 if( tokens.empty() ) continue;
1363
1364 //the line should have a single character that lets us know if it's a...
1365 if( tokens[0] == "comment" ) { // comment ignore
1366 } else if( fileState == HEADER ) {
1367 if( tokens[0] == "ply" ) { // denotes ply File type
1368 } else if( tokens[0] == "format" ) {
1369 if( tokens[1] != "ascii" ) {
1370 if (ERRORS) fprintf( stderr, "[.ply]: [ERROR]: File \"%s\" not ASCII format\n", _filename.c_str() );
1371 if ( INFO ) printf( "[.ply]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1372 in.close();
1373 return false;
1374 }
1375 } else if( tokens[0] == "element" ) { // an element (vertex, face, material)
1376 if( tokens[1] == "vertex" ) {
1377 numVertices = static_cast<GLuint>(strtol(tokens[2].c_str(), nullptr, 10));
1378 elemType = VERTEX;
1379 } else if( tokens[1] == "face" ) {
1380 numFaces = static_cast<GLuint>(strtol(tokens[2].c_str(), nullptr, 10));
1381 elemType = FACE;
1382 } else if( tokens[1] == "edge" ) {
1383
1384 } else if( tokens[1] == "material" ) {
1385 numMaterials = static_cast<GLuint>(strtol(tokens[2].c_str(), nullptr, 10));
1386 elemType = MATERIAL;
1387 } else {
1388
1389 }
1390 } else if( tokens[0] == "property" ) {
1391 if( elemType == VERTEX ) {
1392
1393 } else if( elemType == FACE ) {
1394
1395 } else if( elemType == MATERIAL ) {
1396
1397 }
1398 } else if( tokens[0] == "end_header" ) { // end of the header section
1399 fileState = VERTICES;
1400 }
1401 } else if( fileState == VERTICES ) {
1402 // read in x y z vertex location
1403 auto x = (GLfloat) strtof( tokens[0].c_str(), nullptr ),
1404 y = (GLfloat) strtof( tokens[1].c_str(), nullptr ),
1405 z = (GLfloat) strtof( tokens[2].c_str(), nullptr );
1406
1407 if( x < minX ) minX = x;
1408 if( x > maxX ) maxX = x;
1409 if( y < minY ) minY = y;
1410 if( y > maxY ) maxY = y;
1411 if( z < minZ ) minZ = z;
1412 if( z > maxZ ) maxZ = z;
1413
1414 vSeen++;
1415 // if all vertices have been read in, move on to faces
1416 if( vSeen == numVertices )
1417 fileState = FACES;
1418 } else if( fileState == FACES ) {
1419 auto numberOfVerticesInFace = static_cast<GLuint>(strtol(tokens[0].c_str(), nullptr, 10));
1420 numTriangles += numberOfVerticesInFace - 3 + 1;
1421 } else {
1422 if (INFO) printf( "[.ply]: unknown file state: %d\n", fileState );
1423 }
1424
1425 if (INFO) {
1426 progressCounter++;
1427 if( progressCounter % 5000 == 0 ) {
1428 printf("\33[2K\r");
1429 switch( progressCounter ) {
1430 case 5000: printf("[.ply]: scanning %s...\\", _filename.c_str()); break;
1431 case 10000: printf("[.ply]: scanning %s...|", _filename.c_str()); break;
1432 case 15000: printf("[.ply]: scanning %s.../", _filename.c_str()); break;
1433 case 20000: printf("[.ply]: scanning %s...-", _filename.c_str()); break;
1434 default: break;
1435 }
1436 fflush(stdout);
1437 }
1438 if( progressCounter == 20000 )
1439 progressCounter = 0;
1440 }
1441 }
1442 in.close();
1443
1444 if (INFO) {
1445 printf( "\33[2K\r" );
1446 printf( "[.ply]: scanning %s...done!\n", _filename.c_str() );
1447 printf( "[.ply]: ------------\n" );
1448 printf( "[.ply]: Model Stats:\n" );
1449 printf( "[.ply]: Vertices: \t%u\tNormals: \t%u\tTex Coords:\t%u\n", numVertices, 0, 0 );
1450 printf( "[.ply]: Faces: \t%u\tTriangles: \t%u\n", numFaces, numTriangles );
1451 printf( "[.ply]: Dimensions:\t(%f, %f, %f)\n", (maxX - minX), (maxY - minY), (maxZ - minZ) );
1452 }
1453
1454 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
1455 if (INFO && !_hasVertexNormals)
1456 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" );
1457 _allocateAttributeArrays(numVertices, numTriangles*3);
1458 } else {
1459 if (INFO) printf( "[.ply]: No vertex normals exist on model, vertex normals will be autogenerated\n" );
1460 _allocateAttributeArrays(numTriangles*3, numTriangles*3);
1461 }
1462
1463 if (INFO) printf( "[.ply]: ------------\n" );
1464
1465 std::vector<glm::vec3> verticesTemp;
1466
1467 in.open( _filename );
1468
1469 _uniqueIndex = 0;
1470 _numIndices = 0;
1471
1472 fileState = HEADER;
1473 elemType = NONE;
1474
1475 progressCounter = 0;
1476 vSeen = 0;
1477
1478 while( getline( in, line ) ) {
1479 if( line.length() > 1 && line.at(0) == '\t' )
1480 line = line.substr( 1 );
1481 line.erase( line.find_last_not_of( " \n\r\t" ) + 1 );
1482
1483 std::vector< std::string > tokens = _tokenizeString( line, " \t" );
1484
1485 if( tokens.empty() ) continue;
1486
1487 //the line should have a single character that lets us know if it's a...
1488 if( tokens[0] == "comment" ) { // comment ignore
1489 } else if( fileState == HEADER ) {
1490 if( tokens[0] == "ply" ) { // denotes ply File type
1491 } else if( tokens[0] == "format" ) {
1492 if( tokens[1] != "ascii" ) {
1493 if (ERRORS) fprintf( stderr, "[.ply]: [ERROR]: File \"%s\" not ASCII format\n", _filename.c_str() );
1494 if ( INFO ) printf( "[.ply]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1495 in.close();
1496 return false;
1497 }
1498 } else if( tokens[0] == "element" ) { // an element (vertex, face, material)
1499 if( tokens[1] == "vertex" ) {
1500 numVertices = static_cast<GLuint>(strtol(tokens[2].c_str(), nullptr, 10));
1501 elemType = VERTEX;
1502 } else if( tokens[1] == "face" ) {
1503 numFaces = static_cast<GLuint>(strtol(tokens[2].c_str(), nullptr, 10));
1504 elemType = FACE;
1505 } else if( tokens[1] == "edge" ) {
1506
1507 } else if( tokens[1] == "material" ) {
1508 numMaterials = static_cast<GLuint>(strtol(tokens[2].c_str(), nullptr, 10));
1509 elemType = MATERIAL;
1510 } else {
1511
1512 }
1513 } else if( tokens[0] == "property" ) {
1514 if( elemType == VERTEX ) {
1515
1516 } else if( elemType == FACE ) {
1517
1518 } else if( elemType == MATERIAL ) {
1519
1520 }
1521 } else if( tokens[0] == "end_header" ) { // end of the header section
1522 fileState = VERTICES;
1523 }
1524 } else if( fileState == VERTICES ) {
1525 // read in x y z vertex location
1526 glm::vec3 pos(strtof( tokens[0].c_str(), nullptr ),
1527 strtof( tokens[1].c_str(), nullptr ),
1528 strtof( tokens[2].c_str(), nullptr ) );
1529
1530 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
1531 _vertices[ _uniqueIndex ] = pos;
1532 _uniqueIndex++;
1533 } else {
1534 verticesTemp.push_back(pos );
1535 vSeen++;
1536 }
1537 // if all vertices have been read in, move on to faces
1538 if( _uniqueIndex == numVertices || vSeen == numVertices )
1539 fileState = FACES;
1540 } else if( fileState == FACES ) {
1541 auto numberOfVerticesInFace = static_cast<GLuint>(strtol(tokens[0].c_str(), nullptr, 10));
1542
1543 for( GLuint i = 2; i <= numberOfVerticesInFace - 1; i++ ) {
1544 if( _hasVertexNormals || !sAUTO_GEN_NORMALS ) {
1545 _indices[ _numIndices++ ] = static_cast<GLuint>(strtol(tokens[1].c_str(), nullptr, 10));
1546 _indices[ _numIndices++ ] = static_cast<GLuint>(strtol(tokens[i].c_str(), nullptr, 10));
1547 _indices[ _numIndices++ ] = static_cast<GLuint>(strtol(tokens[i + 1].c_str(), nullptr, 10));
1548 } else {
1549 auto aI = static_cast<GLuint>(strtol(tokens[1].c_str(), nullptr, 10));
1550 auto bI = static_cast<GLuint>(strtol(tokens[i].c_str(), nullptr, 10));
1551 auto cI = static_cast<GLuint>(strtol(tokens[i + 1].c_str(), nullptr, 10));
1552
1553 glm::vec3 a = verticesTemp[aI];
1554 glm::vec3 b = verticesTemp[bI];
1555 glm::vec3 c = verticesTemp[cI];
1556
1557 glm::vec3 ab = b - a; glm::vec3 ac = c - a;
1558 glm::vec3 ba = a - b; glm::vec3 bc = c - b;
1559 glm::vec3 ca = a - c; glm::vec3 cb = b - c;
1560
1561 glm::vec3 aN = glm::normalize( glm::cross( ab, ac ) );
1562 glm::vec3 bN = glm::normalize( glm::cross( bc, ba ) );
1563 glm::vec3 cN = glm::normalize( glm::cross( ca, cb ) );
1564
1565 _vertices[ _uniqueIndex ] = a;
1566 _normals[ _uniqueIndex ] = aN;
1567 _indices[ _numIndices++ ] = _uniqueIndex++;
1568
1569 _vertices[ _uniqueIndex ] = b;
1570 _normals[ _uniqueIndex ] = bN;
1571 _indices[ _numIndices++ ] = _uniqueIndex++;
1572
1573 _vertices[ _uniqueIndex ] = c;
1574 _normals[ _uniqueIndex ] = cN;
1575 _indices[ _numIndices++ ] = _uniqueIndex++;
1576 }
1577 }
1578 } else {
1579 if (INFO) printf( "[.ply]: unknown file state: %d\n", fileState );
1580 }
1581
1582 if (INFO) {
1583 progressCounter++;
1584 if( progressCounter % 5000 == 0 ) {
1585 printf("\33[2K\r");
1586 switch( progressCounter ) {
1587 case 5000: printf("[.ply]: parsing %s...\\", _filename.c_str()); break;
1588 case 10000: printf("[.ply]: parsing %s...|", _filename.c_str()); break;
1589 case 15000: printf("[.ply]: parsing %s.../", _filename.c_str()); break;
1590 case 20000: printf("[.ply]: parsing %s...-", _filename.c_str()); break;
1591 default: break;
1592 }
1593 fflush(stdout);
1594 }
1595 if( progressCounter == 20000 )
1596 progressCounter = 0;
1597 }
1598 }
1599 in.close();
1600
1601 _bufferData();
1602
1603 time(&end);
1604 double seconds = difftime( end, start );
1605
1606 if (INFO) {
1607 printf( "\33[2K\r" );
1608 printf( "[.ply]: parsing %s...done!\n[.ply]: Time to complete: %.3fs\n", _filename.c_str(), seconds );
1609 printf( "[.ply]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1610 }
1611
1612 return result;
1613}
1614
1615inline bool CSCI441::ModelLoader::_loadSTLFile( const bool INFO, const bool ERRORS ) {
1616 bool result = true;
1617
1618 if (INFO) printf( "[.stl]: -=-=-=-=-=-=-=- BEGIN %s Info -=-=-=-=-=-=-=-\n", _filename.c_str() );
1619
1620 time_t start, end;
1621 time(&start);
1622
1623 std::ifstream in( _filename );
1624 if( !in.is_open() ) {
1625 if (ERRORS) fprintf(stderr, "[.stl]: [ERROR]: Could not open \"%s\"\n", _filename.c_str() );
1626 if ( INFO ) printf( "[.stl]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1627 return false;
1628 }
1629
1630 GLuint numVertices = 0, numNormals = 0, numFaces = 0, numTriangles = 0, numVertsInLoop = 0;
1631 GLfloat minX = 999999.0f, maxX = -999999.0f, minY = 999999.0f, maxY = -999999.0f, minZ = 999999.0f, maxZ = -999999.0f;
1632 std::string line;
1633
1634 int progressCounter = 0;
1635 glm::vec3 normalVector = {0.f,0.f,0.f};
1636
1637 while( getline( in, line ) ) {
1638 if( line.length() > 1 && line.at(0) == '\t' )
1639 line = line.substr( 1 );
1640 line.erase( line.find_last_not_of( " \n\r\t" ) + 1 );
1641
1642 std::vector< std::string > tokens = _tokenizeString( line, " \t" );
1643
1644 if( tokens.empty() ) continue;
1645
1646 if( tokens[0] == "solid" ) {
1647
1648 } else if( tokens[0] == "facet" ) {
1649 // read in x y z triangle normal
1650 numNormals++;
1651 } else if( tokens[0] == "outer" && tokens[1] == "loop" ) {
1652 // begin a primitive
1653 numVertsInLoop = 0;
1654 } else if( tokens[0] == "vertex" ) {
1655 GLfloat x = strtof( tokens[1].c_str(), nullptr ),
1656 y = strtof( tokens[2].c_str(), nullptr ),
1657 z = strtof( tokens[3].c_str(), nullptr );
1658
1659 if( x < minX ) minX = x;
1660 if( x > maxX ) maxX = x;
1661 if( y < minY ) minY = y;
1662 if( y > maxY ) maxY = y;
1663 if( z < minZ ) minZ = z;
1664 if( z > maxZ ) maxZ = z;
1665
1666 numVertices++;
1667 numVertsInLoop++;
1668 } else if( tokens[0] == "endloop" ) {
1669 // end primitive
1670 numTriangles += numVertsInLoop - 3 + 1;
1671 } else if( tokens[0] == "endfacet" ) {
1672 numFaces++;
1673 } else if( tokens[0] == "endsolid" ) {
1674
1675 }
1676 else {
1677 if( memchr( line.c_str(), '\0', line.length() ) != nullptr ) {
1678 if (ERRORS) fprintf( stderr, "[.stl]: [ERROR]: Cannot read binary STL file \"%s\"\n", _filename.c_str() );
1679 if ( INFO ) printf( "[.stl]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1680 in.close();
1681 return false;
1682 } else if (INFO) printf( "[.stl]: unknown line: %s\n", line.c_str() );
1683 }
1684
1685 if (INFO) {
1686 progressCounter++;
1687 if( progressCounter % 5000 == 0 ) {
1688 printf("\33[2K\r");
1689 switch( progressCounter ) {
1690 case 5000: printf("[.stl]: scanning %s...\\", _filename.c_str()); break;
1691 case 10000: printf("[.stl]: scanning %s...|", _filename.c_str()); break;
1692 case 15000: printf("[.stl]: scanning %s.../", _filename.c_str()); break;
1693 case 20000: printf("[.stl]: scanning %s...-", _filename.c_str()); break;
1694 default: break;
1695 }
1696 fflush(stdout);
1697 }
1698 if( progressCounter == 20000 )
1699 progressCounter = 0;
1700 }
1701 }
1702 in.close();
1703
1704 if (INFO) {
1705 printf( "\33[2K\r" );
1706 printf( "[.stl]: scanning %s...done!\n", _filename.c_str() );
1707 printf( "[.stl]: ------------\n" );
1708 printf( "[.stl]: Model Stats:\n" );
1709 printf( "[.stl]: Vertices: \t%u\tNormals: \t%u\tTex Coords:\t%u\n", numVertices, numNormals, 0 );
1710 printf( "[.stl]: Faces: \t%u\tTriangles: \t%u\n", numFaces, numTriangles );
1711 printf( "[.stl]: Dimensions:\t(%f, %f, %f)\n", (maxX - minX), (maxY - minY), (maxZ - minZ) );
1712 }
1713
1714 _allocateAttributeArrays(numVertices, numTriangles*3);
1715
1716 if (INFO) printf( "[.stl]: ------------\n" );
1717
1718 in.open( _filename );
1719
1720 _uniqueIndex = 0;
1721 _numIndices = 0;
1722
1723 while( getline( in, line ) ) {
1724 if( line.length() > 1 && line.at(0) == '\t' )
1725 line = line.substr( 1 );
1726 line.erase( line.find_last_not_of( " \n\r\t" ) + 1 );
1727
1728 std::vector< std::string > tokens = _tokenizeString( line, " \t" );
1729
1730 if( tokens.empty() ) continue;
1731
1732 if( tokens[0] == "solid" ) {
1733
1734 } else if( tokens[0] == "facet" ) {
1735 // read in x y z triangle normal
1736 normalVector = glm::vec3( strtof( tokens[2].c_str(), nullptr ),
1737 strtof( tokens[3].c_str(), nullptr ),
1738 strtof( tokens[4].c_str(), nullptr ) );
1739 } else if( tokens[0] == "outer" && tokens[1] == "loop" ) {
1740 // begin a primitive
1741 } else if( tokens[0] == "vertex" ) {
1742 _vertices[ _uniqueIndex ] = glm::vec3(strtof( tokens[1].c_str(), nullptr ),
1743 strtof( tokens[2].c_str(), nullptr ),
1744 strtof( tokens[3].c_str(), nullptr ) );
1745 _normals[ _uniqueIndex ] = normalVector;
1746 _indices[ _numIndices++ ] = _uniqueIndex++;
1747 } else if( tokens[0] == "endloop" ) {
1748 // end primitive
1749 } else if( tokens[0] == "endfacet" ) {
1750
1751 } else if( tokens[0] == "endsolid" ) {
1752
1753 }
1754 else {
1755
1756 }
1757
1758 if (INFO) {
1759 progressCounter++;
1760 if( progressCounter % 5000 == 0 ) {
1761 printf("\33[2K\r");
1762 switch( progressCounter ) {
1763 case 5000: printf("[.stl]: parsing %s...\\", _filename.c_str()); break;
1764 case 10000: printf("[.stl]: parsing %s...|", _filename.c_str()); break;
1765 case 15000: printf("[.stl]: parsing %s.../", _filename.c_str()); break;
1766 case 20000: printf("[.stl]: parsing %s...-", _filename.c_str()); break;
1767 default: break;
1768 }
1769 fflush(stdout);
1770 }
1771 if( progressCounter == 20000 )
1772 progressCounter = 0;
1773 }
1774 }
1775 in.close();
1776
1777 _bufferData();
1778
1779 time(&end);
1780 double seconds = difftime( end, start );
1781
1782 if (INFO) {
1783 printf("\33[2K\r");
1784 printf("[.stl]: parsing %s...done!\n[.stl]: Time to complete: %.3fs\n", _filename.c_str(), seconds);
1785 printf( "[.stl]: -=-=-=-=-=-=-=- END %s Info -=-=-=-=-=-=-=-\n\n", _filename.c_str() );
1786 }
1787
1788 return result;
1789}
1790
1791[[maybe_unused]]
1793 sAUTO_GEN_NORMALS = true;
1794}
1795
1796[[maybe_unused]]
1798 sAUTO_GEN_NORMALS = false;
1799}
1800[[maybe_unused]]
1802 sAUTO_GEN_TANGENTS = true;
1803}
1804
1805[[maybe_unused]]
1807 sAUTO_GEN_TANGENTS = false;
1808}
1809
1810inline void CSCI441::ModelLoader::_allocateAttributeArrays(const GLuint numVertices, const GLuint numIndices) {
1811 _vertices = new glm::vec3[numVertices];
1812 _normals = new glm::vec3[numVertices];
1813 _tangents = new glm::vec4[numVertices];
1814 _texCoords = new glm::vec2[numVertices];
1815 _indices = new GLuint[numIndices];
1816}
1817
1818inline void CSCI441::ModelLoader::_bufferData() const {
1819 glBindVertexArray( _vaod );
1820
1821 glBindBuffer( GL_ARRAY_BUFFER, _vbods[0] );
1822 glBufferData( GL_ARRAY_BUFFER, static_cast<GLsizeiptr>((sizeof(glm::vec3)*2 + sizeof(glm::vec2) + sizeof(glm::vec4)) * _uniqueIndex), nullptr, GL_STATIC_DRAW );
1823 glBufferSubData( GL_ARRAY_BUFFER, 0, static_cast<GLsizeiptr>(sizeof(glm::vec3) * _uniqueIndex), _vertices );
1824 glBufferSubData( GL_ARRAY_BUFFER, static_cast<GLintptr>(sizeof(glm::vec3) * _uniqueIndex), static_cast<GLsizeiptr>(sizeof(glm::vec3) * _uniqueIndex), _normals );
1825 glBufferSubData( GL_ARRAY_BUFFER, static_cast<GLintptr>(sizeof(glm::vec3) * _uniqueIndex * 2), static_cast<GLsizeiptr>(sizeof(glm::vec2) * _uniqueIndex), _texCoords );
1826 glBufferSubData( GL_ARRAY_BUFFER, static_cast<GLintptr>(sizeof(glm::vec3) * _uniqueIndex * 2 + sizeof(glm::vec2) * _uniqueIndex), static_cast<GLsizeiptr>(sizeof(glm::vec4) * _uniqueIndex), _tangents );
1827
1828 glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, _vbods[1] );
1829 glBufferData( GL_ELEMENT_ARRAY_BUFFER, static_cast<GLsizeiptr>(sizeof(GLuint) * _numIndices), _indices, GL_STATIC_DRAW );
1830}
1831
1832//
1833// vector<string> tokenizeString(string input, string delimiters)
1834//
1835// This is a helper function to break a single string into std::vector
1836// of strings, based on a given set of delimiter characters.
1837//
1838inline std::vector<std::string> CSCI441::ModelLoader::_tokenizeString(const std::string& input, const std::string& delimiters) {
1839 if(input.empty())
1840 return {};
1841
1842 auto retVec = std::vector<std::string>();
1843 size_t oldR = 0, r = 0;
1844
1845 //strip all delimiter characters from the front and end of the input string.
1846 GLint lowerValidIndex = 0, upperValidIndex = static_cast<GLint>(input.size()) - 1;
1847 while(static_cast<GLuint>(lowerValidIndex) < input.size() && delimiters.find_first_of(input.at(lowerValidIndex), 0) != std::string::npos)
1848 lowerValidIndex++;
1849
1850 while(upperValidIndex >= 0 && delimiters.find_first_of(input.at(upperValidIndex), 0) != std::string::npos)
1851 upperValidIndex--;
1852
1853 //if the lowest valid index is higher than the highest valid index, they're all delimiters! return nothing.
1854 if(static_cast<GLuint>(lowerValidIndex) >= input.size() || upperValidIndex < 0 || lowerValidIndex > upperValidIndex)
1855 return {};
1856
1857 //remove the delimiters from the beginning and end of the string, if any.
1858 const std::string strippedInput = input.substr(lowerValidIndex, upperValidIndex-lowerValidIndex+1);
1859
1860 //search for each instance of a delimiter character, and create a new token spanning
1861 //from the last valid character up to the delimiter character.
1862 while((r = strippedInput.find_first_of(delimiters, oldR)) != std::string::npos) {
1863 if(oldR != r) {
1864 //but watch out for multiple consecutive delimiters!
1865 retVec.push_back(strippedInput.substr(oldR, r-oldR));
1866 }
1867 oldR = r+1;
1868 }
1869 if(r != 0) {
1870 retVec.push_back(strippedInput.substr(oldR, r-oldR));
1871 }
1872
1873 return retVec;
1874}
1875
1876inline void CSCI441::ModelLoader::_moveFromSrc(ModelLoader& src) {
1877 _hasVertexTexCoords = src._hasVertexTexCoords;
1878 src._hasVertexTexCoords = false;
1879
1880 _hasVertexNormals = src._hasVertexNormals;
1881 src._hasVertexNormals = false;
1882
1883 _vertices = src._vertices;
1884 src._vertices = nullptr;
1885
1886 _texCoords = src._texCoords;
1887 src._texCoords = nullptr;
1888
1889 _normals = src._normals;
1890 src._normals = nullptr;
1891
1892 _tangents = src._tangents;
1893 src._tangents = nullptr;
1894
1895 _indices = src._indices;
1896 src._indices = nullptr;
1897
1898 _uniqueIndex = src._uniqueIndex;
1899 src._uniqueIndex = 0;
1900
1901 _numIndices = src._numIndices;
1902 src._numIndices = 0;
1903
1904 _vbods[0] = src._vbods[0];
1905 _vbods[1] = src._vbods[1];
1906 src._vbods[0] = 0;
1907 src._vbods[1] = 0;
1908
1909 _vaod = src._vaod;
1910 src._vaod = 0;
1911
1912 _filename = std::move(src._filename);
1913 src._filename = "";
1914
1915 _modelType = src._modelType;
1916
1917 _materials = std::move(src._materials);
1918 _materialIndexStartStop = std::move(src._materialIndexStartStop);
1919}
1920
1921inline void CSCI441::ModelLoader::_cleanupSelf() {
1922 delete[] _vertices;
1923 _vertices = nullptr;
1924
1925 delete[] _normals;
1926 _normals = nullptr;
1927
1928 delete[] _tangents;
1929 _tangents = nullptr;
1930
1931 delete[] _texCoords;
1932 _texCoords = nullptr;
1933
1934 delete[] _indices;
1935 _indices = nullptr;
1936
1937 glDeleteBuffers( 2, _vbods );
1938 _vbods[0] = 0;
1939 _vbods[1] = 0;
1940
1941 glDeleteVertexArrays( 1, &_vaod );
1942 _vaod = 0;
1943
1944 _hasVertexTexCoords = false;
1945 _hasVertexNormals = false;
1946 _uniqueIndex = 0;
1947 _numIndices = 0;
1948 _filename = "";
1949
1950 for( const auto& [name, material] : _materials ) {
1951 delete material;
1952 }
1953 _materials.clear();
1954
1955 _materialIndexStartStop.clear();
1956}
1957
1958inline 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 ) {
1959 //combine the 'mask' array with the image data array into an RGBA array.
1960 auto *fullData = new unsigned char[texWidth*texHeight*4];
1961
1962 for(int j = 0; j < texHeight; j++) {
1963 for(int i = 0; i < texWidth; i++) {
1964 if( imageData ) {
1965 fullData[(j*texWidth+i)*4+0] = imageData[(j*texWidth+i)*texChannels+0]; // R
1966 fullData[(j*texWidth+i)*4+1] = imageData[(j*texWidth+i)*texChannels+1]; // G
1967 fullData[(j*texWidth+i)*4+2] = imageData[(j*texWidth+i)*texChannels+2]; // B
1968 } else {
1969 fullData[(j*texWidth+i)*4+0] = 1; // R
1970 fullData[(j*texWidth+i)*4+1] = 1; // G
1971 fullData[(j*texWidth+i)*4+2] = 1; // B
1972 }
1973
1974 if( imageMask ) {
1975 fullData[(j*texWidth+i)*4+3] = imageMask[(j*texWidth+i)*maskChannels+0]; // A
1976 } else {
1977 fullData[(j*texWidth+i)*4+3] = 1; // A
1978 }
1979 }
1980 }
1981 return fullData;
1982}
1983
1984[[maybe_unused]]
1985inline void CSCI441_INTERNAL::flipImageY( const int texWidth, const int texHeight, const int textureChannels, unsigned char *textureData ) {
1986 for( int j = 0; j < texHeight / 2; j++ ) {
1987 for( int i = 0; i < texWidth; i++ ) {
1988 for( int k = 0; k < textureChannels; k++ ) {
1989 const int top = (j*texWidth + i)*textureChannels + k;
1990 const int bot = ((texHeight-j-1)*texWidth + i)*textureChannels + k;
1991
1992 const unsigned char t = textureData[top];
1993 textureData[top] = textureData[bot];
1994 textureData[bot] = t;
1995 }
1996 }
1997 }
1998}
1999
2000#endif // __CSCI441_MODEL_LOADER_HPP__
Loads object models from file and renders using VBOs/VAOs.
Definition: ModelLoader.hpp:54
ModelLoader(const ModelLoader &)=delete
do not allow models to be copied
static void enableAutoGenerateNormals()
Enable auto-generation of vertex normals.
Definition: ModelLoader.hpp:1792
ModelLoader & operator=(const ModelLoader &)=delete
do not allow models to be copied
static void enableAutoGenerateTangents()
Enable auto-generation of vertex tangents.
Definition: ModelLoader.hpp:1801
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:403
GLfloat * getVertices() const
Return the vertex array that makes up the model mesh.
Definition: ModelLoader.hpp:399
GLfloat * getTangents() const
Return the tangent array that corresponds to the model mesh.
Definition: ModelLoader.hpp:401
bool loadModelFile(std::string filename, bool INFO=true, bool ERRORS=true)
Loads a model from the given file.
Definition: ModelLoader.hpp:310
void setAttributeLocations(GLint positionLocation, GLint normalLocation=-1, GLint texCoordLocation=-1, GLint tangentLocation=-1) const
Enables VBO attribute array locations.
Definition: ModelLoader.hpp:338
GLfloat * getNormals() const
Return the normal array that corresponds to the model mesh.
Definition: ModelLoader.hpp:400
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:356
GLuint * getIndices() const
Return the index array that dictates the order to draw the model mesh.
Definition: ModelLoader.hpp:404
GLfloat * getTexCoords() const
Return the texture coordinates array that corresponds to the model mesh.
Definition: ModelLoader.hpp:402
static void disableAutoGenerateTangents()
Disable auto-generation of vertex tangents.
Definition: ModelLoader.hpp:1806
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:398
static void disableAutoGenerateNormals()
Disable auto-generation of vertex normals.
Definition: ModelLoader.hpp:1797
~ModelLoader()
Frees memory associated with model on both CPU and GPU.
Definition: ModelLoader.hpp:277
ModelLoader()
Creates an empty model.
Definition: ModelLoader.hpp:243
Internal material representation for *.mtl files.
CSCI441 Helper Functions for OpenGL.
Definition: ArcballCam.hpp:17