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