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