├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── build_emscripten.bat ├── data ├── anim_1.JPG └── anim_2.JPG └── src ├── CMakeLists.txt ├── anim_blend.cpp ├── anim_blend.h ├── anim_fabrik_ik.cpp ├── anim_fabrik_ik.h ├── anim_global_transform.cpp ├── anim_global_transform.h ├── anim_local_transform.cpp ├── anim_local_transform.h ├── anim_lookat_ik.cpp ├── anim_lookat_ik.h ├── anim_offset.cpp ├── anim_offset.h ├── anim_sample.cpp ├── anim_sample.h ├── animation.cpp ├── animation.h ├── blendspace_1d.cpp ├── blendspace_1d.h ├── blendspace_2d.cpp ├── blendspace_2d.h ├── main.cpp ├── shader ├── bone_fs.glsl ├── bone_vs.glsl ├── fs.glsl ├── skinning_fs.glsl ├── skinning_vs.glsl └── vs.glsl ├── skeletal_mesh.cpp ├── skeletal_mesh.h ├── skeleton.cpp └── skeleton.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | external/ 35 | external/* 36 | bin/ 37 | bin/* 38 | build/ 39 | build/* 40 | lib/ 41 | lib/* 42 | build_em/* 43 | build_em/ 44 | mesh/* 45 | mesh/ 46 | shader/* 47 | shader/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/dwSampleFramework"] 2 | path = external/dwSampleFramework 3 | url = https://github.com/diharaw/dwSampleFramework.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8 FATAL_ERROR) 2 | 3 | project("AnimationStateMachine") 4 | 5 | IF(APPLE) 6 | set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LANGUAGE_STANDARD "c++14") 7 | set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++") 8 | ENDIF() 9 | 10 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/lib") 11 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/lib") 12 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin) 13 | 14 | add_subdirectory(external/dwSampleFramework) 15 | 16 | include_directories("${DW_SAMPLE_FRAMEWORK_INCLUDES}") 17 | 18 | add_subdirectory(src) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Dihara Wijetunga 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License: MIT](https://img.shields.io/packagist/l/doctrine/orm.svg)](https://opensource.org/licenses/MIT) 2 | 3 | # Animation State Machine 4 | An experiment on creating an animation system similar to Unreal Engine 4 from scratch with Assimp, OpenGL and the dwSampleFramework. 5 | 6 | ## Features 7 | * Assimp Skeletal Mesh and Animation parsing. 8 | * Playback. 9 | * Blending. 10 | * Partial Blending. 11 | * Additives. 12 | * Blendspaces. 13 | * Inverse Kinematics. [IN-PROGRESS] 14 | * State Machines. [TODO] 15 | 16 | ## Screenshots 17 | 18 | ![ASM](data/anim_1.JPG) 19 | ![ASM](data/anim_2.JPG) 20 | 21 | ## Roadmap 22 | 23 | * Animation Graph UI 24 | 25 | ## Dependencies 26 | * [dwSampleFramework](https://github.com/diharaw/dwSampleFramework) 27 | 28 | ## License 29 | ``` 30 | Copyright (c) 2019 Dihara Wijetunga 31 | 32 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 33 | associated documentation files (the "Software"), to deal in the Software without restriction, 34 | including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 35 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 36 | subject to the following conditions: 37 | 38 | The above copyright notice and this permission notice shall be included in all copies or substantial 39 | portions of the Software. 40 | 41 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 42 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 43 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 44 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 45 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 46 | ``` 47 | -------------------------------------------------------------------------------- /build_emscripten.bat: -------------------------------------------------------------------------------- 1 | cd build_em 2 | call emsdk activate latest 3 | call vcvarsamd64_x86 4 | cmake .. -DBUILD_SAMPLES=OFF -DASSIMP_BUILD_ASSIMP_TOOLS=OFF -DASSIMP_BUILD_TESTS=OFF -DASSIMP_NO_EXPORT=ON -DBUILD_SHARED_LIBS=OFF -DCMAKE_TOOLCHAIN_FILE=C:\emsdk\emscripten\1.37.28\cmake\Modules\Platform\Emscripten.cmake -G "NMake Makefiles" 5 | timeout /t -1 -------------------------------------------------------------------------------- /data/anim_1.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diharaw/animation-system/3688120568214beb1188b7719deba05f1b89a2e6/data/anim_1.JPG -------------------------------------------------------------------------------- /data/anim_2.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diharaw/animation-system/3688120568214beb1188b7719deba05f1b89a2e6/data/anim_2.JPG -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8 FATAL_ERROR) 2 | 3 | set(CMAKE_CXX_STANDARD 14) 4 | set(CMAKE_CXX_STANDARD_REQUIRED TRUE) 5 | 6 | set(ASM_HEADERS ${PROJECT_SOURCE_DIR}/src/skeletal_mesh.h 7 | ${PROJECT_SOURCE_DIR}/src/animation.h 8 | ${PROJECT_SOURCE_DIR}/src/skeleton.h 9 | ${PROJECT_SOURCE_DIR}/src/anim_blend.h 10 | ${PROJECT_SOURCE_DIR}/src/anim_local_transform.h 11 | ${PROJECT_SOURCE_DIR}/src/anim_global_transform.h 12 | ${PROJECT_SOURCE_DIR}/src/anim_fabrik_ik.h 13 | ${PROJECT_SOURCE_DIR}/src/anim_lookat_ik.h 14 | ${PROJECT_SOURCE_DIR}/src/blendspace_1d.h 15 | ${PROJECT_SOURCE_DIR}/src/blendspace_2d.h 16 | ${PROJECT_SOURCE_DIR}/src/anim_offset.h 17 | ${PROJECT_SOURCE_DIR}/src/anim_sample.h) 18 | 19 | set(ASM_SOURCES ${PROJECT_SOURCE_DIR}/src/main.cpp 20 | ${PROJECT_SOURCE_DIR}/src/skeletal_mesh.cpp 21 | ${PROJECT_SOURCE_DIR}/src/animation.cpp 22 | ${PROJECT_SOURCE_DIR}/src/skeleton.cpp 23 | ${PROJECT_SOURCE_DIR}/src/anim_blend.cpp 24 | ${PROJECT_SOURCE_DIR}/src/anim_local_transform.cpp 25 | ${PROJECT_SOURCE_DIR}/src/anim_global_transform.cpp 26 | ${PROJECT_SOURCE_DIR}/src/anim_fabrik_ik.cpp 27 | ${PROJECT_SOURCE_DIR}/src/anim_lookat_ik.cpp 28 | ${PROJECT_SOURCE_DIR}/src/blendspace_1d.cpp 29 | ${PROJECT_SOURCE_DIR}/src/blendspace_2d.cpp 30 | ${PROJECT_SOURCE_DIR}/src/anim_offset.cpp 31 | ${PROJECT_SOURCE_DIR}/src/anim_sample.cpp) 32 | 33 | if (EMSCRIPTEN) 34 | set(CMAKE_EXECUTABLE_SUFFIX ".html") 35 | endif() 36 | 37 | if(APPLE) 38 | add_executable(AnimationStateMachine MACOSX_BUNDLE ${ASM_HEADERS} ${ASM_SOURCES}) 39 | set(MACOSX_BUNDLE_BUNDLE_NAME "com.dihara.asm") 40 | else() 41 | add_executable(AnimationStateMachine ${ASM_HEADERS} ${ASM_SOURCES}) 42 | endif() 43 | 44 | target_link_libraries(AnimationStateMachine dwSampleFramework) 45 | 46 | if (EMSCRIPTEN) 47 | set_target_properties(AnimationStateMachine PROPERTIES LINK_FLAGS "--embed-file ${PROJECT_SOURCE_DIR}/mesh/Rifle/Rifle_Walk_Fwd.fbx@mesh/Rifle/Rifle_Walk_Fwd.fbx --embed-file ${PROJECT_SOURCE_DIR}/mesh/Rifle/Rifle_Run_Fwd.fbx@mesh/Rifle/Rifle_Run_Fwd.fbx --embed-file ${PROJECT_SOURCE_DIR}/mesh/Rifle/Rifle_Sprint_Fwd.fbx@mesh/Rifle/Rifle_Sprint_Fwd.fbx --embed-file ${PROJECT_SOURCE_DIR}/mesh/Rifle/AimOffsets/Rifle_Aim_Fwd.fbx@mesh/Rifle/AimOffsets/Rifle_Aim_Fwd.fbx --embed-file ${PROJECT_SOURCE_DIR}/mesh/Rifle/AimOffsets/Rifle_Aim_Left_Up.fbx@mesh/Rifle/AimOffsets/Rifle_Aim_Left_Up.fbx --embed-file ${PROJECT_SOURCE_DIR}/mesh/Rifle/AimOffsets/Rifle_Aim_Up.fbx@mesh/Rifle/AimOffsets/Rifle_Aim_Up.fbx --embed-file ${PROJECT_SOURCE_DIR}/mesh/Rifle/AimOffsets/Rifle_Aim_Right_Up.fbx@mesh/Rifle/AimOffsets/Rifle_Aim_Right_Up.fbx --embed-file ${PROJECT_SOURCE_DIR}/mesh/Rifle/AimOffsets/Rifle_Aim_Left.fbx@mesh/Rifle/AimOffsets/Rifle_Aim_Left.fbx --embed-file ${PROJECT_SOURCE_DIR}/mesh/Rifle/AimOffsets/Rifle_Aim_Right.fbx@mesh/Rifle/AimOffsets/Rifle_Aim_Right.fbx --embed-file ${PROJECT_SOURCE_DIR}/mesh/Rifle/AimOffsets/Rifle_Aim_Left_Down.fbx@mesh/Rifle/AimOffsets/Rifle_Aim_Left_Down.fbx --embed-file ${PROJECT_SOURCE_DIR}/mesh/Rifle/AimOffsets/Rifle_Aim_Down.fbx@mesh/Rifle/AimOffsets/Rifle_Aim_Down.fbx --embed-file ${PROJECT_SOURCE_DIR}/mesh/Rifle/AimOffsets/Rifle_Aim_Right_Down.fbx@mesh/Rifle/AimOffsets/Rifle_Aim_Right_Down.fbx --embed-file ${PROJECT_SOURCE_DIR}/mesh/Rifle/AimOffsets/Rifle_Aim_Right_Down.fbx@mesh/Rifle/AimOffsets/Rifle_Aim_Right_Down.fbx --embed-file ${PROJECT_SOURCE_DIR}/shader/vs.glsl@shader/vs.glsl --embed-file ${PROJECT_SOURCE_DIR}/shader/fs.glsl@shader/fs.glsl --embed-file ${PROJECT_SOURCE_DIR}/shader/skinning_vs.glsl@shader/skinning_vs.glsl --embed-file ${PROJECT_SOURCE_DIR}/shader/skinning_fs.glsl@shader/skinning_fs.glsl --embed-file ${PROJECT_SOURCE_DIR}/shader/bone_vs.glsl@shader/bone_vs.glsl --embed-file ${PROJECT_SOURCE_DIR}/shader/bone_fs.glsl@shader/bone_fs.glsl -O3 -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s USE_GLFW=3 -s USE_WEBGL2=1") 48 | endif() 49 | 50 | if (APPLE) 51 | add_custom_command(TARGET AnimationStateMachine POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/src/shader $/AnimationStateMachine.app/Contents/Resources/shader) 52 | else() 53 | add_custom_command(TARGET AnimationStateMachine POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/src/shader $/shader) 54 | endif() 55 | 56 | set_property(TARGET AnimationStateMachine PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/bin/$(Configuration)") -------------------------------------------------------------------------------- /src/anim_blend.cpp: -------------------------------------------------------------------------------- 1 | #include "anim_blend.h" 2 | #define GLM_ENABLE_EXPERIMENTAL 3 | #include 4 | 5 | AnimBlend::AnimBlend(Skeleton* skeleton) : m_skeleton(skeleton) 6 | { 7 | 8 | } 9 | 10 | AnimBlend::~AnimBlend() 11 | { 12 | 13 | } 14 | 15 | Pose* AnimBlend::blend(Pose* base, Pose* secondary, float t) 16 | { 17 | m_pose.num_keyframes = base->num_keyframes; 18 | 19 | for (uint32_t i = 0; i < base->num_keyframes; i++) 20 | { 21 | m_pose.keyframes[i].translation = glm::lerp(base->keyframes[i].translation, secondary->keyframes[i].translation, t); 22 | m_pose.keyframes[i].rotation = glm::slerp(base->keyframes[i].rotation, secondary->keyframes[i].rotation, t); 23 | m_pose.keyframes[i].scale = glm::lerp(base->keyframes[i].scale, secondary->keyframes[i].scale, t); 24 | } 25 | 26 | return &m_pose; 27 | } 28 | 29 | Pose* AnimBlend::blend_partial(Pose* base, Pose* secondary, float t, const std::string& root_joint) 30 | { 31 | m_pose.num_keyframes = base->num_keyframes; 32 | 33 | uint32_t idx = m_skeleton->find_joint_index(root_joint); 34 | bool found = false; 35 | Joint* joints = m_skeleton->joints(); 36 | 37 | if (idx >= 0) 38 | { 39 | for (uint32_t i = 0; i < base->num_keyframes; i++) 40 | { 41 | if (i == idx) 42 | found = true; 43 | 44 | if (i > idx && joints[i].parent_index < idx) 45 | found = false; 46 | 47 | if (found && (joints[i].parent_index >= idx || i == idx)) 48 | { 49 | m_pose.keyframes[i].translation = glm::lerp(base->keyframes[i].translation, secondary->keyframes[i].translation, t); 50 | m_pose.keyframes[i].rotation = glm::slerp(base->keyframes[i].rotation, secondary->keyframes[i].rotation, t); 51 | m_pose.keyframes[i].scale = glm::lerp(base->keyframes[i].scale, secondary->keyframes[i].scale, t); 52 | } 53 | else 54 | { 55 | m_pose.keyframes[i].translation = base->keyframes[i].translation; 56 | m_pose.keyframes[i].rotation = base->keyframes[i].rotation; 57 | m_pose.keyframes[i].scale = base->keyframes[i].scale; 58 | } 59 | } 60 | } 61 | 62 | return &m_pose; 63 | } 64 | 65 | Pose* AnimBlend::blend_additive(Pose* base, Pose* secondary, float t) 66 | { 67 | m_pose.num_keyframes = base->num_keyframes; 68 | 69 | for (uint32_t i = 0; i < base->num_keyframes; i++) 70 | { 71 | m_pose.keyframes[i].translation = base->keyframes[i].translation + secondary->keyframes[i].translation * t; 72 | m_pose.keyframes[i].rotation = base->keyframes[i].rotation * glm::slerp(glm::quat(1.0f, 0.0f, 0.0f, 0.0f), secondary->keyframes[i].rotation, t); 73 | m_pose.keyframes[i].scale = base->keyframes[i].scale + secondary->keyframes[i].scale * t; 74 | } 75 | 76 | return &m_pose; 77 | } 78 | 79 | Pose* AnimBlend::blend_partial_additive(Pose* base, Pose* secondary, float t, const std::string& root_joint) 80 | { 81 | m_pose.num_keyframes = base->num_keyframes; 82 | 83 | uint32_t idx = m_skeleton->find_joint_index(root_joint); 84 | bool found = false; 85 | Joint* joints = m_skeleton->joints(); 86 | 87 | if (idx >= 0) 88 | { 89 | for (uint32_t i = 0; i < base->num_keyframes; i++) 90 | { 91 | if (i == idx) 92 | found = true; 93 | 94 | if (i > idx && joints[i].parent_index < idx) 95 | found = false; 96 | 97 | if (found && (joints[i].parent_index >= idx || i == idx)) 98 | { 99 | m_pose.keyframes[i].translation = base->keyframes[i].translation + secondary->keyframes[i].translation * t; 100 | m_pose.keyframes[i].rotation = base->keyframes[i].rotation * glm::slerp(glm::quat(1.0f, 0.0f, 0.0f, 0.0f), secondary->keyframes[i].rotation, t); 101 | m_pose.keyframes[i].scale = base->keyframes[i].scale + secondary->keyframes[i].scale * t; 102 | } 103 | else 104 | { 105 | m_pose.keyframes[i].translation = base->keyframes[i].translation; 106 | m_pose.keyframes[i].rotation = base->keyframes[i].rotation; 107 | m_pose.keyframes[i].scale = base->keyframes[i].scale; 108 | } 109 | } 110 | } 111 | 112 | return &m_pose; 113 | } 114 | 115 | Pose* AnimBlend::blend_additive_with_reference(Pose* reference, Pose* secondary, float t) 116 | { 117 | m_pose.num_keyframes = reference->num_keyframes; 118 | 119 | for (uint32_t i = 0; i < reference->num_keyframes; i++) 120 | { 121 | glm::vec3 delta_translation = translation_delta(reference->keyframes[i].translation, secondary->keyframes[i].translation); 122 | glm::quat delta_rotation = rotation_delta(reference->keyframes[i].rotation, secondary->keyframes[i].rotation); 123 | glm::vec3 delta_scale = scale_delta(reference->keyframes[i].scale, secondary->keyframes[i].scale); 124 | 125 | m_pose.keyframes[i].translation = reference->keyframes[i].translation + delta_translation * t; 126 | m_pose.keyframes[i].rotation = reference->keyframes[i].rotation * glm::slerp(glm::quat(1.0f, 0.0f, 0.0f, 0.0f), delta_rotation, t); 127 | m_pose.keyframes[i].scale = reference->keyframes[i].scale + delta_scale * t; 128 | } 129 | 130 | return &m_pose; 131 | } 132 | 133 | Pose* AnimBlend::blend_partial_additive_with_reference(Pose* reference, Pose* secondary, float t, const std::string& root_joint) 134 | { 135 | m_pose.num_keyframes = reference->num_keyframes; 136 | 137 | uint32_t idx = m_skeleton->find_joint_index(root_joint); 138 | bool found = false; 139 | Joint* joints = m_skeleton->joints(); 140 | 141 | if (idx >= 0) 142 | { 143 | for (uint32_t i = 0; i < reference->num_keyframes; i++) 144 | { 145 | if (i == idx) 146 | found = true; 147 | 148 | if (i > idx && joints[i].parent_index < idx) 149 | found = false; 150 | 151 | if (found && (joints[i].parent_index >= idx || i == idx)) 152 | { 153 | glm::vec3 delta_translation = translation_delta(reference->keyframes[i].translation, secondary->keyframes[i].translation); 154 | glm::quat delta_rotation = rotation_delta(reference->keyframes[i].rotation, secondary->keyframes[i].rotation); 155 | glm::vec3 delta_scale = scale_delta(reference->keyframes[i].scale, secondary->keyframes[i].scale); 156 | 157 | m_pose.keyframes[i].translation = reference->keyframes[i].translation + delta_translation * t; 158 | m_pose.keyframes[i].rotation = reference->keyframes[i].rotation * glm::slerp(glm::quat(1.0f, 0.0f, 0.0f, 0.0f), delta_rotation, t); 159 | m_pose.keyframes[i].scale = reference->keyframes[i].scale + delta_scale * t; 160 | } 161 | else 162 | { 163 | m_pose.keyframes[i].translation = reference->keyframes[i].translation; 164 | m_pose.keyframes[i].rotation = reference->keyframes[i].rotation; 165 | m_pose.keyframes[i].scale = reference->keyframes[i].scale; 166 | } 167 | } 168 | } 169 | 170 | return &m_pose; 171 | } -------------------------------------------------------------------------------- /src/anim_blend.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "skeletal_mesh.h" 4 | 5 | class AnimBlend 6 | { 7 | public: 8 | AnimBlend(Skeleton* skeleton); 9 | ~AnimBlend(); 10 | 11 | Pose* blend(Pose* base, Pose* secondary, float t); 12 | Pose* blend_partial(Pose* base, Pose* secondary, float t, const std::string& root_joint); 13 | Pose* blend_additive(Pose* base, Pose* secondary, float t); 14 | Pose* blend_partial_additive(Pose* base, Pose* secondary, float t, const std::string& root_joint); 15 | Pose* blend_additive_with_reference(Pose* reference, Pose* secondary, float t); 16 | Pose* blend_partial_additive_with_reference(Pose* reference, Pose* secondary, float t, const std::string& root_joint); 17 | 18 | private: 19 | Skeleton* m_skeleton; 20 | Pose m_pose; 21 | }; -------------------------------------------------------------------------------- /src/anim_fabrik_ik.cpp: -------------------------------------------------------------------------------- 1 | #include "anim_fabrik_ik.h" 2 | #include 3 | #include 4 | #include 5 | 6 | // https://zalo.github.io/blog/inverse-kinematics/ 7 | glm::quat rotation_from_two_vectors(glm::vec3 u, glm::vec3 v) 8 | { 9 | float norm_u_norm_v = sqrt(glm::dot(u, u) * glm::dot(v, v)); 10 | float real_part = norm_u_norm_v + dot(u, v); 11 | glm::vec3 w; 12 | 13 | if (real_part < 1.e-6f * norm_u_norm_v) 14 | { 15 | /* If u and v are exactly opposite, rotate 180 degrees 16 | * around an arbitrary orthogonal axis. Axis normalisation 17 | * can happen later, when we normalise the quaternion. */ 18 | real_part = 0.0f; 19 | w = abs(u.x) > abs(u.z) ? glm::vec3(-u.y, u.x, 0.f) : glm::vec3(0.f, -u.z, u.y); 20 | } 21 | else 22 | { 23 | /* Otherwise, build quaternion the standard way. */ 24 | w = glm::cross(u, v); 25 | } 26 | 27 | return glm::normalize(glm::quat(real_part, w.x, w.y, w.z)); 28 | } 29 | 30 | AnimFabrikIK::AnimFabrikIK(Skeleton* skeleton) : m_skeleton(skeleton) 31 | { 32 | 33 | } 34 | 35 | AnimFabrikIK::~AnimFabrikIK() 36 | { 37 | 38 | } 39 | 40 | PoseTransforms* AnimFabrikIK::solve(glm::mat4 model, PoseTransforms* local_transforms, PoseTransforms* global_transforms, const glm::vec3& end_effector, const std::string& start_bone, const std::string& end_bone) 41 | { 42 | for (int i = 0; i < m_skeleton->num_bones(); i++) 43 | m_transforms.transforms[i] = global_transforms->transforms[i]; 44 | 45 | int32_t start_idx = m_skeleton->find_joint_index(start_bone); 46 | 47 | if (start_idx == -1) 48 | { 49 | DW_LOG_ERROR("FABRIK IK: Requested start bone not found = " + start_bone); 50 | return global_transforms; 51 | } 52 | 53 | int32_t end_idx = m_skeleton->find_joint_index(end_bone); 54 | 55 | if (end_idx == -1) 56 | { 57 | DW_LOG_ERROR("FABRIK IK: Requested end bone not found = " + end_bone); 58 | return global_transforms; 59 | } 60 | 61 | int32_t local_end_idx = find_source_chain_data(model, start_idx, end_idx, global_transforms); 62 | 63 | for (uint32_t i = 0; i < m_iterations; i++) 64 | { 65 | backward_ik(local_end_idx, end_effector); 66 | forward_ik(local_end_idx, end_effector); 67 | } 68 | 69 | modify_transforms(model, start_idx, end_idx, local_transforms); 70 | 71 | return &m_transforms; 72 | } 73 | 74 | int32_t AnimFabrikIK::find_source_chain_data(glm::mat4 model, int32_t start_idx, int32_t end_idx, PoseTransforms* global_transforms) 75 | { 76 | int32_t count = 0; 77 | 78 | for (int32_t i = start_idx; i <= end_idx; i++) 79 | { 80 | int32_t idx = count++; 81 | 82 | glm::mat4 m = model * global_transforms->transforms[i]; 83 | 84 | m_source_joint_pos[idx] = glm::vec3(m[3][0], m[3][1], m[3][2]); 85 | m_iteration_joint_pos[idx] = m_source_joint_pos[idx]; 86 | } 87 | 88 | for (int32_t i = 0; i < (count - 1); i++) 89 | m_bone_lengths[i] = glm::length(m_source_joint_pos[i] - m_source_joint_pos[i + 1]); 90 | 91 | return count - 1; 92 | } 93 | 94 | void AnimFabrikIK::forward_ik(int32_t end_idx, const glm::vec3& end_effector) 95 | { 96 | for (int32_t i = 0; i <= end_idx; i++) 97 | { 98 | if (i == 0) 99 | m_iteration_joint_pos[i] = m_source_joint_pos[i]; 100 | else 101 | { 102 | glm::vec3 dir = glm::normalize(m_iteration_joint_pos[i] - m_iteration_joint_pos[i - 1]); 103 | m_iteration_joint_pos[i] = m_iteration_joint_pos[i - 1] + dir * m_bone_lengths[i - 1]; 104 | } 105 | } 106 | } 107 | 108 | void AnimFabrikIK::backward_ik(int32_t end_idx, const glm::vec3& end_effector) 109 | { 110 | for (int32_t i = end_idx; i >= 0; i--) 111 | { 112 | if (i == end_idx) 113 | m_iteration_joint_pos[i] = end_effector; 114 | else 115 | { 116 | glm::vec3 dir = glm::normalize(m_iteration_joint_pos[i] - m_iteration_joint_pos[i + 1]); 117 | m_iteration_joint_pos[i] = m_iteration_joint_pos[i + 1] + dir * m_bone_lengths[i]; 118 | } 119 | } 120 | } 121 | 122 | void AnimFabrikIK::modify_transforms(glm::mat4 model, int32_t start_idx, int32_t end_idx, PoseTransforms* local_transforms) 123 | { 124 | glm::mat4 inv_model = glm::inverse(model); 125 | 126 | for (int32_t i = start_idx; i < end_idx; i++) 127 | { 128 | // Calculate the vector pointing from the one joint to the next in the source transforms. 129 | glm::vec3 src_joint_start_pos = glm::vec3(m_transforms.transforms[i][3][0], m_transforms.transforms[i][3][1], m_transforms.transforms[i][3][2]); 130 | glm::vec3 src_joint_end_pos = glm::vec3(m_transforms.transforms[i + 1][3][0], m_transforms.transforms[i + 1][3][1], m_transforms.transforms[i + 1][3][2]); 131 | glm::vec3 src_joint_dir = glm::normalize(glm::vec3(src_joint_end_pos) - glm::vec3(src_joint_start_pos)); 132 | 133 | // Calculate the vector pointing from the one joint to the next in the IK transforms. 134 | glm::vec4 dst_joint_start_pos = inv_model * glm::vec4(m_iteration_joint_pos[i - start_idx], 1.0f); 135 | glm::vec4 dst_joint_end_pos = inv_model * glm::vec4(m_iteration_joint_pos[i - start_idx + 1], 1.0f); 136 | glm::vec3 dst_joint_dir = glm::normalize(glm::vec3(dst_joint_end_pos) - glm::vec3(dst_joint_start_pos)); 137 | 138 | // Find the quaternion that rotates from source to destination rotations. 139 | glm::quat src_to_dst_rotation = rotation_from_two_vectors(src_joint_dir, dst_joint_dir); 140 | 141 | // Find the original rotation of the joint. 142 | glm::quat origin_rotation = glm::normalize(glm::quat_cast(m_transforms.transforms[i])); 143 | 144 | // Create a translation matrix from the destination joint position. 145 | glm::mat4 translation = glm::mat4(1.0f); 146 | translation = glm::translate(translation, glm::vec3(dst_joint_start_pos.x, dst_joint_start_pos.y, dst_joint_start_pos.z)); 147 | 148 | // Compute the final joint rotation by adding to the original rotation. 149 | glm::quat final_rotation = glm::normalize(src_to_dst_rotation * origin_rotation); 150 | glm::mat4 rotation = glm::mat4_cast(final_rotation); 151 | 152 | // Compute final joint transform. 153 | m_transforms.transforms[i] = translation * rotation; 154 | } 155 | 156 | Joint* joints = m_skeleton->joints(); 157 | 158 | int32_t i = end_idx; 159 | 160 | while (joints[i].parent_index > start_idx) 161 | { 162 | if (joints[i].parent_index == -1) 163 | m_transforms.transforms[i] = local_transforms->transforms[i]; 164 | else 165 | m_transforms.transforms[i] = m_transforms.transforms[joints[i].parent_index] * local_transforms->transforms[i]; 166 | 167 | i++; 168 | } 169 | } -------------------------------------------------------------------------------- /src/anim_fabrik_ik.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "skeletal_mesh.h" 4 | 5 | #define MAX_IK_CHAIN_SIZE 8 6 | 7 | class AnimFabrikIK 8 | { 9 | public: 10 | AnimFabrikIK(Skeleton* skeleton); 11 | ~AnimFabrikIK(); 12 | 13 | PoseTransforms* solve(glm::mat4 model, PoseTransforms* local_transforms, PoseTransforms* global_transforms, const glm::vec3& end_effector, const std::string& start_bone, const std::string& end_bone); 14 | inline uint32_t num_iterations() { return m_iterations; } 15 | inline void set_iterations(uint32_t itr) { m_iterations = itr; } 16 | 17 | private: 18 | int32_t find_source_chain_data(glm::mat4 model, int32_t start_idx, int32_t end_idx, PoseTransforms* global_transforms); 19 | void forward_ik(int32_t end_idx, const glm::vec3& end_effector); 20 | void backward_ik(int32_t end_idx, const glm::vec3& end_effector); 21 | void modify_transforms(glm::mat4 model, int32_t start_idx, int32_t end_idx, PoseTransforms* local_transforms); 22 | 23 | private: 24 | float m_bone_lengths[MAX_IK_CHAIN_SIZE - 1]; 25 | glm::vec3 m_source_joint_pos[MAX_IK_CHAIN_SIZE]; 26 | glm::vec3 m_iteration_joint_pos[MAX_IK_CHAIN_SIZE]; 27 | 28 | uint32_t m_iterations = 16; 29 | Skeleton* m_skeleton; 30 | PoseTransforms m_transforms; 31 | }; -------------------------------------------------------------------------------- /src/anim_global_transform.cpp: -------------------------------------------------------------------------------- 1 | #include "anim_global_transform.h" 2 | 3 | AnimGlobalTransform::AnimGlobalTransform(Skeleton* skeleton) : m_skeleton(skeleton) 4 | { 5 | for (int i = 0; i < MAX_BONES; i++) 6 | m_transforms.transforms[i] = glm::mat4(1.0f); 7 | } 8 | 9 | AnimGlobalTransform::~AnimGlobalTransform() 10 | { 11 | 12 | } 13 | 14 | PoseTransforms* AnimGlobalTransform::generate_transforms(PoseTransforms* local_transforms) 15 | { 16 | Joint* joints = m_skeleton->joints(); 17 | 18 | for (uint32_t i = 0; i < m_skeleton->num_bones(); i++) 19 | { 20 | if (joints[i].parent_index == -1) 21 | m_transforms.transforms[i] = local_transforms->transforms[i]; 22 | else 23 | m_transforms.transforms[i] = m_transforms.transforms[joints[i].parent_index] * local_transforms->transforms[i]; 24 | } 25 | 26 | return &m_transforms; 27 | } -------------------------------------------------------------------------------- /src/anim_global_transform.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "skeletal_mesh.h" 4 | 5 | class AnimGlobalTransform 6 | { 7 | public: 8 | AnimGlobalTransform(Skeleton* skeleton); 9 | ~AnimGlobalTransform(); 10 | PoseTransforms* generate_transforms(PoseTransforms* local_transforms); 11 | 12 | private: 13 | Skeleton* m_skeleton; 14 | PoseTransforms m_transforms; 15 | }; 16 | -------------------------------------------------------------------------------- /src/anim_local_transform.cpp: -------------------------------------------------------------------------------- 1 | #include "anim_local_transform.h" 2 | #include 3 | #define GLM_ENABLE_EXPERIMENTAL 4 | #include 5 | 6 | AnimLocalTransform::AnimLocalTransform(Skeleton* skeleton) : m_skeleton(skeleton) 7 | { 8 | for (int i = 0; i < MAX_BONES; i++) 9 | m_transforms.transforms[i] = glm::mat4(1.0f); 10 | } 11 | 12 | AnimLocalTransform::~AnimLocalTransform() 13 | { 14 | 15 | } 16 | 17 | PoseTransforms* AnimLocalTransform::generate_transforms(Pose* pose) 18 | { 19 | Joint* joints = m_skeleton->joints(); 20 | 21 | for (uint32_t i = 0; i < m_skeleton->num_bones(); i++) 22 | m_transforms.transforms[i] = transform_from_keyframe(pose->keyframes[i]); 23 | 24 | return &m_transforms; 25 | } 26 | 27 | 28 | glm::mat4 AnimLocalTransform::transform_from_keyframe(const Keyframe& keyframe) 29 | { 30 | glm::mat4 translation = glm::translate(glm::mat4(1.0f), keyframe.translation); 31 | glm::mat4 rotation = glm::toMat4(keyframe.rotation); 32 | glm::mat4 scale = glm::scale(glm::mat4(1.0f), keyframe.scale); 33 | 34 | glm::mat4 local_transform = translation * rotation; // * scale; 35 | 36 | return local_transform; 37 | } -------------------------------------------------------------------------------- /src/anim_local_transform.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "skeletal_mesh.h" 4 | 5 | class AnimLocalTransform 6 | { 7 | public: 8 | AnimLocalTransform(Skeleton* skeleton); 9 | ~AnimLocalTransform(); 10 | PoseTransforms* generate_transforms(Pose* pose); 11 | 12 | private: 13 | glm::mat4 transform_from_keyframe(const Keyframe& keyframe); 14 | 15 | private: 16 | Skeleton* m_skeleton; 17 | PoseTransforms m_transforms; 18 | }; 19 | -------------------------------------------------------------------------------- /src/anim_lookat_ik.cpp: -------------------------------------------------------------------------------- 1 | #include "anim_lookat_ik.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | AnimLookAtIK::AnimLookAtIK(Skeleton* skeleton) : m_skeleton(skeleton) 8 | { 9 | 10 | } 11 | 12 | AnimLookAtIK::~AnimLookAtIK() 13 | { 14 | 15 | } 16 | 17 | PoseTransforms* AnimLookAtIK::look_at(PoseTransforms* input, PoseTransforms* input_local, const glm::vec3& target, float max_angle, const std::string& joint) 18 | { 19 | int32_t idx = m_skeleton->find_joint_index(joint); 20 | 21 | if (idx != -1) 22 | { 23 | glm::mat4 bone_mat = input->transforms[idx]; 24 | bone_mat[3][0] = 0.0f; 25 | bone_mat[3][1] = 0.0f; 26 | bone_mat[3][2] = 0.0f; 27 | bone_mat[3][3] = 1.0f; 28 | 29 | glm::mat4 to_bone_space = glm::inverse(bone_mat); 30 | 31 | glm::vec4 bone_fwd = to_bone_space * glm::vec4(0.0f, 0.0f, 1.0f, 1.0f); 32 | glm::vec3 bone_fwd_dir = glm::normalize(glm::vec3(bone_fwd.x, bone_fwd.y, bone_fwd.z)); 33 | 34 | glm::mat4 global_to_local = glm::inverse(input->transforms[idx]); 35 | 36 | glm::vec4 local_target = to_bone_space * glm::vec4(target, 1.0f); 37 | glm::vec3 local_target_dir = glm::normalize(glm::vec3(local_target.x, local_target.y, local_target.z)); 38 | 39 | glm::vec3 rotation_axis = glm::cross(bone_fwd_dir, local_target_dir); 40 | rotation_axis = glm::normalize(rotation_axis); 41 | 42 | float angle = acosf(glm::dot(local_target_dir, bone_fwd_dir)); 43 | 44 | ImGui::Text("Angle: %f, Target: [%f, %f, %f], Bone: [%f, %f, %f]", glm::degrees(angle), local_target_dir.x, local_target_dir.y, local_target_dir.z, bone_fwd_dir.x, bone_fwd_dir.y, bone_fwd_dir.z); 45 | //glm::vec3 diff = bone_fwd_dir - local_target_dir; 46 | //ImGui::Text("Axis: [%f, %f, %f]", rotation_axis.x, rotation_axis.y, rotation_axis.z); 47 | 48 | angle = std::min(angle, glm::radians(max_angle)); 49 | 50 | glm::mat4 rotation_mat = glm::rotate(glm::mat4(1.0f), angle, rotation_axis); 51 | input->transforms[idx] = input->transforms[idx] * rotation_mat; 52 | 53 | Joint* joints = m_skeleton->joints(); 54 | 55 | for (uint32_t i = (idx + 1); i < m_skeleton->num_bones(); i++) 56 | { 57 | if (i > idx && joints[i].parent_index < idx) 58 | break; 59 | 60 | if (joints[i].parent_index >= idx) 61 | { 62 | if (joints[i].parent_index == -1) 63 | input->transforms[i] = input_local->transforms[i]; 64 | else 65 | input->transforms[i] = input->transforms[joints[i].parent_index] * input_local->transforms[i]; 66 | } 67 | } 68 | 69 | return input; 70 | } 71 | 72 | return nullptr; 73 | } -------------------------------------------------------------------------------- /src/anim_lookat_ik.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "skeletal_mesh.h" 4 | 5 | class AnimLookAtIK 6 | { 7 | public: 8 | AnimLookAtIK(Skeleton* skeleton); 9 | ~AnimLookAtIK(); 10 | 11 | PoseTransforms* look_at(PoseTransforms* input, PoseTransforms* input_local, const glm::vec3& target, float max_angle, const std::string& joint); 12 | 13 | private: 14 | Skeleton* m_skeleton; 15 | PoseTransforms m_transforms; 16 | }; -------------------------------------------------------------------------------- /src/anim_offset.cpp: -------------------------------------------------------------------------------- 1 | #include "anim_offset.h" 2 | 3 | AnimOffset::AnimOffset(Skeleton* skeleton) : m_skeleton(skeleton) 4 | { 5 | 6 | } 7 | 8 | AnimOffset::~AnimOffset() 9 | { 10 | 11 | } 12 | 13 | PoseTransforms* AnimOffset::offset(PoseTransforms* transforms) 14 | { 15 | Joint* joints = m_skeleton->joints(); 16 | 17 | for (uint32_t i = 0; i < m_skeleton->num_bones(); i++) 18 | m_transforms.transforms[i] = (transforms->transforms[i] * joints[i].offset_transform); 19 | 20 | return &m_transforms; 21 | } -------------------------------------------------------------------------------- /src/anim_offset.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "skeletal_mesh.h" 4 | 5 | class AnimOffset 6 | { 7 | public: 8 | AnimOffset(Skeleton* skeleton); 9 | ~AnimOffset(); 10 | PoseTransforms* offset(PoseTransforms* transforms); 11 | 12 | private: 13 | Skeleton* m_skeleton; 14 | PoseTransforms m_transforms; 15 | }; 16 | -------------------------------------------------------------------------------- /src/anim_sample.cpp: -------------------------------------------------------------------------------- 1 | #include "anim_sample.h" 2 | #include 3 | #define GLM_ENABLE_EXPERIMENTAL 4 | #include 5 | 6 | AnimSample::AnimSample(Skeleton* skeleton, Animation* animation) : m_skeleton(skeleton), m_animation(animation), m_playback_rate(1.0f), m_global_time(0.0) 7 | { 8 | 9 | } 10 | 11 | AnimSample::~AnimSample() 12 | { 13 | 14 | } 15 | 16 | Pose* AnimSample::sample(double dt) 17 | { 18 | m_global_time += (dt * m_playback_rate); // dt is Delta Time in seconds. 19 | 20 | float ticks_per_second = (float)(m_animation->ticks_per_second != 0 ? m_animation->ticks_per_second : 25.0f); 21 | float time_in_ticks = ticks_per_second * m_global_time; 22 | m_local_time = fmod(time_in_ticks, m_animation->duration_in_ticks); 23 | m_local_time_normalized = static_cast(m_local_time) / static_cast(m_animation->duration_in_ticks); 24 | 25 | m_pose.num_keyframes = m_skeleton->num_bones(); 26 | 27 | for (int i = 0; i < m_skeleton->num_bones(); i++) 28 | { 29 | const AnimationChannel& channel = m_animation->channels[i]; 30 | 31 | Keyframe result; 32 | 33 | // Calculate interpolated translation 34 | { 35 | if (channel.translation_keyframes.size() == 0) 36 | result.translation = glm::vec3(0.0f); 37 | else 38 | { 39 | const uint32_t idx_1 = find_translation_key(channel.translation_keyframes, m_local_time); 40 | const uint32_t idx_2 = idx_1 + 1; 41 | 42 | if (channel.translation_keyframes.size() == 1) 43 | result.translation = channel.translation_keyframes[idx_1].translation; 44 | else 45 | { 46 | float delta = (float)(channel.translation_keyframes[idx_2].time - channel.translation_keyframes[idx_1].time); 47 | float factor = (m_local_time - (float)channel.translation_keyframes[idx_1].time) / delta; 48 | 49 | result.translation = interpolate_translation(channel.translation_keyframes[idx_1].translation, channel.translation_keyframes[idx_2].translation, factor); 50 | } 51 | } 52 | } 53 | 54 | // Calculate interpolated rotation 55 | { 56 | if (channel.rotation_keyframes.size() == 0) 57 | result.rotation = glm::quat(); 58 | else 59 | { 60 | const uint32_t idx_1 = find_rotation_key(channel.rotation_keyframes, m_local_time); 61 | const uint32_t idx_2 = idx_1 + 1; 62 | 63 | if (channel.rotation_keyframes.size() == 1) 64 | result.rotation = channel.rotation_keyframes[idx_1].rotation; 65 | else 66 | { 67 | float delta = (float)(channel.rotation_keyframes[idx_2].time - channel.rotation_keyframes[idx_1].time); 68 | float factor = (m_local_time - (float)channel.rotation_keyframes[idx_1].time) / delta; 69 | 70 | result.rotation = interpolate_rotation(channel.rotation_keyframes[idx_1].rotation, channel.rotation_keyframes[idx_2].rotation, factor); 71 | } 72 | } 73 | } 74 | 75 | // Calculate interpolated scale 76 | { 77 | if (channel.scale_keyframes.size() == 0) 78 | result.scale = glm::vec3(1.0f); 79 | else 80 | { 81 | const uint32_t idx_1 = find_scale_key(channel.scale_keyframes, m_local_time); 82 | const uint32_t idx_2 = idx_1 + 1; 83 | 84 | if (channel.scale_keyframes.size() == 1) 85 | result.scale = channel.scale_keyframes[idx_1].scale; 86 | else 87 | { 88 | float delta = (float)(channel.scale_keyframes[idx_2].time - channel.scale_keyframes[idx_1].time); 89 | float factor = (m_local_time - (float)channel.scale_keyframes[idx_1].time) / delta; 90 | 91 | result.scale = interpolate_scale(channel.scale_keyframes[idx_1].scale, channel.scale_keyframes[idx_2].scale, factor); 92 | } 93 | } 94 | } 95 | 96 | m_pose.keyframes[i] = result; 97 | } 98 | 99 | return &m_pose; 100 | } 101 | 102 | void AnimSample::set_playback_rate(float rate) 103 | { 104 | if (rate < 0.0f || rate > 1.0f) 105 | return; 106 | 107 | m_playback_rate = rate; 108 | } 109 | 110 | float AnimSample::playback_rate() 111 | { 112 | return m_playback_rate; 113 | } 114 | 115 | glm::vec3 AnimSample::interpolate_translation(const glm::vec3& a, const glm::vec3& b, float t) 116 | { 117 | return glm::lerp(a, b, t); 118 | } 119 | 120 | glm::vec3 AnimSample::interpolate_scale(const glm::vec3& a, const glm::vec3& b, float t) 121 | { 122 | return glm::lerp(a, b, t); 123 | } 124 | 125 | glm::quat AnimSample::interpolate_rotation(const glm::quat& a, const glm::quat& b, float t) 126 | { 127 | return glm::slerp(a, b, t); 128 | } 129 | 130 | uint32_t AnimSample::find_translation_key(const std::vector& translations, double ticks) 131 | { 132 | uint32_t idx = 0; 133 | 134 | for (uint32_t i = 0; i < (translations.size() - 1); i++) 135 | { 136 | if (ticks < translations[i + 1].time) 137 | { 138 | idx = i; 139 | break; 140 | } 141 | } 142 | 143 | return idx; 144 | } 145 | 146 | uint32_t AnimSample::find_rotation_key(const std::vector& rotations, double ticks) 147 | { 148 | uint32_t idx = 0; 149 | 150 | for (uint32_t i = 0; i < (rotations.size() - 1); i++) 151 | { 152 | if (ticks < rotations[i + 1].time) 153 | { 154 | idx = i; 155 | break; 156 | } 157 | } 158 | 159 | return idx; 160 | } 161 | 162 | uint32_t AnimSample::find_scale_key(const std::vector& scale, double ticks) 163 | { 164 | uint32_t idx = 0; 165 | 166 | for (uint32_t i = 0; i < (scale.size() - 1); i++) 167 | { 168 | if (ticks < scale[i + 1].time) 169 | { 170 | idx = i; 171 | break; 172 | } 173 | } 174 | 175 | return idx; 176 | } -------------------------------------------------------------------------------- /src/anim_sample.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "skeletal_mesh.h" 4 | 5 | class AnimSample 6 | { 7 | public: 8 | AnimSample(Skeleton* skeleton, Animation* animation); 9 | ~AnimSample(); 10 | Pose* sample(double dt); 11 | void set_playback_rate(float rate); 12 | float playback_rate(); 13 | 14 | private: 15 | glm::vec3 interpolate_translation(const glm::vec3& a, const glm::vec3& b, float t); 16 | glm::vec3 interpolate_scale(const glm::vec3& a, const glm::vec3& b, float t); 17 | glm::quat interpolate_rotation(const glm::quat& a, const glm::quat& b, float t); 18 | uint32_t find_translation_key(const std::vector& translations, double ticks); 19 | uint32_t find_rotation_key(const std::vector& rotations, double ticks); 20 | uint32_t find_scale_key(const std::vector& scale, double ticks); 21 | 22 | private: 23 | double m_global_time; 24 | double m_local_time; 25 | float m_local_time_normalized; 26 | Skeleton* m_skeleton; 27 | Animation* m_animation; 28 | float m_playback_rate; 29 | Pose m_pose; 30 | }; 31 | -------------------------------------------------------------------------------- /src/animation.cpp: -------------------------------------------------------------------------------- 1 | #include "animation.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "skeleton.h" 7 | 8 | glm::vec3 translation_delta(const glm::vec3& reference, glm::vec3 additive) 9 | { 10 | return additive - reference; 11 | } 12 | 13 | glm::vec3 scale_delta(const glm::vec3& reference, glm::vec3 additive) 14 | { 15 | return additive / reference; 16 | } 17 | 18 | glm::quat rotation_delta(const glm::quat& reference, glm::quat additive) 19 | { 20 | return glm::conjugate(reference) * additive; 21 | } 22 | 23 | Animation* Animation::load(const std::string& name, Skeleton* skeleton, bool additive, Animation* additive_reference) 24 | { 25 | const aiScene* scene; 26 | Assimp::Importer importer; 27 | scene = importer.ReadFile(name, aiProcess_Triangulate | aiProcess_GenSmoothNormals | aiProcess_FlipUVs); 28 | 29 | if (!scene) 30 | { 31 | DW_LOG_ERROR("Failed to load animation file : " + name); 32 | return nullptr; 33 | } 34 | 35 | if (!scene->mAnimations) 36 | { 37 | DW_LOG_ERROR("No Animations available in file : " + name); 38 | return nullptr; 39 | } 40 | 41 | aiAnimation* animation = scene->mAnimations[0]; 42 | 43 | Animation* output_animation = new Animation(); 44 | 45 | output_animation->channels.resize(skeleton->num_bones()); 46 | output_animation->name = std::string(animation->mName.C_Str()); 47 | output_animation->duration = animation->mDuration / animation->mTicksPerSecond; 48 | output_animation->duration_in_ticks = animation->mDuration; 49 | output_animation->ticks_per_second = animation->mTicksPerSecond; 50 | output_animation->keyframe_count = animation->mChannels[0]->mNumPositionKeys; 51 | 52 | for (int i = 0; i < scene->mAnimations[0]->mNumChannels; i++) 53 | { 54 | aiNodeAnim* channel = scene->mAnimations[0]->mChannels[i]; 55 | std::string channel_name = trimmed_name(channel->mNodeName.C_Str()); 56 | 57 | int joint_index = skeleton->find_joint_index(channel_name); 58 | 59 | if (joint_index != -1) 60 | { 61 | output_animation->channels[joint_index].joint_name = channel_name; 62 | 63 | // Translation Keyframes 64 | output_animation->channels[joint_index].translation_keyframes.resize(channel->mNumPositionKeys); 65 | 66 | glm::vec3 reference_translation; 67 | 68 | if (channel->mNumPositionKeys > 0) 69 | { 70 | if (additive_reference) 71 | { 72 | AnimationChannel& additive_channel = additive_reference->channels[joint_index]; 73 | 74 | if (additive_channel.translation_keyframes.size() > 0) 75 | reference_translation = additive_channel.translation_keyframes[0].translation; 76 | } 77 | else 78 | reference_translation = glm::vec3(channel->mPositionKeys[0].mValue.x, channel->mPositionKeys[0].mValue.y, channel->mPositionKeys[0].mValue.z); 79 | } 80 | 81 | 82 | for (int j = 0; j < channel->mNumPositionKeys; j++) 83 | { 84 | output_animation->channels[joint_index].translation_keyframes[j].time = channel->mPositionKeys[j].mTime; 85 | 86 | output_animation->channels[joint_index].translation_keyframes[j].translation = glm::vec3(channel->mPositionKeys[j].mValue.x, 87 | channel->mPositionKeys[j].mValue.y, 88 | channel->mPositionKeys[j].mValue.z); 89 | 90 | if (additive) 91 | output_animation->channels[joint_index].translation_keyframes[j].translation = translation_delta(reference_translation, output_animation->channels[joint_index].translation_keyframes[j].translation); 92 | } 93 | 94 | // Rotation Keyframes 95 | output_animation->channels[joint_index].rotation_keyframes.resize(channel->mNumRotationKeys); 96 | 97 | glm::quat reference_rotation; 98 | 99 | if (channel->mNumRotationKeys > 0) 100 | { 101 | if (additive_reference) 102 | { 103 | AnimationChannel& additive_channel = additive_reference->channels[joint_index]; 104 | 105 | if (additive_channel.rotation_keyframes.size() > 0) 106 | reference_rotation = additive_channel.rotation_keyframes[0].rotation; 107 | } 108 | else 109 | { 110 | reference_rotation = glm::quat(channel->mRotationKeys[0].mValue.w, 111 | channel->mRotationKeys[0].mValue.x, 112 | channel->mRotationKeys[0].mValue.y, 113 | channel->mRotationKeys[0].mValue.z); 114 | } 115 | } 116 | 117 | for (int j = 0; j < channel->mNumRotationKeys; j++) 118 | { 119 | output_animation->channels[joint_index].rotation_keyframes[j].time = channel->mRotationKeys[j].mTime; 120 | 121 | output_animation->channels[joint_index].rotation_keyframes[j].rotation = glm::quat(channel->mRotationKeys[j].mValue.w, 122 | channel->mRotationKeys[j].mValue.x, 123 | channel->mRotationKeys[j].mValue.y, 124 | channel->mRotationKeys[j].mValue.z); 125 | 126 | if (additive) 127 | output_animation->channels[joint_index].rotation_keyframes[j].rotation = rotation_delta(reference_rotation, output_animation->channels[joint_index].rotation_keyframes[j].rotation); 128 | } 129 | 130 | // Scale Keyframes 131 | output_animation->channels[joint_index].scale_keyframes.resize(channel->mNumScalingKeys); 132 | 133 | glm::vec3 reference_scale; 134 | 135 | if (channel->mNumScalingKeys > 0) 136 | { 137 | if (additive_reference) 138 | { 139 | AnimationChannel& additive_channel = additive_reference->channels[joint_index]; 140 | 141 | if (additive_channel.scale_keyframes.size() > 0) 142 | reference_scale = additive_channel.scale_keyframes[0].scale; 143 | } 144 | else 145 | reference_scale = glm::vec3(channel->mScalingKeys[0].mValue.x, channel->mScalingKeys[0].mValue.y, channel->mScalingKeys[0].mValue.z); 146 | } 147 | 148 | for (int j = 0; j < channel->mNumScalingKeys; j++) 149 | { 150 | output_animation->channels[joint_index].scale_keyframes[j].time = channel->mScalingKeys[j].mTime; 151 | 152 | output_animation->channels[joint_index].scale_keyframes[j].scale = glm::vec3(channel->mScalingKeys[j].mValue.x, 153 | channel->mScalingKeys[j].mValue.y, 154 | channel->mScalingKeys[j].mValue.z); 155 | 156 | if (additive) 157 | output_animation->channels[joint_index].scale_keyframes[j].scale = scale_delta(reference_scale, output_animation->channels[joint_index].scale_keyframes[j].scale); 158 | } 159 | } 160 | } 161 | 162 | return output_animation; 163 | } 164 | 165 | std::string trimmed_name(const std::string& name) 166 | { 167 | size_t pos = name.find_first_of(':'); 168 | 169 | if (pos != std::string::npos) 170 | return name.substr(pos + 1); 171 | else 172 | return name; 173 | } -------------------------------------------------------------------------------- /src/animation.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define MAX_BONES 128 11 | 12 | // Contains the translation, rotation and scale of a single bone. 13 | struct Keyframe 14 | { 15 | glm::vec3 translation; 16 | glm::quat rotation; 17 | glm::vec3 scale; 18 | }; 19 | 20 | struct TranslationKey 21 | { 22 | double time; 23 | glm::vec3 translation; 24 | }; 25 | 26 | struct RotationKey 27 | { 28 | double time; 29 | glm::quat rotation; 30 | }; 31 | 32 | struct ScaleKey 33 | { 34 | double time; 35 | glm::vec3 scale; 36 | }; 37 | 38 | // Contains the list of keyframes belonging to a particular bone. 39 | struct AnimationChannel 40 | { 41 | std::string joint_name; 42 | std::vector translation_keyframes; 43 | std::vector rotation_keyframes; 44 | std::vector scale_keyframes; 45 | }; 46 | 47 | // A structure containing Keyframes for each bone at the current point in time of the current animation. 48 | struct Pose 49 | { 50 | uint32_t num_keyframes; 51 | Keyframe keyframes[MAX_BONES]; 52 | }; 53 | 54 | // A list of 4x4 matrices representing the finalized transforms of each bone. 55 | struct PoseTransforms 56 | { 57 | DW_ALIGNED(16) glm::mat4 transforms[MAX_BONES]; 58 | }; 59 | 60 | class Skeleton; 61 | 62 | // Contains an array of Channels. 63 | struct Animation 64 | { 65 | static Animation* load(const std::string& name, Skeleton* skeleton, bool additive = false, Animation* additive_reference = nullptr); 66 | 67 | std::string name; 68 | uint32_t keyframe_count; 69 | std::vector channels; 70 | double duration; 71 | double duration_in_ticks; 72 | double ticks_per_second; 73 | }; 74 | 75 | extern glm::vec3 translation_delta(const glm::vec3& reference, glm::vec3 additive); 76 | extern glm::vec3 scale_delta(const glm::vec3& reference, glm::vec3 additive); 77 | extern glm::quat rotation_delta(const glm::quat& reference, glm::quat additive); 78 | extern std::string trimmed_name(const std::string& name); -------------------------------------------------------------------------------- /src/blendspace_1d.cpp: -------------------------------------------------------------------------------- 1 | #include "blendspace_1d.h" 2 | 3 | Blendspace1D::Blendspace1D(Skeleton* _skeleton, std::vector _nodes) : m_nodes(_nodes) 4 | { 5 | m_blend = std::make_unique(_skeleton); 6 | 7 | if (m_nodes.size() > 0) 8 | { 9 | m_min = m_nodes[0]->value; 10 | m_max = m_nodes[std::max(0.0f, float(m_nodes.size() - 1.0f))]->value; 11 | m_value = m_min; 12 | } 13 | } 14 | 15 | Blendspace1D::~Blendspace1D() 16 | { 17 | for (auto& node : m_nodes) 18 | { 19 | if (node) 20 | delete node; 21 | } 22 | } 23 | 24 | void Blendspace1D::set_value(float value) 25 | { 26 | value = std::max(m_min, value); 27 | value = std::min(m_max, value); 28 | m_value = value; 29 | } 30 | 31 | float Blendspace1D::max() 32 | { 33 | return m_max; 34 | } 35 | 36 | float Blendspace1D::min() 37 | { 38 | return m_min; 39 | } 40 | 41 | float Blendspace1D::value() 42 | { 43 | return m_value; 44 | } 45 | 46 | Pose* Blendspace1D::evaluate(float dt) 47 | { 48 | for (uint32_t i = 0; i < m_nodes.size(); i++) 49 | { 50 | if (m_value == m_nodes[i]->value) 51 | return m_nodes[i]->sampler->sample(dt); 52 | else if (m_value < m_nodes[i]->value) 53 | { 54 | Node* low = m_nodes[i - 1]; 55 | Node* high = m_nodes[i]; 56 | 57 | float blend_factor = (m_value - low->value) / (high->value - low->value); 58 | 59 | Pose* low_pose = low->sampler->sample(dt); 60 | Pose* high_pose = high->sampler->sample(dt); 61 | 62 | return m_blend->blend(low_pose, high_pose, blend_factor); 63 | } 64 | } 65 | 66 | return nullptr; 67 | } -------------------------------------------------------------------------------- /src/blendspace_1d.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "skeletal_mesh.h" 4 | #include "anim_sample.h" 5 | #include "anim_blend.h" 6 | #include 7 | #include 8 | 9 | class Blendspace1D 10 | { 11 | public: 12 | struct Node 13 | { 14 | float value; 15 | Animation* anim; 16 | std::unique_ptr sampler; 17 | 18 | Node(Skeleton* _skeleton, Animation* _anim, float _value) 19 | { 20 | anim = _anim; 21 | sampler = std::make_unique(_skeleton, _anim); 22 | value = _value; 23 | } 24 | }; 25 | 26 | public: 27 | Blendspace1D(Skeleton* _skeleton, std::vector _nodes); 28 | ~Blendspace1D(); 29 | void set_value(float value); 30 | float max(); 31 | float min(); 32 | float value(); 33 | Pose* evaluate(float dt); 34 | 35 | private: 36 | float m_value = 0.0f; 37 | float m_min = 0.0f; 38 | float m_max = 0.0f; 39 | std::vector m_nodes; 40 | std::unique_ptr m_blend; 41 | }; -------------------------------------------------------------------------------- /src/blendspace_2d.cpp: -------------------------------------------------------------------------------- 1 | #include "blendspace_2d.h" 2 | #include 3 | 4 | Blendspace2D::Blendspace2D(Skeleton* skeleton, const std::vector& rows) : m_rows(rows) 5 | { 6 | m_blend_1 = std::make_unique(skeleton); 7 | m_blend_2 = std::make_unique(skeleton); 8 | m_blend_3 = std::make_unique(skeleton); 9 | 10 | assert(m_rows.size() > 0); 11 | assert(m_rows[0].nodes.size() > 0); 12 | 13 | m_y_max = m_rows[0].value; 14 | m_y_min = m_rows[0].value; 15 | 16 | m_x_max = m_rows[0].nodes[0]->value; 17 | m_x_min = m_rows[0].nodes[0]->value; 18 | 19 | for (const auto& row : m_rows) 20 | { 21 | m_y_max = std::max(m_y_max, row.value); 22 | m_y_min = std::min(m_y_min, row.value); 23 | 24 | assert(row.nodes.size() > 0); 25 | 26 | for (const auto& node : row.nodes) 27 | { 28 | assert(node != nullptr); 29 | 30 | m_x_max = std::max(m_x_max, node->value); 31 | m_x_min = std::min(m_x_min, node->value); 32 | } 33 | } 34 | 35 | m_x_value = m_x_min; 36 | m_y_value = m_y_min; 37 | } 38 | 39 | Blendspace2D::~Blendspace2D() 40 | { 41 | for (const auto& row : m_rows) 42 | { 43 | for (const auto& node : row.nodes) 44 | { 45 | if (node) 46 | delete node; 47 | } 48 | } 49 | } 50 | 51 | void Blendspace2D::set_x_value(float value) 52 | { 53 | value = std::max(m_x_min, value); 54 | value = std::min(m_x_max, value); 55 | m_x_value = value; 56 | } 57 | 58 | float Blendspace2D::max_x() 59 | { 60 | return m_x_max; 61 | } 62 | 63 | float Blendspace2D::min_x() 64 | { 65 | return m_x_min; 66 | } 67 | 68 | float Blendspace2D::value_x() 69 | { 70 | return m_x_value; 71 | } 72 | 73 | void Blendspace2D::set_y_value(float value) 74 | { 75 | value = std::max(m_y_min, value); 76 | value = std::min(m_y_max, value); 77 | m_y_value = value; 78 | } 79 | 80 | float Blendspace2D::max_y() 81 | { 82 | return m_y_max; 83 | } 84 | 85 | float Blendspace2D::min_y() 86 | { 87 | return m_y_min; 88 | } 89 | 90 | float Blendspace2D::value_y() 91 | { 92 | return m_y_value; 93 | } 94 | 95 | Pose* Blendspace2D::evaluate(float dt) 96 | { 97 | for (uint32_t i = 0; i < m_rows.size(); i++) 98 | { 99 | if (m_y_value == m_rows[i].value) 100 | return blended_pose_from_row(m_rows[i], m_blend_1.get(), dt); 101 | else if (m_y_value < m_rows[i].value) 102 | { 103 | const Row& low = m_rows[i - 1]; 104 | const Row& high = m_rows[i]; 105 | 106 | float blend_factor = (m_y_value - low.value) / (high.value - low.value); 107 | 108 | Pose* low_pose = blended_pose_from_row(low, m_blend_1.get(), dt); 109 | Pose* high_pose = blended_pose_from_row(high, m_blend_2.get(), dt); 110 | 111 | return m_blend_3->blend(low_pose, high_pose, blend_factor); 112 | } 113 | } 114 | 115 | return nullptr; 116 | } 117 | 118 | Pose* Blendspace2D::blended_pose_from_row(const Row& row, AnimBlend* blend, float dt) 119 | { 120 | if (row.nodes.size() == 1) 121 | return row.nodes[0]->sampler->sample(dt); 122 | 123 | for (uint32_t j = 0; j < row.nodes.size(); j++) 124 | { 125 | if (m_x_value == row.nodes[j]->value) 126 | return row.nodes[j]->sampler->sample(dt); 127 | else if (m_x_value < row.nodes[j]->value) 128 | { 129 | Node* low = row.nodes[j - 1]; 130 | Node* high = row.nodes[j]; 131 | 132 | return blended_pose_from_nodes(low, high, blend, dt); 133 | } 134 | } 135 | 136 | return nullptr; 137 | } 138 | 139 | Pose* Blendspace2D::blended_pose_from_nodes(Node* low, Node* high, AnimBlend* blend, float dt) 140 | { 141 | float blend_factor = (m_x_value - low->value) / (high->value - low->value); 142 | 143 | Pose* low_pose = low->sampler->sample(dt); 144 | Pose* high_pose = high->sampler->sample(dt); 145 | 146 | return blend->blend(low_pose, high_pose, blend_factor); 147 | } -------------------------------------------------------------------------------- /src/blendspace_2d.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "skeletal_mesh.h" 4 | #include "anim_sample.h" 5 | #include "anim_blend.h" 6 | 7 | class Blendspace2D 8 | { 9 | public: 10 | struct Node 11 | { 12 | float value; 13 | Animation* anim; 14 | std::unique_ptr sampler; 15 | 16 | Node(Skeleton* _skeleton, Animation* _anim, float _value) 17 | { 18 | anim = _anim; 19 | sampler = std::make_unique(_skeleton, _anim); 20 | value = _value; 21 | } 22 | }; 23 | 24 | struct Row 25 | { 26 | float value; 27 | std::vector nodes; 28 | }; 29 | 30 | public: 31 | Blendspace2D(Skeleton* skeleton, const std::vector& rows); 32 | ~Blendspace2D(); 33 | void set_x_value(float value); 34 | float max_x(); 35 | float min_x(); 36 | float value_x(); 37 | void set_y_value(float value); 38 | float max_y(); 39 | float min_y(); 40 | float value_y(); 41 | Pose* evaluate(float dt); 42 | 43 | private: 44 | Pose* blended_pose_from_row(const Row& row, AnimBlend* blend, float dt); 45 | Pose* blended_pose_from_nodes(Node* low, Node* high, AnimBlend* blend, float dt); 46 | 47 | private: 48 | float m_x_max; 49 | float m_x_min; 50 | float m_y_max; 51 | float m_y_min; 52 | float m_x_value; 53 | float m_y_value; 54 | std::vector m_rows; 55 | std::unique_ptr m_blend_1; 56 | std::unique_ptr m_blend_2; 57 | std::unique_ptr m_blend_3; 58 | }; -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #define NOMINMAX 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "skeletal_mesh.h" 10 | #include "anim_sample.h" 11 | #include "anim_local_transform.h" 12 | #include "anim_global_transform.h" 13 | #include "anim_offset.h" 14 | #include "anim_blend.h" 15 | #include "blendspace_1d.h" 16 | #include "blendspace_2d.h" 17 | #include "anim_fabrik_ik.h" 18 | 19 | // Uniform buffer data structure. 20 | struct ObjectUniforms 21 | { 22 | DW_ALIGNED(16) glm::mat4 model; 23 | }; 24 | 25 | struct GlobalUniforms 26 | { 27 | DW_ALIGNED(16) glm::mat4 view; 28 | DW_ALIGNED(16) glm::mat4 projection; 29 | }; 30 | 31 | struct BoneVertex 32 | { 33 | glm::vec3 position; 34 | glm::vec3 normal; 35 | }; 36 | 37 | #define CAMERA_FAR_PLANE 10000.0f 38 | 39 | class AnimationStateMachine : public dw::Application 40 | { 41 | protected: 42 | 43 | // ----------------------------------------------------------------------------------------------------------------------------------- 44 | 45 | bool init(int argc, const char* argv[]) override 46 | { 47 | // Create GPU resources. 48 | if (!create_shaders()) 49 | return false; 50 | 51 | if (!create_bone_mesh()) 52 | return false; 53 | 54 | if (!create_uniform_buffer()) 55 | return false; 56 | 57 | // Load mesh. 58 | if (!load_mesh()) 59 | return false; 60 | 61 | DW_LOG_INFO("Loaded Mesh!"); 62 | 63 | // Load animations. 64 | if (!load_animations()) 65 | return false; 66 | 67 | DW_LOG_INFO("Loaded Animations!"); 68 | 69 | // Create camera. 70 | create_camera(); 71 | 72 | for (int i = 0; i < MAX_BONES; i++) 73 | m_pose_transforms.transforms[i] = glm::mat4(1.0f); 74 | 75 | m_index_stack.reserve(256); 76 | m_joint_pos.reserve(256); 77 | 78 | DW_LOG_INFO("Loading Done!"); 79 | 80 | return true; 81 | } 82 | 83 | // ----------------------------------------------------------------------------------------------------------------------------------- 84 | 85 | void update(double delta) override 86 | { 87 | // Debug GUI 88 | gui(); 89 | 90 | // Update camera. 91 | update_camera(); 92 | 93 | // Update global uniforms. 94 | Joint* joints = m_skeletal_mesh->skeleton()->joints(); 95 | 96 | for (int i = 0; i < m_skeletal_mesh->skeleton()->num_bones(); i++) 97 | m_pose_transforms.transforms[i] = glm::inverse(joints[i].offset_transform); 98 | 99 | update_global_uniforms(m_global_uniforms); 100 | update_object_uniforms(m_character_transforms); 101 | 102 | // Bind and set viewport. 103 | glBindFramebuffer(GL_FRAMEBUFFER, 0); 104 | glViewport(0, 0, m_width, m_height); 105 | 106 | // Clear default framebuffer. 107 | glClearColor(0.5f, 0.5f, 0.5f, 1.0f); 108 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 109 | 110 | // Bind states. 111 | glEnable(GL_DEPTH_TEST); 112 | glDisable(GL_CULL_FACE); 113 | 114 | // Update Skeleton 115 | update_animations(); 116 | 117 | // Render Mesh. 118 | if (m_visualize_mesh) 119 | render_skeletal_meshes(); 120 | 121 | // Render Joints. 122 | if (m_visualize_joints) 123 | visualize_skeleton(m_skeletal_mesh->skeleton()); 124 | 125 | // Render Bones. 126 | if (m_visualize_bones) 127 | visualize_bones(m_skeletal_mesh->skeleton()); 128 | 129 | m_debug_draw.sphere(0.1f, m_ik_pos, glm::vec3(1.0f, 1.0f, 0.0f)); 130 | 131 | // Render debug draw. 132 | m_debug_draw.render(nullptr, m_width, m_height, m_debug_mode ? m_debug_camera->m_view_projection : m_main_camera->m_view_projection); 133 | } 134 | 135 | // ----------------------------------------------------------------------------------------------------------------------------------- 136 | 137 | void shutdown() override 138 | { 139 | 140 | } 141 | 142 | // ----------------------------------------------------------------------------------------------------------------------------------- 143 | 144 | void window_resized(int width, int height) override 145 | { 146 | // Override window resized method to update camera projection. 147 | m_main_camera->update_projection(60.0f, 0.1f, CAMERA_FAR_PLANE, float(m_width) / float(m_height)); 148 | m_debug_camera->update_projection(60.0f, 0.1f, CAMERA_FAR_PLANE * 2.0f, float(m_width) / float(m_height)); 149 | } 150 | 151 | // ----------------------------------------------------------------------------------------------------------------------------------- 152 | 153 | void key_pressed(int code) override 154 | { 155 | // Handle forward movement. 156 | if(code == GLFW_KEY_W) 157 | m_heading_speed = m_camera_speed; 158 | else if(code == GLFW_KEY_S) 159 | m_heading_speed = -m_camera_speed; 160 | 161 | // Handle sideways movement. 162 | if(code == GLFW_KEY_A) 163 | m_sideways_speed = -m_camera_speed; 164 | else if(code == GLFW_KEY_D) 165 | m_sideways_speed = m_camera_speed; 166 | } 167 | 168 | // ----------------------------------------------------------------------------------------------------------------------------------- 169 | 170 | void key_released(int code) override 171 | { 172 | // Handle forward movement. 173 | if(code == GLFW_KEY_W || code == GLFW_KEY_S) 174 | m_heading_speed = 0.0f; 175 | 176 | // Handle sideways movement. 177 | if(code == GLFW_KEY_A || code == GLFW_KEY_D) 178 | m_sideways_speed = 0.0f; 179 | } 180 | 181 | // ----------------------------------------------------------------------------------------------------------------------------------- 182 | 183 | void mouse_pressed(int code) override 184 | { 185 | // Enable mouse look. 186 | if (code == GLFW_MOUSE_BUTTON_RIGHT) 187 | m_mouse_look = true; 188 | } 189 | 190 | // ----------------------------------------------------------------------------------------------------------------------------------- 191 | 192 | void mouse_released(int code) override 193 | { 194 | // Disable mouse look. 195 | if (code == GLFW_MOUSE_BUTTON_RIGHT) 196 | m_mouse_look = false; 197 | } 198 | 199 | // ----------------------------------------------------------------------------------------------------------------------------------- 200 | 201 | protected: 202 | 203 | // ----------------------------------------------------------------------------------------------------------------------------------- 204 | 205 | dw::AppSettings intial_app_settings() override 206 | { 207 | dw::AppSettings settings; 208 | 209 | settings.resizable = true; 210 | settings.maximized = false; 211 | settings.refresh_rate = 60; 212 | settings.major_ver = 4; 213 | settings.width = 1920; 214 | settings.height = 1080; 215 | settings.title = "Animation Systems - Dihara Wijetunga"; 216 | 217 | return settings; 218 | } 219 | 220 | // ----------------------------------------------------------------------------------------------------------------------------------- 221 | 222 | private: 223 | 224 | // ----------------------------------------------------------------------------------------------------------------------------------- 225 | 226 | bool create_bone_mesh() 227 | { 228 | const float kInter = 0.2f; 229 | const float kScale = 1.0f; 230 | 231 | const glm::vec3 pos[6] = 232 | { 233 | glm::vec3(1.f, 0.f, 0.f) * kScale, glm::vec3(kInter, .1f, .1f) * kScale, 234 | glm::vec3(kInter, .1f, -.1f) * kScale, glm::vec3(kInter, -.1f, -.1f) * kScale, 235 | glm::vec3(kInter, -.1f, .1f) * kScale, glm::vec3(0.f, 0.f, 0.f) * kScale 236 | }; 237 | 238 | const glm::vec3 normals[8] = 239 | { 240 | glm::normalize(glm::cross(pos[2] - pos[1], pos[2] - pos[0])), 241 | glm::normalize(glm::cross(pos[1] - pos[2], pos[1] - pos[5])), 242 | glm::normalize(glm::cross(pos[3] - pos[2], pos[3] - pos[0])), 243 | glm::normalize(glm::cross(pos[2] - pos[3], pos[2] - pos[5])), 244 | glm::normalize(glm::cross(pos[4] - pos[3], pos[4] - pos[0])), 245 | glm::normalize(glm::cross(pos[3] - pos[4], pos[3] - pos[5])), 246 | glm::normalize(glm::cross(pos[1] - pos[4], pos[1] - pos[0])), 247 | glm::normalize(glm::cross(pos[4] - pos[1], pos[4] - pos[5])) 248 | }; 249 | 250 | const BoneVertex bones[24] = 251 | { 252 | {pos[0], normals[0]}, {pos[2], normals[0]}, 253 | {pos[1], normals[0]}, {pos[5], normals[1]}, 254 | {pos[1], normals[1]}, {pos[2], normals[1]}, 255 | {pos[0], normals[2]}, {pos[3], normals[2]}, 256 | {pos[2], normals[2]}, {pos[5], normals[3]}, 257 | {pos[2], normals[3]}, {pos[3], normals[3]}, 258 | {pos[0], normals[4]}, {pos[4], normals[4]}, 259 | {pos[3], normals[4]}, {pos[5], normals[5]}, 260 | {pos[3], normals[5]}, {pos[4], normals[5]}, 261 | {pos[0], normals[6]}, {pos[1], normals[6]}, 262 | {pos[4], normals[6]}, {pos[5], normals[7]}, 263 | {pos[4], normals[7]}, {pos[1], normals[7]} 264 | }; 265 | 266 | m_bone_vbo = std::make_unique(GL_STATIC_DRAW, sizeof(BoneVertex) * 24, (BoneVertex*)&bones[0]); 267 | 268 | if (!m_bone_vbo) 269 | { 270 | DW_LOG_ERROR("Failed to create Vertex Buffer"); 271 | return false; 272 | } 273 | 274 | dw::gl::VertexAttrib attribs[] = 275 | { 276 | { 3, GL_FLOAT, false, 0 }, 277 | { 3, GL_FLOAT, false, offsetof(BoneVertex, normal) } 278 | }; 279 | 280 | // Create vertex array. 281 | m_bone_vao = std::make_unique(m_bone_vbo.get(), nullptr, sizeof(BoneVertex), 2, attribs); 282 | 283 | if (!m_bone_vao) 284 | { 285 | DW_LOG_ERROR("Failed to create Vertex Array"); 286 | return false; 287 | } 288 | 289 | return true; 290 | } 291 | 292 | // ----------------------------------------------------------------------------------------------------------------------------------- 293 | 294 | bool create_shaders() 295 | { 296 | // Create general shaders 297 | m_vs = std::unique_ptr(dw::gl::Shader::create_from_file(GL_VERTEX_SHADER, "shader/vs.glsl")); 298 | m_fs = std::unique_ptr(dw::gl::Shader::create_from_file(GL_FRAGMENT_SHADER, "shader/fs.glsl")); 299 | 300 | if (!m_vs || !m_fs) 301 | { 302 | DW_LOG_FATAL("Failed to create Shaders"); 303 | return false; 304 | } 305 | 306 | // Create general shader program 307 | dw::gl::Shader* shaders[] = { m_vs.get(), m_fs.get() }; 308 | m_program = std::make_unique(2, shaders); 309 | 310 | if (!m_program) 311 | { 312 | DW_LOG_FATAL("Failed to create Shader Program"); 313 | return false; 314 | } 315 | 316 | m_program->uniform_block_binding("u_GlobalUBO", 0); 317 | m_program->uniform_block_binding("u_ObjectUBO", 1); 318 | 319 | // Create Animation shaders 320 | m_anim_vs = std::unique_ptr(dw::gl::Shader::create_from_file(GL_VERTEX_SHADER, "shader/skinning_vs.glsl")); 321 | m_anim_fs = std::unique_ptr(dw::gl::Shader::create_from_file(GL_FRAGMENT_SHADER, "shader/skinning_fs.glsl")); 322 | 323 | if (!m_anim_vs || !m_anim_fs) 324 | { 325 | DW_LOG_FATAL("Failed to create Animation Shaders"); 326 | return false; 327 | } 328 | 329 | // Create Animation shader program 330 | dw::gl::Shader* anim_shaders[] = { m_anim_vs.get(), m_anim_fs.get() }; 331 | m_anim_program = std::make_unique(2, anim_shaders); 332 | 333 | if (!m_anim_program) 334 | { 335 | DW_LOG_FATAL("Failed to create Animation Shader Program"); 336 | return false; 337 | } 338 | 339 | m_anim_program->uniform_block_binding("u_GlobalUBO", 0); 340 | m_anim_program->uniform_block_binding("u_ObjectUBO", 1); 341 | m_anim_program->uniform_block_binding("u_BoneUBO", 2); 342 | 343 | // Create Bone shaders 344 | m_bone_vs = std::unique_ptr(dw::gl::Shader::create_from_file(GL_VERTEX_SHADER, "shader/bone_vs.glsl")); 345 | m_bone_fs = std::unique_ptr(dw::gl::Shader::create_from_file(GL_FRAGMENT_SHADER, "shader/bone_fs.glsl")); 346 | 347 | if (!m_bone_vs || !m_bone_fs) 348 | { 349 | DW_LOG_FATAL("Failed to create Bone Shaders"); 350 | return false; 351 | } 352 | 353 | // Create Bone shader program 354 | dw::gl::Shader* bone_shaders[] = { m_bone_vs.get(), m_bone_fs.get() }; 355 | m_bone_program = std::make_unique(2, bone_shaders); 356 | 357 | if (!m_bone_program) 358 | { 359 | DW_LOG_FATAL("Failed to create Bone Shader Program"); 360 | return false; 361 | } 362 | 363 | m_bone_program->uniform_block_binding("u_GlobalUBO", 0); 364 | m_bone_program->uniform_block_binding("u_ObjectUBO", 1); 365 | m_bone_program->uniform_block_binding("u_BoneUBO", 2); 366 | 367 | return true; 368 | } 369 | 370 | // ----------------------------------------------------------------------------------------------------------------------------------- 371 | 372 | bool create_uniform_buffer() 373 | { 374 | // Create uniform buffer for object matrix data 375 | m_object_ubo = std::make_unique(GL_DYNAMIC_DRAW, sizeof(ObjectUniforms)); 376 | 377 | // Create uniform buffer for global data 378 | m_global_ubo = std::make_unique(GL_DYNAMIC_DRAW, sizeof(GlobalUniforms)); 379 | 380 | // Create uniform buffer for CSM data 381 | m_bone_ubo = std::make_unique(GL_DYNAMIC_DRAW, sizeof(PoseTransforms)); 382 | 383 | return true; 384 | } 385 | 386 | // ----------------------------------------------------------------------------------------------------------------------------------- 387 | 388 | bool load_mesh() 389 | { 390 | m_skeletal_mesh = std::unique_ptr(SkeletalMesh::load("mesh/Rifle/Rifle_Walk_Fwd.fbx")); 391 | 392 | if (!m_skeletal_mesh) 393 | { 394 | DW_LOG_FATAL("Failed to load mesh!"); 395 | return false; 396 | } 397 | 398 | return true; 399 | } 400 | 401 | // ----------------------------------------------------------------------------------------------------------------------------------- 402 | 403 | bool load_animations() 404 | { 405 | m_walk_animation = std::unique_ptr(Animation::load("mesh/Rifle/Rifle_Walk_Fwd.fbx", m_skeletal_mesh->skeleton())); 406 | 407 | if (!m_walk_animation) 408 | { 409 | DW_LOG_FATAL("Failed to load animation!"); 410 | return false; 411 | } 412 | 413 | m_jog_animation = std::unique_ptr(Animation::load("mesh/Rifle/Rifle_Run_Fwd.fbx", m_skeletal_mesh->skeleton())); 414 | 415 | if (!m_jog_animation) 416 | { 417 | DW_LOG_FATAL("Failed to load animation!"); 418 | return false; 419 | } 420 | 421 | m_run_animation = std::unique_ptr(Animation::load("mesh/Rifle/Rifle_Sprint_Fwd.fbx", m_skeletal_mesh->skeleton())); 422 | 423 | if (!m_run_animation) 424 | { 425 | DW_LOG_FATAL("Failed to load animation!"); 426 | return false; 427 | } 428 | 429 | m_additive_base_animation = std::unique_ptr(Animation::load("mesh/Rifle/AimOffsets/Rifle_Aim_Fwd.fbx", m_skeletal_mesh->skeleton())); 430 | 431 | if (!m_additive_base_animation) 432 | { 433 | DW_LOG_FATAL("Failed to load animation!"); 434 | return false; 435 | } 436 | 437 | m_aim_lu_animation = std::unique_ptr(Animation::load("mesh/Rifle/AimOffsets/Rifle_Aim_Left_Up.fbx", m_skeletal_mesh->skeleton(), true, m_additive_base_animation.get())); 438 | 439 | if (!m_aim_lu_animation) 440 | { 441 | DW_LOG_FATAL("Failed to load animation!"); 442 | return false; 443 | } 444 | 445 | m_aim_cu_animation = std::unique_ptr(Animation::load("mesh/Rifle/AimOffsets/Rifle_Aim_Up.fbx", m_skeletal_mesh->skeleton(), true, m_additive_base_animation.get())); 446 | 447 | if (!m_aim_cu_animation) 448 | { 449 | DW_LOG_FATAL("Failed to load animation!"); 450 | return false; 451 | } 452 | 453 | m_aim_ru_animation = std::unique_ptr(Animation::load("mesh/Rifle/AimOffsets/Rifle_Aim_Right_Up.fbx", m_skeletal_mesh->skeleton(), true, m_additive_base_animation.get())); 454 | 455 | if (!m_aim_ru_animation) 456 | { 457 | DW_LOG_FATAL("Failed to load animation!"); 458 | return false; 459 | } 460 | 461 | m_aim_l_animation = std::unique_ptr(Animation::load("mesh/Rifle/AimOffsets/Rifle_Aim_Left.fbx", m_skeletal_mesh->skeleton(), true, m_additive_base_animation.get())); 462 | 463 | if (!m_aim_l_animation) 464 | { 465 | DW_LOG_FATAL("Failed to load animation!"); 466 | return false; 467 | } 468 | 469 | m_aim_c_animation = std::unique_ptr(Animation::load("mesh/Rifle/AimOffsets/Rifle_Aim_Fwd.fbx", m_skeletal_mesh->skeleton(), true, m_additive_base_animation.get())); 470 | 471 | if (!m_aim_c_animation) 472 | { 473 | DW_LOG_FATAL("Failed to load animation!"); 474 | return false; 475 | } 476 | 477 | m_aim_r_animation = std::unique_ptr(Animation::load("mesh/Rifle/AimOffsets/Rifle_Aim_Right.fbx", m_skeletal_mesh->skeleton(), true, m_additive_base_animation.get())); 478 | 479 | if (!m_aim_r_animation) 480 | { 481 | DW_LOG_FATAL("Failed to load animation!"); 482 | return false; 483 | } 484 | 485 | m_aim_ld_animation = std::unique_ptr(Animation::load("mesh/Rifle/AimOffsets/Rifle_Aim_Left_Down.fbx", m_skeletal_mesh->skeleton(), true, m_additive_base_animation.get())); 486 | 487 | if (!m_aim_ld_animation) 488 | { 489 | DW_LOG_FATAL("Failed to load animation!"); 490 | return false; 491 | } 492 | 493 | m_aim_cd_animation = std::unique_ptr(Animation::load("mesh/Rifle/AimOffsets/Rifle_Aim_Down.fbx", m_skeletal_mesh->skeleton(), true, m_additive_base_animation.get())); 494 | 495 | if (!m_aim_cd_animation) 496 | { 497 | DW_LOG_FATAL("Failed to load animation!"); 498 | return false; 499 | } 500 | 501 | m_aim_rd_animation = std::unique_ptr(Animation::load("mesh/Rifle/AimOffsets/Rifle_Aim_Right_Down.fbx", m_skeletal_mesh->skeleton(), true, m_additive_base_animation.get())); 502 | 503 | if (!m_aim_rd_animation) 504 | { 505 | DW_LOG_FATAL("Failed to load animation!"); 506 | return false; 507 | } 508 | 509 | m_walk_sampler = std::make_unique(m_skeletal_mesh->skeleton(), m_walk_animation.get()); 510 | m_run_sampler = std::make_unique(m_skeletal_mesh->skeleton(), m_run_animation.get()); 511 | 512 | m_local_transform = std::make_unique(m_skeletal_mesh->skeleton()); 513 | m_global_transform = std::make_unique(m_skeletal_mesh->skeleton()); 514 | m_fabrik_ik = std::make_unique(m_skeletal_mesh->skeleton()); 515 | m_offset = std::make_unique(m_skeletal_mesh->skeleton()); 516 | m_blend = std::make_unique(m_skeletal_mesh->skeleton()); 517 | 518 | std::vector nodes = { 519 | new Blendspace1D::Node(m_skeletal_mesh->skeleton(), m_walk_animation.get(), 0.0f), 520 | new Blendspace1D::Node(m_skeletal_mesh->skeleton(), m_jog_animation.get(), 50.0f), 521 | new Blendspace1D::Node(m_skeletal_mesh->skeleton(), m_run_animation.get(), 100.0f) 522 | }; 523 | 524 | m_blendspace_1d = std::make_unique(m_skeletal_mesh->skeleton(), nodes); 525 | 526 | std::vector rows = { 527 | { -90.0f,{ 528 | new Blendspace2D::Node(m_skeletal_mesh->skeleton(), m_aim_rd_animation.get(), -90.0f), 529 | new Blendspace2D::Node(m_skeletal_mesh->skeleton(), m_aim_cd_animation.get(), 0.0f), 530 | new Blendspace2D::Node(m_skeletal_mesh->skeleton(), m_aim_ld_animation.get(), 90.0f) 531 | } 532 | }, 533 | { 0.0f,{ 534 | new Blendspace2D::Node(m_skeletal_mesh->skeleton(), m_aim_r_animation.get(), -90.0f), 535 | new Blendspace2D::Node(m_skeletal_mesh->skeleton(), m_aim_c_animation.get(), 0.0f), 536 | new Blendspace2D::Node(m_skeletal_mesh->skeleton(), m_aim_l_animation.get(), 90.0f) 537 | } 538 | }, 539 | { 90.0f,{ 540 | new Blendspace2D::Node(m_skeletal_mesh->skeleton(), m_aim_ru_animation.get(), -90.0f), 541 | new Blendspace2D::Node(m_skeletal_mesh->skeleton(), m_aim_cu_animation.get(), 0.0f), 542 | new Blendspace2D::Node(m_skeletal_mesh->skeleton(), m_aim_lu_animation.get(), 90.0f) 543 | } 544 | }, 545 | }; 546 | 547 | m_blendspace_2d = std::make_unique(m_skeletal_mesh->skeleton(), rows); 548 | 549 | return true; 550 | } 551 | 552 | // ----------------------------------------------------------------------------------------------------------------------------------- 553 | 554 | void create_camera() 555 | { 556 | m_main_camera = std::make_unique(60.0f, 0.1f, CAMERA_FAR_PLANE, float(m_width) / float(m_height), glm::vec3(0.0f, 5.0f, 20.0f), glm::vec3(0.0f, 0.0, -1.0f)); 557 | m_debug_camera = std::make_unique(60.0f, 0.1f, CAMERA_FAR_PLANE * 2.0f, float(m_width) / float(m_height), glm::vec3(0.0f, 5.0f, 20.0f), glm::vec3(0.0f, 0.0, -1.0f)); 558 | } 559 | 560 | // ----------------------------------------------------------------------------------------------------------------------------------- 561 | 562 | void render_skeleton(Skeleton* skeleton) 563 | { 564 | glClear(GL_DEPTH_BUFFER_BIT); 565 | 566 | Joint* joints = skeleton->joints(); 567 | 568 | m_bone_program->use(); 569 | 570 | // Bind uniform buffers. 571 | m_global_ubo->bind_base(0); 572 | m_object_ubo->bind_base(1); 573 | m_bone_ubo->bind_base(2); 574 | 575 | m_bone_vao->bind(); 576 | 577 | glDrawArraysInstanced(GL_TRIANGLES, 0, 24, skeleton->num_bones()); 578 | } 579 | 580 | // ----------------------------------------------------------------------------------------------------------------------------------- 581 | 582 | void render_mesh(SkeletalMesh* mesh, const ObjectUniforms& transforms, const PoseTransforms& bones) 583 | { 584 | // Bind uniform buffers. 585 | m_object_ubo->bind_base(1); 586 | m_bone_ubo->bind_base(2); 587 | 588 | // Bind vertex array. 589 | mesh->bind_vao(); 590 | 591 | for (uint32_t i = 0; i < mesh->num_sub_meshes(); i++) 592 | { 593 | SubMesh& submesh = mesh->sub_mesh(i); 594 | 595 | #if defined(__EMSCRIPTEN__) 596 | 597 | #else 598 | // Issue draw call. 599 | glDrawElementsBaseVertex(GL_TRIANGLES, submesh.num_indices, GL_UNSIGNED_INT, (void*)(sizeof(unsigned int) * submesh.base_index), submesh.base_vertex); 600 | #endif 601 | } 602 | } 603 | 604 | // ----------------------------------------------------------------------------------------------------------------------------------- 605 | 606 | void render_skeletal_meshes() 607 | { 608 | // Bind shader program. 609 | m_anim_program->use(); 610 | 611 | // Bind uniform buffers. 612 | m_global_ubo->bind_base(0); 613 | 614 | // Draw meshes. 615 | render_mesh(m_skeletal_mesh.get(), m_character_transforms, m_pose_transforms); 616 | } 617 | 618 | // ----------------------------------------------------------------------------------------------------------------------------------- 619 | 620 | void update_object_uniforms(const ObjectUniforms& transform) 621 | { 622 | void* ptr = m_object_ubo->map(GL_WRITE_ONLY); 623 | memcpy(ptr, &transform, sizeof(ObjectUniforms)); 624 | m_object_ubo->unmap(); 625 | } 626 | 627 | // ----------------------------------------------------------------------------------------------------------------------------------- 628 | 629 | void update_global_uniforms(const GlobalUniforms& global) 630 | { 631 | void* ptr = m_global_ubo->map(GL_WRITE_ONLY); 632 | memcpy(ptr, &global, sizeof(GlobalUniforms)); 633 | m_global_ubo->unmap(); 634 | } 635 | 636 | // ----------------------------------------------------------------------------------------------------------------------------------- 637 | 638 | void update_bone_uniforms(PoseTransforms* bones) 639 | { 640 | void* ptr = m_bone_ubo->map(GL_WRITE_ONLY); 641 | memcpy(ptr, bones, sizeof(PoseTransforms)); 642 | m_bone_ubo->unmap(); 643 | } 644 | 645 | // ----------------------------------------------------------------------------------------------------------------------------------- 646 | 647 | void update_transforms(dw::Camera* camera) 648 | { 649 | // Update camera matrices. 650 | m_global_uniforms.view = camera->m_view; 651 | m_global_uniforms.projection = camera->m_projection; 652 | 653 | // Update plane transforms. 654 | m_plane_transforms.model = glm::mat4(1.0f); 655 | 656 | // Update character transforms. 657 | m_character_transforms.model = glm::rotate(m_plane_transforms.model, glm::radians(-90.0f), glm::vec3(1.0f, 0.0f, 0.0f)); 658 | m_character_transforms.model = glm::scale(m_character_transforms.model, glm::vec3(0.1f)); 659 | } 660 | 661 | // ----------------------------------------------------------------------------------------------------------------------------------- 662 | 663 | void update_camera() 664 | { 665 | dw::Camera* current = m_main_camera.get(); 666 | 667 | if (m_debug_mode) 668 | current = m_debug_camera.get(); 669 | 670 | float forward_delta = m_heading_speed * m_delta; 671 | float right_delta = m_sideways_speed * m_delta; 672 | 673 | current->set_translation_delta(current->m_forward, forward_delta); 674 | current->set_translation_delta(current->m_right, right_delta); 675 | 676 | double d = 1 - exp(log(0.5) * m_springness * m_delta_seconds); 677 | 678 | m_camera_x = m_mouse_delta_x * m_camera_sensitivity; 679 | m_camera_y = m_mouse_delta_y * m_camera_sensitivity; 680 | 681 | if (m_mouse_look) 682 | { 683 | // Activate Mouse Look 684 | current->set_rotatation_delta(glm::vec3((float)(m_camera_y), 685 | (float)(m_camera_x), 686 | (float)(0.0f))); 687 | } 688 | else 689 | { 690 | current->set_rotatation_delta(glm::vec3((float)(0), 691 | (float)(0), 692 | (float)(0))); 693 | } 694 | 695 | current->update(); 696 | 697 | update_transforms(current); 698 | } 699 | 700 | // ----------------------------------------------------------------------------------------------------------------------------------- 701 | 702 | void gui() 703 | { 704 | visualize_hierarchy(m_skeletal_mesh->skeleton()); 705 | } 706 | 707 | // ----------------------------------------------------------------------------------------------------------------------------------- 708 | 709 | void update_animations() 710 | { 711 | Pose* locomotion_pose = m_blendspace_1d->evaluate(m_delta_seconds); 712 | Pose* aim_pose = m_blendspace_2d->evaluate(m_delta_seconds); 713 | Pose* final_pose = m_blend->blend_partial_additive(locomotion_pose, aim_pose, m_additive_blend_factor, "spine_01"); 714 | 715 | PoseTransforms* local_transforms = m_local_transform->generate_transforms(final_pose); 716 | PoseTransforms* global_transforms = m_global_transform->generate_transforms(local_transforms); 717 | 718 | if (!m_ik_pos_set) 719 | { 720 | m_ik_pos_set = true; 721 | int32_t hand_idx = m_skeletal_mesh->skeleton()->find_joint_index("hand_l"); 722 | 723 | glm::mat4 m = m_character_transforms.model * global_transforms->transforms[hand_idx]; 724 | 725 | m_ik_pos = glm::vec3(m[3][0], m[3][1], m[3][2]); 726 | } 727 | 728 | PoseTransforms* ik_transforms = m_fabrik_ik->solve(m_character_transforms.model, local_transforms, global_transforms, m_ik_pos, "clavicle_l", "hand_l"); 729 | PoseTransforms* final_transforms = m_offset->offset(ik_transforms); 730 | 731 | update_bone_uniforms(final_transforms); 732 | update_skeleton_debug(m_skeletal_mesh->skeleton(), ik_transforms); 733 | } 734 | 735 | // ----------------------------------------------------------------------------------------------------------------------------------- 736 | 737 | void update_skeleton_debug(Skeleton* skeleton, PoseTransforms* transforms) 738 | { 739 | m_joint_pos.clear(); 740 | 741 | Joint* joints = skeleton->joints(); 742 | 743 | for (int i = 0; i < skeleton->num_bones(); i++) 744 | { 745 | glm::mat4 joint = joints[i].offset_transform; 746 | glm::mat4 mat = m_character_transforms.model * transforms->transforms[i]; 747 | 748 | m_joint_pos.push_back(glm::vec3(mat[3][0], mat[3][1], mat[3][2])); 749 | 750 | if (m_visualize_axis) 751 | m_debug_draw.transform(mat); 752 | } 753 | } 754 | 755 | // ----------------------------------------------------------------------------------------------------------------------------------- 756 | 757 | void visualize_skeleton(Skeleton* skeleton) 758 | { 759 | for (int i = 0; i < m_joint_pos.size(); i++) 760 | { 761 | glm::vec3 color = glm::vec3(1.0f, 0.0f, 0.0f); 762 | 763 | if (i == 0) 764 | color = glm::vec3(0.0f, 0.0f, 1.0f); 765 | 766 | if (m_selected_node == i) 767 | color = glm::vec3(0.0f, 1.0f, 0.0f); 768 | 769 | m_debug_draw.sphere(0.1f, m_joint_pos[i], color); 770 | } 771 | } 772 | 773 | // ----------------------------------------------------------------------------------------------------------------------------------- 774 | 775 | void visualize_bones(Skeleton* skeleton) 776 | { 777 | Joint* joints = skeleton->joints(); 778 | 779 | for (int i = 0; i < skeleton->num_bones(); i++) 780 | { 781 | if (joints[i].parent_index == -1) 782 | continue; 783 | 784 | m_debug_draw.line(m_joint_pos[i], m_joint_pos[joints[i].parent_index], glm::vec3(0.0f, 1.0f, 0.0f)); 785 | } 786 | } 787 | 788 | // ----------------------------------------------------------------------------------------------------------------------------------- 789 | 790 | void visualize_hierarchy(Skeleton* skeleton) 791 | { 792 | static bool skeleton_window = true; 793 | 794 | ImGui::Begin("Skeletal Animation", &skeleton_window); 795 | 796 | ImGui::Checkbox("Visualize Mesh", &m_visualize_mesh); 797 | ImGui::Checkbox("Visualize Joints", &m_visualize_joints); 798 | ImGui::Checkbox("Visualize Bones", &m_visualize_bones); 799 | ImGui::Checkbox("Visualize Bone Axis", &m_visualize_axis); 800 | ImGui::SliderFloat("IK Target", &m_ik_pos.y, 5.0f, 20.0f); 801 | 802 | float rate = m_walk_sampler->playback_rate(); 803 | ImGui::SliderFloat("Playback Rate", &rate, 0.1f, 1.0f); 804 | m_walk_sampler->set_playback_rate(rate); 805 | 806 | float min = m_blendspace_1d->min(); 807 | float max = m_blendspace_1d->max(); 808 | float value = m_blendspace_1d->value(); 809 | ImGui::SliderFloat("Speed", &value, min, max); 810 | m_blendspace_1d->set_value(value); 811 | 812 | ImGui::SliderFloat("Additive Weight", &m_additive_blend_factor, 0.0f, 1.0f); 813 | ImGui::SliderFloat("Pitch", &m_pitch_blend, -90.0f, 90.0f); 814 | ImGui::SliderFloat("Yaw", &m_yaw_blend, -90.0f, 90.0f); 815 | 816 | m_blendspace_2d->set_x_value(m_yaw_blend); 817 | m_blendspace_2d->set_y_value(m_pitch_blend); 818 | 819 | ImGui::Separator(); 820 | 821 | ImGui::Text("Hierarchy"); 822 | 823 | Joint* joints = skeleton->joints(); 824 | 825 | for (int i = 0; i < skeleton->num_bones(); i++) 826 | { 827 | if (m_index_stack.size() > 0 && joints[i].parent_index < m_index_stack.back().first) 828 | { 829 | while (m_index_stack.back().first != joints[i].parent_index) 830 | { 831 | if (m_index_stack.back().second) 832 | ImGui::TreePop(); 833 | 834 | m_index_stack.pop_back(); 835 | } 836 | } 837 | 838 | bool parent_opened = false; 839 | 840 | for (auto& p : m_index_stack) 841 | { 842 | if (p.first == joints[i].parent_index && p.second) 843 | { 844 | parent_opened = true; 845 | break; 846 | } 847 | } 848 | 849 | if (!parent_opened && m_index_stack.size() > 0) 850 | continue; 851 | 852 | ImGuiTreeNodeFlags node_flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | (m_selected_node == i ? ImGuiTreeNodeFlags_Selected : 0); 853 | bool opened = ImGui::TreeNodeEx(joints[i].name.c_str(), node_flags); 854 | 855 | if (ImGui::IsItemClicked()) 856 | m_selected_node = i; 857 | 858 | m_index_stack.push_back({ i, opened }); 859 | } 860 | 861 | if (m_index_stack.size() > 0) 862 | { 863 | while (m_index_stack.size() > 0) 864 | { 865 | if (m_index_stack.back().second) 866 | ImGui::TreePop(); 867 | 868 | m_index_stack.pop_back(); 869 | } 870 | } 871 | 872 | ImGui::End(); 873 | } 874 | 875 | // ----------------------------------------------------------------------------------------------------------------------------------- 876 | 877 | private: 878 | // General GPU resources. 879 | std::unique_ptr m_vs; 880 | std::unique_ptr m_fs; 881 | std::unique_ptr m_program; 882 | std::unique_ptr m_object_ubo; 883 | std::unique_ptr m_bone_ubo; 884 | std::unique_ptr m_global_ubo; 885 | 886 | // Animation shaders. 887 | std::unique_ptr m_anim_vs; 888 | std::unique_ptr m_anim_fs; 889 | std::unique_ptr m_anim_program; 890 | 891 | // Bone shaders. 892 | std::unique_ptr m_bone_vs; 893 | std::unique_ptr m_bone_fs; 894 | std::unique_ptr m_bone_program; 895 | 896 | // Camera. 897 | std::unique_ptr m_main_camera; 898 | std::unique_ptr m_debug_camera; 899 | 900 | // Uniforms. 901 | ObjectUniforms m_plane_transforms; 902 | ObjectUniforms m_character_transforms; 903 | GlobalUniforms m_global_uniforms; 904 | PoseTransforms m_pose_transforms; 905 | 906 | // Animations 907 | std::unique_ptr m_walk_animation; 908 | std::unique_ptr m_jog_animation; 909 | std::unique_ptr m_run_animation; 910 | std::unique_ptr m_additive_base_animation; 911 | std::unique_ptr m_walk_sampler; 912 | std::unique_ptr m_run_sampler; 913 | std::unique_ptr m_local_transform; 914 | std::unique_ptr m_global_transform; 915 | std::unique_ptr m_fabrik_ik; 916 | std::unique_ptr m_offset; 917 | std::unique_ptr m_blend; 918 | std::unique_ptr m_blendspace_1d; 919 | 920 | std::unique_ptr m_aim_lu_animation; 921 | std::unique_ptr m_aim_cu_animation; 922 | std::unique_ptr m_aim_ru_animation; 923 | std::unique_ptr m_aim_l_animation; 924 | std::unique_ptr m_aim_c_animation; 925 | std::unique_ptr m_aim_r_animation; 926 | std::unique_ptr m_aim_ld_animation; 927 | std::unique_ptr m_aim_cd_animation; 928 | std::unique_ptr m_aim_rd_animation; 929 | std::unique_ptr m_blendspace_2d; 930 | 931 | // Mesh 932 | std::unique_ptr m_skeletal_mesh; 933 | 934 | // Bone Mesh 935 | std::unique_ptr m_bone_vbo; 936 | std::unique_ptr m_bone_ibo; 937 | std::unique_ptr m_bone_vao; 938 | 939 | // Camera controls. 940 | bool m_mouse_look = false; 941 | bool m_debug_mode = false; 942 | float m_heading_speed = 0.0f; 943 | float m_sideways_speed = 0.0f; 944 | float m_camera_sensitivity = 0.05f; 945 | float m_camera_speed = 0.01f; 946 | 947 | // GUI 948 | bool m_visualize_mesh = true; 949 | bool m_visualize_joints = false; 950 | bool m_visualize_bones = false; 951 | bool m_visualize_axis = false; 952 | 953 | // Camera orientation. 954 | float m_camera_x; 955 | float m_camera_y; 956 | float m_springness = 1.0f; 957 | float m_blend_factor = 0.0f; 958 | float m_additive_blend_factor = 0.0f; 959 | float m_pitch_blend = 0.0f; 960 | float m_yaw_blend = 0.0f; 961 | 962 | int32_t m_selected_node = -1; 963 | std::vector m_joint_pos; 964 | std::vector> m_index_stack; 965 | glm::vec3 m_ik_pos; 966 | bool m_ik_pos_set = false; 967 | }; 968 | 969 | DW_DECLARE_MAIN(AnimationStateMachine) 970 | -------------------------------------------------------------------------------- /src/shader/bone_fs.glsl: -------------------------------------------------------------------------------- 1 | 2 | out vec3 PS_OUT_Color; 3 | 4 | in vec3 PS_IN_FragPos; 5 | in vec3 PS_IN_Normal; 6 | 7 | void main() 8 | { 9 | vec3 light_pos = vec3(-200.0, 200.0, 0.0); 10 | vec3 n = normalize(PS_IN_Normal); 11 | vec3 l = normalize(light_pos - PS_IN_FragPos); 12 | float lambert = max(0.0f, dot(n, l)); 13 | vec3 diffuse = vec3(1.0f, 0.0f, 0.0f); 14 | vec3 ambient = diffuse * 0.03; 15 | vec3 color = diffuse * lambert + ambient; 16 | PS_OUT_Color = color; 17 | } 18 | -------------------------------------------------------------------------------- /src/shader/bone_vs.glsl: -------------------------------------------------------------------------------- 1 | layout (location = 0) in vec3 VS_IN_Position; 2 | layout (location = 1) in vec3 VS_IN_Normal; 3 | 4 | const int MAX_BONES = 128; 5 | 6 | layout (std140) uniform u_GlobalUBO 7 | { 8 | mat4 view; 9 | mat4 projection; 10 | }; 11 | 12 | layout (std140) uniform u_ObjectUBO 13 | { 14 | mat4 model; 15 | }; 16 | 17 | layout (std140) uniform u_BoneUBO 18 | { 19 | mat4 bones[MAX_BONES]; 20 | }; 21 | 22 | out vec3 PS_IN_FragPos; 23 | out vec3 PS_IN_Normal; 24 | 25 | mat4 get_world_matrix(mat4 joint) 26 | { 27 | joint = transpose(joint); 28 | // Rebuilds bone properties. 29 | // Bone length is set to zero to disable leaf rendering. 30 | float is_bone = joint[3].w; 31 | vec3 bone_dir = vec3(joint[0].w, joint[1].w, joint[2].w) * is_bone; 32 | float bone_len = length(bone_dir); 33 | // Setup rendering world matrix. 34 | float dot = dot(joint[2].xyz, bone_dir); 35 | vec3 binormal = abs(dot) < .01 ? joint[2].xyz : joint[1].xyz; 36 | 37 | mat4 world_matrix; 38 | world_matrix[0] = vec4(bone_dir, 0.); 39 | world_matrix[1] = vec4(bone_len * normalize(cross(binormal, bone_dir)), 0.); 40 | world_matrix[2] = vec4(bone_len * normalize(cross(bone_dir, world_matrix[1].xyz)), 0.0); 41 | world_matrix[3] = vec4(joint[3].xyz, 1.); 42 | return world_matrix; 43 | } 44 | 45 | void main() 46 | { 47 | mat4 model_mat = model * (bones[gl_InstanceID]); 48 | vec4 position = model_mat * vec4(VS_IN_Position, 1.0f); 49 | 50 | PS_IN_FragPos = position.xyz; 51 | 52 | mat3 model_bone_bat = mat3(model_mat); 53 | 54 | PS_IN_Normal = normalize(model_bone_bat * VS_IN_Normal); 55 | 56 | gl_Position = projection * view * position; 57 | } 58 | -------------------------------------------------------------------------------- /src/shader/fs.glsl: -------------------------------------------------------------------------------- 1 | 2 | out vec3 PS_OUT_Color; 3 | 4 | in vec3 PS_IN_FragPos; 5 | in vec3 PS_IN_Normal; 6 | 7 | void main() 8 | { 9 | PS_OUT_Color = vec3(0.0f, 0.635f ,0.909f); 10 | } 11 | -------------------------------------------------------------------------------- /src/shader/skinning_fs.glsl: -------------------------------------------------------------------------------- 1 | 2 | out vec3 PS_OUT_Color; 3 | 4 | in vec3 PS_IN_FragPos; 5 | in vec3 PS_IN_Normal; 6 | 7 | void main() 8 | { 9 | vec3 light_pos = vec3(-200.0, 200.0, 0.0); 10 | vec3 n = normalize(PS_IN_Normal); 11 | vec3 l = normalize(light_pos - PS_IN_FragPos); 12 | float lambert = max(0.0f, dot(n, l)); 13 | vec3 diffuse = vec3(1.0f); 14 | vec3 ambient = diffuse * 0.03; 15 | vec3 color = diffuse * lambert + ambient; 16 | PS_OUT_Color = color; 17 | } 18 | -------------------------------------------------------------------------------- /src/shader/skinning_vs.glsl: -------------------------------------------------------------------------------- 1 | layout (location = 0) in vec3 VS_IN_Position; 2 | layout (location = 1) in vec2 VS_IN_Texcoord; 3 | layout (location = 2) in vec3 VS_IN_Normal; 4 | layout (location = 3) in vec3 VS_IN_Tangent; 5 | layout (location = 4) in ivec4 VS_IN_BoneIDs; 6 | layout (location = 5) in vec4 VS_IN_Weights; 7 | 8 | const int MAX_BONES = 128; 9 | 10 | layout (std140) uniform u_GlobalUBO 11 | { 12 | mat4 view; 13 | mat4 projection; 14 | }; 15 | 16 | layout (std140) uniform u_ObjectUBO 17 | { 18 | mat4 model; 19 | }; 20 | 21 | layout (std140) uniform u_BoneUBO 22 | { 23 | mat4 bones[MAX_BONES]; 24 | }; 25 | 26 | out vec3 PS_IN_FragPos; 27 | out vec3 PS_IN_Normal; 28 | 29 | void main() 30 | { 31 | mat4 bone_transform = bones[VS_IN_BoneIDs[0]] * VS_IN_Weights[0]; 32 | bone_transform += bones[VS_IN_BoneIDs[1]] * VS_IN_Weights[1]; 33 | bone_transform += bones[VS_IN_BoneIDs[2]] * VS_IN_Weights[2]; 34 | bone_transform += bones[VS_IN_BoneIDs[3]] * VS_IN_Weights[3]; 35 | 36 | //mat4 bone_transform = mat4(1.0); 37 | 38 | mat4 model_mat = model * bone_transform; 39 | vec4 position = vec4(VS_IN_Position, 1.0f); 40 | 41 | vec4 world_pos = model_mat * position; 42 | PS_IN_FragPos = world_pos.xyz; 43 | 44 | mat3 model_bone_bat = mat3(model_mat); 45 | 46 | PS_IN_Normal = normalize(model_bone_bat * VS_IN_Normal); 47 | 48 | gl_Position = projection * view * world_pos; 49 | } 50 | -------------------------------------------------------------------------------- /src/shader/vs.glsl: -------------------------------------------------------------------------------- 1 | layout (location = 0) in vec3 VS_IN_Position; 2 | layout (location = 1) in vec2 VS_IN_Texcoord; 3 | layout (location = 2) in vec3 VS_IN_Normal; 4 | layout (location = 3) in vec3 VS_IN_Tangent; 5 | layout (location = 4) in ivec4 VS_IN_BoneIDs; 6 | layout (location = 5) in vec4 VS_IN_Weights; 7 | 8 | const int MAX_BONES = 128; 9 | 10 | layout (std140) uniform u_GlobalUBO 11 | { 12 | mat4 projection; 13 | mat4 view; 14 | }; 15 | 16 | layout (std140) uniform u_ObjectUBO 17 | { 18 | mat4 model; 19 | }; 20 | 21 | layout (std140) uniform u_BoneUBO 22 | { 23 | mat4 bones[MAX_BONES]; 24 | }; 25 | 26 | out vec3 PS_IN_FragPos; 27 | out vec3 PS_IN_Normal; 28 | 29 | void main() 30 | { 31 | mat4 bone_transform = bones[VS_IN_BoneIDs[0]] * VS_IN_Weights[0]; 32 | bone_transform += bones[VS_IN_BoneIDs[1]] * VS_IN_Weights[1]; 33 | bone_transform += bones[VS_IN_BoneIDs[2]] * VS_IN_Weights[2]; 34 | bone_transform += bones[VS_IN_BoneIDs[3]] * VS_IN_Weights[3]; 35 | 36 | vec4 position = bone_transform * vec4(VS_IN_Position, 1.0f); 37 | 38 | vec4 world_pos = model * position; 39 | PS_IN_FragPos = world_pos.xyz; 40 | 41 | mat3 model_bone_bat = mat3(model * bone_transform); 42 | 43 | PS_IN_Normal = normalize(model_bone_bat * VS_IN_Normal); 44 | 45 | gl_Position = projection * view * world_pos; 46 | } 47 | -------------------------------------------------------------------------------- /src/skeletal_mesh.cpp: -------------------------------------------------------------------------------- 1 | #include "skeletal_mesh.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "logger.h" 7 | 8 | SkeletalMesh* SkeletalMesh::load(const std::string& name, Skeleton* skeleton) 9 | { 10 | const aiScene* scene; 11 | Assimp::Importer importer; 12 | 13 | scene = importer.ReadFile(name, aiProcess_Triangulate | aiProcess_GenSmoothNormals | aiProcess_FlipUVs | aiProcess_CalcTangentSpace); 14 | 15 | if (!scene) 16 | { 17 | DW_LOG_ERROR("Failed to load Skeletal Mesh in file : " + name); 18 | return nullptr; 19 | } 20 | 21 | SkeletalMesh* skeletal_mesh = new SkeletalMesh(); 22 | 23 | if (skeleton) 24 | skeletal_mesh->m_skeleton = skeleton; 25 | else 26 | skeletal_mesh->m_skeleton = Skeleton::create(scene); 27 | 28 | skeletal_mesh->m_sub_meshes.resize(scene->mNumMeshes); 29 | 30 | const aiVector3D Zero3D(0.0f, 0.0f, 0.0f); 31 | const aiColor4D Zero4D(0.0f, 0.0f, 0.0f, 0.0f); 32 | 33 | uint32_t num_vertices = 0; 34 | uint32_t num_indices = 0; 35 | 36 | skeletal_mesh->m_has_vertex_colors = false; 37 | 38 | for (int i = 0; i < scene->mNumMeshes; i++) 39 | { 40 | skeletal_mesh->m_sub_meshes[i].num_indices = scene->mMeshes[i]->mNumFaces * 3; 41 | skeletal_mesh->m_sub_meshes[i].base_vertex = num_vertices; 42 | skeletal_mesh->m_sub_meshes[i].base_index = num_indices; 43 | 44 | num_vertices += scene->mMeshes[i]->mNumVertices; 45 | num_indices += skeletal_mesh->m_sub_meshes[i].num_indices; 46 | 47 | for (int j = 0; j < scene->mMeshes[i]->mNumVertices; j++) 48 | { 49 | const aiVector3D* pPos = &(scene->mMeshes[i]->mVertices[j]); 50 | const aiVector3D* pNormal = &(scene->mMeshes[i]->mNormals[j]); 51 | const aiVector3D* pTexCoord = scene->mMeshes[i]->HasTextureCoords(0) ? &(scene->mMeshes[i]->mTextureCoords[0][j]) : &Zero3D; 52 | const aiColor4D* pColor = scene->mMeshes[i]->HasVertexColors(0) ? &(scene->mMeshes[i]->mColors[0][j]) : &Zero4D; 53 | 54 | if (skeletal_mesh->m_has_vertex_colors) 55 | { 56 | SkeletalColoredVertex vertex; 57 | vertex.position = glm::vec3(pPos->x, pPos->y, pPos->z); 58 | vertex.normal = glm::vec3(pNormal->x, pNormal->y, pNormal->z); 59 | vertex.texcoord = glm::vec2(pTexCoord->x, pTexCoord->y); 60 | vertex.color = glm::vec4(pColor->r, pColor->g, pColor->b, pColor->a); 61 | vertex.bone_indices = glm::ivec4(0, 0, 0, 0); 62 | vertex.bone_weights = glm::vec4(0.0f, 0.0f, 0.0f, 0.0f); 63 | 64 | skeletal_mesh->m_color_vertices.push_back(vertex); 65 | } 66 | else 67 | { 68 | SkeletalVertex vertex; 69 | vertex.position = glm::vec3(pPos->x, pPos->y, pPos->z); 70 | vertex.normal = glm::vec3(pNormal->x, pNormal->y, pNormal->z); 71 | vertex.texcoord = glm::vec2(pTexCoord->x, pTexCoord->y); 72 | vertex.bone_indices = glm::ivec4(0, 0, 0, 0); 73 | vertex.bone_weights = glm::vec4(0.0f, 0.0f, 0.0f, 0.0f); 74 | 75 | skeletal_mesh->m_vertices.push_back(vertex); 76 | } 77 | } 78 | 79 | for (int j = 0; j < scene->mMeshes[i]->mNumFaces; j++) 80 | { 81 | aiFace& face = scene->mMeshes[i]->mFaces[j]; 82 | 83 | skeletal_mesh->m_indices.push_back(face.mIndices[0]); 84 | skeletal_mesh->m_indices.push_back(face.mIndices[1]); 85 | skeletal_mesh->m_indices.push_back(face.mIndices[2]); 86 | } 87 | } 88 | 89 | for (int32_t i = 0; i < skeletal_mesh->m_sub_meshes.size(); i++) 90 | { 91 | for (uint32_t j = 0; j < scene->mMeshes[i]->mNumBones; j++) 92 | { 93 | std::string joint_name = trimmed_name(scene->mMeshes[i]->mBones[j]->mName.C_Str()); 94 | 95 | int joint_index = skeletal_mesh->m_skeleton->find_joint_index(joint_name); 96 | 97 | for (int k = 0; k < scene->mMeshes[i]->mBones[j]->mNumWeights; k++) 98 | { 99 | uint32_t VertexID = skeletal_mesh->m_sub_meshes[i].base_vertex + scene->mMeshes[i]->mBones[j]->mWeights[k].mVertexId; 100 | float Weight = scene->mMeshes[i]->mBones[j]->mWeights[k].mWeight; 101 | 102 | for (int x = 0; x < 4; x++) // hardcoded to 4, since vec4's only contain 4 elements 103 | { 104 | if (!skeletal_mesh->m_has_vertex_colors && skeletal_mesh->m_vertices[VertexID].bone_weights[x] == 0.0f) 105 | { 106 | skeletal_mesh->m_vertices[VertexID].bone_indices[x] = joint_index; 107 | skeletal_mesh->m_vertices[VertexID].bone_weights[x] = Weight; 108 | break; 109 | } 110 | } 111 | } 112 | } 113 | } 114 | 115 | skeletal_mesh->create_gpu_objects(); 116 | 117 | return skeletal_mesh; 118 | } 119 | 120 | SkeletalMesh::SkeletalMesh() 121 | { 122 | 123 | } 124 | 125 | SkeletalMesh::~SkeletalMesh() 126 | { 127 | 128 | } 129 | 130 | void SkeletalMesh::bind_vao() 131 | { 132 | m_vao->bind(); 133 | } 134 | 135 | void SkeletalMesh::create_gpu_objects() 136 | { 137 | if (m_has_vertex_colors) 138 | { 139 | // Create vertex buffer. 140 | m_vbo = std::make_unique(GL_STATIC_DRAW, sizeof(SkeletalColoredVertex) * m_color_vertices.size(), &m_color_vertices[0]); 141 | 142 | if (!m_vbo) 143 | DW_LOG_ERROR("Failed to create Vertex Buffer"); 144 | } 145 | else 146 | { 147 | // Create vertex buffer. 148 | m_vbo = std::make_unique(GL_STATIC_DRAW, sizeof(SkeletalVertex) * m_vertices.size(), &m_vertices[0]); 149 | 150 | if (!m_vbo) 151 | DW_LOG_ERROR("Failed to create Index Buffer"); 152 | } 153 | 154 | // Create index buffer. 155 | m_ibo = std::make_unique(GL_STATIC_DRAW, sizeof(uint32_t) * m_indices.size(), &m_indices[0]); 156 | 157 | if (!m_ibo) 158 | DW_LOG_ERROR("Failed to create Index Buffer"); 159 | 160 | if (m_has_vertex_colors) 161 | { 162 | // Declare vertex attributes. 163 | dw::gl::VertexAttrib attribs[] = 164 | { 165 | { 3, GL_FLOAT, false, 0 }, 166 | { 2, GL_FLOAT, false, offsetof(SkeletalColoredVertex, texcoord) }, 167 | { 3, GL_FLOAT, false, offsetof(SkeletalColoredVertex, normal) }, 168 | { 4, GL_FLOAT, false, offsetof(SkeletalColoredVertex, color) }, 169 | { 4, GL_INT, false, offsetof(SkeletalColoredVertex, bone_indices) }, 170 | { 4, GL_FLOAT, false, offsetof(SkeletalColoredVertex, bone_weights) } 171 | }; 172 | 173 | // Create vertex array. 174 | m_vao = std::make_unique(m_vbo.get(), m_ibo.get(), sizeof(SkeletalColoredVertex), 6, attribs); 175 | 176 | if (!m_vao) 177 | DW_LOG_ERROR("Failed to create Vertex Array"); 178 | } 179 | else 180 | { 181 | // Declare vertex attributes. 182 | dw::gl::VertexAttrib attribs[] = 183 | { 184 | { 3, GL_FLOAT, false, 0 }, 185 | { 2, GL_FLOAT, false, offsetof(SkeletalVertex, texcoord) }, 186 | { 3, GL_FLOAT, false, offsetof(SkeletalVertex, normal) }, 187 | { 3, GL_FLOAT, false, offsetof(SkeletalVertex, tangent) }, 188 | { 4, GL_INT, false, offsetof(SkeletalVertex, bone_indices) }, 189 | { 4, GL_FLOAT, false, offsetof(SkeletalVertex, bone_weights) } 190 | }; 191 | 192 | // Create vertex array. 193 | m_vao = std::make_unique(m_vbo.get(), m_ibo.get(), sizeof(SkeletalVertex), 6, attribs); 194 | 195 | if (!m_vao) 196 | DW_LOG_ERROR("Failed to create Vertex Array"); 197 | } 198 | } -------------------------------------------------------------------------------- /src/skeletal_mesh.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "skeleton.h" 4 | #include 5 | #include 6 | 7 | struct SkeletalVertex 8 | { 9 | glm::vec3 position; 10 | glm::vec2 texcoord; 11 | glm::vec3 normal; 12 | glm::vec3 tangent; 13 | glm::ivec4 bone_indices; 14 | glm::vec4 bone_weights; 15 | }; 16 | 17 | struct SkeletalColoredVertex 18 | { 19 | glm::vec3 position; 20 | glm::vec2 texcoord; 21 | glm::vec3 normal; 22 | glm::vec4 color; 23 | glm::ivec4 bone_indices; 24 | glm::vec4 bone_weights; 25 | }; 26 | 27 | struct SubMesh 28 | { 29 | uint32_t num_indices = 0; 30 | uint32_t base_vertex = 0; 31 | uint32_t base_index = 0; 32 | }; 33 | 34 | class SkeletalMesh 35 | { 36 | public: 37 | static SkeletalMesh* load(const std::string& name, Skeleton* skeleton = nullptr); 38 | 39 | SkeletalMesh(); 40 | ~SkeletalMesh(); 41 | void bind_vao(); 42 | 43 | inline SubMesh& sub_mesh(uint32_t idx) { return m_sub_meshes[idx]; } 44 | inline uint32_t num_sub_meshes() { return m_sub_meshes.size(); } 45 | inline Skeleton* skeleton() { return m_skeleton; } 46 | 47 | private: 48 | void create_gpu_objects(); 49 | 50 | private: 51 | std::unique_ptr m_ibo; 52 | std::unique_ptr m_vbo; 53 | std::unique_ptr m_vao; 54 | std::vector m_sub_meshes; 55 | std::vector m_vertices; 56 | std::vector m_color_vertices; 57 | std::vector m_indices; 58 | Skeleton* m_skeleton; 59 | bool m_has_vertex_colors; 60 | }; -------------------------------------------------------------------------------- /src/skeleton.cpp: -------------------------------------------------------------------------------- 1 | #include "skeleton.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | void print_scene_heirarchy(aiNode* node) 10 | { 11 | if (node) 12 | { 13 | std::cout << node->mName.C_Str() << std::endl; 14 | 15 | for (int i = 0; i < node->mNumChildren; i++) 16 | print_scene_heirarchy(node->mChildren[i]); 17 | } 18 | } 19 | 20 | Skeleton* Skeleton::create(const aiScene* scene) 21 | { 22 | Skeleton* skeleton = new Skeleton(); 23 | 24 | std::vector temp_bone_list(256); 25 | std::unordered_set bone_map; 26 | 27 | std::cout << "\nBegin Print Scene\n" << std::endl; 28 | print_scene_heirarchy(scene->mRootNode); 29 | std::cout << "\nEnd Print Scene\n" << std::endl; 30 | 31 | skeleton->build_bone_list(scene->mRootNode, scene, temp_bone_list, bone_map); 32 | skeleton->m_joints.reserve(skeleton->m_num_joints); 33 | skeleton->build_skeleton(scene->mRootNode, 0, scene, temp_bone_list); 34 | 35 | std::cout << "\nBegin Print Joint List\n" << std::endl; 36 | 37 | for (int i = 0; i < skeleton->m_joints.size(); i++) 38 | { 39 | auto& joint = skeleton->m_joints[i]; 40 | std::cout << "Index: " << i << ", Name: " << joint.name << ", Parent: " << joint.parent_index << std::endl; 41 | } 42 | 43 | std::cout << "\nEnd Print Joint List\n" << std::endl; 44 | 45 | return skeleton; 46 | } 47 | 48 | Skeleton::Skeleton() 49 | { 50 | m_num_joints = 0; 51 | } 52 | 53 | Skeleton::~Skeleton() 54 | { 55 | 56 | } 57 | 58 | void Skeleton::build_bone_list(aiNode* node, const aiScene* scene, std::vector& temp_bone_list, std::unordered_set& bone_map) 59 | { 60 | for (int i = 0; i < node->mNumMeshes; i++) 61 | { 62 | aiMesh* current_mesh = scene->mMeshes[node->mMeshes[i]]; 63 | 64 | for (int j = 0; j < current_mesh->mNumBones; j++) 65 | { 66 | std::string bone_name = std::string(current_mesh->mBones[j]->mName.C_Str()); 67 | 68 | if (bone_map.find(bone_name) == bone_map.end()) 69 | { 70 | temp_bone_list[m_num_joints] = current_mesh->mBones[j]; 71 | m_num_joints++; 72 | 73 | bone_map.insert(bone_name); 74 | } 75 | } 76 | } 77 | 78 | for (int i = 0; i < node->mNumChildren; i++) 79 | build_bone_list(node->mChildren[i], scene, temp_bone_list, bone_map); 80 | } 81 | 82 | void Skeleton::build_skeleton(aiNode* node, int bone_index, const aiScene* scene, std::vector& temp_bone_list) 83 | { 84 | std::string node_name = trimmed_name(node->mName.C_Str()); 85 | 86 | int count = bone_index; 87 | 88 | for (int i = 0; i < m_num_joints; i++) 89 | { 90 | std::string bone_name = trimmed_name(temp_bone_list[i]->mName.C_Str()); 91 | 92 | if (bone_name == node_name) 93 | { 94 | Joint joint; 95 | 96 | joint.name = bone_name; 97 | joint.offset_transform = glm::transpose(glm::make_mat4(&temp_bone_list[i]->mOffsetMatrix.a1)); 98 | 99 | aiNode* parent = node->mParent; 100 | int index; 101 | 102 | while (parent) 103 | { 104 | index = find_joint_index(trimmed_name(parent->mName.C_Str())); 105 | 106 | if (index == -1) 107 | parent = parent->mParent; 108 | else 109 | break; 110 | } 111 | 112 | joint.parent_index = index; 113 | m_joints.push_back(joint); 114 | 115 | break; 116 | } 117 | } 118 | 119 | for (int i = 0; i < node->mNumChildren; i++) 120 | build_skeleton(node->mChildren[i], m_num_joints, scene, temp_bone_list); 121 | } 122 | 123 | int32_t Skeleton::find_joint_index(const std::string& channel_name) 124 | { 125 | for (int i = 0; i < m_joints.size(); i++) 126 | { 127 | if (m_joints[i].name == channel_name) 128 | return i; 129 | } 130 | 131 | return -1; 132 | } -------------------------------------------------------------------------------- /src/skeleton.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "animation.h" 4 | #include 5 | 6 | struct aiNode; 7 | struct aiBone; 8 | struct aiScene; 9 | 10 | struct Joint 11 | { 12 | std::string name; 13 | glm::mat4 offset_transform; 14 | int32_t parent_index; 15 | }; 16 | 17 | class Skeleton 18 | { 19 | public: 20 | static Skeleton* create(const aiScene* scene); 21 | 22 | Skeleton(); 23 | ~Skeleton(); 24 | int32_t find_joint_index(const std::string& channel_name); 25 | 26 | inline uint32_t num_bones() { return m_num_joints; } 27 | inline Joint* joints() { return &m_joints[0]; } 28 | 29 | private: 30 | void build_bone_list(aiNode* node, const aiScene* scene, std::vector& temp_bone_list, std::unordered_set& bone_map); 31 | void build_skeleton(aiNode* node, int bone_index, const aiScene* scene, std::vector& temp_bone_list); 32 | 33 | private: 34 | uint32_t m_num_joints; 35 | std::vector m_joints; 36 | }; --------------------------------------------------------------------------------