diff --git a/include/Animation.h b/include/Animation.h new file mode 100644 index 0000000..5522e32 --- /dev/null +++ b/include/Animation.h @@ -0,0 +1,74 @@ +#ifndef ANIMATION_H +#define ANIMATION_H + +#include "Model.h" + +#include // Время + +// Тип интерполяции +enum INTERPOLIATION_TYPE +{ + STEP, + LINEAR, + CUBICSPLINE +}; + +// Анимируемый параметр +enum TARGET_PATH +{ + POSITION, + ROTATION, + SCALE +}; + +// Поведение при завершении шкалы ключевых кадров +enum ANIM_ENDINGS +{ + STOP, + TO_BEGIN, + CYCLED +}; + +union PARAMETER_TYPE +{ + glm::quat quat; + glm::vec3 vec3; +}; + +// Касательные для кубической-сплайн интерполяции +struct Tangents +{ + PARAMETER_TYPE in; + PARAMETER_TYPE out; +}; + +// Канал анимации +struct Channel +{ + void process(float dtime, ANIM_ENDINGS endings); // Выполнить анимацию для канала с учетом времени относительно начала + + class Node* target = NULL; // Анимируемый узел + TARGET_PATH path = POSITION; // Анимируемый параметр + + INTERPOLIATION_TYPE interpolation = STEP; // Тип интерполяции + std::vector timestamps; // Временные метки !ОБЯЗАТЕЛЬНО ОТСОРТИРОВАНЫ ПО ВОЗРАСТАНИЮ! + std::vector values; // Данные по параметру (ИНДЕКС СООТВЕТСТВУЕТ ВРЕМЕННЫМ МЕТКАМ) + std::vector tangents; // Касательные для CUBICSPLINE +}; + +// Класс анимации +class Animation +{ + public: + void begin(); // Задает состояние анимации как начало через запоминание времени + void end(); // Заканчивает выполнение анимации + void process(); // Вычисляет анимацию для каждого канала на основании текущего времени + bool isEnabled(); // Возвращает состояние анимации (вкл/выкл) + std::vector channels; // Каналы анимации + ANIM_ENDINGS endings = CYCLED; + private: + std::chrono::steady_clock::time_point begin_time; // Время начала анимации + bool enabled = false; +}; + +#endif // ANIMATION_H diff --git a/src/Animation.cpp b/src/Animation.cpp new file mode 100644 index 0000000..9c2e725 --- /dev/null +++ b/src/Animation.cpp @@ -0,0 +1,137 @@ +#include "Animation.h" + +#include + +// Выполнить анимацию для канала с учетом времени относительно начала +void Channel::process(float dtime, ANIM_ENDINGS endings) +{ + // Если указатель на узел не пустой и есть ключевые кадры + if (target && timestamps.size() && timestamps.size() == values.size()) + { + // Если анимация зациклена + if (endings == CYCLED) + { + // Получаем общую длительность анимации + float totalDuration = timestamps.back(); + // Обновляем dtime для зацикливания + if (totalDuration > 0) + { + dtime = fmod(dtime, totalDuration); + } + } + + // Итоговое значение параметра + PARAMETER_TYPE parameterValue; + // Если только один кадр, используем значение этого кадра + if (timestamps.size() == 1) + { + // Применяем значение к целевому узлу + parameterValue = values[0]; + } + else + { + // Поиск подходящих индексов для интерполяции + size_t index1 = 0, index2 = 1; + for (; index2 < timestamps.size(); ++index2) + { + if (timestamps[index2] >= dtime) + { + break; + } + index1 = index2; + } + + // Если время выходит за рамки последнего кадра, используем значение в зависимости от поведения при окончании анимации + if (index2 == timestamps.size()) + { + if (endings == STOP) + parameterValue = values.back(); + else + if (endings == TO_BEGIN) + parameterValue = values.front(); + } + else + { + // Вычисляем коэффициент интерполяции + float t = (dtime - timestamps[index1]) / (timestamps[index2] - timestamps[index1]); + + // Выбор метода интерполяции и интерполяция + switch (interpolation) + { + case STEP: + parameterValue = values[index1]; + break; + case LINEAR: + if (path == ROTATION) + parameterValue.quat = glm::slerp(values[index1].quat, values[index2].quat, t); + else + parameterValue.vec3 = glm::lerp(values[index1].vec3, values[index2].vec3, t); + break; + case CUBICSPLINE: + float t2 = t*t; + float t3 = t2*t; + if (path == ROTATION) + { + // Обеспечение кратчайшего пути для интерполяции + glm::quat q2Adjusted = values[index2]. quat; + glm::quat t2Adjusted = tangents[index2].in. quat; + if (glm::dot(values[index1].quat, values[index2].quat) < 0) + { + q2Adjusted = -q2Adjusted; + t2Adjusted = -t2Adjusted; + } + parameterValue.quat = (2*t3 - 3*t2 + 1) * values[index1]. quat + + (t3 - 2*t2 + t) * tangents[index1].out.quat + + (-2*t3 + 3*t2) * q2Adjusted + + (t3 - t2) * t2Adjusted; + parameterValue.quat = glm::normalize(parameterValue.quat); + } + else + parameterValue.vec3 = (2*t3 - 3*t2 + 1) * values[index1]. vec3 + + (t3 - 2*t2 + t) * tangents[index1].out.vec3 + + (-2*t3 + 3*t2) * values[index2]. vec3 + + (t3 - t2) * tangents[index2].in. vec3; + break; + } + } + } + + if (path == POSITION) + target->e_position() = parameterValue.vec3; + if (path == ROTATION) + target->e_rotation() = parameterValue.quat; + if (path == SCALE) + target->e_scale() = parameterValue.vec3; + + } +} + +// Задает состояние анимации как начало +void Animation::begin() +{ + begin_time = std::chrono::steady_clock::now(); + enabled = true; +} + +// Заканчивает выполнение анимации +void Animation::end() +{ + enabled = false; +} + +// Возвращает состояние анимации (вкл/выкл) +bool Animation::isEnabled() +{ + return enabled; +} + +// Выполняет анимацию для всех каналов +void Animation::process() +{ + float dtime = std::chrono::duration(std::chrono::steady_clock::now() - begin_time).count() / 1000.0f; + for (Channel & channel : channels) + { + // channel.interpolation = STEP; + channel.process(dtime, endings); + } +}