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