Permalink
Cannot retrieve contributors at this time
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
253 lines (208 sloc)
9.76 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <render/RenderComponents.h> | |
#include <render/Render.h> | |
//#include <render/ModelManager_GL.h> | |
#include <scene/SceneGraph.h> | |
#include <entity/EntityManager.h> | |
#include <glm/vec3.hpp> | |
#include <glm/gtc/quaternion.hpp> | |
#include <render/model/Model_GL.h> | |
using namespace griffin; | |
using namespace griffin::render; | |
static Model_GL g_tempModel; | |
struct NodeAnimationTransform { | |
float weight; | |
glm::vec3 translation; | |
glm::quat rotation; | |
glm::vec3 scaling; | |
}; | |
NodeAnimationTransform getNodeTransformForTrack(const MeshAnimations& animations, AnimationTrack& track, NodeAnimation& nodeAnim, float animTime) | |
{ | |
using namespace glm; | |
NodeAnimationTransform result{}; | |
// This code requires at least one key from each of position, rotation and scaling so we avoid | |
// having to decompose the default node matrix. Luckily it appears that assimp always gives us | |
// at least one key for each channel, but that could also be from Blender specifically. This | |
// assertion tells us if there is a missing channel in the animation. | |
assert(nodeAnim.numPositionKeys > 0 && nodeAnim.numRotationKeys > 0 && nodeAnim.numScalingKeys > 0 && | |
"animation requires at least one key per channel"); | |
// Translation keyframes | |
{ | |
int key1 = -1; | |
int key2 = -1; | |
// get nearest two key frames | |
for (uint32_t k = nodeAnim.positionKeysIndexOffset; k < nodeAnim.positionKeysIndexOffset + nodeAnim.numPositionKeys; ++k) { | |
auto& posKey = animations.positionKeys[k]; | |
if (animTime < posKey.time) { | |
key1 = (k == nodeAnim.positionKeysIndexOffset ? k : k - 1); | |
key2 = k; | |
break; | |
} | |
} | |
// went past the last key | |
if (key1 == -1) { | |
key1 = nodeAnim.positionKeysIndexOffset + nodeAnim.numPositionKeys - 1; | |
key2 = key1; | |
} | |
// TODO: look at pre/post state, we may be able to exit early and accept the default modelToWorld when key1 == key2, depending on the state | |
// Also, the key1 or key2 at either end of the animation may have to be set to default node transform instead of clamping the animations frame | |
float time1 = animations.positionKeys[key1].time; | |
vec3 pos1(animations.positionKeys[key1].x, animations.positionKeys[key1].y, animations.positionKeys[key1].z); | |
float time2 = animations.positionKeys[key2].time; | |
vec3 pos2(animations.positionKeys[key2].x, animations.positionKeys[key2].y, animations.positionKeys[key2].z); | |
float interp = 0.0f; | |
if (key1 != key2) { | |
interp = (animTime - time1) / (time2 - time1); | |
} | |
// TODO: allow interpolation curves other than linear... hermite, cubic, spring system, etc. | |
result.translation = mix(pos1, pos2, interp); | |
} | |
// Rotation keyframes | |
{ | |
int key1 = -1; | |
int key2 = -1; | |
// get nearest two key frames | |
for (uint32_t k = nodeAnim.rotationKeysIndexOffset; k < nodeAnim.rotationKeysIndexOffset + nodeAnim.numRotationKeys; ++k) { | |
auto& rotKey = animations.rotationKeys[k]; | |
if (animTime < rotKey.time) { | |
key1 = (k == nodeAnim.rotationKeysIndexOffset ? k : k - 1); | |
key2 = k; | |
break; | |
} | |
} | |
// went past the last key | |
if (key1 == -1) { | |
key1 = nodeAnim.rotationKeysIndexOffset + nodeAnim.numRotationKeys - 1; | |
key2 = key1; | |
} | |
// TODO: look at pre/post state, we may be able to exit early and accept the default modelToWorld when key1 == key2, depending on the state | |
// Also, the key1 or key2 at either end of the animation may have to be set to default node transform instead of clamping the animations frame | |
float time1 = animations.rotationKeys[key1].time; | |
quat rot1(animations.rotationKeys[key1].w, animations.rotationKeys[key1].x, animations.rotationKeys[key1].y, animations.rotationKeys[key1].z); | |
float time2 = animations.rotationKeys[key2].time; | |
quat rot2(animations.rotationKeys[key2].w, animations.rotationKeys[key2].x, animations.rotationKeys[key2].y, animations.rotationKeys[key2].z); | |
float interp = 0.0f; | |
if (key1 != key2) { | |
interp = (animTime - time1) / (time2 - time1); | |
} | |
// TODO: allow interpolation curves other than linear... hermite, cubic, spring system, etc. | |
result.rotation = normalize(lerp(rot1, rot2, interp)); | |
} | |
// Scaling keyframes | |
{ | |
int key1 = -1; | |
int key2 = -1; | |
// get nearest two key frames | |
for (uint32_t k = nodeAnim.scalingKeysIndexOffset; k < nodeAnim.scalingKeysIndexOffset + nodeAnim.numScalingKeys; ++k) { | |
auto& scaleKey = animations.scalingKeys[k]; | |
if (animTime < scaleKey.time) { | |
key1 = (k == nodeAnim.scalingKeysIndexOffset ? k : k - 1); | |
key2 = k; | |
break; | |
} | |
} | |
// went past the last key | |
if (key1 == -1) { | |
key1 = nodeAnim.scalingKeysIndexOffset + nodeAnim.numScalingKeys - 1; | |
key2 = key1; | |
} | |
// TODO: look at pre/post state, we may be able to exit early and accept the default modelToWorld when key1 == key2, depending on the state | |
// Also, the key1 or key2 at either end of the animation may have to be set to default node transform instead of clamping the animations frame | |
float time1 = animations.scalingKeys[key1].time; | |
vec3 scale1(animations.scalingKeys[key1].x, animations.scalingKeys[key1].y, animations.scalingKeys[key1].z); | |
float time2 = animations.scalingKeys[key2].time; | |
vec3 scale2(animations.scalingKeys[key2].x, animations.scalingKeys[key2].y, animations.scalingKeys[key2].z); | |
float interp = 0.0f; | |
if (key1 != key2) { | |
interp = (animTime - time1) / (time2 - time1); | |
} | |
// TODO: allow interpolation curves other than linear... hermite, cubic, spring system, etc. | |
result.scaling = mix(scale1, scale2, interp); | |
} | |
return result; | |
} | |
// TODO: make sure animation takes place only when instance is going to be visible after early frustum cull | |
void updateMeshInstanceAnimations(entity::EntityManager& entityMgr) | |
{ | |
using namespace glm; | |
auto& animationStore = entityMgr.getComponentStore<MeshAnimationTrack>(); | |
// for each animated mesh instance | |
for (auto& animInst : animationStore.getComponents().getItems()) { | |
auto& meshInst = entityMgr.getComponent<scene::ModelInstance>(animInst.entityId); | |
auto& mesh = g_tempModel.m_mesh; // TODO: don't use the global model, get model from resource system?? | |
// for all node animation components in this mesh instance | |
for (auto cmpId : entityMgr.getAllEntityComponents(animInst.entityId)) { | |
if (cmpId.typeId == MeshNodeAnimation::componentType) { | |
auto& nodeAnimCmp = entityMgr.getComponent<MeshNodeAnimation>(cmpId); | |
NodeAnimationTransform trackTransforms[MAX_MESH_ANIMATION_TRACKS + 1] = {}; | |
float totalWeight = 0.0f; | |
int numAnimationsBlended = 0; | |
// for each animation track | |
for (uint32_t a = 0; a < mesh.getAnimations().numAnimationTracks; ++a) { | |
float animTime = animInst.component.animation[a].nextAnimationTime; | |
float animWeight = animInst.component.animation[a].nextAnimationWeight; | |
if (animWeight <= 0.0f) { | |
continue; // animation is disabled | |
} | |
auto& track = mesh.getAnimations().animations[a]; | |
// find the node animation that targets this node | |
for (uint32_t na = track.nodeAnimationsIndexOffset; na < track.nodeAnimationsIndexOffset + track.numNodeAnimations; ++na) { | |
auto& nodeAnim = mesh.getAnimations().nodeAnimations[na]; | |
if (nodeAnim.sceneNodeIndex == nodeAnimCmp.nodeIndex) { | |
trackTransforms[numAnimationsBlended] = getNodeTransformForTrack(mesh.getAnimations(), track, nodeAnim, animTime); | |
trackTransforms[numAnimationsBlended].weight = animWeight; | |
++numAnimationsBlended; | |
totalWeight += animWeight; | |
break; | |
} | |
} | |
} | |
nodeAnimCmp.prevActive = nodeAnimCmp.nextActive; | |
if (nodeAnimCmp.prevActive == 1) { | |
nodeAnimCmp.prevTranslationLocal = nodeAnimCmp.nextTranslationLocal; | |
nodeAnimCmp.prevRotationLocal = nodeAnimCmp.nextRotationLocal; | |
nodeAnimCmp.prevScalingLocal = nodeAnimCmp.nextScalingLocal; | |
} | |
if (numAnimationsBlended == 0) { | |
nodeAnimCmp.nextActive = 0; | |
} | |
// blend this nodes's contributions from all animation tracks | |
else { | |
nodeAnimCmp.nextActive = 1; | |
// if total weight < 1, get the remainder from the default transform | |
if (1.0f - totalWeight > epsilon<float>()) { | |
trackTransforms[numAnimationsBlended].translation = nodeAnimCmp.defaultTranslation; | |
trackTransforms[numAnimationsBlended].rotation = nodeAnimCmp.defaultRotation; | |
trackTransforms[numAnimationsBlended].scaling = nodeAnimCmp.defaultScaling; | |
trackTransforms[numAnimationsBlended].weight = 1.0f - totalWeight; | |
++numAnimationsBlended; | |
} | |
if (numAnimationsBlended == 1) { | |
nodeAnimCmp.nextTranslationLocal = trackTransforms[0].translation; | |
nodeAnimCmp.nextRotationLocal = trackTransforms[0].rotation; | |
nodeAnimCmp.nextScalingLocal = trackTransforms[0].scaling; | |
} | |
else { | |
// blend all animations | |
for (int t = 1; t < numAnimationsBlended; ++t) { | |
float relativeWeight = trackTransforms[t - 1].weight / (trackTransforms[t - 1].weight + trackTransforms[t].weight); | |
trackTransforms[t].translation = mix(trackTransforms[t - 1].translation, trackTransforms[t].translation, relativeWeight); | |
trackTransforms[t].rotation = normalize(lerp(trackTransforms[t - 1].rotation, trackTransforms[t].rotation, relativeWeight)); | |
trackTransforms[t].scaling = mix(trackTransforms[t - 1].scaling, trackTransforms[t].scaling, relativeWeight); | |
trackTransforms[t].weight = trackTransforms[t - 1].weight + trackTransforms[t].weight; | |
} | |
nodeAnimCmp.nextTranslationLocal = trackTransforms[numAnimationsBlended - 1].translation; | |
nodeAnimCmp.nextRotationLocal = trackTransforms[numAnimationsBlended - 1].rotation; | |
nodeAnimCmp.nextScalingLocal = trackTransforms[numAnimationsBlended - 1].scaling; | |
} | |
} | |
// TODO: this should probably go into the render function??? | |
/* | |
// compose the transform matrix | |
nodeTransform = mat4_cast(nodeRotation); | |
nodeTransform[3].xyz = nodeTranslation; | |
nodeTransform = scale(nodeTransform, nodeScale); | |
*/ | |
} | |
} | |
} | |
} |