• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2022 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "animation_system.h"
17 
18 #include <algorithm>
19 #include <cfloat>
20 #include <cinttypes>
21 #include <numeric>
22 
23 #include <3d/ecs/components/animation_component.h>
24 #include <3d/ecs/components/animation_input_component.h>
25 #include <3d/ecs/components/animation_output_component.h>
26 #include <3d/ecs/components/animation_state_component.h>
27 #include <3d/ecs/components/animation_track_component.h>
28 #include <3d/ecs/components/name_component.h>
29 #include <3d/ecs/components/node_component.h>
30 #include <3d/ecs/components/transform_component.h>
31 #include <3d/ecs/systems/intf_node_system.h>
32 #include <base/math/mathf.h>
33 #include <base/math/quaternion_util.h>
34 #include <base/math/spline.h>
35 #include <base/util/uid_util.h>
36 #include <core/ecs/intf_ecs.h>
37 #include <core/implementation_uids.h>
38 #include <core/intf_engine.h>
39 #include <core/log.h>
40 #include <core/namespace.h>
41 #include <core/perf/cpu_perf_scope.h>
42 #include <core/perf/intf_performance_data_manager.h>
43 #include <core/plugin/intf_plugin_register.h>
44 #include <core/property_tools/property_api_impl.inl>
45 #include <core/property_tools/property_data.h>
46 #include <core/property_tools/property_macros.h>
47 
48 #include "ecs/components/initial_transform_component.h"
49 #include "ecs/systems/animation_playback.h"
50 #include "util/log.h"
51 
52 CORE3D_BEGIN_NAMESPACE()
53 using namespace BASE_NS;
54 using namespace CORE_NS;
55 
56 struct AnimationSystem::PropertyEntry {
57     uint64_t componentAndProperty;
58     IComponentManager* component;
59     uintptr_t propertyOffset;
60     const Property* property;
61 };
62 
63 struct AnimationSystem::InterpolationData {
64     AnimationTrackComponent::Interpolation mode;
65     size_t startIndex;
66     size_t endIndex;
67 
68     float t;
69     float weight;
70 };
71 
72 class AnimationSystem::InitTask final : public IThreadPool::ITask {
73 public:
InitTask(AnimationSystem & system,size_t offset,size_t count)74     InitTask(AnimationSystem& system, size_t offset, size_t count) : system_(system), offset_(offset), count_(count) {};
75 
operator ()()76     void operator()() override
77     {
78         const auto offset = Math::min(offset_, system_.trackOrder_.size());
79         const auto count = Math::min(count_, system_.trackOrder_.size() - offset);
80         const auto results = array_view(static_cast<const uint32_t*>(system_.trackOrder_.data()) + offset, count);
81         system_.InitializeTrackValues(results);
82     }
83 
84 protected:
Destroy()85     void Destroy() override {}
86 
87 private:
88     AnimationSystem& system_;
89     size_t offset_;
90     size_t count_;
91 };
92 
93 class AnimationSystem::FrameIndexTask final : public IThreadPool::ITask {
94 public:
FrameIndexTask(AnimationSystem & system,size_t offset,size_t count)95     FrameIndexTask(AnimationSystem& system, size_t offset, size_t count)
96         : system_(system), offset_(offset), count_(count) {};
97 
operator ()()98     void operator()() override
99     {
100         const auto offset = Math::min(offset_, system_.trackOrder_.size());
101         const auto count = Math::min(count_, system_.trackOrder_.size() - offset);
102         const auto results = array_view(static_cast<const uint32_t*>(system_.trackOrder_.data()) + offset, count);
103         system_.CalculateFrameIndices(results);
104     }
105 
106 protected:
Destroy()107     void Destroy() override {}
108 
109 private:
110     AnimationSystem& system_;
111     size_t offset_;
112     size_t count_;
113 };
114 
115 class AnimationSystem::AnimateTask final : public IThreadPool::ITask {
116 public:
AnimateTask(AnimationSystem & system,size_t offset,size_t count)117     AnimateTask(AnimationSystem& system, size_t offset, size_t count)
118         : system_(system), offset_(offset), count_(count) {};
119 
operator ()()120     void operator()() override
121     {
122         const auto offset = Math::min(offset_, system_.trackOrder_.size());
123         const auto count = Math::min(count_, system_.trackOrder_.size() - offset);
124         const auto results = array_view(static_cast<const uint32_t*>(system_.trackOrder_.data()) + offset, count);
125         system_.AnimateTracks(results);
126     }
127 
128 protected:
Destroy()129     void Destroy() override {}
130 
131 private:
132     AnimationSystem& system_;
133     size_t offset_;
134     size_t count_;
135 };
136 
137 namespace {
138 PROPERTY_LIST(
139     AnimationSystem::Properties, SystemMetadata, MEMBER_PROPERTY(minTaskSize, "Task size", PropertyFlags::IS_SLIDER))
140 
141 constexpr PropertyTypeDecl PROPERTY_HANDLE_PTR_T = PROPERTYTYPE(CORE_NS::IPropertyHandle*);
142 
143 // values contain in-tangent, spline vertex, out-tangent.
144 template<typename T>
145 struct SplineValues {
146     T inTangent;
147     T splineVertex;
148     T outTangent;
149 };
150 
151 template<typename To, typename From>
Cast(From * from)152 inline To Cast(From* from)
153 {
154     if constexpr (is_const_v<remove_pointer_t<From>>) {
155         return static_cast<const To>(static_cast<const void*>(from));
156     } else {
157         return static_cast<To>(static_cast<void*>(from));
158     }
159 }
160 
161 template<typename To, typename From>
Cast(From & from)162 inline To Cast(From& from)
163 {
164     if constexpr (is_const_v<remove_reference_t<From>>) {
165         return *static_cast<const remove_reference_t<To>*>(static_cast<const void*>(&from));
166     } else {
167         return *static_cast<remove_reference_t<To>*>(static_cast<void*>(&from));
168     }
169 }
170 
171 template<typename To, typename From>
Get(From && from)172 static inline To Get(From&& from)
173 {
174     if constexpr (is_same_v<remove_const_t<remove_reference_t<To>>, float>) {
175         return from.initialData.floatValue;
176     }
177     if constexpr (is_same_v<remove_const_t<remove_reference_t<To>>, Math::Vec2>) {
178         return from.initialData.vec2Value;
179     }
180     if constexpr (is_same_v<remove_const_t<remove_reference_t<To>>, Math::Vec3>) {
181         return from.initialData.vec3Value;
182     }
183     if constexpr (is_same_v<remove_const_t<remove_reference_t<To>>, Math::Vec4>) {
184         return from.initialData.vec4Value;
185     }
186 }
187 
188 template<class T>
Step(const T & start,const T & end,float offset)189 T Step(const T& start, const T& end, float offset)
190 {
191     return (offset > 0.5f) ? end : start;
192 }
193 
InterpolateQuatSplineGLTF(const Math::Vec4 & start,const Math::Vec4 & outTangent,const Math::Vec4 & end,const Math::Vec4 & inTangent,float offset)194 Math::Quat InterpolateQuatSplineGLTF(const Math::Vec4& start, const Math::Vec4& outTangent, const Math::Vec4& end,
195     const Math::Vec4& inTangent, float offset)
196 {
197     const Math::Vec4 vec = Math::Hermite(start, outTangent, end, inTangent, offset);
198     return Normalize(Cast<Math::Quat>(vec));
199 }
200 
Lerp(const float & v1,const float & v2,float t)201 constexpr float Lerp(const float& v1, const float& v2, float t)
202 {
203     t = Math::clamp01(t);
204     return v1 + (v2 - v1) * t;
205 }
206 
Assign(const PropertyTypeDecl & type,uint8_t * dst,const InitialTransformComponent & src)207 void Assign(const PropertyTypeDecl& type, uint8_t* dst, const InitialTransformComponent& src)
208 {
209     switch (type) {
210         // Primitive types
211         case PropertyType::FLOAT_T: {
212             *Cast<float*>(dst) = src.initialData.floatValue;
213             break;
214         }
215 
216         // Extended types
217         case PropertyType::VEC2_T: {
218             *Cast<Math::Vec2*>(dst) = src.initialData.vec2Value;
219             break;
220         }
221 
222         case PropertyType::VEC3_T: {
223             *Cast<Math::Vec3*>(dst) = src.initialData.vec3Value;
224             break;
225         }
226 
227         case PropertyType::VEC4_T: {
228             *Cast<Math::Vec4*>(dst) = src.initialData.vec4Value;
229             break;
230         }
231 
232         case PropertyType::QUAT_T: {
233             *Cast<Math::Quat*>(dst) = src.initialData.quatValue;
234             break;
235         }
236 
237         case PropertyType::FLOAT_VECTOR_T: {
238             // target property is an actual vector
239             auto* dstVector = Cast<vector<float>*>(dst);
240             // while initial values is the contents of the vector i.e. an array.
241             const auto& srcVec = src.initialData.floatVectorValue;
242             const auto* srcF = srcVec.data();
243             const auto count = Math::min(srcVec.size(), dstVector->size());
244             std::copy(srcF, srcF + count, dstVector->data());
245             break;
246         }
247         default: {
248             CORE_LOG_ONCE_D(to_string(type.typeHash), "AnimateTrack failed, unsupported type %s", type.name.data());
249             break;
250         }
251     }
252 }
253 
254 template<typename T>
Mult(uint8_t * dst,const T & src)255 inline void Mult(uint8_t* dst, const T& src)
256 {
257     auto* dstType = Cast<T*>(dst);
258     *dstType = src * *dstType;
259 }
260 
261 template<typename T>
Add(uint8_t * dst,const T src)262 inline void Add(uint8_t* dst, const T src)
263 {
264     auto* dstType = Cast<T*>(dst);
265     *dstType = *dstType + src;
266 }
267 
Add(const PropertyTypeDecl & type,uint8_t * dst,const InitialTransformComponent & src)268 void Add(const PropertyTypeDecl& type, uint8_t* dst, const InitialTransformComponent& src)
269 {
270     switch (type) {
271         // Primitive types
272         case PropertyType::FLOAT_T: {
273             Add<float>(dst, src.initialData.floatValue);
274             break;
275         }
276 
277         // Extended types
278         case PropertyType::VEC2_T: {
279             Add<Math::Vec2>(dst, src.initialData.vec2Value);
280             break;
281         }
282 
283         case PropertyType::VEC3_T: {
284             Add<Math::Vec3>(dst, src.initialData.vec3Value);
285             break;
286         }
287 
288         case PropertyType::VEC4_T: {
289             Add<Math::Vec4>(dst, src.initialData.vec4Value);
290             break;
291         }
292 
293         case PropertyType::QUAT_T: {
294             Mult<Math::Quat>(dst, src.initialData.quatValue);
295             break;
296         }
297 
298         case PropertyType::FLOAT_VECTOR_T: {
299             // target property is an actual vector
300             auto* dstVector = Cast<vector<float>*>(dst);
301             // while result is the contents of the vector i.e. an array.
302             auto& srcVector = src.initialData.floatVectorValue;
303             const auto* srcF = srcVector.data();
304             const auto count = Math::min(srcVector.size(), dstVector->size());
305             std::transform(srcF, srcF + count, dstVector->data(), dstVector->data(),
306                 [](float initial, float result) { return initial + result; });
307             break;
308         }
309         default: {
310             CORE_LOG_ONCE_D(to_string(type.typeHash), "AnimateTrack failed, unsupported type %s", type.name.data());
311             break;
312         }
313     }
314 }
315 
316 struct AnimationKeyDataOffsets {
317     size_t startKeyOffset;
318     size_t endKeyOffset;
319 };
320 
321 template<typename T>
Interpolate(const AnimationSystem::InterpolationData & interpolation,array_view<const uint8_t> frameValues,const InitialTransformComponent & initialValue,InitialTransformComponent & animatedValue)322 void Interpolate(const AnimationSystem::InterpolationData& interpolation, array_view<const uint8_t> frameValues,
323     const InitialTransformComponent& initialValue, InitialTransformComponent& animatedValue)
324 {
325     auto values = array_view(Cast<const T*>(frameValues.data()), frameValues.size() / sizeof(T));
326     if ((interpolation.startIndex < values.size()) && (interpolation.endIndex < values.size())) {
327         T result;
328         switch (interpolation.mode) {
329             case AnimationTrackComponent::Interpolation::STEP:
330                 result = Step(values[interpolation.startIndex], values[interpolation.endIndex], interpolation.t);
331                 break;
332 
333             default:
334             case AnimationTrackComponent::Interpolation::LINEAR:
335                 result = Lerp(values[interpolation.startIndex], values[interpolation.endIndex], interpolation.t);
336                 break;
337 
338             case AnimationTrackComponent::Interpolation::SPLINE: {
339                 const auto& p0 = Cast<const SplineValues<T>&>(values[interpolation.startIndex]);
340                 const auto& p1 = Cast<const SplineValues<T>&>(values[interpolation.endIndex]);
341                 result = Math::Hermite(p0.splineVertex, p0.outTangent, p1.splineVertex, p1.inTangent, interpolation.t);
342                 break;
343             }
344         }
345         const T& initial = Get<const T&>(initialValue);
346         // Could assign to animatedValue directly but that happens via a jump to operator=. Types have been checked in
347         // AnimateTracks.
348         T& target = Get<T&>(animatedValue);
349         // Convert result to relative and apply weight.
350         target = (result - initial) * interpolation.weight;
351     } else {
352         Get<T&>(animatedValue) = {};
353     }
354 }
355 
356 template<>
Interpolate(const AnimationSystem::InterpolationData & interpolation,array_view<const uint8_t> frameValues,const InitialTransformComponent & initialValue,InitialTransformComponent & animatedValue)357 void Interpolate<Math::Quat>(const AnimationSystem::InterpolationData& interpolation,
358     array_view<const uint8_t> frameValues, const InitialTransformComponent& initialValue,
359     InitialTransformComponent& animatedValue)
360 {
361     static constexpr Math::Quat identity(0.0f, 0.0f, 0.0f, 1.0f);
362     auto values = array_view(Cast<const Math::Vec4*>(frameValues.data()), frameValues.size() / sizeof(Math::Vec4));
363     if ((interpolation.startIndex < values.size()) && (interpolation.endIndex < values.size())) {
364         Math::Quat result;
365 
366         switch (interpolation.mode) {
367             case AnimationTrackComponent::Interpolation::STEP: {
368                 result = Step(Cast<const Math::Quat&>(values[interpolation.startIndex]),
369                     Cast<const Math::Quat&>(values[interpolation.endIndex]), interpolation.t);
370                 break;
371             }
372 
373             default:
374             case AnimationTrackComponent::Interpolation::LINEAR: {
375                 result = Slerp(Cast<const Math::Quat&>(values[interpolation.startIndex]),
376                     Cast<const Math::Quat&>(values[interpolation.endIndex]), interpolation.t);
377                 break;
378             }
379 
380             case AnimationTrackComponent::Interpolation::SPLINE: {
381                 auto const p0 = reinterpret_cast<const SplineValues<Math::Vec4>*>(&values[interpolation.startIndex]);
382                 auto const p1 = reinterpret_cast<const SplineValues<Math::Vec4>*>(&values[interpolation.endIndex]);
383                 result = InterpolateQuatSplineGLTF(
384                     p0->splineVertex, p0->outTangent, p1->splineVertex, p1->inTangent, interpolation.t);
385                 break;
386             }
387         }
388         const Math::Quat inverseInitialRotation = Inverse(initialValue.initialData.quatValue);
389         const Math::Quat delta = Normalize(Slerp(identity, result * inverseInitialRotation, interpolation.weight));
390 
391         animatedValue.initialData.quatValue = delta;
392     } else {
393         animatedValue.initialData.quatValue = identity;
394     }
395 }
396 
AnimateArray(const AnimationSystem::InterpolationData & interpolation,array_view<const uint8_t> frameValues,const InitialTransformComponent & initialValue,InitialTransformComponent & animatedValue)397 void AnimateArray(const AnimationSystem::InterpolationData& interpolation, array_view<const uint8_t> frameValues,
398     const InitialTransformComponent& initialValue, InitialTransformComponent& animatedValue)
399 {
400     const auto initialValues =
401         array_view(initialValue.initialData.floatVectorValue.data(), initialValue.initialData.floatVectorValue.size());
402 
403     auto animated = array_view(
404         animatedValue.initialData.floatVectorValue.data(), animatedValue.initialData.floatVectorValue.size());
405     const auto targetCount = animated.size();
406 
407     // Re-calculate the start/end offsets as each value is actually an array of values.
408     const size_t startDataOffset = interpolation.startIndex * targetCount;
409     const size_t endDataOffset = interpolation.endIndex * targetCount;
410 
411     if (interpolation.mode == AnimationTrackComponent::Interpolation::SPLINE) {
412         const auto values = array_view(
413             Cast<const SplineValues<float>*>(frameValues.data()), frameValues.size() / sizeof(SplineValues<float>));
414         if (((startDataOffset + targetCount) <= values.size()) && ((endDataOffset + targetCount) <= values.size())) {
415             auto p0 = values.data() + startDataOffset;
416             auto p1 = values.data() + endDataOffset;
417             const auto p0E = p0 + targetCount;
418             auto iI = initialValues.cbegin();
419             auto aI = animated.begin();
420             for (; p0 != p0E; ++p0, ++p1, ++iI, ++aI) {
421                 *aI =
422                     (Math::Hermite(p0->splineVertex, p0->outTangent, p1->splineVertex, p1->inTangent, interpolation.t) -
423                         *iI) *
424                     interpolation.weight;
425             }
426         }
427     } else {
428         const auto values = array_view(Cast<const float*>(frameValues.data()), frameValues.size() / sizeof(float));
429         if (((startDataOffset + targetCount) <= values.size()) && ((endDataOffset + targetCount) <= values.size())) {
430             auto p0 = values.data() + startDataOffset;
431             auto p1 = values.data() + endDataOffset;
432             const auto p0E = p0 + targetCount;
433             auto iI = initialValues.cbegin();
434             auto aI = animated.begin();
435             if (interpolation.mode == AnimationTrackComponent::Interpolation::STEP) {
436                 for (; p0 != p0E; ++p0, ++p1, ++iI, ++aI) {
437                     *aI = (Step(*p0, *p1, interpolation.t) - *iI) * interpolation.weight;
438                 }
439             } else {
440                 for (; p0 != p0E; ++p0, ++p1, ++iI, ++aI) {
441                     *aI = (Math::lerp(*p0, *p1, interpolation.t) - *iI) * interpolation.weight;
442                 }
443             }
444         }
445     }
446 }
447 
AnimateTrack(const PropertyTypeDecl & type,const AnimationSystem::InterpolationData & interpolationData,const AnimationOutputComponent & outputComponent,const InitialTransformComponent & initialValue,InitialTransformComponent & resultValue)448 void AnimateTrack(const PropertyTypeDecl& type, const AnimationSystem::InterpolationData& interpolationData,
449     const AnimationOutputComponent& outputComponent, const InitialTransformComponent& initialValue,
450     InitialTransformComponent& resultValue)
451 {
452     switch (type) {
453         // Primitive types
454         case PropertyType::FLOAT_T: {
455             Interpolate<float>(interpolationData, outputComponent.data, initialValue, resultValue);
456             break;
457         }
458 
459         // Extended types
460         case PropertyType::VEC2_T: {
461             Interpolate<Math::Vec2>(interpolationData, outputComponent.data, initialValue, resultValue);
462             break;
463         }
464 
465         case PropertyType::VEC3_T: {
466             Interpolate<Math::Vec3>(interpolationData, outputComponent.data, initialValue, resultValue);
467             break;
468         }
469 
470         case PropertyType::VEC4_T: {
471             Interpolate<Math::Vec4>(interpolationData, outputComponent.data, initialValue, resultValue);
472             break;
473         }
474 
475         case PropertyType::QUAT_T: {
476             Interpolate<Math::Quat>(interpolationData, outputComponent.data, initialValue, resultValue);
477             break;
478         }
479 
480         case PropertyType::FLOAT_VECTOR_T: {
481             AnimateArray(interpolationData, outputComponent.data, initialValue, resultValue);
482             break;
483         }
484         default: {
485             CORE_LOG_ONCE_D(to_string(type.typeHash), "AnimateTrack failed, unsupported type %s", type.name.data());
486             return;
487         }
488     }
489 }
490 
FindFrameIndices(const bool forward,const float currentTimestamp,const array_view<const float> timestamps,size_t & currentFrameIndex,size_t & nextFrameIndex)491 void FindFrameIndices(const bool forward, const float currentTimestamp, const array_view<const float> timestamps,
492     size_t& currentFrameIndex, size_t& nextFrameIndex)
493 {
494     size_t current;
495     size_t next;
496     const auto begin = timestamps.begin();
497     const auto end = timestamps.end();
498     const auto pos = std::upper_bound(begin, end, currentTimestamp);
499     if (forward) {
500         // Find next frame (forward).
501         next = static_cast<size_t>(std::distance(begin, pos));
502         current = next - 1;
503     } else {
504         // Find next frame (backward).
505         current = static_cast<size_t>(std::distance(begin, pos));
506         next = current ? current - 1 : 0;
507     }
508 
509     // Clamp timestamps to valid range.
510     currentFrameIndex = std::clamp(current, size_t(0), timestamps.size() - 1);
511     nextFrameIndex = std::clamp(next, size_t(0), timestamps.size() - 1);
512 }
513 
UpdateStateAndTracks(IAnimationStateComponentManager & stateManager_,IAnimationTrackComponentManager & trackManager_,IAnimationInputComponentManager & inputManager_,Entity animationEntity,const AnimationComponent & animationComponent,array_view<const Entity> targetEntities)514 void UpdateStateAndTracks(IAnimationStateComponentManager& stateManager_,
515     IAnimationTrackComponentManager& trackManager_, IAnimationInputComponentManager& inputManager_,
516     Entity animationEntity, const AnimationComponent& animationComponent, array_view<const Entity> targetEntities)
517 {
518     auto stateHandle = stateManager_.Write(animationEntity);
519     stateHandle->trackStates.reserve(animationComponent.tracks.size());
520 
521     auto& entityManager = stateManager_.GetEcs().GetEntityManager();
522     auto targetIt = targetEntities.begin();
523     for (const auto& trackEntity : animationComponent.tracks) {
524         stateHandle->trackStates.push_back({ Entity(trackEntity), 0U });
525         auto& trackState = stateHandle->trackStates.back();
526 
527         if (auto track = trackManager_.Write(trackEntity); track) {
528             track->target = entityManager.GetReferenceCounted(*targetIt);
529 
530             if (auto inputData = inputManager_.Read(track->timestamps); inputData) {
531                 trackState.length = inputData->timestamps.size();
532             }
533         }
534         ++targetIt;
535     }
536 }
537 
CopyInitialDataComponent(InitialTransformComponent & dst,const Property * property,const array_view<const uint8_t> src)538 void CopyInitialDataComponent(
539     InitialTransformComponent& dst, const Property* property, const array_view<const uint8_t> src)
540 {
541     switch (property->type) {
542         case PropertyType::FLOAT_T: {
543             dst = *Cast<const float*>(src.data());
544             break;
545         }
546 
547         case PropertyType::VEC2_T: {
548             dst = *Cast<const Math::Vec2*>(src.data());
549             break;
550         }
551 
552         case PropertyType::VEC3_T: {
553             dst = *Cast<const Math::Vec3*>(src.data());
554             break;
555         }
556 
557         case PropertyType::VEC4_T: {
558             dst = *Cast<const Math::Vec4*>(src.data());
559             break;
560         }
561 
562         case PropertyType::QUAT_T: {
563             dst = *Cast<const Math::Quat*>(src.data());
564             break;
565         }
566 
567         case PropertyType::FLOAT_VECTOR_T: {
568             dst = *Cast<const vector<float>*>(src.data());
569             break;
570         }
571         default: {
572             CORE_LOG_ONCE_D(
573                 to_string(property->type.typeHash), "AnimateTrack failed, unsupported type %s", property->name.data());
574             return;
575         }
576     }
577 }
578 
FindDynamicProperty(const AnimationTrackComponent & animationTrack,const IPropertyHandle * dynamicProperties,AnimationSystem::PropertyEntry entry)579 AnimationSystem::PropertyEntry FindDynamicProperty(const AnimationTrackComponent& animationTrack,
580     const IPropertyHandle* dynamicProperties, AnimationSystem::PropertyEntry entry)
581 {
582     if (dynamicProperties) {
583         auto propertyName = string_view(animationTrack.property);
584         if (propertyName.starts_with(entry.property->name)) {
585             propertyName.remove_prefix(entry.property->name.size() + 1U);
586         }
587 
588         auto c = PropertyData::FindProperty(dynamicProperties->Owner()->MetaData(), propertyName);
589         if (c) {
590             entry.property = c.property;
591             entry.propertyOffset = c.offset;
592         }
593     }
594     return entry;
595 }
596 
ResetTime(AnimationStateComponent & state,const AnimationComponent & animation)597 void ResetTime(AnimationStateComponent& state, const AnimationComponent& animation)
598 {
599     state.time = (animation.duration != 0.f) ? std::fmod(state.time, animation.duration) : 0.f;
600     if (state.time < 0.f) {
601         // When speed is negative time will end up negative. Wrap around to end of animation length.
602         state.time = animation.duration + state.time;
603     }
604 
605     // This is called when SetTimePosition was called, animation was stopped, or playback is repeted. In all cases
606     // mark the animation dirty so animation system will run at least one update cycle to get the animated object to
607     // correct state.
608     state.dirty = true;
609 }
610 } // namespace
611 
AnimationSystem(IEcs & ecs)612 AnimationSystem::AnimationSystem(IEcs& ecs)
613     : ecs_(ecs), systemPropertyApi_(&systemProperties_, array_view(SystemMetadata)),
614       initialTransformManager_(*(GetManager<IInitialTransformComponentManager>(ecs_))),
615       animationManager_(*(GetManager<IAnimationComponentManager>(ecs_))),
616       inputManager_(*(GetManager<IAnimationInputComponentManager>(ecs_))),
617       outputManager_(*(GetManager<IAnimationOutputComponentManager>(ecs_))),
618       stateManager_(*(GetManager<IAnimationStateComponentManager>(ecs_))),
619       animationTrackManager_(*(GetManager<IAnimationTrackComponentManager>(ecs_))),
620       nameManager_(*(GetManager<INameComponentManager>(ecs_))), threadPool_(ecs.GetThreadPool())
621 {}
622 
SetActive(bool state)623 void AnimationSystem::SetActive(bool state)
624 {
625     active_ = state;
626 }
627 
IsActive() const628 bool AnimationSystem::IsActive() const
629 {
630     return active_;
631 }
632 
GetName() const633 string_view AnimationSystem::GetName() const
634 {
635     return CORE3D_NS::GetName(this);
636 }
637 
GetUid() const638 Uid AnimationSystem::GetUid() const
639 {
640     return UID;
641 }
642 
GetProperties()643 IPropertyHandle* AnimationSystem::GetProperties()
644 {
645     return systemPropertyApi_.GetData();
646 }
647 
GetProperties() const648 const IPropertyHandle* AnimationSystem::GetProperties() const
649 {
650     return systemPropertyApi_.GetData();
651 }
652 
SetProperties(const IPropertyHandle & data)653 void AnimationSystem::SetProperties(const IPropertyHandle& data)
654 {
655     if (data.Owner() != &systemPropertyApi_) {
656         return;
657     }
658     if (const auto in = ScopedHandle<const AnimationSystem::Properties>(&data); in) {
659         systemProperties_.minTaskSize = in->minTaskSize;
660     }
661 }
662 
GetECS() const663 const IEcs& AnimationSystem::GetECS() const
664 {
665     return ecs_;
666 }
667 
Initialize()668 void AnimationSystem::Initialize()
669 {
670     ecs_.AddListener(animationManager_, *this);
671     ecs_.AddListener(animationTrackManager_, *this);
672     {
673         const ComponentQuery::Operation operations[] = {
674             { animationTrackManager_, ComponentQuery::Operation::REQUIRE },
675         };
676         trackQuery_.SetEcsListenersEnabled(true);
677         trackQuery_.SetupQuery(initialTransformManager_, operations, true);
678     }
679     {
680         const ComponentQuery::Operation operations[] = {
681             { stateManager_, ComponentQuery::Operation::REQUIRE },
682         };
683         animationQuery_.SetEcsListenersEnabled(true);
684         animationQuery_.SetupQuery(animationManager_, operations, false);
685     }
686 }
687 
Update(bool frameRenderingQueued,uint64_t time,uint64_t delta)688 bool AnimationSystem::Update(bool frameRenderingQueued, uint64_t time, uint64_t delta)
689 {
690 #if (CORE3D_DEV_ENABLED == 1)
691     CORE_CPU_PERF_SCOPE("CORE3D", "AnimationSystem", "Update", CORE3D_PROFILER_DEFAULT_COLOR);
692 #endif
693     if (!active_) {
694         return false;
695     }
696 
697     if ((stateGeneration_ == stateManager_.GetGenerationCounter()) && [](const auto& stateManager) -> bool {
698             const auto states = stateManager.GetComponentCount();
699             for (auto id = 0U; id < states; ++id) {
700                 if (stateManager.Read(id)->dirty) {
701                     return false;
702                 }
703             }
704             return true;
705         }(stateManager_)) {
706         return false;
707     }
708 
709     animationQuery_.Execute();
710     trackQuery_.Execute();
711 
712     if (animationGeneration_ != animationManager_.GetGenerationCounter()) {
713         // Handle stopped animations first to avoid glitches. Alternative would be filtering out tracks which are
714         // stopped, but there's another track targeting the same property.
715         auto results = animationQuery_.GetResults();
716         animationOrder_.clear();
717         animationOrder_.reserve(results.size());
718         uint32_t index = 0U;
719         uint32_t stopped = 0U;
720         for (const auto& row : results) {
721             auto animationHandle = animationManager_.Read(row.components[0]);
722             if (animationHandle->state == AnimationComponent::PlaybackState::STOP) {
723                 animationOrder_.insert(animationOrder_.cbegin() + stopped, index);
724                 ++stopped;
725             } else {
726                 animationOrder_.push_back(index);
727             }
728             ++index;
729         }
730     }
731 
732     // Reset trackValues_ to stopped state before walking through animations.
733     trackValues_.resize(animationTrackManager_.GetComponentCount());
734     for (auto& values : trackValues_) {
735         values.state = TrackState::STOPPED;
736     }
737 
738     frameIndices_.resize(animationTrackManager_.GetComponentCount());
739 
740     // For each animation update the playback state, time position and weight, and gather tracks in trackOrder_.
741     trackOrder_.clear();
742     trackOrder_.reserve(animationTrackManager_.GetComponentCount());
743 
744     UpdateAnimationStates(delta);
745 
746     {
747         // Would constructing a reordered ResultRow array be better than indices into the results? Would be one
748         // indirection less in processing...
749     }
750 
751     // Calculate how many tasks will be needed
752     {
753         const auto threadCount = threadPool_->GetNumberOfThreads() + 1;
754         const auto resultCount = trackOrder_.size();
755         taskSize_ = Math::max(systemProperties_.minTaskSize, resultCount / threadCount);
756         tasks_ = ((resultCount / taskSize_)) ? (resultCount / taskSize_) - 1U : 0U;
757         remaining_ = resultCount - (tasks_ * taskSize_);
758     }
759 
760     taskId_ = 0U;
761     taskResults_.clear();
762 
763     // Start tasks for filling the initial values to trackValues_.
764     InitializeTrackValues();
765 
766     // For each track batch reset the target properties to initial values, wait for trackValues_ to be filled and start
767     // new task to handle the actual animation.
768     AnimateTrackValues();
769     ResetToInitialTrackValues();
770 
771     // Update target properties, this will apply animations on top of current value.
772     WriteUpdatedTrackValues();
773 
774     stateGeneration_ = stateManager_.GetGenerationCounter();
775     return !animationQuery_.GetResults().empty();
776 }
777 
Uninitialize()778 void AnimationSystem::Uninitialize()
779 {
780     ecs_.RemoveListener(animationManager_, *this);
781     ecs_.RemoveListener(animationTrackManager_, *this);
782 
783     trackQuery_.SetEcsListenersEnabled(false);
784     animationQuery_.SetEcsListenersEnabled(false);
785 
786     animations_.clear();
787 }
788 
CreatePlayback(Entity const & animationEntity,ISceneNode const & node)789 IAnimationPlayback* AnimationSystem::CreatePlayback(Entity const& animationEntity, ISceneNode const& node)
790 {
791     vector<Entity> targetEntities;
792     if (const auto animationData = animationManager_.Read(animationEntity); animationData) {
793         targetEntities.reserve(animationData->tracks.size());
794         for (const auto& trackEntity : animationData->tracks) {
795             if (auto nameHandle = nameManager_.Read(trackEntity); nameHandle) {
796                 if (auto targetNode = node.LookupNodeByPath(nameHandle->name); targetNode) {
797                     targetEntities.push_back(targetNode->GetEntity());
798                 } else {
799                     CORE_LOG_D("Cannot find target node for animation track '%s'.", nameHandle->name.data());
800                     return nullptr;
801                 }
802             } else {
803                 CORE_LOG_D("Cannot match unnamed animation track.");
804                 return nullptr;
805             }
806         }
807     }
808     return CreatePlayback(animationEntity, targetEntities);
809 }
810 
CreatePlayback(const Entity animationEntity,const array_view<const Entity> targetEntities)811 IAnimationPlayback* AnimationSystem::CreatePlayback(
812     const Entity animationEntity, const array_view<const Entity> targetEntities)
813 {
814     // animationEntity must be have an animation component and track count must match target entites, and
815     // target entities must be valid.
816     if (const auto animationHandle = animationManager_.Read(animationEntity);
817         !animationHandle || (animationHandle->tracks.size() != targetEntities.size()) ||
818         !std::all_of(targetEntities.begin(), targetEntities.end(),
819             [](const Entity& entity) { return EntityUtil::IsValid(entity); })) {
820         return nullptr;
821     } else {
822         if (!stateManager_.HasComponent(animationEntity)) {
823             stateManager_.Create(animationEntity);
824         }
825         UpdateStateAndTracks(
826             stateManager_, animationTrackManager_, inputManager_, animationEntity, *animationHandle, targetEntities);
827     }
828     // Create animation runtime.
829     auto playback = make_unique<AnimationPlayback>(animationEntity, targetEntities, ecs_);
830 
831     IAnimationPlayback* result = playback.get();
832 
833     animations_.push_back(move(playback));
834 
835     return result;
836 }
837 
DestroyPlayback(IAnimationPlayback * playback)838 void AnimationSystem::DestroyPlayback(IAnimationPlayback* playback)
839 {
840     for (auto it = animations_.cbegin(); it != animations_.cend(); ++it) {
841         if (it->get() == playback) {
842             animations_.erase(it);
843             break;
844         }
845     }
846 }
847 
GetPlaybackCount() const848 size_t AnimationSystem::GetPlaybackCount() const
849 {
850     return animations_.size();
851 }
852 
GetPlayback(size_t index) const853 IAnimationPlayback* AnimationSystem::GetPlayback(size_t index) const
854 {
855     if (index < animations_.size()) {
856         return animations_[index].get();
857     }
858     return nullptr;
859 }
860 
OnComponentEvent(EventType type,const IComponentManager & componentManager,const array_view<const Entity> entities)861 void AnimationSystem::OnComponentEvent(
862     EventType type, const IComponentManager& componentManager, const array_view<const Entity> entities)
863 {
864     switch (type) {
865         case IEcs::ComponentListener::EventType::CREATED: {
866             if (&componentManager == &animationManager_) {
867                 OnAnimationComponentsCreated(entities);
868             }
869             break;
870         }
871         case IEcs::ComponentListener::EventType::DESTROYED: {
872             if (&componentManager == &animationManager_) {
873                 for (const auto entity : entities) {
874                     stateManager_.Destroy(entity);
875                 }
876             }
877             break;
878         }
879         case IEcs::ComponentListener::EventType::MODIFIED: {
880             if (&componentManager == &animationManager_) {
881                 OnAnimationComponentsUpdated(entities);
882             } else if (&componentManager == &animationTrackManager_) {
883                 OnAnimationTrackComponentsUpdated(entities);
884             }
885             break;
886         }
887         case IEcs::ComponentListener::EventType::MOVED: {
888             // Not using directly with ComponentIds.
889             break;
890         }
891     }
892 }
893 
OnAnimationComponentsCreated(BASE_NS::array_view<const CORE_NS::Entity> entities)894 void AnimationSystem::OnAnimationComponentsCreated(BASE_NS::array_view<const CORE_NS::Entity> entities)
895 {
896     for (const auto entity : entities) {
897         auto stateHandle = stateManager_.Write(entity);
898         if (!stateHandle) {
899             // create state only if wasn't been created already. state contains the playback position and someone might
900             // have set that already.
901             stateManager_.Create(entity);
902             stateHandle = stateManager_.Write(entity);
903             if (!stateHandle) {
904                 continue;
905             }
906         }
907         AnimationStateComponent& state = *stateHandle;
908         if (auto animationHandle = animationManager_.Read(entity); animationHandle) {
909             state.trackStates.reserve(animationHandle->tracks.size());
910 
911             for (const auto& trackEntity : animationHandle->tracks) {
912                 state.trackStates.push_back({ Entity(trackEntity), 0U });
913                 AnimationStateComponent::TrackState& trackState = state.trackStates.back();
914 
915                 const uint32_t componentId = initialTransformManager_.GetComponentId(trackState.entity);
916                 if (componentId == IComponentManager::INVALID_COMPONENT_ID) {
917                     // Store initial state for later use.
918                     if (auto trackHandle = animationTrackManager_.Read(trackState.entity); trackHandle) {
919                         InitializeInitialDataComponent(trackState.entity, *trackHandle);
920                     }
921                 }
922             }
923         }
924     }
925 }
926 
OnAnimationComponentsUpdated(BASE_NS::array_view<const CORE_NS::Entity> entities)927 void AnimationSystem::OnAnimationComponentsUpdated(BASE_NS::array_view<const CORE_NS::Entity> entities)
928 {
929     for (const auto entity : entities) {
930         auto animationHandle = animationManager_.Read(entity);
931         if (!animationHandle) {
932             // if there's no animation component then there's no need for state component either.
933             stateManager_.Destroy(entity);
934             continue;
935         }
936         auto stateHandle = stateManager_.Write(entity);
937         if (!stateHandle) {
938             // state component should have been created already, but if hasn't been for some reason then just add one.
939             stateManager_.Create(entity);
940             stateHandle = stateManager_.Write(entity);
941             if (!stateHandle) {
942                 continue;
943             }
944         }
945         const auto oldState = stateHandle->state;
946         const auto newState = animationHandle->state;
947         if (newState != AnimationComponent::PlaybackState::STOP) {
948             stateHandle->dirty = true;
949         }
950         if (oldState != newState) {
951             if (oldState == AnimationComponent::PlaybackState::STOP &&
952                 newState == AnimationComponent::PlaybackState::PLAY) {
953                 ResetTime(*stateHandle, *animationHandle);
954                 // if playing backwards and time has been reset to zero jump to the end of the animation.
955                 if ((animationHandle->speed < 0.f) && stateHandle->time == 0.f) {
956                     stateHandle->time = animationHandle->duration;
957                 }
958             } else if (newState == AnimationComponent::PlaybackState::STOP) {
959                 stateHandle->time = 0.0f;
960                 stateHandle->currentLoop = 0;
961                 // setting the animation as completed allows to identify which animations have been set to stopped state
962                 // for this frame. for such animations the tracks are rest to the initial state, but for previously
963                 // stopped animations we do nothing.
964                 stateHandle->completed = true;
965             } else if (newState == AnimationComponent::PlaybackState::PLAY) {
966                 stateHandle->completed = false;
967             }
968             stateHandle->state = animationHandle->state;
969         }
970 
971         auto& trackStates = stateHandle->trackStates;
972         if (trackStates.size() != animationHandle->tracks.size()) {
973             trackStates.resize(animationHandle->tracks.size());
974         }
975         auto stateIt = trackStates.begin();
976         for (const auto& trackEntity : animationHandle->tracks) {
977             stateIt->entity = trackEntity;
978             if (auto track = animationTrackManager_.Read(trackEntity); track) {
979                 if (auto inputData = inputManager_.Read(track->timestamps); inputData) {
980                     auto& input = *inputData;
981                     stateIt->length = input.timestamps.size();
982                 }
983             }
984             ++stateIt;
985         }
986     }
987 }
988 
OnAnimationTrackComponentsUpdated(BASE_NS::array_view<const CORE_NS::Entity> entities)989 void AnimationSystem::OnAnimationTrackComponentsUpdated(BASE_NS::array_view<const CORE_NS::Entity> entities)
990 {
991     for (const auto entity : entities) {
992         if (auto trackHandle = animationTrackManager_.Read(entity); trackHandle) {
993             InitializeInitialDataComponent(entity, *trackHandle);
994         }
995     }
996 }
997 
UpdateAnimationStates(uint64_t delta)998 void AnimationSystem::UpdateAnimationStates(uint64_t delta)
999 {
1000 #if (CORE3D_DEV_ENABLED == 1)
1001     CORE_CPU_PERF_SCOPE("CORE3D", "AnimationSystem", "UpdateAnimationStates", CORE3D_PROFILER_DEFAULT_COLOR);
1002 #endif
1003     const auto deltaS = static_cast<float>(delta) / 1000000.f;
1004     auto results = animationQuery_.GetResults();
1005     for (const auto& index : animationOrder_) {
1006         const auto& row = results[index];
1007         bool setPaused = false;
1008         {
1009             auto animationHandle = animationManager_.Read(row.components[0]);
1010             auto stateHandle = stateManager_.Write(row.components[1]);
1011             if (animationHandle && stateHandle) {
1012                 UpdateAnimation(*stateHandle, *animationHandle, trackQuery_, deltaS);
1013                 setPaused =
1014                     (stateHandle->completed && animationHandle->state == AnimationComponent::PlaybackState::PLAY);
1015             }
1016         }
1017         if (setPaused) {
1018             animationManager_.Write(row.components[0])->state = AnimationComponent::PlaybackState::PAUSE;
1019         }
1020     }
1021 }
1022 
UpdateAnimation(AnimationStateComponent & state,const AnimationComponent & animation,const CORE_NS::ComponentQuery & trackQuery,float delta)1023 void AnimationSystem::UpdateAnimation(AnimationStateComponent& state, const AnimationComponent& animation,
1024     const CORE_NS::ComponentQuery& trackQuery, float delta)
1025 {
1026     const auto* const begin = trackQuery_.GetResults().data();
1027     switch (animation.state) {
1028         case AnimationComponent::PlaybackState::STOP: {
1029             // Mark the tracks of this animation as stopped so that they are skipped in later steps.
1030             const auto stoppingOrStopped = state.completed ? TrackState::STOPPING : TrackState::STOPPED;
1031             for (const auto& trackState : state.trackStates) {
1032                 if (!EntityUtil::IsValid(trackState.entity)) {
1033                     continue;
1034                 }
1035                 if (auto row = trackQuery.FindResultRow(trackState.entity); row) {
1036                     trackOrder_.push_back(static_cast<uint32_t>(std::distance(begin, row)));
1037                     trackValues_[row->components[1]].state = stoppingOrStopped;
1038                 }
1039             }
1040             // Stopped animation doesn't need any actions.
1041             state.dirty = false;
1042             state.completed = false;
1043             return;
1044         }
1045         case AnimationComponent::PlaybackState::PLAY: {
1046             if (!(state.options & AnimationStateComponent::FlagBits::MANUAL_PROGRESS)) {
1047                 // Update time (in seconds).
1048                 state.time += animation.speed * delta;
1049             }
1050             break;
1051         }
1052         case AnimationComponent::PlaybackState::PAUSE: {
1053             // Paused animation doesn't need updates after this one.
1054             state.dirty = false;
1055             break;
1056         }
1057         default:
1058             break;
1059     }
1060 
1061     // All tracks completed?
1062     const auto timePosition = state.time + animation.startOffset;
1063     if ((timePosition >= (animation.duration + FLT_EPSILON)) || (timePosition <= -FLT_EPSILON)) {
1064         const bool repeat = (animation.repeatCount == AnimationComponent::REPEAT_COUNT_INFINITE) || // Infinite repeat.
1065                             animation.repeatCount > state.currentLoop; // Need to repeat until repeat count reached.
1066         if (repeat) {
1067             // Need to repeat, go to start.
1068             state.currentLoop++;
1069 
1070             ResetTime(state, animation);
1071         } else {
1072             // Animation completed.
1073             state.completed = true;
1074             state.time = Math::clamp(state.time, -FLT_EPSILON, animation.duration + FLT_EPSILON);
1075         }
1076     }
1077 
1078     // Set the animation's state to each track.
1079     const auto forward = (animation.speed >= 0.f);
1080     const auto pausedOrPlaying = state.completed ? TrackState::PAUSED : TrackState::PLAYING;
1081     for (const auto& trackState : state.trackStates) {
1082         auto row = EntityUtil::IsValid(trackState.entity) ? trackQuery.FindResultRow(trackState.entity) : nullptr;
1083         if (row) {
1084             trackOrder_.push_back(static_cast<uint32_t>(std::distance(begin, row)));
1085             auto& track = trackValues_[row->components[1]];
1086             track.timePosition = timePosition;
1087             track.weight = animation.weight;
1088             track.state = pausedOrPlaying;
1089             track.forward = forward;
1090         }
1091     }
1092 }
1093 
InitializeTrackValues()1094 void AnimationSystem::InitializeTrackValues()
1095 {
1096 #if (CORE3D_DEV_ENABLED == 1)
1097     CORE_CPU_PERF_SCOPE("CORE3D", "AnimationSystem", "InitializeTrackValues", CORE3D_PROFILER_DEFAULT_COLOR);
1098 #endif
1099     initTasks_.clear();
1100     if (tasks_) {
1101         // Reserve for twice as many tasks as both init and anim task results are in the same vector.
1102         taskResults_.reserve(taskResults_.size() + tasks_ * 2U);
1103 
1104         initTasks_.reserve(remaining_ ? (tasks_ + 1U) : tasks_);
1105         initTaskStart_ = taskId_;
1106         for (size_t i = 0; i < tasks_; ++i) {
1107             auto& task = initTasks_.emplace_back(*this, i * taskSize_, taskSize_);
1108             taskResults_.push_back(threadPool_->Push(IThreadPool::ITask::Ptr { &task }));
1109             ++taskId_;
1110         }
1111     }
1112     if (remaining_ >= systemProperties_.minTaskSize) {
1113         auto& task = initTasks_.emplace_back(*this, tasks_ * taskSize_, remaining_);
1114         taskResults_.push_back(threadPool_->Push(IThreadPool::ITask::Ptr { &task }));
1115         ++taskId_;
1116     } else {
1117         InitializeTrackValues(array_view(static_cast<const uint32_t*>(trackOrder_.data()), remaining_));
1118     }
1119 }
1120 
AnimateTrackValues()1121 void AnimationSystem::AnimateTrackValues()
1122 {
1123 #if (CORE3D_DEV_ENABLED == 1)
1124     CORE_CPU_PERF_SCOPE("CORE3D", "AnimationSystem", "AnimateTrackValues", CORE3D_PROFILER_DEFAULT_COLOR);
1125 #endif
1126 
1127     frameIndexTasks_.clear();
1128     frameIndexTasks_.reserve(remaining_ ? (tasks_ + 1U) : tasks_);
1129 
1130     animTasks_.clear();
1131     animTasks_.reserve(remaining_ ? (tasks_ + 1U) : tasks_);
1132     animTaskStart_ = taskId_;
1133 
1134     auto batch = [this](size_t i, size_t offset, size_t count) {
1135         // Start task for calculating which keyframes are used.
1136         auto& frameIndexTask = frameIndexTasks_.emplace_back(*this, offset, count);
1137         threadPool_->PushNoWait(IThreadPool::ITask::Ptr { &frameIndexTask });
1138 
1139         // Start task for interpolating between the selected keyframes.
1140         // This work requires the initial values as well as the keyframe indices.
1141         auto& task = animTasks_.emplace_back(*this, offset, count);
1142         const IThreadPool::ITask* dependencies[] = { &initTasks_[i], &frameIndexTask };
1143         taskResults_.push_back(threadPool_->Push(IThreadPool::ITask::Ptr { &task }, dependencies));
1144         ++taskId_;
1145     };
1146     for (size_t i = 0U; i < tasks_; ++i) {
1147         batch(i, i * taskSize_, taskSize_);
1148     }
1149     if (remaining_ >= systemProperties_.minTaskSize) {
1150         batch(tasks_, tasks_ * taskSize_, remaining_);
1151     } else {
1152         const auto results = array_view(static_cast<const uint32_t*>(trackOrder_.data()), remaining_);
1153         CalculateFrameIndices(results);
1154         AnimateTracks(results);
1155     }
1156 }
1157 
ResetToInitialTrackValues()1158 void AnimationSystem::ResetToInitialTrackValues()
1159 {
1160 #if (CORE3D_DEV_ENABLED == 1)
1161     CORE_CPU_PERF_SCOPE("CORE3D", "AnimationSystem", "ResetToInitialTrackValues", CORE3D_PROFILER_DEFAULT_COLOR);
1162 #endif
1163 
1164     auto batch = [this](size_t i, size_t count) {
1165         // Wait that initial values for this track batch have been filled
1166         taskResults_[i]->Wait();
1167 
1168         // While task is running reset the target property so we can later sum the results of multiple tracks.
1169         ResetTargetProperties(array_view(static_cast<const uint32_t*>(trackOrder_.data()) + i * taskSize_, count));
1170     };
1171     for (size_t i = 0U; i < tasks_; ++i) {
1172         batch(i, taskSize_);
1173     }
1174     if (remaining_ >= systemProperties_.minTaskSize) {
1175         batch(tasks_, remaining_);
1176     } else {
1177         const auto results = array_view(static_cast<const uint32_t*>(trackOrder_.data()), remaining_);
1178         ResetTargetProperties(results);
1179     }
1180 }
1181 
WriteUpdatedTrackValues()1182 void AnimationSystem::WriteUpdatedTrackValues()
1183 {
1184 #if (CORE3D_DEV_ENABLED == 1)
1185     CORE_CPU_PERF_SCOPE("CORE3D", "AnimationSystem", "WriteUpdatedTrackValues", CORE3D_PROFILER_DEFAULT_COLOR);
1186 #endif
1187     auto batch = [this](size_t i, size_t count) {
1188         // Wait for animation task for this batch to finish.
1189         taskResults_[i + animTaskStart_]->Wait();
1190 
1191         // Apply the result of each track animation to the target property.
1192         ApplyResults(array_view(static_cast<const uint32_t*>(trackOrder_.data()) + i * taskSize_, count));
1193     };
1194     for (size_t i = 0U; i < tasks_; ++i) {
1195         batch(i, taskSize_);
1196     }
1197     if (remaining_ >= systemProperties_.minTaskSize) {
1198         batch(tasks_, remaining_);
1199     } else {
1200         ApplyResults(array_view(static_cast<const uint32_t*>(trackOrder_.data()), remaining_));
1201     }
1202 }
1203 
ResetTargetProperties(array_view<const uint32_t> resultIndices)1204 void AnimationSystem::ResetTargetProperties(array_view<const uint32_t> resultIndices)
1205 {
1206 #if (CORE3D_DEV_ENABLED == 1)
1207     CORE_CPU_PERF_SCOPE("CORE3D", "AnimationSystem", "ResetTargetProperties", CORE3D_PROFILER_DEFAULT_COLOR);
1208 #endif
1209     auto results = trackQuery_.GetResults();
1210     for (auto index : resultIndices) {
1211         const ComponentQuery::ResultRow& row = results[index];
1212         const auto trackId = row.components[1];
1213         auto& trackValues = trackValues_[trackId];
1214         if (trackValues.state == TrackState::STOPPED) {
1215             continue;
1216         }
1217         auto trackHandle = animationTrackManager_.Read(trackId);
1218         auto entry = GetEntry(*trackHandle);
1219         if (entry.component && entry.property) {
1220             if (IPropertyHandle* targetHandle = entry.component->GetData(trackHandle->target); targetHandle) {
1221                 // special handling for IPropertyHandle*. need to fetch the property types behind the pointer and
1222                 // update targetHandle.
1223                 if (entry.property->type == PROPERTY_HANDLE_PTR_T) {
1224                     auto componentBase = ScopedHandle<const uint8_t>(targetHandle);
1225                     auto* dynamicProperties = *Cast<IPropertyHandle* const*>(&*componentBase + entry.propertyOffset);
1226                     if (dynamicProperties) {
1227                         entry = FindDynamicProperty(*trackHandle, dynamicProperties, entry);
1228                         targetHandle = dynamicProperties;
1229                     } else {
1230                         continue;
1231                     }
1232                 }
1233                 if (entry.property->type == trackValues.initial.type) {
1234                     if (auto baseAddress = ScopedHandle<uint8_t>(targetHandle); baseAddress) {
1235                         Assign(entry.property->type, &*baseAddress + entry.propertyOffset, trackValues.initial);
1236                     }
1237                 } else {
1238                     CORE_LOG_ONCE_D(to_string(Hash(trackId, entry.property->type.typeHash)),
1239                         "ResetTargetProperties failed, type mismatch %" PRIx64, entry.property->type.typeHash);
1240                 }
1241             }
1242         }
1243     }
1244 }
1245 
InitializeTrackValues(array_view<const uint32_t> resultIndices)1246 void AnimationSystem::InitializeTrackValues(array_view<const uint32_t> resultIndices)
1247 {
1248 #if (CORE3D_DEV_ENABLED == 1)
1249     CORE_CPU_PERF_SCOPE("CORE3D", "AnimationSystem", "InitializeTrackValues", CORE3D_PROFILER_DEFAULT_COLOR);
1250 #endif
1251     auto& initialTransformManager = initialTransformManager_;
1252     auto& trackValues = trackValues_;
1253     auto results = trackQuery_.GetResults();
1254     for (auto index : resultIndices) {
1255         const ComponentQuery::ResultRow& row = results[index];
1256         auto initialHandle = initialTransformManager.Read(row.components[0]);
1257         auto& values = trackValues[row.components[1]];
1258         values.initial = *initialHandle;
1259         values.result = *initialHandle;
1260         values.updated = false;
1261     }
1262 }
1263 
CalculateFrameIndices(array_view<const uint32_t> resultIndices)1264 void AnimationSystem::CalculateFrameIndices(array_view<const uint32_t> resultIndices)
1265 {
1266 #if (CORE3D_DEV_ENABLED == 1)
1267     CORE_CPU_PERF_SCOPE("CORE3D", "AnimationSystem", "CalculateFrameIndices", CORE3D_PROFILER_DEFAULT_COLOR);
1268 #endif
1269     auto results = trackQuery_.GetResults();
1270     for (auto index : resultIndices) {
1271         const ComponentQuery::ResultRow& row = results[index];
1272         const auto trackId = row.components[1];
1273         if (trackValues_[trackId].state > TrackState::PAUSED) {
1274             continue;
1275         }
1276         if (auto track = animationTrackManager_.Read(trackId); track) {
1277             if (const auto inputData = inputManager_.Read(track->timestamps); inputData) {
1278                 const array_view<const float> timestamps = inputData->timestamps;
1279                 // Ensure we have data.
1280                 if (!timestamps.empty()) {
1281                     size_t currentFrameIndex;
1282                     size_t nextFrameIndex;
1283 
1284                     const auto currentTime = trackValues_[trackId].timePosition;
1285                     FindFrameIndices(
1286                         trackValues_[trackId].forward, currentTime, timestamps, currentFrameIndex, nextFrameIndex);
1287 
1288                     float currentOffset = 0.f;
1289                     if (currentFrameIndex != nextFrameIndex) {
1290                         const float startFrameTime = timestamps[currentFrameIndex];
1291                         const float endFrameTime = timestamps[nextFrameIndex];
1292 
1293                         currentOffset = (currentTime - startFrameTime) / (endFrameTime - startFrameTime);
1294                         currentOffset = std::clamp(currentOffset, 0.0f, 1.0f);
1295                     }
1296 
1297                     frameIndices_[trackId] = FrameData { currentOffset, currentFrameIndex, nextFrameIndex };
1298                 }
1299             }
1300         }
1301     }
1302 }
1303 
AnimateTracks(array_view<const uint32_t> resultIndices)1304 void AnimationSystem::AnimateTracks(array_view<const uint32_t> resultIndices)
1305 {
1306 #if (CORE3D_DEV_ENABLED == 1)
1307     CORE_CPU_PERF_SCOPE("CORE3D", "AnimationSystem", "AnimateTracks", CORE3D_PROFILER_DEFAULT_COLOR);
1308 #endif
1309     auto results = trackQuery_.GetResults();
1310     for (auto index : resultIndices) {
1311         const ComponentQuery::ResultRow& row = results[index];
1312         const auto trackId = row.components[1];
1313         auto& trackValues = trackValues_[trackId];
1314         if (trackValues.state > TrackState::PAUSED) {
1315             continue;
1316         }
1317         if (auto trackHandle = animationTrackManager_.Read(trackId); trackHandle) {
1318             if (auto outputHandle = outputManager_.Read(trackHandle->data); outputHandle) {
1319                 if (outputHandle->data.empty()) {
1320                     continue;
1321                 }
1322 
1323                 auto entry = GetEntry(*trackHandle);
1324                 if (!entry.component || !entry.property) {
1325                     return;
1326                 }
1327 
1328                 // special handling for IPropertyHandle*. need to fetch the property types behind the pointer.
1329                 if (entry.property->type == PROPERTY_HANDLE_PTR_T) {
1330                     if (const IPropertyHandle* targetHandle = entry.component->GetData(trackHandle->target);
1331                         targetHandle) {
1332                         auto componentBase = ScopedHandle<const uint8_t>(targetHandle);
1333                         auto* dynamicProperties =
1334                             *Cast<const IPropertyHandle* const*>(&*componentBase + entry.propertyOffset);
1335                         if (dynamicProperties) {
1336                             entry = FindDynamicProperty(*trackHandle, dynamicProperties, entry);
1337                         }
1338                     }
1339                 }
1340 
1341                 if ((outputHandle->type != entry.property->type) || (outputHandle->type != trackValues.initial.type) ||
1342                     (outputHandle->type != trackValues.result.type)) {
1343                     CORE_LOG_ONCE_D(to_string(Hash(trackId, outputHandle->type)),
1344                         "AnimateTrack failed, unexpected type %" PRIx64, outputHandle->type);
1345                     continue;
1346                 }
1347 
1348                 // spline interpolation takes three values: in-tangent, data point, and out-tangent
1349                 const size_t inputCount =
1350                     (trackHandle->interpolationMode == AnimationTrackComponent::Interpolation::SPLINE) ? 3U : 1U;
1351                 const FrameData& currentFrame = frameIndices_[trackId];
1352                 const InterpolationData interpolationData { trackHandle->interpolationMode,
1353                     currentFrame.currentFrameIndex * inputCount, currentFrame.nextFrameIndex * inputCount,
1354                     currentFrame.currentOffset, trackValues.weight };
1355 
1356                 // initial data is needed only when weight < 1 or there are multiple animations targeting the same
1357                 // property.
1358                 // identifying such tracks would save updating the initial data in AnimationSystem::Update and
1359                 // interpolating between initial and result.
1360                 AnimateTrack(
1361                     entry.property->type, interpolationData, *outputHandle, trackValues.initial, trackValues.result);
1362                 trackValues.updated = true;
1363             }
1364         }
1365     }
1366 }
1367 
ApplyResults(array_view<const uint32_t> resultIndices)1368 void AnimationSystem::ApplyResults(array_view<const uint32_t> resultIndices)
1369 {
1370     auto results = trackQuery_.GetResults();
1371     for (auto index : resultIndices) {
1372         const ComponentQuery::ResultRow& row = results[index];
1373         const auto trackId = row.components[1];
1374         auto& values = trackValues_[trackId];
1375 
1376         if (values.state < TrackState::STOPPING && values.updated) {
1377             values.updated = false;
1378             auto trackHandle = animationTrackManager_.Read(trackId);
1379             auto entry = GetEntry(*trackHandle);
1380             if (entry.component && entry.property) {
1381                 if (IPropertyHandle* targetHandle = entry.component->GetData(trackHandle->target); targetHandle) {
1382                     // special handling for IPropertyHandle*. need to fetch the property types behind the pointer and
1383                     // update targetHandle.
1384                     if (entry.property->type == PROPERTY_HANDLE_PTR_T) {
1385                         auto componentBase = ScopedHandle<const uint8_t>(targetHandle);
1386                         auto* dynamicProperties =
1387                             *Cast<IPropertyHandle* const*>(&*componentBase + entry.propertyOffset);
1388                         if (dynamicProperties) {
1389                             entry = FindDynamicProperty(*trackHandle, dynamicProperties, entry);
1390                             targetHandle = dynamicProperties;
1391                         } else {
1392                             continue;
1393                         }
1394                     }
1395                     if (entry.property->type == values.result.type) {
1396                         if (auto baseAddress = ScopedHandle<uint8_t>(targetHandle); baseAddress) {
1397                             Add(entry.property->type, &*baseAddress + entry.propertyOffset, values.result);
1398                         }
1399                     } else {
1400                         CORE_LOG_ONCE_D(to_string(Hash(trackId, entry.property->type.typeHash)),
1401                             "ApplyResults failed, type mismatch %" PRIx64, entry.property->type.typeHash);
1402                     }
1403                 }
1404             }
1405         }
1406     }
1407 }
1408 
GetEntry(const AnimationTrackComponent & track)1409 const AnimationSystem::PropertyEntry& AnimationSystem::GetEntry(const AnimationTrackComponent& track)
1410 {
1411     // Cache component manager and property lookups
1412     const auto key = Hash(track.component, track.property);
1413     auto pos = std::lower_bound(std::begin(propertyCache_), std::end(propertyCache_), key,
1414         [](const PropertyEntry& element, uint64_t value) { return element.componentAndProperty < value; });
1415     // Add new manager-propery combinations
1416     if ((pos == std::end(propertyCache_)) || (pos->componentAndProperty != key)) {
1417         pos = propertyCache_.insert(pos, PropertyEntry { key, ecs_.GetComponentManager(track.component), 0U, nullptr });
1418     }
1419     // Try to get the component manager if it's missing
1420     if (!pos->component) {
1421         pos->component = ecs_.GetComponentManager(track.component);
1422     }
1423 
1424     // Try to get the property type if it's missing and there's a valid target
1425     if (pos->component && !pos->property && EntityUtil::IsValid(track.target)) {
1426         if (IPropertyHandle* targetHandle = pos->component->GetData(track.target); targetHandle) {
1427             const auto propertyOffset = PropertyData::FindProperty(targetHandle->Owner()->MetaData(), track.property);
1428             if (propertyOffset.property) {
1429                 pos->propertyOffset = propertyOffset.offset;
1430                 pos->property = propertyOffset.property;
1431             }
1432         }
1433     }
1434     return *pos;
1435 }
1436 
InitializeInitialDataComponent(Entity trackEntity,const AnimationTrackComponent & animationTrack)1437 void AnimationSystem::InitializeInitialDataComponent(Entity trackEntity, const AnimationTrackComponent& animationTrack)
1438 {
1439     if (auto entry = GetEntry(animationTrack); entry.component && entry.property) {
1440         if (IPropertyHandle* targetHandle = entry.component->GetData(animationTrack.target); targetHandle) {
1441             initialTransformManager_.Create(trackEntity);
1442             auto dstHandle = initialTransformManager_.Write(trackEntity);
1443 
1444             auto componentBase = ScopedHandle<const uint8_t>(targetHandle);
1445             if (entry.property->type != PROPERTY_HANDLE_PTR_T) {
1446                 auto src = array_view(&*componentBase + entry.propertyOffset, entry.property->size);
1447                 CopyInitialDataComponent(*dstHandle, entry.property, src);
1448             } else {
1449                 // special handling for property handles
1450                 auto* dynamicProperties = *Cast<const IPropertyHandle* const*>(&*componentBase + entry.propertyOffset);
1451                 if (dynamicProperties) {
1452                     entry = FindDynamicProperty(animationTrack, dynamicProperties, entry);
1453                     auto dynamicPropertiesBase = ScopedHandle<const uint8_t>(dynamicProperties);
1454                     auto src = array_view(&*dynamicPropertiesBase + entry.propertyOffset, entry.property->size);
1455                     CopyInitialDataComponent(*dstHandle, entry.property, src);
1456                 }
1457             }
1458         }
1459     }
1460 }
1461 
IAnimationSystemInstance(IEcs & ecs)1462 ISystem* IAnimationSystemInstance(IEcs& ecs)
1463 {
1464     return new AnimationSystem(ecs);
1465 }
1466 
IAnimationSystemDestroy(ISystem * instance)1467 void IAnimationSystemDestroy(ISystem* instance)
1468 {
1469     delete static_cast<AnimationSystem*>(instance);
1470 }
1471 CORE3D_END_NAMESPACE()
1472