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