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