Cogs.Core
AnimationSystem.cpp
1#include "AnimationSystem.h"
2
3#include "Context.h"
4
5#include "Resources/Animation.h"
6#include "Resources/Skeleton.h"
7#include "Resources/Mesh.h"
8
9#include "Systems/Core/TransformSystem.h"
10
11#include "Components/Core/SceneComponent.h"
12#include "Components/Core/MeshComponent.h"
13
14#include "Services/Time.h"
15#include "Services/Variables.h"
16
17#include "Utilities/Parallel.h"
18
19#include "Foundation/Logging/Logger.h"
20#include "Foundation/ComponentModel/Entity.h"
21
22#include <glm/gtx/compatibility.hpp>
23
24using namespace Cogs::Core;
25
26
27namespace
28{
29 Cogs::Logging::Log logger = Cogs::Logging::getLogger("AnimationSystem");
30}
31
32
33void Cogs::Core::updateSkeletonPose(Skeleton & skeleton)
34{
35 if (skeleton.bones.empty()) return;
36
37 for (auto & bone : skeleton.bones) {
38 bool isRoot = bone.parentBone == size_t(- 1);
39
40 if (isRoot) {
41 bone.absolute = bone.relative;
42 } else {
43 auto & parent = skeleton.bones[bone.parentBone];
44
45 bone.absolute = parent.absolute * bone.relative;
46 }
47 }
48}
49
50template<typename T, typename Collection>
51T evalTrack(Collection & keyFrames, T defaultValue, float time)
52{
53 if (keyFrames.empty()) return defaultValue;
54
55 size_t frame = 0;
56
57 for (; frame < keyFrames.size() - 1; ++frame) {
58 if (time < keyFrames[frame + 1].t) break;
59 }
60
61 T key = keyFrames[frame].value;
62
63 size_t nextFrame = frame + 1;
64 if (nextFrame >= keyFrames.size()) {
65 return key;
66 }
67
68 T nextKey = keyFrames[nextFrame].value;
69
70 const float range = keyFrames[frame + 1].t - keyFrames[frame].t;
71 const float diff = time - keyFrames[frame].t;
72
73 float v = diff / range;
74 v = glm::max(0.0f, glm::min(v, 1.0f));
75
76 return glm::lerp(key, nextKey, v);
77}
78
79template<typename T, typename Collection>
80T evalRotationTrack(Collection & keyFrames, T defaultValue, float time)
81{
82 if (keyFrames.empty()) return defaultValue;
83
84 size_t frame = 0;
85
86 for (; frame < keyFrames.size() - 1; ++frame) {
87 if (time < keyFrames[frame + 1].t) break;
88 }
89
90 T key = keyFrames[frame].value;
91
92 size_t nextFrame = frame + 1;
93 if (nextFrame >= keyFrames.size()) {
94 return key;
95 }
96
97 T nextKey = keyFrames[nextFrame].value;
98
99 const float range = keyFrames[frame + 1].t - keyFrames[frame].t;
100 const float diff = time - keyFrames[frame].t;
101
102 float v = diff / range;
103 v = glm::max(0.0f, glm::min(v, 1.0f));
104
105 return glm::slerp(key, nextKey, v);
106}
107
108void evalClip(Skeleton & skeleton, const AnimationClip & clip, AnimationCache & cache, float time)
109{
110 const auto numTracks = clip.tracks.size();
111 cache.tracks.resize(numTracks);
112
113 for (size_t j = 0; j < numTracks; ++j) {
114 auto & track = clip.tracks[j];
115
116 if (track.boneIndex == uint32_t(-1)) continue;
117
118 Bone & bone = skeleton.bones[track.boneIndex];
119 const auto position = evalTrack(track.translations, bone.pos, time);
120 const auto scale = evalTrack(track.scales, bone.scale, time);
121 const auto rotation = evalRotationTrack(track.rotations, bone.rot, time);
122
123 bone.relative = glm::translate(position) * glm::mat4_cast(rotation) * glm::scale(scale);
124 }
125}
126
127Entity * findEntityByName(const Cogs::StringView & name, Entity * entity)
128{
129 auto sceneComponent = entity->getComponent<SceneComponent>();
130
131 for (auto & child : sceneComponent->children) {
132 if (name == child->getName()) {
133 return child.get();
134 } else {
135 auto found = findEntityByName(name, child.get());
136
137 if (found) return found;
138 }
139 }
140
141 return nullptr;
142}
143
144void Cogs::Core::AnimationSystem::update(AnimationComponent & animationComponent, float globalTime)
145{
146 if (!animationComponent.animation) return;
147
148 auto & animationData = getData(&animationComponent);
149
150 auto animation = animationComponent.animation.resolve();
151
152 if (!animationData.skeleton) {
153 animationData.skeleton = std::make_unique<Skeleton>();
154 animationData.pose = std::make_unique<PoseData>();
155 }
156
157 if (animationData.skeleton->bones.empty()) {
158 *animationData.skeleton = animation->skeleton;
159
160 for (size_t i = 0; i < kMaxBones; ++i) {
161 animationData.pose->transforms[i] = glm::mat4(1.0f);
162 }
163 }
164
165 if (!animationComponent.master && animationComponent.clipIndex != -1) {
166 int clipIndex = animationComponent.clipIndex;
167 if (clipIndex < 0 || static_cast<size_t>(clipIndex) >= animation->clips.size()) {
168 LOG_WARNING(logger, "Animation clip index %d out of bounds.", clipIndex);
169 return;
170 }
171 auto & clip = animation->clips[clipIndex];
172
173 float time = (globalTime + animationComponent.timeOffset) * animationComponent.timeFactor * (clip.resolution ? clip.resolution : 1000.0f);
174 time = clip.duration != 0 ? std::fmod(time, clip.duration) : 0.0f;
175
176 auto & skeleton = *animationData.skeleton;
177
178 evalClip(skeleton, clip, animationData.cache, time);
179 updateSkeletonPose(skeleton);
180
181 animationData.transformCache.resize(skeleton.bones.size());
182
183 for (size_t i = 0; i < skeleton.bones.size(); ++i) {
184 auto & b = skeleton.bones[i];
185
186 if (b.hasOffset && b.used) {
187 b.absolute = skeleton.bindPose * b.absolute * b.inverseBindPose;
188 }
189
190 if (b.animated) {
191 if (animationData.root == nullptr) {
192 if (Component* masterAnim = animationComponent.master.resolve(); masterAnim) {
193 animationData.root = masterAnim->getContainer();
194 }
195 else {
196 animationData.root = animationComponent.getContainer();
197 }
198 }
199 assert(animationData.root);
200
201 TransformComponent* trComp = nullptr;
202 if (animationData.transformCache[i]) {
203 auto * comp = animationData.transformCache[i].resolveComponent<TransformComponent>();
204 if (animationData.transformCache[i].generation != comp->getGeneration()) {
205 // Stale handle
206 animationData.transformCache[i] = ComponentHandle::Empty();
207 }
208 else {
209 trComp = comp;
210 }
211 }
212 if (!trComp) {
213 if (auto e = findEntityByName(b.name, animationData.root); e) {
214 animationData.transformCache[i] = e->getComponentHandle<TransformComponent>();
215 trComp = animationData.transformCache[i].resolveComponent<TransformComponent>();
216 }
217 }
218 if(trComp) {
219 trComp->transform = b.relative;
220 trComp->transformFlags = 1;
221 trComp->setChanged();
222 }
223 }
224 }
225 }
226
227 auto * meshComponent = animationComponent.getComponent<MeshComponent>();
228 if (meshComponent && meshComponent->meshHandle) {
229 auto * mesh = meshComponent->meshHandle.resolve();
230
231 if (mesh->getPoseIndexes().size() > kMaxBones) {
232 LOG_WARNING(logger, "Bones array too large.");
233 return;
234 }
235
236 animationData.pose->numPoses = mesh->getPoseIndexes().size();
237
238 auto poseSkeleton = animationData.skeleton.get();
239 if (animationComponent.master) {
240 auto masterComponent = animationComponent.master.resolveComponent<AnimationComponent>();
241 poseSkeleton = getData(masterComponent).skeleton.get();
242 }
243
244 for (size_t i = 0; i < mesh->getPoseIndexes().size(); ++i) {
245 animationData.pose->transforms[i] = poseSkeleton->bones[mesh->getPoseIndexes()[i]].absolute;
246 }
247 }
248}
249
251{
252 const float globalTime = static_cast<float>(context->time->getAnimationTime());
253
254 const bool workParallel = context->engine->workParallel();
255
256 if (workParallel) {
257 auto task = Parallel::processComponents(
258 context, pool, "AnimationSystem::updateMaster", [&](AnimationComponent & animationComponent, size_t /*j*/) {
259 if (!animationComponent.master) {
260 update(animationComponent, globalTime);
261 }
262 });
263
264 context->taskManager->destroy(task);
265
266 task = Parallel::processComponents(
267 context, pool, "AnimationSystem::update", [&](AnimationComponent & animationComponent, size_t /*j*/) {
268 if (animationComponent.master) {
269 update(animationComponent, globalTime);
270 }
271 });
272
273 context->taskManager->destroy(task);
274 } else {
275 for (auto & animationComponent : pool) {
276 update(animationComponent, globalTime);
277 }
278 }
279}
Base class for Component instances.
Definition: Component.h:143
void setChanged()
Sets the component to the ComponentFlags::Changed state with carry.
Definition: Component.h:202
ComponentType * getComponent() const
Definition: Component.h:159
class Entity * getContainer() const
Get the container currently owning this component instance.
Definition: Component.h:151
ComponentHandle getComponentHandle() const
Definition: Component.h:177
Container for components, providing composition of dynamic entities.
Definition: Entity.h:18
T * getComponent() const
Get a pointer to the first component implementing the given type in the entity.
Definition: Entity.h:35
void update()
Updates the system state to that of the current frame.
size_t size()
Returns the number of active components.
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
std::unique_ptr< class TaskManager > taskManager
TaskManager service instance.
Definition: Context.h:186
std::unique_ptr< class Time > time
Time service instance.
Definition: Context.h:198
std::unique_ptr< class Engine > engine
Engine instance.
Definition: Context.h:222
Contains a handle to a Mesh resource to use when rendering using the MeshRenderComponent.
Definition: MeshComponent.h:15
MeshHandle meshHandle
Handle to a Mesh resource to use when rendering.
Definition: MeshComponent.h:29
Contains information on how the entity behaves in the scene.
Defines a 4x4 transformation matrix for the entity and a global offset for root entities.
uint32_t transformFlags
Transform flags.
glm::mat4 transform
Complete transformation.
Log implementation class.
Definition: LogManager.h:139
Provides a weakly referenced view over the contents of a string.
Definition: StringView.h:24
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:180
COGSFOUNDATION_API class Component * resolve() const
Resolve the handle, returning a pointer to the held Component instance.
Definition: Component.cpp:65
static ComponentHandle Empty()
Returns an empty, invalid handle. Will evaluate to false if tested against using operator bool().
Definition: Component.h:119
ComponentType * resolveComponent() const
Definition: Component.h:90
Component handling animation of multiple Poses of an Animation Resource.
float timeOffset
Animation time offset in seconds.
int clipIndex
Active index of the Animation Clips. -1 = freeze Animation.
AnimationHandle animation
Animation resource handle.
ComponentModel::ComponentHandle master
Internal handle of Master when animating. Handled by AnimationSystem.
float timeFactor
Animation Time Scale factor.
ResourceType * resolve() const
Resolve the handle, returning a pointer to the actual resource.