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