Skip to content

Commit

Permalink
Improved animation blending by adding bidirectionality and queue
Browse files Browse the repository at this point in the history
  • Loading branch information
patricklbell committed Oct 18, 2022
1 parent 4619c5f commit 31aff45
Show file tree
Hide file tree
Showing 9 changed files with 306 additions and 220 deletions.
1 change: 1 addition & 0 deletions include/assets.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ struct AnimatedMesh {

std::unordered_map<std::string, Animation> name_animation_map;
};
float getAnimationDuration(const AnimatedMesh &animesh, const std::string& name);

void tickBonesKeyframe(AnimatedMesh::BoneKeyframes& keyframes, float time, bool looping);

Expand Down
89 changes: 50 additions & 39 deletions include/entities.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ struct Entity {


struct MeshEntity : Entity {
glm::vec3 position = glm::vec3(0.0);
glm::quat rotation = glm::quat(0.0,0.0,0.0,1.0);
glm::mat3 scale = glm::mat3(1.0);
glm::vec3 position = glm::vec3(0.0);
glm::quat rotation = glm::quat(0.0, 0.0, 0.0, 1.0);
glm::mat3 scale = glm::mat3(1.0);

// @editor
glm::vec3 gizmo_position_offset = glm::vec3(0.0);
Expand All @@ -59,7 +59,7 @@ struct MeshEntity : Entity {

uint8_t casts_shadow = true;

MeshEntity(Id _id=NULLID) : Entity(_id){
MeshEntity(Id _id = NULLID) : Entity(_id) {
type = EntityType::MESH_ENTITY;
}
};
Expand All @@ -74,43 +74,55 @@ struct AnimatedMeshEntity : MeshEntity {
// @editor Used by editor to toggle drawing animation, ignored when playing
bool draw_animated = false;

//
// Animation state machine
//
bool loop = false;
bool playing = false;
float current_time = 0.0f;
float time_scale = 1.0f;
AnimatedMesh::Animation* animation = nullptr;
// Global multiplier for all animations, here for convenience
float time_scale_mult = 1.0;

//
// Animation blending
// Animation state machine
//
bool blending = false;
float current_bias = 0.0f;
float bias = 0.5f;
float current_time_blend_to = 0.0f;
float time_scale_blend_to = 0.0f;
AnimatedMesh::Animation* animation_blend_to = nullptr;
// Transforms to apply to animation which we are blending to, this is useful
// if there is some transform coming when animation finishes, eg turning, stepping
glm::mat4 transform_blend_to;
// These are the transforms that are actually applied after animation finishes blending
glm::vec3 position_blend_to;
glm::quat rotation_blend_to;
// If there are multiple events we need to keep the previous one buffered for
// blending, this flag determines wheter event 0 is playing
bool playing_first = true;
struct AnimationEvent {
bool playing = true;
bool loop = false;

float start_time = 0.0f;
float current_time = 0.0f;
float duration = 1.0f;
float time_scale = 1.0f;

AnimatedMesh::Animation* animation = nullptr;

// Blending with previous and next animation/default state
bool blend = false;
float blend_prev_end = 0.1;
float blend_prev_amount = 0.5;
float blend_next_start = 0.9;
float blend_next_amount = 0.5;

// Transforms to apply during animation
glm::vec3 delta_position = glm::vec3(0.0);
glm::quat delta_rotation = glm::quat();
glm::mat4 delta_transform = glm::mat4(1.0f); // This must be updated or transform won't properly be blended

bool transform_animation = false;
// Whether to apply these transforms to entity when animation finishes
bool transform_entity = false;
bool transform_inverted = false; // Used for blending with previous
};
std::vector<AnimationEvent> animation_events;
AnimationEvent default_event;

AnimatedMeshEntity(Id _id = NULLID) : MeshEntity(_id) {
type = EntityType::ANIMATED_MESH_ENTITY;
}

bool tick(float dt);
void init();
bool play(const std::string& name, float start_time, float _time_scale, bool _loop);
bool playBlended(const std::string& name1, float start_time1, float _time_scale1,
const std::string& name2, float start_time2, float _time_scale2,
glm::vec3 delta_pos, glm::quat delta_rot,
float _bias, bool _loop);
float getAnimationDuration(const std::string& name);
AnimationEvent *play(const std::string& name, float start_time = 0.0f, bool fallback = false, bool immediate = false, bool playing = true);
bool isAnimationFinished();
bool isDefaultAnimationFinished();
};


Expand Down Expand Up @@ -151,22 +163,21 @@ struct VegetationEntity : MeshEntity {


enum class PlayerActionType : uint64_t {
STEP_FORWARD = 0,
NONE = 0,
STEP_FORWARD,
TURN_LEFT,
TURN_RIGHT,
};

struct PlayerAction {
PlayerActionType type;
PlayerActionType type = PlayerActionType::NONE;

glm::vec3 beg_position;
glm::quat beg_rotation;
glm::vec3 delta_position;
glm::quat delta_rotation;
glm::fvec3 delta_position = glm::vec3(0.0);
glm::fquat delta_rotation = glm::quat();

bool active = false;
float duration;
float time;
float duration = 0.0f;
float time = 0.0f;
};

struct PlayerEntity : AnimatedMeshEntity {
Expand Down
11 changes: 11 additions & 0 deletions src/assets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1326,6 +1326,17 @@ AnimatedMesh* AssetManager::createAnimatedMesh(const std::string& handle) {
return animesh;
}

float getAnimationDuration(const AnimatedMesh& animesh, const std::string& name) {
auto lu = animesh.name_animation_map.find(name);
if (lu == animesh.name_animation_map.end()) {
std::cerr << "Failed to get animation duration " << name << " because it wasn't loaded\n"; // @debug
return 0.0;
}
else {
return lu->second.duration / lu->second.ticks_per_second;
}
}

//
// --------------------------------- Textures ----------------------------------
//
Expand Down
2 changes: 1 addition & 1 deletion src/controls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,7 @@ void handleGameControls(EntityManager* &entity_manager, AssetManager& asset_mana
// Handle player controls
if (entity_manager->player != NULLID) {
auto player = (PlayerEntity*)entity_manager->getEntity(entity_manager->player);
if (player != nullptr && glfwGetTime() - last_input_time > 3*dt) {
if (player != nullptr) {
if (glfwGetKey(window, GLFW_KEY_LEFT)) {
last_input_time = glfwGetTime();
player->turn_left();
Expand Down
24 changes: 12 additions & 12 deletions src/editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1653,7 +1653,7 @@ void drawEditorGui(EntityManager &entity_manager, AssetManager &asset_manager){
entity_type = lu->second;
}

ImGui::TextWrapped("Entity Index: %d Version: %d, %s", selection.ids[0].i, selection.ids[0].v, entity_type.c_str());
ImGui::TextWrapped("Entity Index: %d Version: %d, %s", (int)selection.ids[0].i, (int)selection.ids[0].v, entity_type.c_str());
}
else
ImGui::TextWrapped("Multiple Entities Selected");
Expand Down Expand Up @@ -1853,16 +1853,16 @@ void drawEditorGui(EntityManager &entity_manager, AssetManager &asset_manager){
auto a_e = (AnimatedMeshEntity*)fe;

if (ImGui::CollapsingHeader("Animations")) {
if (a_e->animesh != NULL) {
std::string animation_name = a_e->animation == NULL ? "None" : a_e->animation->name;
if (a_e->animesh != nullptr) {
std::string animation_name = a_e->default_event.animation == nullptr ? "None" : a_e->default_event.animation->name;
ImGui::SetNextItemWidth(ImGui::GetWindowWidth() - 2.0f * pad);
if (ImGui::BeginCombo("##asset-combo", animation_name.c_str())) {
for(auto &p : a_e->animesh->name_animation_map){
bool is_selected = (&p.second == a_e->animation);
for (auto& p : a_e->animesh->name_animation_map) {
bool is_selected = (&p.second == a_e->default_event.animation);
if (ImGui::Selectable(p.first.c_str(), is_selected))
a_e->animation = &p.second;
a_e->default_event.animation = &p.second;
if (is_selected)
ImGui::SetItemDefaultFocus();
ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
Expand All @@ -1873,14 +1873,14 @@ void drawEditorGui(EntityManager &entity_manager, AssetManager &asset_manager){
float time_scale = 1.0f;
bool loop = false;
bool playing = false;
if (a_e->animation != NULL) {
if (a_e->default_event.animation != nullptr) {
ImGui::Checkbox("Draw Animated: ", &a_e->draw_animated);
if (ImGui::SliderFloat("Current Time: ", &a_e->current_time, 0.0f, a_e->animation->duration, "%.3f")) {
if (ImGui::SliderFloat("Current Time: ", &a_e->default_event.current_time, 0.0f, a_e->default_event.animation->duration, "%.3f")) {
a_e->init();
}
ImGui::SliderFloat("Time Scale: ", &a_e->time_scale, -10.0f, 10.0f, "%.3f");
ImGui::Checkbox("Loop: ", &a_e->loop);
ImGui::Checkbox("Playing: ", &a_e->playing);
ImGui::SliderFloat("Time Scale: ", &a_e->default_event.time_scale, -10.0f, 10.0f, "%.3f");
ImGui::Checkbox("Loop: ", &a_e->default_event.loop);
ImGui::Checkbox("Playing: ", &a_e->default_event.playing);
}
}
}
Expand Down
Loading

0 comments on commit 31aff45

Please sign in to comment.