Created
December 19, 2024 14:35
-
-
Save Kirandeep-Singh-Khehra/8c0af6079f41efcc3162a9e9c9badc83 to your computer and use it in GitHub Desktop.
Simple implementation of multiple animation blend and helper function for Raylib 3d ModelAnimation.
This file contains hidden or 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
/* | |
Simple functon to blend n different RayLib 3d ModelAnimations. This function just update bones. So, skinning shader is required. | |
*/ | |
#ifndef _KIRAN_RAY_MODELANIMATION_ADV_ | |
#define _KIRAN_RAY_MODELANIMATION_ADV_ | |
#include "raylib.h" | |
#include "rlgl.h" | |
#include "raymath.h" | |
#include <stdlib.h> | |
#include <assert.h> | |
// DECLARATIONS // | |
#define ROLL(x, min, max) ((x) < (min) ? (max) - ((min) - (x)) % ((max) - (min)) : (min) + ((x) - (min)) % ((max) - (min))) | |
void UpdateModelAnimationBonesPro(Model model, int N, ...); | |
void UpdateModelVertsToCurrentBones(Model model); | |
// IMPLEMENTATIONS // | |
void UpdateModelAnimationBonesPro(Model model, int N, ...) | |
{ | |
// Early stop | |
if (N < 1) { | |
return; | |
} | |
// Store args in readable format | |
ModelAnimation anims[N]; | |
int frame[N]; | |
float weight[N]; // `float` as second argument to `va_arg(arg, float)` is of promotable type `float` and will be promoted to `double` | |
va_list args; | |
va_start(args, N); | |
for (int animIndex = 0; animIndex < N; animIndex ++) { | |
anims[animIndex] = va_arg(args, ModelAnimation); | |
frame[animIndex] = va_arg(args, int); | |
weight[animIndex] = (float)va_arg(args, double); | |
} | |
va_end(args); | |
for (int animIndex = 0; animIndex < N; animIndex ++) { | |
if ((anims[animIndex].frameCount > 0) && (anims[animIndex].bones != NULL) && (anims[animIndex].framePoses != NULL)) { | |
frame[animIndex] = ROLL(frame[animIndex], 0, anims[animIndex].frameCount); | |
} else { | |
return; | |
} | |
} | |
for (int i = 0; i < model.meshCount; i++) | |
{ | |
if (model.meshes[i].boneMatrices) | |
{ | |
for (int animIndex = 0; animIndex < N; animIndex ++) { | |
assert(model.meshes[i].boneCount == anims[animIndex].boneCount); | |
} | |
for (int boneId = 0; boneId < model.meshes[i].boneCount; boneId++) | |
{ | |
Vector3 inTranslation = model.bindPose[boneId].translation; | |
Quaternion inRotation = model.bindPose[boneId].rotation; | |
Vector3 inScale = model.bindPose[boneId].scale; | |
Vector3 outTranslation = anims[0].framePoses[frame[0]][boneId].translation; | |
Quaternion outRotation = anims[0].framePoses[frame[0]][boneId].rotation; | |
Vector3 outScale = anims[0].framePoses[frame[0]][boneId].scale; | |
Vector3 outNTranslation; | |
Quaternion outNRotation; | |
Vector3 outNScale; | |
float blendFactor = weight[0]; | |
for (int animIndex = 1; animIndex < N; animIndex ++) { | |
if (weight[animIndex] == 0) continue; // otherwise if first 2 weights are 0 then new blendFactor second time will be nan | |
outNTranslation = anims[animIndex].framePoses[frame[animIndex]][boneId].translation; | |
outNRotation = anims[animIndex].framePoses[frame[animIndex]][boneId].rotation; | |
outNScale = anims[animIndex].framePoses[frame[animIndex]][boneId].scale; | |
blendFactor = weight[animIndex] / (weight[animIndex] + blendFactor); | |
outTranslation = Vector3Lerp(outTranslation, outNTranslation, blendFactor); | |
outRotation = QuaternionSlerp(outRotation, outNRotation, blendFactor); | |
outScale = Vector3Lerp(outScale, outNScale, blendFactor); | |
} | |
Vector3 invTranslation = Vector3RotateByQuaternion(Vector3Negate(inTranslation), QuaternionInvert(inRotation)); | |
Quaternion invRotation = QuaternionInvert(inRotation); | |
Vector3 invScale = Vector3Divide((Vector3){ 1.0f, 1.0f, 1.0f }, inScale); | |
Vector3 boneTranslation = Vector3Add( | |
Vector3RotateByQuaternion(Vector3Multiply(outScale, invTranslation), | |
outRotation), outTranslation); | |
Quaternion boneRotation = QuaternionMultiply(outRotation, invRotation); | |
Vector3 boneScale = Vector3Multiply(outScale, invScale); | |
Matrix boneMatrix = MatrixMultiply(MatrixMultiply( | |
QuaternionToMatrix(boneRotation), | |
MatrixTranslate(boneTranslation.x, boneTranslation.y, boneTranslation.z)), | |
MatrixScale(boneScale.x, boneScale.y, boneScale.z)); | |
model.meshes[i].boneMatrices[boneId] = boneMatrix; | |
} | |
} | |
} | |
} | |
// Update model vertex data (positions and normals) from mesh bone data | |
// NOTE: Updated data is uploaded to GPU | |
void UpdateModelVertsToCurrentBones(Model model) | |
{ | |
for (int m = 0; m < model.meshCount; m++) | |
{ | |
Mesh mesh = model.meshes[m]; | |
Vector3 animVertex = { 0 }; | |
Vector3 animNormal = { 0 }; | |
int boneId = 0; | |
int boneCounter = 0; | |
float boneWeight = 0.0; | |
bool updated = false; // Flag to check when anim vertex information is updated | |
const int vValues = mesh.vertexCount*3; | |
for (int vCounter = 0; vCounter < vValues; vCounter += 3) | |
{ | |
mesh.animVertices[vCounter] = 0; | |
mesh.animVertices[vCounter + 1] = 0; | |
mesh.animVertices[vCounter + 2] = 0; | |
if (mesh.animNormals != NULL) | |
{ | |
mesh.animNormals[vCounter] = 0; | |
mesh.animNormals[vCounter + 1] = 0; | |
mesh.animNormals[vCounter + 2] = 0; | |
} | |
// Iterates over 4 bones per vertex | |
for (int j = 0; j < 4; j++, boneCounter++) | |
{ | |
boneWeight = mesh.boneWeights[boneCounter]; | |
boneId = mesh.boneIds[boneCounter]; | |
// Early stop when no transformation will be applied | |
if (boneWeight == 0.0f) continue; | |
animVertex = (Vector3){ mesh.vertices[vCounter], mesh.vertices[vCounter + 1], mesh.vertices[vCounter + 2] }; | |
animVertex = Vector3Transform(animVertex,model.meshes[m].boneMatrices[boneId]); | |
mesh.animVertices[vCounter] += animVertex.x*boneWeight; | |
mesh.animVertices[vCounter+1] += animVertex.y*boneWeight; | |
mesh.animVertices[vCounter+2] += animVertex.z*boneWeight; | |
updated = true; | |
// Normals processing | |
// NOTE: We use meshes.baseNormals (default normal) to calculate meshes.normals (animated normals) | |
if (mesh.normals != NULL) | |
{ | |
animNormal = (Vector3){ mesh.normals[vCounter], mesh.normals[vCounter + 1], mesh.normals[vCounter + 2] }; | |
animNormal = Vector3Transform(animNormal,model.meshes[m].boneMatrices[boneId]); | |
mesh.animNormals[vCounter] += animNormal.x*boneWeight; | |
mesh.animNormals[vCounter + 1] += animNormal.y*boneWeight; | |
mesh.animNormals[vCounter + 2] += animNormal.z*boneWeight; | |
} | |
} | |
} | |
if (updated) | |
{ | |
rlUpdateVertexBuffer(mesh.vboId[0], mesh.animVertices, mesh.vertexCount*3*sizeof(float), 0); // Update vertex position | |
rlUpdateVertexBuffer(mesh.vboId[2], mesh.animNormals, mesh.vertexCount*3*sizeof(float), 0); // Update vertex normals | |
} | |
} | |
} | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Sample usage
Try to use GPU skinning. Or call the following method to update model vertices(CPU side).