CSCI441 OpenGL Library 5.25.0.0
CS@Mines CSCI441 Computer Graphics Course Library
Loading...
Searching...
No Matches
MD5Model.hpp
Go to the documentation of this file.
1
12#ifndef CSCI441_MD5_MODEL_HPP
13#define CSCI441_MD5_MODEL_HPP
14
15/*
16 * md5mesh model loader + animation
17 * last modification: Dr. Jeffrey Paone
18 * encapsulated into a class
19 * supports texturing
20 *
21 * Doom3's md5mesh viewer with animation. Mesh and Animation declaration
22 * See http://tfc.duke.free.fr/coding/md5-specs-en.html for more details
23 *
24 * Copyright (c) 2005-2007 David HENRY
25 *
26 * Permission is hereby granted, free of charge, to any person
27 * obtaining a copy of this software and associated documentation
28 * files (the "Software"), to deal in the Software without
29 * restriction, including without limitation the rights to use,
30 * copy, modify, merge, publish, distribute, sublicense, and/or
31 * sell copies of the Software, and to permit persons to whom the
32 * Software is furnished to do so, subject to the following conditions:
33 *
34 * The above copyright notice and this permission notice shall be
35 * included in all copies or substantial portions of the Software.
36 *
37 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
38 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
39 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
40 *
41 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
42 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
43 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
44 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
45 *
46 */
47
48#include "constants.h"
49#include "MD5Model_types.hpp"
50#include "TextureUtils.hpp"
51
52#ifdef CSCI441_USE_GLEW
53 #include <GL/glew.h>
54#else
55 #include <glad/gl.h>
56#endif
57
58#include <glm/exponential.hpp>
59#include <glm/ext/quaternion_common.hpp>
60
61#define GLM_ENABLE_EXPERIMENTAL
62#include <glm/gtx/quaternion.hpp>
63
64#include <cassert>
65#include <cmath>
66#include <cstdio>
67#include <cstdlib>
68#include <map>
69
70namespace CSCI441 {
71
76 class [[maybe_unused]] MD5Model {
77 public:
83 // allocate space for one animation by default
84 _numAnimations = 1;
85 // create the initial animation
86 _animations = new CSCI441_INTERNAL::MD5Animation*[_numAnimations];
87 _animations[0] = new CSCI441_INTERNAL::MD5Animation();
88 // and the associated animation state information
89 _animationInfos = new CSCI441_INTERNAL::MD5AnimationState[_numAnimations];
90 }
94 ~MD5Model();
95
99 MD5Model(const MD5Model&) = delete;
103 MD5Model& operator=(const MD5Model&) = delete;
104
108 MD5Model(MD5Model&& src) noexcept {
109 _moveFromSrc(src);
110 }
114 MD5Model& operator=(MD5Model&& src) noexcept {
115 if (this != &src) {
116 _moveFromSrc(src);
117 }
118 return *this;
119 }
120
127 [[maybe_unused]] bool loadMD5Model(const char* MD5_MESH_FILE, const char* MD5_ANIM_FILE = "");
128
133 [[nodiscard]] bool isAnimated() const { return _isAnimated; }
134
135 // md5mesh prototypes
141 [[nodiscard]] bool readMD5Model(const char* FILENAME);
153 [[maybe_unused]] void allocVertexArrays(GLuint vPosAttribLoc, GLuint vColorAttribLoc, GLuint vTexCoordAttribLoc, GLuint vNormalAttribLoc = 0, GLuint vTangentAttribLoc = 0);
154
162 [[maybe_unused]] void setActiveTextures(GLint diffuseMapActiveTexture, GLint specularMapActiveTexture, GLint normalMapActiveTexture, GLint heightMapActiveTexture);
163
167 [[maybe_unused]] void draw() const;
171 [[maybe_unused]] void drawSkeleton() const;
172
173 // md5anim prototypes
180 [[nodiscard]] bool readMD5Anim(const char* filename, GLushort targetAnimationIndex = 0);
186 [[nodiscard]] bool addMD5Anim(const char* filename);
193 [[nodiscard]] GLushort getNumberOfAnimations() const {
194 // one slot is always allocated, but if model is not allocated then return 0
195 if (!_isAnimated) return 0;
196 // model is animated, return number of animations that were successfully loaded
197 return _numAnimations;
198 }
204 void useTargetAnimationIndex(GLushort targetAnimationIndex);
209 void animate(GLfloat dt);
210
211 // md5material prototypes
217 static void readMD5Material(const char* FILENAME);
221 static void releaseMD5Materials();
222
223 private:
228 CSCI441_INTERNAL::MD5Joint* _baseSkeleton = nullptr;
233 CSCI441_INTERNAL::MD5Mesh* _meshes = nullptr;
238 GLint _numJoints = 0;
243 GLint _numMeshes = 0;
244
245 // vertex array related stuff
250 GLint _maxVertices = 0;
255 GLint _maxTriangles = 0;
260 glm::vec3* _vertexArray = nullptr;
265 glm::vec3* _normalArray = nullptr;
271 glm::vec4* _tangentArray = nullptr;
276 glm::vec2* _texelArray = nullptr;
281 GLuint* _vertexIndicesArray = nullptr;
285 GLuint _vao = 0;
291 GLuint _vbo[2] = {0, 0};
292
293 // skeleton related stuff
297 GLuint _skeletonVAO = 0;
301 GLuint _skeletonVBO = 0;
305 CSCI441_INTERNAL::MD5Joint* _skeleton = nullptr;
306
307 // animation related stuff
311 CSCI441_INTERNAL::MD5Animation** _animations = nullptr;
315 GLushort _numAnimations = 0;
319 GLushort _currentAnimationIndex = 0;
323 bool _isAnimated = false;
327 CSCI441_INTERNAL::MD5AnimationState* _animationInfos = nullptr;
328
329 // material related stuff
330 static std::map< std::string, CSCI441_INTERNAL::MD5MaterialShader* > _materials;
331 static std::map< std::string, GLuint > _textureMap;
332
336 GLint _diffuseActiveTexture = GL_TEXTURE0;
340 GLint _specularActiveTexture = GL_TEXTURE1;
344 GLint _normalActiveTexture = GL_TEXTURE2;
348 GLint _heightActiveTexture = GL_TEXTURE3;
349
350 // helper functions
356 void _prepareMesh(const CSCI441_INTERNAL::MD5Mesh* pMESH) const;
361 auto _drawMesh(const CSCI441_INTERNAL::MD5Mesh* pMESH) const -> void;
368 [[nodiscard]] bool _checkAnimValidity(GLushort targetAnimationIndex) const;
377 static void _buildFrameSkeleton(const CSCI441_INTERNAL::MD5JointInfo* pJOINT_INFOS,
378 const CSCI441_INTERNAL::MD5BaseFrameJoint* pBASE_FRAME,
379 const GLfloat* pANIM_FRAME_DATA,
380 const CSCI441_INTERNAL::MD5Joint* pSkeletonFrame,
381 GLint NUM_JOINTS);
386 void _interpolateSkeletons(GLfloat interp);
390 void _freeModel();
394 void _freeVertexArrays();
398 void _freeAnim();
399
404 void _moveFromSrc(MD5Model &src);
405
414 static size_t _trim(char* out, size_t len, const char* str);
415
423 static bool _registerShaderTexture( CSCI441_INTERNAL::MD5Texture* texture );
424 };
425}
426
427//----------------------------------------------------------------------------------------------------
428
429inline
431{
432 _freeModel();
433 _freeVertexArrays();
434 _freeAnim();
435}
436
437// load our MD5 model
438[[maybe_unused]]
439inline bool
441 const char* MD5_MESH_FILE,
442 const char* MD5_ANIM_FILE
443) {
444 // Load MD5 _model file
445 if( readMD5Model(MD5_MESH_FILE) ) {
446 // if MD5 animation file name provided
447 if(strcmp(MD5_ANIM_FILE, "") != 0 ) {
448 // Load MD5 animation file
449 if( !readMD5Anim(MD5_ANIM_FILE) ) {
450 return false;
451 }
452 }
453 if( !isAnimated() ) {
454 printf ("[.MD5_ANIM_FILE]: no animation loaded.\n");
455 }
456 } else {
457 return false;
458 }
459 return true;
460}
461
462// Load an MD5 model from file.
463inline bool
465 const char* FILENAME
466) {
467 char buff[512];
468 GLint version = 0;
469 GLint currentMesh = 0;
470
471 GLint totalVertices = 0;
472 GLint totalWeights = 0;
473 GLint totalTriangles = 0;
474
475 GLfloat minX = 999999, minY = 999999, minZ = 999999;
476 GLfloat maxX = -999999, maxY = -999999, maxZ = -999999;
477
478 fprintf(stdout, "[.md5mesh]: about to read %s\n", FILENAME );
479
480 FILE *fp = fopen(FILENAME, "rb" );
481 if( !fp ) {
482 fprintf (stderr, "[.md5mesh]: Error: couldn't open \"%s\"!\n", FILENAME);
483 return false;
484 }
485
486 while( !feof(fp) ) {
487 // Read whole line
488 fgets( buff, sizeof(buff), fp );
489
490 if( sscanf(buff, " MD5Version %d", &version) == 1 ) {
491 if( version != 10 ) {
492 // Bad version
493 fprintf (stderr, "[.md5mesh]: Error: bad model version\n");
494 fclose (fp);
495 return false;
496 }
497 } else if( sscanf(buff, " numJoints %d", &_numJoints) == 1 ) {
498 if( _numJoints > 0 ) {
499 // Allocate memory for base skeleton joints
500 _baseSkeleton = new CSCI441_INTERNAL::MD5Joint[_numJoints];
501 }
502 } else if( sscanf(buff, " numMeshes %d", &_numMeshes) == 1 ) {
503 if( _numMeshes > 0 ) {
504 // Allocate memory for meshes
505 _meshes = new CSCI441_INTERNAL::MD5Mesh[_numMeshes];
506 }
507 } else if( strncmp(buff, "joints {", 8) == 0 ) {
508 // Read each joint
509 for(GLint i = 0; i < _numJoints; ++i) {
510 CSCI441_INTERNAL::MD5Joint *joint = &_baseSkeleton[i];
511
512 // Read whole line
513 fgets( buff, sizeof(buff), fp );
514
515 if( sscanf(buff, "%s %d ( %f %f %f ) ( %f %f %f )",
516 joint->name, &joint->parent,
517 &joint->position[0], &joint->position[1], &joint->position[2],
518 &joint->orientation[0],&joint->orientation[1], &joint->orientation[2]) == 8
519 ) {
520 // Compute the w component
521 joint->orientation.w = glm::extractRealComponent(joint->orientation);
522 }
523 }
524 } else if( strncmp(buff, "mesh {", 6) == 0 ) {
525 CSCI441_INTERNAL::MD5Mesh *mesh = &_meshes[currentMesh];
526 GLint vert_index = 0;
527 GLint tri_index = 0;
528 GLint weight_index = 0;
529 GLfloat floatData[4];
530 GLint intData[3];
531
532 while( buff[0] != '}' && !feof(fp) ) {
533 // Read whole line
534 fgets( buff, sizeof(buff), fp );
535
536 if( strstr( buff, "shader ") ) {
537 GLint quote = 0, j = 0;
538
539 char shaderName[512] = "";
540
541 // Copy the shader name without the quote marks
542 for(unsigned long uli = 0; uli < sizeof(buff) && (quote < 2); ++uli) {
543 if( buff[uli] == '\"' )
544 quote++;
545
546 if( (quote == 1) && (buff[uli] != '\"') ) {
547 shaderName[j] = buff[uli];
548 j++;
549 }
550 }
551 // there was a shader name
552 if( j > 0 ) {
553 auto materialIter = _materials.find(shaderName);
554 if (materialIter != _materials.end()) {
555 mesh->shader = materialIter->second;
556 } else {
557 fprintf(stderr, "[.md5mesh | ERROR]: Could not find material shader \"%s\"\n", shaderName);
558 }
559 }
560 } else if( sscanf(buff, " numverts %d", &mesh->numVertices) == 1 ) {
561 if( mesh->numVertices > 0 ) {
562 // Allocate memory for vertices
563 mesh->vertices = new CSCI441_INTERNAL::MD5Vertex[mesh->numVertices];
564 }
565
566 if( mesh->numVertices > _maxVertices )
567 _maxVertices = mesh->numVertices;
568
569 totalVertices += mesh->numVertices;
570 } else if( sscanf(buff, " numtris %d", &mesh->numTriangles) == 1 ) {
571 if( mesh->numTriangles > 0 ) {
572 // Allocate memory for triangles
573 mesh->triangles = new CSCI441_INTERNAL::MD5Triangle[mesh->numTriangles];
574 }
575
576 if( mesh->numTriangles > _maxTriangles )
577 _maxTriangles = mesh->numTriangles;
578
579 totalTriangles += mesh->numTriangles;
580 } else if( sscanf(buff, " numweights %d", &mesh->numWeights) == 1 ) {
581 if( mesh->numWeights > 0 ) {
582 // Allocate memory for vertex weights
583 mesh->weights = new CSCI441_INTERNAL::MD5Weight[mesh->numWeights];
584 }
585
586 totalWeights += mesh->numWeights;
587 } else if( sscanf(buff, " vert %d ( %f %f ) %d %d",
588 &vert_index,
589 &floatData[0], &floatData[1],
590 &intData[0], &intData[1]) == 5
591 ) {
592 // Copy vertex data
593 mesh->vertices[vert_index].texCoord.s = floatData[0];
594 mesh->vertices[vert_index].texCoord.t = floatData[1];
595 mesh->vertices[vert_index].start = intData[0];
596 mesh->vertices[vert_index].count = intData[1];
597 } else if( sscanf(buff, " tri %d %d %d %d",
598 &tri_index,
599 &intData[0], &intData[1], &intData[2]) == 4
600 ) {
601 // Copy triangle data
602 mesh->triangles[tri_index ].index[0] = intData[0];
603 mesh->triangles[tri_index ].index[1] = intData[1];
604 mesh->triangles[tri_index ].index[2] = intData[2];
605 } else if( sscanf(buff, " weight %d %d %f ( %f %f %f )",
606 &weight_index, &intData[0], &floatData[3],
607 &floatData[0], &floatData[1], &floatData[2]) == 6
608 ) {
609 // Copy vertex data
610 mesh->weights[weight_index].joint = intData[0];
611 mesh->weights[weight_index].bias = floatData[3];
612 mesh->weights[weight_index].position[0] = floatData[0];
613 mesh->weights[weight_index].position[1] = floatData[1];
614 mesh->weights[weight_index].position[2] = floatData[2];
615
616 if( floatData[0] < minX ) { minX = floatData[0]; }
617 if( floatData[0] > maxX ) { maxX = floatData[0]; }
618 if( floatData[1] < minY ) { minY = floatData[1]; }
619 if( floatData[1] > maxY ) { maxY = floatData[1]; }
620 if( floatData[2] < minZ ) { minZ = floatData[2]; }
621 if( floatData[2] > maxZ ) { maxZ = floatData[2]; }
622 }
623 }
624
625 currentMesh++;
626 }
627 }
628
629 fclose(fp);
630
631 _skeleton = _baseSkeleton;
632
633 fprintf(stdout, "[.md5mesh]: finished reading %s\n", FILENAME );
634 fprintf(stdout, "[.md5mesh]: read in %d meshes, %d joints, %d vertices, %d weights, and %d triangles\n", _numMeshes, _numJoints, totalVertices, totalWeights, totalTriangles );
635 fprintf(stdout, "[.md5mesh]: base pose %f units across in X, %f units across in Y, %f units across in Z\n", (maxX - minX), (maxY-minY), (maxZ - minZ) );
636 fprintf(stdout, "\n" );
637
638 return true;
639}
640
641inline void
642CSCI441::MD5Model::_freeModel()
643{
644 delete[] _baseSkeleton;
645 if (_baseSkeleton == _skeleton) _skeleton = nullptr; // if there is no animation, prevent double delete
646 _baseSkeleton = nullptr;
647
648 delete[] _meshes;
649 _meshes = nullptr;
650}
651
652[[maybe_unused]]
653inline void
655{
656 // Draw each mesh of the model
657 for(GLint i = 0; i < _numMeshes; ++i) {
658 CSCI441_INTERNAL::MD5Mesh& mesh = _meshes[i]; // get the mesh
659 _prepareMesh(&mesh); // do some preprocessing on it
660 _drawMesh(&mesh); // draw it
661 }
662}
663
664inline void
665CSCI441::MD5Model::_prepareMesh(
666 const CSCI441_INTERNAL::MD5Mesh *pMESH
667) const {
668 GLint i, j, k;
669
670 // Setup vertex indices
671 for(k = 0, i = 0; i < pMESH->numTriangles; ++i) {
672 for(j = 0; j < 3; ++j, ++k)
673 _vertexIndicesArray[k] = pMESH->triangles[i].index[j];
674 }
675
676 // Setup vertices
677 auto normalAccum = new glm::vec3[pMESH->numVertices];
678 auto tangentAccum = new glm::vec3[pMESH->numVertices];
679 auto bitangentAccum = new glm::vec3[pMESH->numVertices];
680
681 for(i = 0; i < pMESH->numVertices; ++i) {
682 normalAccum[i] = glm::vec3(0.0f);
683 tangentAccum[i] = glm::vec3(0.0f);
684 bitangentAccum[i] = glm::vec3(0.0f);
685
686 glm::vec3 finalVertex = {0.0f, 0.0f, 0.0f };
687
688 // Calculate final vertex to draw with weights
689 for(j = 0; j < pMESH->vertices[i].count; ++j) {
690 const CSCI441_INTERNAL::MD5Weight *weight = &pMESH->weights[pMESH->vertices[i].start + j];
691 const CSCI441_INTERNAL::MD5Joint *joint = &_skeleton[weight->joint];
692
693 // Calculate transformed vertex for this weight
694 const glm::vec3 weightedVertex = glm::rotate(joint->orientation, glm::vec4(weight->position, 0.0f));
695
696 // The sum of all weight->bias should be 1.0
697 finalVertex.x += (joint->position.x + weightedVertex.x) * weight->bias;
698 finalVertex.y += (joint->position.y + weightedVertex.y) * weight->bias;
699 finalVertex.z += (joint->position.z + weightedVertex.z) * weight->bias;
700 }
701
702 _vertexArray[i].x = finalVertex.x;
703 _vertexArray[i].y = finalVertex.y;
704 _vertexArray[i].z = finalVertex.z;
705
706 _texelArray[i].s = pMESH->vertices[i].texCoord.s;
707 _texelArray[i].t = pMESH->vertices[i].texCoord.t;
708 }
709
710 for(i = 0; i < pMESH->numTriangles; ++i) {
711 GLint idx0 = pMESH->triangles[i].index[0];
712 GLint idx1 = pMESH->triangles[i].index[1];
713 GLint idx2 = pMESH->triangles[i].index[2];
714
715 glm::vec3 v0 = _vertexArray[ idx0 ];
716 glm::vec3 v1 = _vertexArray[ idx1 ];
717 glm::vec3 v2 = _vertexArray[ idx2 ];
718
719 glm::vec2 uv0 = _texelArray[ idx0 ];
720 glm::vec2 uv1 = _texelArray[ idx1 ];
721 glm::vec2 uv2 = _texelArray[ idx2 ];
722
723 glm::vec3 edge1 = v1 - v0;
724 glm::vec3 edge2 = v2 - v0;
725 glm::vec2 deltaUV1 = uv1 - uv0;
726 glm::vec2 deltaUV2 = uv2 - uv0;
727
728 GLfloat f = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y);
729
730 glm::vec3 normal = cross(edge1, edge2);
731 glm::vec3 tangent = f * (deltaUV2.y * edge1 - deltaUV1.y * edge2);
732 glm::vec3 bitangent = f * (deltaUV2.x * edge1 - deltaUV1.x * edge2);
733
734 normalAccum[ idx0 ] += normal; tangentAccum[ idx0 ] += tangent; bitangentAccum[ idx0 ] += bitangent;
735 normalAccum[ idx1 ] += normal; tangentAccum[ idx1 ] += tangent; bitangentAccum[ idx1 ] += bitangent;
736 normalAccum[ idx2 ] += normal; tangentAccum[ idx2 ] += tangent; bitangentAccum[ idx2 ] += bitangent;
737 }
738
739 for(i = 0; i < pMESH->numVertices; ++i) {
740 glm::vec3& n = normalAccum[i];
741 glm::vec3& t = tangentAccum[i];
742 glm::vec3& b = bitangentAccum[i];
743
744 glm::vec3 normal = glm::normalize( n );
745 // Gram-Schmidt Orthogonalization
746 glm::vec3 tangent = glm::normalize( t - (glm::dot(normal, t) * normal) );
747 glm::vec3 bitangent = glm::normalize( b );
748
749 _normalArray[ i ] = normal;
750
751 _tangentArray[ i ] = glm::vec4(tangent, 0.0f);
752 // store handedness in w
753 _tangentArray[ i ].w = (glm::dot( glm::cross(normal, tangent), bitangent) < 0.0f) ? -1.0f : 1.0f;
754 }
755
756 delete[] normalAccum;
757 delete[] tangentAccum;
758 delete[] bitangentAccum;
759
760 glBindVertexArray(_vao );
761
762 glBindBuffer(GL_ARRAY_BUFFER, _vbo[0] );
763 glBufferSubData(GL_ARRAY_BUFFER, 0, static_cast<GLsizeiptr>(sizeof(glm::vec3)) * pMESH->numVertices, &_vertexArray[0] );
764 glBufferSubData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(sizeof(glm::vec3)) * _maxVertices * 1, static_cast<GLsizeiptr>(sizeof(glm::vec3)) * pMESH->numVertices, &_normalArray[0] );
765 glBufferSubData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(sizeof(glm::vec3)) * _maxVertices * 2, static_cast<GLsizeiptr>(sizeof(glm::vec4)) * pMESH->numVertices, &_tangentArray[0] );
766 glBufferSubData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(sizeof(glm::vec3)) * _maxVertices * 2 + static_cast<GLsizeiptr>(sizeof(glm::vec4)) * _maxVertices, static_cast<GLsizeiptr>(sizeof(glm::vec2)) * pMESH->numVertices, &_texelArray[0] );
767
768 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _vbo[1] );
769 glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, static_cast<GLsizeiptr>(sizeof(GLuint)) * pMESH->numTriangles * 3, _vertexIndicesArray );
770}
771
772inline void
773CSCI441::MD5Model::_drawMesh(
774 const CSCI441_INTERNAL::MD5Mesh *pMESH
775) const {
776 if (pMESH->shader != nullptr) {
777 // fprintf(stdout, "applying shader %s\n", pMESH->shader->name);
778 if (pMESH->shader->textures[CSCI441_INTERNAL::MD5MaterialShader::SPECULAR].texHandle != 0) {
779 // Bind Specular Map if exists
780 glActiveTexture(_specularActiveTexture);
781 glBindTexture(GL_TEXTURE_2D, pMESH->shader->textures[CSCI441_INTERNAL::MD5MaterialShader::SPECULAR].texHandle );
782 // fprintf(stdout, "bound specular texture %u to %d (%d)\n", pMESH->shader->textures[CSCI441_INTERNAL::MD5MaterialShader::SPECULAR].texHandle, _specularActiveTexture, GL_TEXTURE0);
783 }
784 if (pMESH->shader->textures[CSCI441_INTERNAL::MD5MaterialShader::NORMAL].texHandle != 0) {
785 // Bind Normal Map if exists
786 glActiveTexture(_normalActiveTexture);
787 glBindTexture(GL_TEXTURE_2D, pMESH->shader->textures[CSCI441_INTERNAL::MD5MaterialShader::NORMAL].texHandle );
788 }
789 if (pMESH->shader->textures[CSCI441_INTERNAL::MD5MaterialShader::HEIGHT].texHandle != 0) {
790 // Bind Height Map if exists
791 glActiveTexture(_heightActiveTexture);
792 glBindTexture(GL_TEXTURE_2D, pMESH->shader->textures[CSCI441_INTERNAL::MD5MaterialShader::HEIGHT].texHandle );
793 }
794 // bind diffuse last because ideally it is texture 0 and will remain for future renderings
795 if (pMESH->shader->textures[CSCI441_INTERNAL::MD5MaterialShader::DIFFUSE].texHandle != 0) {
796 // Bind Diffuse Map if exists
797 glActiveTexture(_diffuseActiveTexture);
798 glBindTexture(GL_TEXTURE_2D, pMESH->shader->textures[CSCI441_INTERNAL::MD5MaterialShader::DIFFUSE].texHandle );
799 // fprintf(stdout, "bound diffuse texture %u to %d (%d)\n", pMESH->shader->textures[CSCI441_INTERNAL::MD5MaterialShader::DIFFUSE].texHandle, _diffuseActiveTexture, GL_TEXTURE0);
800 }
801 if (_diffuseActiveTexture != GL_TEXTURE0) {
802 // reset back to active texture being zero
803 glActiveTexture(GL_TEXTURE0);
804 }
805 }
806
807 glBindVertexArray(_vao );
808 glDrawElements(GL_TRIANGLES, pMESH->numTriangles * 3, GL_UNSIGNED_INT, (void*)nullptr );
809}
810
811[[maybe_unused]]
812inline void
814 const GLuint vPosAttribLoc,
815 const GLuint vColorAttribLoc,
816 const GLuint vTexCoordAttribLoc,
817 const GLuint vNormalAttribLoc,
818 const GLuint vTangentAttribLoc
819) {
820 // layout
821 // position normal tangent texCoord
822
823 _vertexArray = new glm::vec3[_maxVertices];
824 _normalArray = new glm::vec3[_maxVertices];
825 _tangentArray = new glm::vec4[_maxVertices];
826 _texelArray = new glm::vec2[_maxVertices];
827 _vertexIndicesArray = new GLuint[_maxTriangles * 3];
828
829 glGenVertexArrays( 1, &_vao );
830 glBindVertexArray(_vao );
831
832 glGenBuffers(2, _vbo );
833 glBindBuffer(GL_ARRAY_BUFFER, _vbo[0] );
834 glBufferData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(sizeof(glm::vec3)) * _maxVertices * 2 + static_cast<GLsizeiptr>(sizeof(glm::vec4)) * _maxVertices + static_cast<GLsizeiptr>(sizeof(glm::vec2)) * _maxVertices, nullptr, GL_DYNAMIC_DRAW );
835
836 glEnableVertexAttribArray( vPosAttribLoc );
837 glVertexAttribPointer( vPosAttribLoc, 3, GL_FLOAT, GL_FALSE, 0, static_cast<void *>(nullptr) );
838
839 if (vNormalAttribLoc != 0) {
840 glEnableVertexAttribArray( vNormalAttribLoc );
841 glVertexAttribPointer( vNormalAttribLoc, 3, GL_FLOAT, GL_FALSE, 0, reinterpret_cast<void *>(sizeof(glm::vec3) * _maxVertices * 1) );
842 }
843
844 if (vTangentAttribLoc != 0) {
845 glEnableVertexAttribArray( vTangentAttribLoc );
846 glVertexAttribPointer( vTangentAttribLoc, 4, GL_FLOAT, GL_FALSE, 0, reinterpret_cast<void *>(sizeof(glm::vec3) * _maxVertices * 2) );
847 }
848
849 if (vTexCoordAttribLoc != 0) {
850 glEnableVertexAttribArray( vTexCoordAttribLoc );
851 glVertexAttribPointer( vTexCoordAttribLoc, 2, GL_FLOAT, GL_FALSE, 0, reinterpret_cast<void *>(sizeof(glm::vec3) * _maxVertices * 2 + sizeof(glm::vec4) * _maxVertices) );
852 }
853
854 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _vbo[1] );
855 glBufferData(GL_ELEMENT_ARRAY_BUFFER, static_cast<GLsizeiptr>(sizeof(GLuint)) * _maxTriangles * 3, nullptr, GL_DYNAMIC_DRAW );
856
857 printf("[.md5mesh]: Model VAO/VBO/IBO registered at %u/%u/%u\n", _vao, _vbo[0], _vbo[1] );
858
859 glGenVertexArrays( 1, &_skeletonVAO );
860 glBindVertexArray(_skeletonVAO );
861
862 glGenBuffers( 1, &_skeletonVBO );
863 glBindBuffer(GL_ARRAY_BUFFER, _skeletonVBO );
864 glBufferData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(sizeof(glm::vec3)) * _numJoints * 3 * 2, nullptr, GL_DYNAMIC_DRAW );
865
866 glEnableVertexAttribArray( vPosAttribLoc ); // vPos
867 glVertexAttribPointer( vPosAttribLoc, 3, GL_FLOAT, GL_FALSE, 0, static_cast<void *>(nullptr) );
868
869 if (vColorAttribLoc != 0) {
870 glEnableVertexAttribArray( vColorAttribLoc ); // vColor
871 glVertexAttribPointer( vColorAttribLoc, 3, GL_FLOAT, GL_FALSE, 0, reinterpret_cast<void *>(sizeof(glm::vec3) * _numJoints * 3) );
872 }
873
874 printf("[.md5mesh]: Skeleton VAO/VBO registered at %u/%u\n", _skeletonVAO, _skeletonVBO );
875}
876
877inline void
879 const GLint diffuseMapActiveTexture = GL_TEXTURE0,
880 const GLint specularMapActiveTexture = GL_TEXTURE1,
881 const GLint normalMapActiveTexture = GL_TEXTURE2,
882 const GLint heightMapActiveTexture = GL_TEXTURE3
883) {
884 _diffuseActiveTexture = diffuseMapActiveTexture;
885 _specularActiveTexture = specularMapActiveTexture;
886 _normalActiveTexture = normalMapActiveTexture;
887 _heightActiveTexture = heightMapActiveTexture;
888}
889
890inline void
891CSCI441::MD5Model::_freeVertexArrays()
892{
893 delete[] _vertexArray;
894 _vertexArray = nullptr;
895
896 delete[] _normalArray;
897 _normalArray = nullptr;
898
899 delete[] _tangentArray;
900 _tangentArray = nullptr;
901
902 delete[] _vertexIndicesArray;
903 _vertexIndicesArray = nullptr;
904
905 delete[] _texelArray;
906 _texelArray = nullptr;
907
908 glDeleteVertexArrays( 1, &_vao );
909 _vao = 0;
910
911 glDeleteBuffers(2, _vbo );
912 _vbo[0] = 0;
913 _vbo[1] = 0;
914
915 glDeleteVertexArrays( 1, &_skeletonVAO );
916 _skeletonVAO = 0;
917
918 glDeleteBuffers( 1, &_skeletonVBO );
919 _skeletonVBO = 0;
920}
921
922[[maybe_unused]]
923inline void
925{
926 glBindVertexArray(_skeletonVAO );
927 glBindBuffer(GL_ARRAY_BUFFER, _skeletonVBO );
928
929 constexpr glm::vec3 jointColor = {1.0f, 1.0f, 0.0f };
930 constexpr glm::vec3 boneColor = {1.0f, 0.0f, 1.0f };
931
932 // put in points for joints
933 for(GLint i = 0; i < _numJoints; ++i ) {
934 glBufferSubData(GL_ARRAY_BUFFER, i * static_cast<GLsizeiptr>(sizeof(glm::vec3)), sizeof(glm::vec3), &(_skeleton[i].position) );
935 glBufferSubData(GL_ARRAY_BUFFER, i * static_cast<GLsizeiptr>(sizeof(glm::vec3)) + static_cast<GLsizeiptr>(sizeof(glm::vec3)) * _numJoints * 3, sizeof(glm::vec3), &jointColor[0]);
936 }
937
938 // put in lines for bones
939 GLint numBones = 0;
940 for(GLint i = 0; i < _numJoints; ++i ) {
941 if( _skeleton[i].parent != CSCI441_INTERNAL::MD5Joint::NULL_JOINT ) {
942 glBufferSubData(
943 GL_ARRAY_BUFFER,
944 static_cast<GLsizeiptr>(sizeof(glm::vec3)) * _numJoints + (i * 2) * static_cast<GLsizeiptr>(sizeof(glm::vec3)),
945 static_cast<GLsizeiptr>(sizeof(glm::vec3)),
946 &(_skeleton[_skeleton[i].parent].position)
947 );
948 glBufferSubData(
949 GL_ARRAY_BUFFER,
950 static_cast<GLsizeiptr>(sizeof(glm::vec3)) * _numJoints + (i * 2) * static_cast<GLsizeiptr>(sizeof(glm::vec3)) + static_cast<GLsizeiptr>(sizeof(glm::vec3)) * _numJoints * 3,
951 sizeof(glm::vec3),
952 &boneColor[0]
953 );
954
955 glBufferSubData(
956 GL_ARRAY_BUFFER,
957 static_cast<GLsizeiptr>(sizeof(glm::vec3)) * _numJoints + (i * 2) * static_cast<GLsizeiptr>(sizeof(glm::vec3)) + static_cast<GLsizeiptr>(sizeof(glm::vec3)),
958 static_cast<GLsizeiptr>(sizeof(glm::vec3)),
959 &(_skeleton[i].position)
960 );
961 glBufferSubData(
962 GL_ARRAY_BUFFER,
963 static_cast<GLsizeiptr>(sizeof(glm::vec3)) * _numJoints + (i * 2) * static_cast<GLsizeiptr>(sizeof(glm::vec3)) + static_cast<GLsizeiptr>(sizeof(glm::vec3)) + static_cast<GLsizeiptr>(sizeof(glm::vec3)) * _numJoints * 3,
964 sizeof(glm::vec3),
965 &boneColor[0]
966 );
967 numBones++;
968 }
969 }
970
971 glPointSize(5.0f);
972 glDrawArrays(GL_POINTS, 0, _numJoints );
973 glPointSize(1.0f);
974
975 glLineWidth( 3.0f );
976 glDrawArrays(GL_LINES, _numJoints, numBones * 2 );
977 glLineWidth(1.0f);
978}
979
980inline bool
981CSCI441::MD5Model::_checkAnimValidity(const GLushort targetAnimationIndex) const
982{
983 // md5mesh and md5anim must have the same number of joints
984 if( _numJoints != _animations[targetAnimationIndex]->getNumberOfJoints() ) {
985 fprintf(stderr, "[.md5anim | ERROR]: skeleton and animation do not have same number of joints. cannot apply animation %u to skeleton\n", targetAnimationIndex);
986 return false;
987 }
988 if (_animations[targetAnimationIndex]->getNumberOfJoints() == 0 ) {
989 fprintf(stderr, "[.md5anim | ERROR]: animation has zero joints. cannot apply animation %u to skeleton\n\n", targetAnimationIndex);
990 return false;
991 }
992
993 // We just check with frame[0]
994 for(GLint i = 0; i < _numJoints; ++i) {
995 // Joints must have the same parent index
996 if (_baseSkeleton[i].parent != _animations[targetAnimationIndex]->getSkeletonFrameJoint(0, i).parent) {
997 fprintf(stderr, "[.md5anim | ERROR]: skeleton and animation joints do not have same parent index. cannot apply animation %u to skeleton\n", targetAnimationIndex);
998 return false;
999 }
1000
1001 // Joints must have the same name
1002 if (strcmp (_baseSkeleton[i].name, _animations[targetAnimationIndex]->getSkeletonFrameJoint(0, i).name) != 0) {
1003 fprintf(stderr, "[.md5anim | ERROR]: skeleton and animation joints do not have same name. cannot apply animation %u to skeleton\n", targetAnimationIndex);
1004 return false;
1005 }
1006 }
1007
1008 fprintf(stdout, "[.md5anim]: skeleton and animation match. animation %u can be applied to skeleton\n", targetAnimationIndex);
1009 return true;
1010}
1011
1012inline void
1013CSCI441::MD5Model::_buildFrameSkeleton(
1014 const CSCI441_INTERNAL::MD5JointInfo* pJOINT_INFOS,
1015 const CSCI441_INTERNAL::MD5BaseFrameJoint* pBASE_FRAME,
1016 const GLfloat* pANIM_FRAME_DATA,
1017 const CSCI441_INTERNAL::MD5Joint* pSkeletonFrame,
1018 const GLint NUM_JOINTS
1019) {
1020 if(pJOINT_INFOS == nullptr
1021 || pBASE_FRAME == nullptr
1022 || pANIM_FRAME_DATA == nullptr
1023 || pSkeletonFrame == nullptr) return;
1024
1025 for(GLint i = 0; i < NUM_JOINTS; ++i) {
1026 const CSCI441_INTERNAL::MD5BaseFrameJoint *baseJoint = &pBASE_FRAME[i];
1027 glm::vec3 animatedPosition = baseJoint->position;
1028 glm::quat animatedOrientation = baseJoint->orientation;
1029 GLint j = 0;
1030
1031 // Tx
1032 if(pJOINT_INFOS[i].flags & 1 ) {
1033 animatedPosition.x = pANIM_FRAME_DATA[pJOINT_INFOS[i].startIndex + j];
1034 ++j;
1035 }
1036
1037 // Ty
1038 if(pJOINT_INFOS[i].flags & 2 ) {
1039 animatedPosition.y = pANIM_FRAME_DATA[pJOINT_INFOS[i].startIndex + j];
1040 ++j;
1041 }
1042
1043 // Tz
1044 if(pJOINT_INFOS[i].flags & 4 ) {
1045 animatedPosition.z = pANIM_FRAME_DATA[pJOINT_INFOS[i].startIndex + j];
1046 ++j;
1047 }
1048
1049 // Qx
1050 if(pJOINT_INFOS[i].flags & 8 ) {
1051 animatedOrientation.x = pANIM_FRAME_DATA[pJOINT_INFOS[i].startIndex + j];
1052 ++j;
1053 }
1054
1055 // Qy
1056 if(pJOINT_INFOS[i].flags & 16 ) {
1057 animatedOrientation.y = pANIM_FRAME_DATA[pJOINT_INFOS[i].startIndex + j];
1058 ++j;
1059 }
1060
1061 // Qz
1062 if(pJOINT_INFOS[i].flags & 32 ) {
1063 animatedOrientation.z = pANIM_FRAME_DATA[pJOINT_INFOS[i].startIndex + j];
1064 }
1065
1066 // Compute orientation quaternion's w value
1067 animatedOrientation.w = glm::extractRealComponent(animatedOrientation);
1068
1069 // NOTE: we assume that this joint's parent has
1070 // already been calculated, i.e. joint's ID should
1071 // never be smaller than its parent ID.
1072 const auto thisJoint = const_cast<CSCI441_INTERNAL::MD5Joint *>(&pSkeletonFrame[i]);
1073
1074 const GLint parent = pJOINT_INFOS[i].parent;
1075 thisJoint->parent = parent;
1076 strcpy (thisJoint->name, pJOINT_INFOS[i].name);
1077
1078 // Has parent?
1079 if( thisJoint->parent == CSCI441_INTERNAL::MD5Joint::NULL_JOINT ) {
1080 thisJoint->position = animatedPosition;
1081 thisJoint->orientation = animatedOrientation;
1082 } else {
1083 const CSCI441_INTERNAL::MD5Joint *parentJoint = &pSkeletonFrame[parent];
1084 glm::vec3 rotatedPosition = glm::rotate(parentJoint->orientation, glm::vec4(animatedPosition, 0.0f));
1085
1086 // Add positions
1087 thisJoint->position = parentJoint->position + rotatedPosition;
1088
1089 // Concatenate rotations
1090 thisJoint->orientation = glm::normalize( glm::cross(parentJoint->orientation, animatedOrientation) );
1091 }
1092 }
1093}
1094
1095inline bool
1097 const char *filename,
1098 const GLushort targetAnimationIndex
1099) {
1100 if (targetAnimationIndex >= _numAnimations) {
1101 fprintf (stderr, "[.md5anim]: Error: target animation index %u is out of range for currently allocated animations (which is %u)\n", targetAnimationIndex, _numAnimations);
1102 fprintf (stderr, "[.md5anim]: Hey Developer, if you wish to add an animation to the sequence, use CSCI441::MD5Model::addMD5Anim(const char*)\n");
1103 return false;
1104 }
1105
1106 char buff[512];
1107 CSCI441_INTERNAL::MD5JointInfo *jointInfos = nullptr;
1108 CSCI441_INTERNAL::MD5BaseFrameJoint *baseFrame = nullptr;
1109 GLfloat *animFrameData = nullptr;
1110 GLint version;
1111 GLint numAnimatedComponents;
1112 GLint frameIndex, numFrames, numJoints;
1113 GLint i;
1114
1115 printf( "[.md5anim]: about to read %s into animation %u\n", filename, targetAnimationIndex );
1116
1117 FILE *fp = fopen( filename, "rb" );
1118 if( !fp ) {
1119 fprintf (stderr, "[.md5anim]: Error: couldn't open \"%s\"!\n", filename);
1120 return false;
1121 }
1122
1123 while( !feof(fp) ) {
1124 // Read whole line
1125 fgets( buff, sizeof(buff), fp );
1126
1127 if( sscanf(buff, " MD5Version %d", &version) == 1 ) {
1128 if( version != 10 ) {
1129 // Bad version
1130 fprintf (stderr, "[.md5anim]: Error: bad animation version\n");
1131 fclose (fp);
1132 return false;
1133 }
1134 } else if( sscanf(buff, " numFrames %d", &numFrames) == 1 ) {
1135 // Allocate memory for skeleton frames and bounding boxes
1136 _animations[targetAnimationIndex]->setNumberOfFrames(numFrames);
1137 } else if( sscanf(buff, " numJoints %d", &numJoints) == 1 ) {
1138 if (jointInfos != nullptr) {
1139 fprintf( stderr, "[.md5anim]: Error: md5anim file malformed. numJoints already specified\n" );
1140 }
1141 if( numJoints > 0 ) {
1142 _animations[targetAnimationIndex]->setNumberOfJoints(numJoints);
1143
1144 // Allocate temporary memory for building skeleton frames
1145 jointInfos = new CSCI441_INTERNAL::MD5JointInfo[numJoints];
1146 baseFrame = new CSCI441_INTERNAL::MD5BaseFrameJoint[numJoints];
1147 }
1148 } else if( sscanf(buff, " frameRate %d", &_animations[targetAnimationIndex]->frameRate) == 1 ) {
1149
1150 } else if( sscanf(buff, " numAnimatedComponents %d", &numAnimatedComponents) == 1 ) {
1151 if (animFrameData != nullptr) {
1152 fprintf( stderr, "[.md5anim]: Error: md5anim file malformed. numAnimatedComponents already specified\n" );
1153 }
1154 if( numAnimatedComponents > 0 ) {
1155 // Allocate memory for animation frame data
1156 animFrameData = new GLfloat[numAnimatedComponents];
1157 }
1158 } else if( strncmp(buff, "hierarchy {", 11) == 0 ) {
1159 if (jointInfos == nullptr) {
1160 fprintf( stderr, "[.md5anim]: Error: md5anim file malformed. numJoints not specified prior to hierarchy\n" );
1161 } else {
1162 for(i = 0; i < numJoints; ++i) {
1163 // Read whole line
1164 fgets( buff, sizeof(buff), fp );
1165
1166 // Read joint info
1167 sscanf(buff, " %s %d %d %d",
1168 jointInfos[i].name, &jointInfos[i].parent,
1169 &jointInfos[i].flags, &jointInfos[i].startIndex);
1170 }
1171 }
1172 } else if( strncmp(buff, "bounds {", 8) == 0 ) {
1173 if (_animations[targetAnimationIndex]->getNumberOfFrames() == 0) {
1174 fprintf( stderr, "[.md5anim]: Error: md5anim file malformed. numFrames not specified prior to bounds\n" );
1175 } else {
1176 for(i = 0; i < _animations[targetAnimationIndex]->getNumberOfFrames(); ++i) {
1177 // Read whole line
1178 fgets( buff, sizeof(buff), fp );
1179
1180 // Read bounding box
1181 sscanf(buff, " ( %f %f %f ) ( %f %f %f )",
1182 &_animations[targetAnimationIndex]->getBoundingBox(i).min[0], &_animations[targetAnimationIndex]->getBoundingBox(i).min[1], &_animations[targetAnimationIndex]->getBoundingBox(i).min[2],
1183 &_animations[targetAnimationIndex]->getBoundingBox(i).max[0], &_animations[targetAnimationIndex]->getBoundingBox(i).max[1], &_animations[targetAnimationIndex]->getBoundingBox(i).max[2]);
1184 }
1185 }
1186 } else if( strncmp(buff, "baseframe {", 10) == 0 ) {
1187 if (baseFrame == nullptr) {
1188 fprintf( stderr, "[.md5anim]: Error: md5anim file malformed. numJoints not specified prior to baseframe\n" );
1189 } else {
1190 for(i = 0; i < numJoints; ++i) {
1191 // Read whole line
1192 fgets( buff, sizeof(buff), fp );
1193
1194 // Read base frame joint
1195 if( sscanf(buff, " ( %f %f %f ) ( %f %f %f )",
1196 &baseFrame[i].position[0], &baseFrame[i].position[1], &baseFrame[i].position[2],
1197 &baseFrame[i].orientation[0], &baseFrame[i].orientation[1], &baseFrame[i].orientation[2]) == 6 ) {
1198 // Compute the w component
1199 baseFrame[i].orientation.w = glm::extractRealComponent(baseFrame[i].orientation);
1200 }
1201 }
1202 }
1203 } else if(sscanf(buff, " frame %d", &frameIndex) == 1 ) {
1204 if (animFrameData == nullptr) {
1205 fprintf( stderr, "[.md5anim]: Error: md5anim file malformed. numAnimatedComponents not specified prior to frame\n" );
1206 } else if (_animations[targetAnimationIndex]->getNumberOfFrames() == 0) {
1207 fprintf( stderr, "[.md5anim]: Error: md5anim file malformed. numFrames not specified prior to frame\n" );
1208 } else if (baseFrame == nullptr) {
1209 fprintf( stderr, "[.md5anim]: Error: md5anim file malformed. baseframe not specified prior to frame\n" );
1210 } else if (jointInfos == nullptr) {
1211 fprintf( stderr, "[.md5anim]: Error: md5anim file malformed. numJoints not specified prior to frame\n" );
1212 } else {
1213 // Read frame data
1214 for(i = 0; i < numAnimatedComponents; ++i)
1215 fscanf( fp, "%f", &animFrameData[i] );
1216
1217 // Build frame _skeleton from the collected data
1218 _buildFrameSkeleton(jointInfos, baseFrame, animFrameData,
1219 _animations[targetAnimationIndex]->getSkeletonFrame(frameIndex),
1220 numJoints);
1221 }
1222 }
1223 }
1224
1225 fclose( fp );
1226
1227 printf( "[.md5anim]: finished reading %s into animation %u\n", filename, targetAnimationIndex );
1228 printf( "[.md5anim]: read in %d frames of %d joints with %d animated components\n", _animations[targetAnimationIndex]->getNumberOfFrames(), _animations[targetAnimationIndex]->getNumberOfJoints(), numAnimatedComponents );
1229 printf( "[.md5anim]: animation's frame rate is %d\n", _animations[targetAnimationIndex]->frameRate );
1230
1231 // Free temporary data allocated
1232 delete[] animFrameData;
1233 delete[] baseFrame;
1234 delete[] jointInfos;
1235
1236 // successful loading...set up animation parameters
1237 _animationInfos[targetAnimationIndex].currFrame = 0;
1238 _animationInfos[targetAnimationIndex].nextFrame = 1;
1239
1240 _animationInfos[targetAnimationIndex].lastTime = 0.0f;
1241 _animationInfos[targetAnimationIndex].maxTime = 1.0f / static_cast<GLfloat>(_animations[targetAnimationIndex]->frameRate);
1242
1243 // Allocate memory for animated _skeleton
1244 if (_animations[targetAnimationIndex]->getNumberOfJoints() == 0) {
1245 fprintf( stderr, "[.md5anim]: Error: md5anim file malformed. numJoints never specified\n" );
1246 } else {
1247 _skeleton = new CSCI441_INTERNAL::MD5Joint[_animations[targetAnimationIndex]->getNumberOfJoints()];
1248 }
1249
1250 if( _checkAnimValidity(targetAnimationIndex) ) {
1251 _isAnimated = true;
1252 // compute initial pose
1253 animate(0.0);
1254 }
1255
1256 return true;
1257}
1258
1259inline bool
1261 const char* filename
1262) {
1263 // extend animation array
1264 auto newAnimations = new CSCI441_INTERNAL::MD5Animation*[_numAnimations + 1];
1265 auto newAnimationInfos = new CSCI441_INTERNAL::MD5AnimationState[_numAnimations + 1];
1266
1267 for( int i = 0; i < _numAnimations; ++i ) {
1268 newAnimations[i] = _animations[i];
1269 newAnimationInfos[i] = _animationInfos[i];
1270 }
1271 newAnimations[_numAnimations] = new CSCI441_INTERNAL::MD5Animation();
1272
1273 delete[] _animations;
1274 delete[] _animationInfos;
1275
1276 _animations = newAnimations;
1277 _animationInfos = newAnimationInfos;
1278
1279 _numAnimations++;
1280
1281 // read animation into new array position
1282 fprintf( stdout, "\n[.md5anim]: preparing to read %s into new animation %u\n", filename, (_numAnimations-1) );
1283 const bool readSuccess = readMD5Anim(filename, _numAnimations - 1);
1284
1285 if (readSuccess) {
1286 fprintf( stdout, "[.md5anim]: successfully read %s into new animation %u\n", filename, (_numAnimations-1) );
1287 } else {
1288 fprintf( stderr, "[.md5anim]: Error: could not read %s into new animation %u\n", filename, (_numAnimations-1) );
1289
1290 // undo array growth
1291 newAnimations = new CSCI441_INTERNAL::MD5Animation*[_numAnimations-1];
1292 newAnimationInfos = new CSCI441_INTERNAL::MD5AnimationState[_numAnimations-1];
1293
1294 for (int i = 0; i < _numAnimations - 1; ++i) {
1295 newAnimations[i] = _animations[i];
1296 newAnimationInfos[i] = _animationInfos[i];
1297 }
1298 delete _animations[_numAnimations-1];
1299
1300 delete[] _animations;
1301 delete[] _animationInfos;
1302
1303 _animations = newAnimations;
1304 _animationInfos = newAnimationInfos;
1305
1306 _numAnimations--;
1307 }
1308
1309 return readSuccess;
1310}
1311
1312inline void
1314 const GLushort targetAnimationIndex
1315) {
1316 // if target index is within range
1317 if (targetAnimationIndex < _numAnimations) {
1318 // update animation to run through
1319 _currentAnimationIndex = targetAnimationIndex;
1320
1321 // set to base animation
1322 _animationInfos[targetAnimationIndex].currFrame = 0;
1323 _animationInfos[targetAnimationIndex].nextFrame = 1;
1324
1325 _animationInfos[targetAnimationIndex].lastTime = 0.0f;
1326 _animationInfos[targetAnimationIndex].maxTime = 1.0f / static_cast<GLfloat>(_animations[targetAnimationIndex]->frameRate);
1327
1328 animate(0.f);
1329 }
1330}
1331
1332inline void
1333CSCI441::MD5Model::_freeAnim()
1334{
1335 delete[] _skeleton;
1336 _skeleton = nullptr;
1337
1338 for (int i = 0; i < _numAnimations; i++) {
1339 delete _animations[i];
1340 }
1341 delete[] _animations;
1342 _animations = nullptr;
1343
1344 delete[] _animationInfos;
1345 _animationInfos = nullptr;
1346}
1347
1348inline void CSCI441::MD5Model::_moveFromSrc(MD5Model &src) {
1349 this->_baseSkeleton = src._baseSkeleton;
1350 src._baseSkeleton = nullptr;
1351
1352 this->_meshes = src._meshes;
1353 src._meshes = nullptr;
1354
1355 this->_maxVertices = src._maxVertices;
1356 src._maxVertices = 0;
1357
1358 this->_maxTriangles = src._maxTriangles;
1359 src._maxTriangles = 0;
1360
1361 this->_vertexArray = src._vertexArray;
1362 src._vertexArray = nullptr;
1363
1364 this->_normalArray = src._normalArray;
1365 src._normalArray = nullptr;
1366
1367 this->_tangentArray = src._tangentArray;
1368 src._tangentArray = nullptr;
1369
1370 this->_texelArray = src._texelArray;
1371 src._texelArray = nullptr;
1372
1373 this->_vertexIndicesArray = src._vertexIndicesArray;
1374 src._vertexIndicesArray = nullptr;
1375
1376 this->_vao = src._vao;
1377 src._vao = 0;
1378
1379 this->_vbo[0] = src._vbo[0];
1380 this->_vbo[1] = src._vbo[1];
1381 src._vbo[0] = 0;
1382 src._vbo[1] = 0;
1383
1384 this->_skeletonVAO = src._skeletonVAO;
1385 src._skeletonVAO = 0;
1386
1387 this->_skeletonVBO = src._skeletonVBO;
1388 src._skeletonVBO = 0;
1389
1390 this->_skeleton = src._skeleton;
1391 src._skeleton = nullptr;
1392
1393 this->_animations = src._animations;
1394 src._animations = nullptr;
1395
1396 this->_numAnimations = src._numAnimations;
1397 src._numAnimations = 0;
1398
1399 this->_currentAnimationIndex = src._currentAnimationIndex;
1400 src._currentAnimationIndex = 0;
1401
1402 this->_isAnimated = src._isAnimated;
1403 src._isAnimated = false;
1404
1405 this->_animationInfos = src._animationInfos;
1406 src._animationInfos = nullptr;
1407}
1408
1409inline void
1410CSCI441::MD5Model::_interpolateSkeletons(const GLfloat interp)
1411{
1412 const CSCI441_INTERNAL::MD5Joint *skeletonA = _animations[_currentAnimationIndex]->getSkeletonFrame(_animationInfos[_currentAnimationIndex].currFrame);
1413 const CSCI441_INTERNAL::MD5Joint *skeletonB = _animations[_currentAnimationIndex]->getSkeletonFrame(_animationInfos[_currentAnimationIndex].nextFrame);
1414
1415 for(GLint i = 0; i < _animations[_currentAnimationIndex]->getNumberOfJoints(); ++i) {
1416 // Copy parent index
1417 _skeleton[i].parent = skeletonA[i].parent;
1418
1419 // Linear interpolation for position
1420 _skeleton[i].position = glm::mix(skeletonA[i].position, skeletonB[i].position, interp);
1421
1422 // Spherical linear interpolation for orientation
1423 _skeleton[i].orientation = glm::slerp(skeletonA[i].orientation, skeletonB[i].orientation, interp);
1424 }
1425}
1426
1427// Perform animation related computations. Calculate the current and
1428// next frames, given a delta time.
1429inline void
1431{
1432 const GLint maxFrames = _animations[_currentAnimationIndex]->getNumberOfFrames() - 1;
1433 if (maxFrames <= 0) return;
1434
1435 _animationInfos[_currentAnimationIndex].lastTime += dt;
1436
1437 // move to next frame
1438 if( _animationInfos[_currentAnimationIndex].lastTime >= _animationInfos[_currentAnimationIndex].maxTime ) {
1439 _animationInfos[_currentAnimationIndex].currFrame++;
1440 _animationInfos[_currentAnimationIndex].nextFrame++;
1441 _animationInfos[_currentAnimationIndex].lastTime = 0.0;
1442
1443 if( _animationInfos[_currentAnimationIndex].currFrame > maxFrames )
1444 _animationInfos[_currentAnimationIndex].currFrame = 0;
1445
1446 if( _animationInfos[_currentAnimationIndex].nextFrame > maxFrames )
1447 _animationInfos[_currentAnimationIndex].nextFrame = 0;
1448 }
1449
1450 // Interpolate skeletons between two frames
1451 _interpolateSkeletons( _animationInfos[_currentAnimationIndex].lastTime * static_cast<GLfloat>(_animations[_currentAnimationIndex]->frameRate) );
1452}
1453
1454inline std::map< std::string, CSCI441_INTERNAL::MD5MaterialShader* > CSCI441::MD5Model::_materials;
1455inline std::map< std::string, GLuint > CSCI441::MD5Model::_textureMap;
1456
1457
1458inline size_t CSCI441::MD5Model::_trim(char* out, const size_t len, const char* str) {
1459 if(len == 0)
1460 return 0;
1461
1462 // Trim leading space
1463 while( isspace(static_cast<unsigned char>(*str)) ) str++;
1464
1465 if(*str == 0) // All spaces?
1466 {
1467 *out = 0;
1468 return 1;
1469 }
1470
1471 // Trim trailing space
1472 const char *end = str + strlen(str) - 1;
1473 while( end > str && isspace(static_cast<unsigned char>(*end)) ) end--;
1474 end++;
1475
1476 // Set output size to minimum of trimmed string length and buffer size minus 1
1477 const size_t out_size = (end - str) < len - 1 ? (end - str) : len - 1;
1478
1479 // Copy trimmed string and add null terminator
1480 memcpy(out, str, out_size);
1481 out[out_size] = '\0';
1482
1483 return out_size;
1484}
1485
1486inline bool CSCI441::MD5Model::_registerShaderTexture(CSCI441_INTERNAL::MD5Texture *texture) {
1487 if (const auto textureIter = _textureMap.find( texture->filename ); textureIter == _textureMap.end()) {
1488 texture->texHandle = CSCI441::TextureUtils::loadAndRegisterTexture( texture->filename, GL_LINEAR, GL_LINEAR_MIPMAP_LINEAR, GL_REPEAT, GL_REPEAT, GL_FALSE, GL_FALSE);
1489 if( texture->texHandle == 0 ) {
1490 // silently failing to avoid many unnecessary error messages
1491 //fprintf(stderr, "[.md5mtr | ERROR]: Could not load diffuse map %s for shader %s\n", shader->textures[MD5MaterialShader::TextureMap::DIFFUSE].filename, shader->shader);
1492 } else {
1493 // only display successes which likely refer to what we are using
1494 _textureMap.insert( std::pair( texture->filename, texture->texHandle) );
1495 return true;
1496 }
1497 } else {
1498 texture->texHandle = textureIter->second;
1499 }
1500 return false;
1501}
1502
1503inline void
1505 char buff[512], buff2[512];
1506 GLushort numTextures = 0;
1507
1508 fprintf(stdout, "\n[.md5mtr]: about to read %s\n", FILENAME );
1509
1510 FILE *fp = fopen(FILENAME, "rb" );
1511 if( !fp ) {
1512 fprintf (stderr, "[.md5mtr]: Error: couldn't open \"%s\"!\n", FILENAME);
1513 return;
1514 }
1515
1516 while( !feof(fp) ) {
1517 // Read whole line
1518 fgets( buff, sizeof(buff), fp );
1519
1520 _trim(buff, 512, buff);
1521
1522 if( sscanf(buff, "table %s", buff2) == 1 ) {
1523 // fprintf(stdout, "[.md5mtr]: ignoring table line\n");
1524 } else if ( sscanf(buff, "//%s", buff2) == 1 ) {
1525 // fprintf(stdout, "[.md5mtr]: ignoring comment\n");
1526 } else if (strnlen(buff, sizeof(buff)) == 0) {
1527 // fprintf(stdout, "[.md5mtr]: ignoring empty line\n");
1528 } else {
1529 // assuming this begins a shader
1530 auto shader = new CSCI441_INTERNAL::MD5MaterialShader();
1531
1532 strncpy(shader->name, buff, sizeof(buff));
1533 // fprintf(stdout, "[.md5mtr]: parsing shader \"%s\"\n", shader->shader);
1534
1535 unsigned short numBlocks = 0;
1536
1537 // read line, fails in event of malformed file
1538 while ( fgets( buff, sizeof(buff), fp ) != nullptr) {
1539 _trim(buff, 512, buff);
1540
1541 if (strchr(buff, '{') != nullptr) {
1542 // found opening block
1543 ++numBlocks;
1544 }
1545 else if (sscanf(buff, " diffusemap %s", buff2) == 1) {
1546 // fprintf(stdout, "[.md5mtr]: attempting to read diffusemap %s\n", buff2);
1547 strncpy( shader->textures[CSCI441_INTERNAL::MD5MaterialShader::TextureMap::DIFFUSE].filename, buff2, sizeof(buff2) );
1548 if (_registerShaderTexture( &shader->textures[CSCI441_INTERNAL::MD5MaterialShader::TextureMap::DIFFUSE] )) {
1549 ++numTextures;
1550 }
1551 }
1552 else if (sscanf(buff, " specularmap %s", buff2) == 1) {
1553 // fprintf(stdout, "[.md5mtr]: attempting to read specularmap %s\n", buff2);
1554 strncpy( shader->textures[CSCI441_INTERNAL::MD5MaterialShader::TextureMap::SPECULAR].filename, buff2, sizeof(buff2) );
1555 if (_registerShaderTexture( &shader->textures[CSCI441_INTERNAL::MD5MaterialShader::TextureMap::SPECULAR] )) {
1556 ++numTextures;
1557 }
1558 }
1559 // make sure getting top level shader bumpmap
1560 else if (numBlocks == 1 && sscanf(buff, " bumpmap %s", buff2) == 1) {
1561 // fprintf(stdout, "[.md5mtr]: attempting to read bumpmap line \"%s\"\n", buff);
1562 // tokenize string
1563 std::vector< char* > tokens;
1564 char* pch = strtok(buff, " \t(),");
1565 while (pch != NULL) {
1566 tokens.push_back( pch );
1567 pch = strtok(NULL, " \t(),");
1568 }
1569 // fprintf(stdout, "[.md5mtr]: found %lu tokens\n", tokens.size());
1570 // line is formatted: bumpmap normalTxtr
1571 // normal map texture filename is in tokens[1]
1572 if (tokens.size() == 2) {
1573 strncpy( shader->textures[CSCI441_INTERNAL::MD5MaterialShader::TextureMap::NORMAL].filename, tokens[1], strlen(tokens[1]) );
1574 if (_registerShaderTexture( &shader->textures[CSCI441_INTERNAL::MD5MaterialShader::TextureMap::NORMAL] )) {
1575 ++numTextures;
1576 }
1577 }
1578 // line is formatted: bumpmap heightmap(heightTxtr, scale)
1579 // height map texture filename is in tokens[2]
1580 // displacement scale is in tokens[3]
1581 else if (tokens.size() == 4) {
1582 strncpy( shader->textures[CSCI441_INTERNAL::MD5MaterialShader::TextureMap::HEIGHT].filename, tokens[2], strlen(tokens[2]) );
1583 shader->displacementScale = strtol(tokens[3], NULL, 10);
1584 if (_registerShaderTexture( &shader->textures[CSCI441_INTERNAL::MD5MaterialShader::TextureMap::HEIGHT] )) {
1585 ++numTextures;
1586 }
1587 }
1588 // line is formatted: bumpmap addnormals(normalTxtr, heightMap(heightTxtr, scale))
1589 // normal map texture filename is in tokens[2]
1590 // height map texture filename is in tokens[4]
1591 // displacement scale is in tokens[5]
1592 else if (tokens.size() == 6) {
1593 strncpy( shader->textures[CSCI441_INTERNAL::MD5MaterialShader::TextureMap::NORMAL].filename, tokens[2], strlen(tokens[2]) );
1594 if (_registerShaderTexture( &shader->textures[CSCI441_INTERNAL::MD5MaterialShader::TextureMap::NORMAL] )) {
1595 ++numTextures;
1596 }
1597 strncpy( shader->textures[CSCI441_INTERNAL::MD5MaterialShader::TextureMap::HEIGHT].filename, tokens[4], strlen(tokens[4]) );
1598 shader->displacementScale = strtol(tokens[5], NULL, 10);
1599 if (_registerShaderTexture( &shader->textures[CSCI441_INTERNAL::MD5MaterialShader::TextureMap::HEIGHT] )) {
1600 ++numTextures;
1601 }
1602 }
1603 }
1604 else if (strchr(buff, '}') != nullptr) {
1605 --numBlocks;
1606 // if all blocks have been closed, then we are at end of shader
1607 if (numBlocks == 0) {
1608 break;
1609 }
1610 }
1611 }
1612
1613 _materials.insert( std::pair(shader->name, shader) );
1614 }
1615 }
1616 fclose(fp);
1617
1618 fprintf(stdout, "[.md5mtr]: finished reading %s\n", FILENAME );
1619 fprintf(stdout, "[.md5mtr]: read in %lu shaders and %u textures\n\n", _materials.size(), numTextures );
1620}
1621
1622inline void
1624 for (auto &[name, material] : _materials) {
1625 glDeleteTextures(1, &material->textures[CSCI441_INTERNAL::MD5MaterialShader::TextureMap::DIFFUSE].texHandle);
1626 glDeleteTextures(1, &material->textures[CSCI441_INTERNAL::MD5MaterialShader::TextureMap::SPECULAR].texHandle);
1627 glDeleteTextures(1, &material->textures[CSCI441_INTERNAL::MD5MaterialShader::TextureMap::HEIGHT].texHandle);
1628 glDeleteTextures(1, &material->textures[CSCI441_INTERNAL::MD5MaterialShader::TextureMap::NORMAL].texHandle);
1629 delete material;
1630 }
1631}
1632
1633#endif//CSCI441_MD5_MODEL_HPP
Doom3 MD5 Model + Animation type implementations.
Helper functions to work with OpenGL Textures.
GLuint loadAndRegisterTexture(const char *filename, GLint minFilter=GL_LINEAR, GLint magFilter=GL_LINEAR, GLint wrapS=GL_REPEAT, GLint wrapT=GL_REPEAT, GLboolean flipOnY=GL_TRUE, GLboolean printAllMessages=GL_TRUE, GLboolean enableMipmaps=GL_TRUE, GLboolean enableAniso=GL_TRUE)
loads and registers a texture into memory returning a texture handle
Definition: TextureUtils.hpp:171
stores a Doom3 MD5 Mesh + Animation
Definition: MD5Model.hpp:76
bool addMD5Anim(const char *filename)
adds another MD5 Animation sequence to the model's set of animation
Definition: MD5Model.hpp:1260
static void readMD5Material(const char *FILENAME)
loads textures corresponding to MD5 Shaders
Definition: MD5Model.hpp:1504
void draw() const
draws all the meshes that make up the model
Definition: MD5Model.hpp:654
GLushort getNumberOfAnimations() const
returns the number of animations that were successfully loaded against the model
Definition: MD5Model.hpp:193
bool readMD5Model(const char *FILENAME)
parses md5mesh file and allocates corresponding mesh data
Definition: MD5Model.hpp:464
bool readMD5Anim(const char *filename, GLushort targetAnimationIndex=0)
reads in an animation sequence from an external file
Definition: MD5Model.hpp:1096
bool isAnimated() const
returns if the MD5 Model has an accompanying animation
Definition: MD5Model.hpp:133
bool loadMD5Model(const char *MD5_MESH_FILE, const char *MD5_ANIM_FILE="")
loads a corresponding md5mesh and md5anim file to the object
Definition: MD5Model.hpp:440
void setActiveTextures(GLint diffuseMapActiveTexture, GLint specularMapActiveTexture, GLint normalMapActiveTexture, GLint heightMapActiveTexture)
specify which texture targets each texture map should be bound to when rendering
Definition: MD5Model.hpp:878
MD5Model()
initializes an empty MD5 Model
Definition: MD5Model.hpp:82
MD5Model & operator=(const MD5Model &)=delete
do not allow MD5 models to be copied
void allocVertexArrays(GLuint vPosAttribLoc, GLuint vColorAttribLoc, GLuint vTexCoordAttribLoc, GLuint vNormalAttribLoc=0, GLuint vTangentAttribLoc=0)
binds model VBOs to attribute pointer locations
Definition: MD5Model.hpp:813
void animate(GLfloat dt)
advances the model forward in its animation sequence the corresponding amount of time based on frame ...
Definition: MD5Model.hpp:1430
MD5Model & operator=(MD5Model &&src) noexcept
do not allow MD5 models to be moved
Definition: MD5Model.hpp:114
void useTargetAnimationIndex(GLushort targetAnimationIndex)
update current animation to be running through
Definition: MD5Model.hpp:1313
MD5Model(const MD5Model &)=delete
do not allow MD5 models to be copied
MD5Model(MD5Model &&src) noexcept
do not allow MD5 models to be moved
Definition: MD5Model.hpp:108
~MD5Model()
deallocates any used memory on the CPU and GPU
Definition: MD5Model.hpp:430
static void releaseMD5Materials()
deletes textures from GPU that were registered during parsing of *.mtr file
Definition: MD5Model.hpp:1623
void drawSkeleton() const
draws the skeleton joints (as points) and bones (as lines)
Definition: MD5Model.hpp:924
CSCI441 Helper Functions for OpenGL.
Definition: ArcballCam.hpp:17