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