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