Skip to content

Instantly share code, notes, and snippets.

@mlfarrell
Created October 21, 2020 20:13
Show Gist options
  • Save mlfarrell/a53e7c8b17f19d6a4e47fe2dc01e48ce to your computer and use it in GitHub Desktop.
Save mlfarrell/a53e7c8b17f19d6a4e47fe2dc01e48ce to your computer and use it in GitHub Desktop.
Skeletal animation
static const int currentAnimation = 0;
typedef unordered_map<aiString, const aiNodeAnim *> NodeAnimMap;
void EntityModel::boneAnimTransform(float timeInSeconds, vutil::GrowableArray *transforms, aiScene *aiSceneObj, const float16 *globalInverseTransform, bool loop)
{
const aiScene *scene = aiSceneObj;
mat4 identity = mat4Identity;
if(!nodeAnimMapPtr)
{
nodeAnimMapPtr = new NodeAnimMap();
}
//NSLog(@"%d", scene->mNumAnimations);
float ticksPerSecond = (float)(scene->mAnimations[currentAnimation]->mTicksPerSecond != 0 ? scene->mAnimations[currentAnimation]->mTicksPerSecond : 25.0f);
float timeInTicks = timeInSeconds * ticksPerSecond;
float animationTime = timeInTicks;
if(loop)
animationTime = fmod(animationTime, (float)scene->mAnimations[currentAnimation]->mDuration);
else
animationTime = min<float>(animationTime, scene->mAnimations[currentAnimation]->mDuration-0.0001f);
mat4 *globalInverse = (mat4 *)globalInverseTransform;
readAnimNodeHeirarchy(animationTime, scene->mRootNode, identity, scene, *globalInverse);
if(transforms->getCount() == 0)
transforms->addElements((int)boneMapping().size());
for(uint i = 0; i < boneMapping().size(); i++)
{
memcpy(transforms->elementAtIndex(i), &((BoneTransformInfo *)editBoneTransforms->elementAtIndex(i))->finalTransform, sizeof(float16));
}
}
static const aiNodeAnim *findNodeAnim(const aiAnimation *pAnimation, const aiString &NodeName, NodeAnimMap &nodeMap)
{
auto iter = nodeMap.find(NodeName);
if(iter != nodeMap.end())
{
return iter->second;
}
for(uint i = 0; i < pAnimation->mNumChannels; i++)
{
const aiNodeAnim *pNodeAnim = pAnimation->mChannels[i];
if(pNodeAnim->mNodeName == NodeName)
{
nodeMap[NodeName] = pNodeAnim;
return pNodeAnim;
}
}
nodeMap[NodeName] = NULL;
return NULL;
}
static uint findPosition(float AnimationTime, const aiNodeAnim* pNodeAnim)
{
for (uint i = 0 ; i < pNodeAnim->mNumPositionKeys - 1 ; i++) {
if (AnimationTime < (float)pNodeAnim->mPositionKeys[i + 1].mTime) {
return i;
}
}
assert(0);
return 0;
}
static uint findRotation(float AnimationTime, const aiNodeAnim* pNodeAnim)
{
assert(pNodeAnim->mNumRotationKeys > 0);
for (uint i = 0 ; i < pNodeAnim->mNumRotationKeys - 1 ; i++) {
if (AnimationTime < (float)pNodeAnim->mRotationKeys[i + 1].mTime) {
return i;
}
}
assert(0);
return 0;
}
static uint findScaling(float AnimationTime, const aiNodeAnim* pNodeAnim)
{
assert(pNodeAnim->mNumScalingKeys > 0);
for (uint i = 0 ; i < pNodeAnim->mNumScalingKeys - 1 ; i++) {
if (AnimationTime < (float)pNodeAnim->mScalingKeys[i + 1].mTime) {
return i;
}
}
assert(0);
return 0;
}
static float clamp(float f)
{
return (f < 0.0f) ? 0.0f : ((f > 1.0f) ? 1.0f : f);
}
static void calcInterpolatedPosition(aiVector3D &Out, float AnimationTime, const aiNodeAnim *pNodeAnim)
{
if(pNodeAnim->mNumPositionKeys == 1)
{
Out = pNodeAnim->mPositionKeys[0].mValue;
return;
}
uint PositionIndex = findPosition(AnimationTime, pNodeAnim);
uint NextPositionIndex = (PositionIndex + 1);
assert(NextPositionIndex < pNodeAnim->mNumPositionKeys);
float DeltaTime = (float)(pNodeAnim->mPositionKeys[NextPositionIndex].mTime - pNodeAnim->mPositionKeys[PositionIndex].mTime);
float Factor = clamp((AnimationTime - (float)pNodeAnim->mPositionKeys[PositionIndex].mTime) / DeltaTime);
const aiVector3D& Start = pNodeAnim->mPositionKeys[PositionIndex].mValue;
const aiVector3D& End = pNodeAnim->mPositionKeys[NextPositionIndex].mValue;
aiVector3D Delta = End - Start;
Out = Start + Factor * Delta;
}
static void calcInterpolatedRotation(aiQuaternion &Out, float AnimationTime, const aiNodeAnim *pNodeAnim)
{
// we need at least two values to interpolate...
if (pNodeAnim->mNumRotationKeys == 1) {
Out = pNodeAnim->mRotationKeys[0].mValue;
return;
}
uint RotationIndex = findRotation(AnimationTime, pNodeAnim);
uint NextRotationIndex = (RotationIndex + 1);
assert(NextRotationIndex < pNodeAnim->mNumRotationKeys);
float DeltaTime = (float)(pNodeAnim->mRotationKeys[NextRotationIndex].mTime - pNodeAnim->mRotationKeys[RotationIndex].mTime);
float Factor = clamp((AnimationTime - (float)pNodeAnim->mRotationKeys[RotationIndex].mTime) / DeltaTime);
const aiQuaternion& StartRotationQ = pNodeAnim->mRotationKeys[RotationIndex].mValue;
const aiQuaternion& EndRotationQ = pNodeAnim->mRotationKeys[NextRotationIndex].mValue;
aiQuaternion::Interpolate(Out, StartRotationQ, EndRotationQ, Factor);
Out = Out.Normalize();
}
static void calcInterpolatedScaling(aiVector3D &Out, float AnimationTime, const aiNodeAnim* pNodeAnim)
{
if (pNodeAnim->mNumScalingKeys == 1) {
Out = pNodeAnim->mScalingKeys[0].mValue;
return;
}
uint ScalingIndex = findScaling(AnimationTime, pNodeAnim);
uint NextScalingIndex = (ScalingIndex + 1);
assert(NextScalingIndex < pNodeAnim->mNumScalingKeys);
float DeltaTime = (float)(pNodeAnim->mScalingKeys[NextScalingIndex].mTime - pNodeAnim->mScalingKeys[ScalingIndex].mTime);
float Factor = clamp((AnimationTime - (float)pNodeAnim->mScalingKeys[ScalingIndex].mTime) / DeltaTime);
//assert(Factor >= 0.0f && Factor <= 1.0f);
const aiVector3D& Start = pNodeAnim->mScalingKeys[ScalingIndex].mValue;
const aiVector3D& End = pNodeAnim->mScalingKeys[NextScalingIndex].mValue;
aiVector3D Delta = End - Start;
Out = Start + Factor * Delta;
}
void EntityModel::readAnimNodeHeirarchy(float animationTime, const aiNode *pNode, const mat4 &parentTransform, const aiScene *scene, const mat4 &globalInverseTransform)
{
const aiString &nodeName = pNode->mName;
const aiAnimation *pAnimation = scene->mAnimations[currentAnimation];
mat4 nodeTransformation;
aiMatrix4x4 nodeTrans = pNode->mTransformation;
memcpy(&nodeTransformation, &(nodeTrans.Transpose()), sizeof(float16));
const aiNodeAnim *pNodeAnim = findNodeAnim(pAnimation, nodeName, *((NodeAnimMap *)nodeAnimMapPtr));
if(pNodeAnim)
{
// Interpolate scaling and generate scaling transformation matrix
aiVector3D scaling;
calcInterpolatedScaling(scaling, animationTime, pNodeAnim);
mat4 scalingM = mat4::makeScale(scaling.x, scaling.y, scaling.z);
// Interpolate rotation and generate rotation transformation matrix
aiQuaternion rotationQ;
calcInterpolatedRotation(rotationQ, animationTime, pNodeAnim);
quat q = quat(rotationQ.x, rotationQ.y, rotationQ.z, rotationQ.w);
mat4 rotationM = mat4(q);
// Interpolate translation and generate translation transformation matrix
aiVector3D translation;
calcInterpolatedPosition(translation, animationTime, pNodeAnim);
mat4 translationM = mat4::makeTranslation(translation.x, translation.y, translation.z);
// Combine the above transformations
nodeTransformation = (translationM * rotationM) * scalingM;
}
mat4 globalTransformation = parentTransform * nodeTransformation;
auto iter = boneMapping().find(nodeName);
if(iter != boneMapping().end())
{
uint boneIndex = iter->second;
BoneTransformInfo *boneInfo = (BoneTransformInfo *)editBoneTransforms->elementAtIndex(boneIndex);
mat4 *boneOff = (mat4 *)&boneInfo->boneOffset;
mat4 trans = (globalInverseTransform * globalTransformation) * (*boneOff);
memcpy(&boneInfo->finalTransform, &trans, sizeof(float16));
}
for(uint i = 0; i < pNode->mNumChildren; i++)
{
readAnimNodeHeirarchy(animationTime, pNode->mChildren[i], globalTransformation, scene, globalInverseTransform);
}
}
BoneMap &EntityModel::boneMapping()
{
if(!boneMappingPtr)
boneMappingPtr = new BoneMap();
return *((BoneMap *)boneMappingPtr);
}
size_t EntityModel::boneTransformsSize()
{
return boneMapping().size();
}
void EntityModel::updateBoneAnimVBOs(vgl::BufferArray *fromBuffer, int fromBufferIndex)
{
if(editBoneData && editBoneData->getCount())
{
if(!fromBuffer)
{
boneBuffers = make_shared<vgl::BufferArray>();
boneBuffers->provideData(0, sizeof(BoneData) * editBoneData->getCount(), editBoneData->getData());
}
else
{
boneBuffers = make_shared<vgl::BufferArray>();
fromBuffer->copyDataTo(boneBuffers.get(), 0, 0, sizeof(BoneData) * editBoneData->getCount());
}
setupVAO();
}
}
void SkeletalAnimationShaderEffect::setupBoneTransformUniformLocations(vutil::GrowableArray::Pointer bt)
{
if(uniformBoneTransformLocations)
free(uniformBoneTransformLocations);
uniformBoneTransformLocations = (GLint *)malloc(sizeof(GLint)*MAX_BONES*2);
for(int i = 0; i < MAX_BONES; i++)
{
ostringstream ostr;
ostr << "bones[" << i << "]";
auto name = ostr.str();
GLint loc = glGetUniformLocation(shaderProgram, name.c_str());
uniformBoneTransformLocations[i] = loc;
loc = glGetUniformLocation(lightRenderShaderProgram, name.c_str());
uniformBoneTransformLocations[MAX_BONES+i] = loc;
}
}
//...
void SkeletalAnimationShaderEffect::prepareToDrawFromState(vgl::StateMachine *machine)
{
//...
glBindBufferBase(GL_UNIFORM_BUFFER, 0, boneTransformsUBO);
glUniformBlockBinding(shaderProgram, boneTransformsUniformBlockIndex, 0);
UserShaderEffect::prepareToDrawFromState(machine);
}
void SkeletalAnimationShaderEffect::setBoneTransforms(vutil::GrowableArray::Pointer bt)
{
boneTransforms = bt;
glBindBuffer(GL_UNIFORM_BUFFER, boneTransformsUBO);
glBufferData(GL_UNIFORM_BUFFER, boneTransforms->getCount()*boneTransforms->getElementSize(), boneTransforms->getData(), GL_DYNAMIC_DRAW);
//setupBoneTransformUniformLocations(bt);
}
//...
//Load skeletal animation bones
if(mesh->mNumBones)
{
if(!e.editBoneData)
e.editBoneData = make_shared<GrowableArray>(sizeof(BoneData));
e.editBoneData->addElements(mesh->mNumVertices);
for(int j = 0; j < mesh->mNumVertices; j++)
{
BoneData *bdata = (BoneData *)e.editBoneData->elementAtIndex(baseVertex+j);
memset(bdata, 0, sizeof(BoneData));
}
}
//...
in vec4 position;
in vec3 normal;
in vec2 texcoord0;
in ivec4 boneIDs;
in vec4 weights;
#ifdef SSAO
out mediump vec4 va_pos_proj;
#endif
out vec2 varTexcoord;
out vec3 varNormal;
out mediump vec4 ec_pos;
out float shadowBias;
out vec4 shadowCoord;
#ifdef SECOND_SHADOW
out vec4 secondShadowCoord;
#endif
uniform float shadowBiasFactor;
const int MAX_BONES = 145;
layout (std140) uniform InstancedBones
{
mat4 bones[MAX_BONES];
};
uniform highp mat4 modelViewProjectionMatrix;
uniform highp mat4 modelViewMatrix;
uniform mat3 normalMatrix;
struct Light
{
mediump vec4 position;
lowp vec4 ambient, diffuse, specular;
mediump vec3 spotDirection;
float spotExponent, spotCosCutoff;
float constantAttenuation, linearAttenuation, quadraticAttenuation;
mat4 biasShadowMatrix;
};
#ifdef SECOND_SHADOW
uniform mat4 secondBiasShadowMatrix;
#endif
uniform Light lights[8];
//debug
//out vec3 transformedPosition;
void main()
{
mat4 boneTransform = bones[boneIDs[0]] * weights[0];
boneTransform += bones[boneIDs[1]] * weights[1];
boneTransform += bones[boneIDs[2]] * weights[2];
boneTransform += bones[boneIDs[3]] * weights[3];
vec4 norm = boneTransform * vec4(normal, 0.0);
varTexcoord = texcoord0;
varNormal = normalMatrix * norm.xyz;
vec4 pos = boneTransform * position;
//Compute vert position in eye coordinates
ec_pos = (modelViewMatrix * pos);
shadowBias = shadowBiasFactor;
shadowCoord = lights[0].biasShadowMatrix * pos;
#ifdef SECOND_SHADOW
secondShadowCoord = secondBiasShadowMatrix * pos;
#endif
#ifdef SSAO
va_pos_proj = modelViewProjectionMatrix * pos;
gl_Position = va_pos_proj;
#else
gl_Position = modelViewProjectionMatrix * pos;
#endif
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment