• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2024 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 && c.propertyPath == propertyName) {
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         stateManager_.Create(entity);
898         auto stateHandle = stateManager_.Write(entity);
899         AnimationStateComponent& state = *stateHandle;
900         if (auto animationHandle = animationManager_.Read(entity); animationHandle) {
901             state.trackStates.reserve(animationHandle->tracks.size());
902 
903             for (const auto& trackEntity : animationHandle->tracks) {
904                 state.trackStates.push_back({ Entity(trackEntity), 0U });
905                 AnimationStateComponent::TrackState& trackState = state.trackStates.back();
906 
907                 const uint32_t componentId = initialTransformManager_.GetComponentId(trackState.entity);
908                 if (componentId == IComponentManager::INVALID_COMPONENT_ID) {
909                     // Store initial state for later use.
910                     if (auto trackHandle = animationTrackManager_.Read(trackState.entity); trackHandle) {
911                         InitializeInitialDataComponent(trackState.entity, *trackHandle);
912                     }
913                 }
914             }
915         }
916     }
917 }
918 
OnAnimationComponentsUpdated(BASE_NS::array_view<const CORE_NS::Entity> entities)919 void AnimationSystem::OnAnimationComponentsUpdated(BASE_NS::array_view<const CORE_NS::Entity> entities)
920 {
921     for (const auto entity : entities) {
922         auto animationHandle = animationManager_.Read(entity);
923         if (!animationHandle) {
924             // if there's no animation component then there's no need for state component either.
925             stateManager_.Destroy(entity);
926             continue;
927         }
928         auto stateHandle = stateManager_.Write(entity);
929         if (!stateHandle) {
930             // state component should have been created already, but if hasn't been for some reason then just add one.
931             stateManager_.Create(entity);
932             stateHandle = stateManager_.Write(entity);
933             if (!stateHandle) {
934                 continue;
935             }
936         }
937         const auto oldState = stateHandle->state;
938         const auto newState = animationHandle->state;
939         if (newState != AnimationComponent::PlaybackState::STOP) {
940             stateHandle->dirty = true;
941         }
942         if (oldState != newState) {
943             if (oldState == AnimationComponent::PlaybackState::STOP &&
944                 newState == AnimationComponent::PlaybackState::PLAY) {
945                 ResetTime(*stateHandle, *animationHandle);
946                 // if playing backwards and time has been reset to zero jump to the end of the animation.
947                 if ((animationHandle->speed < 0.f) && stateHandle->time == 0.f) {
948                     stateHandle->time = animationHandle->duration;
949                 }
950             } else if (newState == AnimationComponent::PlaybackState::STOP) {
951                 stateHandle->time = 0.0f;
952                 stateHandle->currentLoop = 0;
953                 // setting the animation as completed allows to identify which animations have been set to stopped state
954                 // for this frame. for such animations the tracks are rest to the initial state, but for previously
955                 // stopped animations we do nothing.
956                 stateHandle->completed = true;
957             } else if (newState == AnimationComponent::PlaybackState::PLAY) {
958                 stateHandle->completed = false;
959             }
960             stateHandle->state = animationHandle->state;
961         }
962 
963         auto& trackStates = stateHandle->trackStates;
964         if (trackStates.size() != animationHandle->tracks.size()) {
965             trackStates.resize(animationHandle->tracks.size());
966         }
967         auto stateIt = trackStates.begin();
968         for (const auto& trackEntity : animationHandle->tracks) {
969             stateIt->entity = trackEntity;
970             if (auto track = animationTrackManager_.Read(trackEntity); track) {
971                 if (auto inputData = inputManager_.Read(track->timestamps); inputData) {
972                     auto& input = *inputData;
973                     stateIt->length = input.timestamps.size();
974                 }
975             }
976             ++stateIt;
977         }
978     }
979 }
980 
OnAnimationTrackComponentsUpdated(BASE_NS::array_view<const CORE_NS::Entity> entities)981 void AnimationSystem::OnAnimationTrackComponentsUpdated(BASE_NS::array_view<const CORE_NS::Entity> entities)
982 {
983     for (const auto entity : entities) {
984         if (auto trackHandle = animationTrackManager_.Read(entity); trackHandle) {
985             InitializeInitialDataComponent(entity, *trackHandle);
986         }
987     }
988 }
989 
UpdateAnimationStates(uint64_t delta)990 void AnimationSystem::UpdateAnimationStates(uint64_t delta)
991 {
992 #if (CORE3D_DEV_ENABLED == 1)
993     CORE_CPU_PERF_SCOPE("CORE3D", "AnimationSystem", "UpdateAnimationStates", CORE3D_PROFILER_DEFAULT_COLOR);
994 #endif
995     const auto deltaS = static_cast<float>(delta) / 1000000.f;
996     auto results = animationQuery_.GetResults();
997     for (const auto& index : animationOrder_) {
998         const auto& row = results[index];
999         bool setPaused = false;
1000         {
1001             auto animationHandle = animationManager_.Read(row.components[0]);
1002             auto stateHandle = stateManager_.Write(row.components[1]);
1003             if (animationHandle && stateHandle) {
1004                 UpdateAnimation(*stateHandle, *animationHandle, trackQuery_, deltaS);
1005                 setPaused =
1006                     (stateHandle->completed && animationHandle->state == AnimationComponent::PlaybackState::PLAY);
1007             }
1008         }
1009         if (setPaused) {
1010             animationManager_.Write(row.components[0])->state = AnimationComponent::PlaybackState::PAUSE;
1011         }
1012     }
1013 }
1014 
UpdateAnimation(AnimationStateComponent & state,const AnimationComponent & animation,const CORE_NS::ComponentQuery & trackQuery,float delta)1015 void AnimationSystem::UpdateAnimation(AnimationStateComponent& state, const AnimationComponent& animation,
1016     const CORE_NS::ComponentQuery& trackQuery, float delta)
1017 {
1018     const auto* const begin = trackQuery_.GetResults().data();
1019     switch (animation.state) {
1020         case AnimationComponent::PlaybackState::STOP: {
1021             // Mark the tracks of this animation as stopped so that they are skipped in later steps.
1022             const auto stoppingOrStopped = state.completed ? TrackState::STOPPING : TrackState::STOPPED;
1023             for (const auto& trackState : state.trackStates) {
1024                 if (!EntityUtil::IsValid(trackState.entity)) {
1025                     continue;
1026                 }
1027                 if (auto row = trackQuery.FindResultRow(trackState.entity); row) {
1028                     trackOrder_.push_back(static_cast<uint32_t>(std::distance(begin, row)));
1029                     trackValues_[row->components[1]].state = stoppingOrStopped;
1030                 }
1031             }
1032             // Stopped animation doesn't need any actions.
1033             state.dirty = false;
1034             state.completed = false;
1035             return;
1036         }
1037         case AnimationComponent::PlaybackState::PLAY: {
1038             if (!(state.options & AnimationStateComponent::FlagBits::MANUAL_PROGRESS)) {
1039                 // Update time (in seconds).
1040                 state.time += animation.speed * delta;
1041             }
1042             break;
1043         }
1044         case AnimationComponent::PlaybackState::PAUSE: {
1045             // Paused animation doesn't need updates after this one.
1046             state.dirty = false;
1047             break;
1048         }
1049         default:
1050             break;
1051     }
1052 
1053     // All tracks completed?
1054     const auto timePosition = state.time + animation.startOffset;
1055     if ((timePosition >= (animation.duration + FLT_EPSILON)) || (timePosition <= -FLT_EPSILON)) {
1056         const bool repeat = (animation.repeatCount == AnimationComponent::REPEAT_COUNT_INFINITE) || // Infinite repeat.
1057                             animation.repeatCount > state.currentLoop; // Need to repeat until repeat count reached.
1058         if (repeat) {
1059             // Need to repeat, go to start.
1060             state.currentLoop++;
1061 
1062             ResetTime(state, animation);
1063         } else {
1064             // Animation completed.
1065             state.completed = true;
1066             state.time = Math::clamp(state.time, -FLT_EPSILON, animation.duration + FLT_EPSILON);
1067         }
1068     }
1069 
1070     // Set the animation's state to each track.
1071     const auto forward = (animation.speed >= 0.f);
1072     const auto pausedOrPlaying = state.completed ? TrackState::PAUSED : TrackState::PLAYING;
1073     for (const auto& trackState : state.trackStates) {
1074         auto row = EntityUtil::IsValid(trackState.entity) ? trackQuery.FindResultRow(trackState.entity) : nullptr;
1075         if (row) {
1076             trackOrder_.push_back(static_cast<uint32_t>(std::distance(begin, row)));
1077             auto& track = trackValues_[row->components[1]];
1078             track.timePosition = timePosition;
1079             track.weight = animation.weight;
1080             track.state = pausedOrPlaying;
1081             track.forward = forward;
1082         }
1083     }
1084 }
1085 
InitializeTrackValues()1086 void AnimationSystem::InitializeTrackValues()
1087 {
1088 #if (CORE3D_DEV_ENABLED == 1)
1089     CORE_CPU_PERF_SCOPE("CORE3D", "AnimationSystem", "InitializeTrackValues", CORE3D_PROFILER_DEFAULT_COLOR);
1090 #endif
1091     initTasks_.clear();
1092     if (tasks_) {
1093         // Reserve for twice as many tasks as both init and anim task results are in the same vector.
1094         taskResults_.reserve(taskResults_.size() + tasks_ * 2U);
1095 
1096         initTasks_.reserve(remaining_ ? (tasks_ + 1U) : tasks_);
1097         initTaskStart_ = taskId_;
1098         for (size_t i = 0; i < tasks_; ++i) {
1099             auto& task = initTasks_.emplace_back(*this, i * taskSize_, taskSize_);
1100             taskResults_.push_back(threadPool_->Push(IThreadPool::ITask::Ptr { &task }));
1101             ++taskId_;
1102         }
1103     }
1104     if (remaining_ >= systemProperties_.minTaskSize) {
1105         auto& task = initTasks_.emplace_back(*this, tasks_ * taskSize_, remaining_);
1106         taskResults_.push_back(threadPool_->Push(IThreadPool::ITask::Ptr { &task }));
1107         ++taskId_;
1108     } else {
1109         InitializeTrackValues(array_view(static_cast<const uint32_t*>(trackOrder_.data()), remaining_));
1110     }
1111 }
1112 
AnimateTrackValues()1113 void AnimationSystem::AnimateTrackValues()
1114 {
1115 #if (CORE3D_DEV_ENABLED == 1)
1116     CORE_CPU_PERF_SCOPE("CORE3D", "AnimationSystem", "AnimateTrackValues", CORE3D_PROFILER_DEFAULT_COLOR);
1117 #endif
1118 
1119     frameIndexTasks_.clear();
1120     frameIndexTasks_.reserve(remaining_ ? (tasks_ + 1U) : tasks_);
1121 
1122     animTasks_.clear();
1123     animTasks_.reserve(remaining_ ? (tasks_ + 1U) : tasks_);
1124     animTaskStart_ = taskId_;
1125 
1126     auto batch = [this](size_t i, size_t offset, size_t count) {
1127         // Start task for calculating which keyframes are used.
1128         auto& frameIndexTask = frameIndexTasks_.emplace_back(*this, offset, count);
1129         threadPool_->PushNoWait(IThreadPool::ITask::Ptr { &frameIndexTask });
1130 
1131         // Start task for interpolating between the selected keyframes.
1132         // This work requires the initial values as well as the keyframe indices.
1133         auto& task = animTasks_.emplace_back(*this, offset, count);
1134         const IThreadPool::ITask* dependencies[] = { &initTasks_[i], &frameIndexTask };
1135         taskResults_.push_back(threadPool_->Push(IThreadPool::ITask::Ptr { &task }, dependencies));
1136         ++taskId_;
1137     };
1138     for (size_t i = 0U; i < tasks_; ++i) {
1139         batch(i, i * taskSize_, taskSize_);
1140     }
1141     if (remaining_ >= systemProperties_.minTaskSize) {
1142         batch(tasks_, tasks_ * taskSize_, remaining_);
1143     } else {
1144         const auto results = array_view(static_cast<const uint32_t*>(trackOrder_.data()), remaining_);
1145         CalculateFrameIndices(results);
1146         AnimateTracks(results);
1147     }
1148 }
1149 
ResetToInitialTrackValues()1150 void AnimationSystem::ResetToInitialTrackValues()
1151 {
1152 #if (CORE3D_DEV_ENABLED == 1)
1153     CORE_CPU_PERF_SCOPE("CORE3D", "AnimationSystem", "ResetToInitialTrackValues", CORE3D_PROFILER_DEFAULT_COLOR);
1154 #endif
1155 
1156     auto batch = [this](size_t i, size_t count) {
1157         // Wait that initial values for this track batch have been filled
1158         taskResults_[i]->Wait();
1159 
1160         // While task is running reset the target property so we can later sum the results of multiple tracks.
1161         ResetTargetProperties(array_view(static_cast<const uint32_t*>(trackOrder_.data()) + i * taskSize_, count));
1162     };
1163     for (size_t i = 0U; i < tasks_; ++i) {
1164         batch(i, taskSize_);
1165     }
1166     if (remaining_ >= systemProperties_.minTaskSize) {
1167         batch(tasks_, remaining_);
1168     } else {
1169         const auto results = array_view(static_cast<const uint32_t*>(trackOrder_.data()), remaining_);
1170         ResetTargetProperties(results);
1171     }
1172 }
1173 
WriteUpdatedTrackValues()1174 void AnimationSystem::WriteUpdatedTrackValues()
1175 {
1176 #if (CORE3D_DEV_ENABLED == 1)
1177     CORE_CPU_PERF_SCOPE("CORE3D", "AnimationSystem", "WriteUpdatedTrackValues", CORE3D_PROFILER_DEFAULT_COLOR);
1178 #endif
1179     auto batch = [this](size_t i, size_t count) {
1180         // Wait for animation task for this batch to finish.
1181         taskResults_[i + animTaskStart_]->Wait();
1182 
1183         // Apply the result of each track animation to the target property.
1184         ApplyResults(array_view(static_cast<const uint32_t*>(trackOrder_.data()) + i * taskSize_, count));
1185     };
1186     for (size_t i = 0U; i < tasks_; ++i) {
1187         batch(i, taskSize_);
1188     }
1189     if (remaining_ >= systemProperties_.minTaskSize) {
1190         batch(tasks_, remaining_);
1191     } else {
1192         ApplyResults(array_view(static_cast<const uint32_t*>(trackOrder_.data()), remaining_));
1193     }
1194 }
1195 
ResetTargetProperties(array_view<const uint32_t> resultIndices)1196 void AnimationSystem::ResetTargetProperties(array_view<const uint32_t> resultIndices)
1197 {
1198 #if (CORE3D_DEV_ENABLED == 1)
1199     CORE_CPU_PERF_SCOPE("CORE3D", "AnimationSystem", "ResetTargetProperties", CORE3D_PROFILER_DEFAULT_COLOR);
1200 #endif
1201     auto results = trackQuery_.GetResults();
1202     for (auto index : resultIndices) {
1203         const ComponentQuery::ResultRow& row = results[index];
1204         const auto trackId = row.components[1];
1205         auto& trackValues = trackValues_[trackId];
1206         if (trackValues.state == TrackState::STOPPED) {
1207             continue;
1208         }
1209         auto trackHandle = animationTrackManager_.Read(trackId);
1210         auto entry = GetEntry(*trackHandle);
1211         if (entry.component && entry.property) {
1212             if (IPropertyHandle* targetHandle = entry.component->GetData(trackHandle->target); targetHandle) {
1213                 // special handling for IPropertyHandle*. need to fetch the property types behind the pointer and
1214                 // update targetHandle.
1215                 if (entry.property->type == PROPERTY_HANDLE_PTR_T) {
1216                     auto componentBase = ScopedHandle<const uint8_t>(targetHandle);
1217                     auto* dynamicProperties = *Cast<IPropertyHandle* const*>(&*componentBase + entry.propertyOffset);
1218                     if (dynamicProperties) {
1219                         entry = FindDynamicProperty(*trackHandle, dynamicProperties, entry);
1220                         targetHandle = dynamicProperties;
1221                     } else {
1222                         continue;
1223                     }
1224                 }
1225                 if (entry.property->type == trackValues.initial.type) {
1226                     if (auto baseAddress = ScopedHandle<uint8_t>(targetHandle); baseAddress) {
1227                         Assign(entry.property->type, &*baseAddress + entry.propertyOffset, trackValues.initial);
1228                     }
1229                 } else {
1230                     CORE_LOG_ONCE_D(to_string(Hash(trackId, entry.property->type.typeHash)),
1231                         "ResetTargetProperties failed, type mismatch %" PRIx64, entry.property->type.typeHash);
1232                 }
1233             }
1234         }
1235     }
1236 }
1237 
InitializeTrackValues(array_view<const uint32_t> resultIndices)1238 void AnimationSystem::InitializeTrackValues(array_view<const uint32_t> resultIndices)
1239 {
1240 #if (CORE3D_DEV_ENABLED == 1)
1241     CORE_CPU_PERF_SCOPE("CORE3D", "AnimationSystem", "InitializeTrackValues", CORE3D_PROFILER_DEFAULT_COLOR);
1242 #endif
1243     auto& initialTransformManager = initialTransformManager_;
1244     auto& trackValues = trackValues_;
1245     auto results = trackQuery_.GetResults();
1246     for (auto index : resultIndices) {
1247         const ComponentQuery::ResultRow& row = results[index];
1248         auto initialHandle = initialTransformManager.Read(row.components[0]);
1249         auto& values = trackValues[row.components[1]];
1250         values.initial = *initialHandle;
1251         values.result = *initialHandle;
1252         values.updated = false;
1253     }
1254 }
1255 
CalculateFrameIndices(array_view<const uint32_t> resultIndices)1256 void AnimationSystem::CalculateFrameIndices(array_view<const uint32_t> resultIndices)
1257 {
1258 #if (CORE3D_DEV_ENABLED == 1)
1259     CORE_CPU_PERF_SCOPE("CORE3D", "AnimationSystem", "CalculateFrameIndices", CORE3D_PROFILER_DEFAULT_COLOR);
1260 #endif
1261     auto results = trackQuery_.GetResults();
1262     for (auto index : resultIndices) {
1263         const ComponentQuery::ResultRow& row = results[index];
1264         const auto trackId = row.components[1];
1265         if (trackValues_[trackId].state > TrackState::PAUSED) {
1266             continue;
1267         }
1268         if (auto track = animationTrackManager_.Read(trackId); track) {
1269             if (const auto inputData = inputManager_.Read(track->timestamps); inputData) {
1270                 const array_view<const float> timestamps = inputData->timestamps;
1271                 // Ensure we have data.
1272                 if (!timestamps.empty()) {
1273                     size_t currentFrameIndex;
1274                     size_t nextFrameIndex;
1275 
1276                     const auto currentTime = trackValues_[trackId].timePosition;
1277                     FindFrameIndices(
1278                         trackValues_[trackId].forward, currentTime, timestamps, currentFrameIndex, nextFrameIndex);
1279 
1280                     float currentOffset = 0.f;
1281                     if (currentFrameIndex != nextFrameIndex) {
1282                         const float startFrameTime = timestamps[currentFrameIndex];
1283                         const float endFrameTime = timestamps[nextFrameIndex];
1284 
1285                         currentOffset = (currentTime - startFrameTime) / (endFrameTime - startFrameTime);
1286                         currentOffset = std::clamp(currentOffset, 0.0f, 1.0f);
1287                     }
1288 
1289                     frameIndices_[trackId] = FrameData { currentOffset, currentFrameIndex, nextFrameIndex };
1290                 }
1291             }
1292         }
1293     }
1294 }
1295 
AnimateTracks(array_view<const uint32_t> resultIndices)1296 void AnimationSystem::AnimateTracks(array_view<const uint32_t> resultIndices)
1297 {
1298 #if (CORE3D_DEV_ENABLED == 1)
1299     CORE_CPU_PERF_SCOPE("CORE3D", "AnimationSystem", "AnimateTracks", CORE3D_PROFILER_DEFAULT_COLOR);
1300 #endif
1301     auto results = trackQuery_.GetResults();
1302     for (auto index : resultIndices) {
1303         const ComponentQuery::ResultRow& row = results[index];
1304         const auto trackId = row.components[1];
1305         auto& trackValues = trackValues_[trackId];
1306         if (trackValues.state > TrackState::PAUSED) {
1307             continue;
1308         }
1309         if (auto trackHandle = animationTrackManager_.Read(trackId); trackHandle) {
1310             if (auto outputHandle = outputManager_.Read(trackHandle->data); outputHandle) {
1311                 if (outputHandle->data.empty()) {
1312                     continue;
1313                 }
1314 
1315                 auto entry = GetEntry(*trackHandle);
1316                 if (!entry.component || !entry.property) {
1317                     return;
1318                 }
1319 
1320                 // special handling for IPropertyHandle*. need to fetch the property types behind the pointer.
1321                 if (entry.property->type == PROPERTY_HANDLE_PTR_T) {
1322                     if (const IPropertyHandle* targetHandle = entry.component->GetData(trackHandle->target);
1323                         targetHandle) {
1324                         auto componentBase = ScopedHandle<const uint8_t>(targetHandle);
1325                         auto* dynamicProperties =
1326                             *Cast<const IPropertyHandle* const*>(&*componentBase + entry.propertyOffset);
1327                         if (dynamicProperties) {
1328                             entry = FindDynamicProperty(*trackHandle, dynamicProperties, entry);
1329                         }
1330                     }
1331                 }
1332 
1333                 if ((outputHandle->type != entry.property->type) || (outputHandle->type != trackValues.initial.type) ||
1334                     (outputHandle->type != trackValues.result.type)) {
1335                     CORE_LOG_ONCE_D(to_string(Hash(trackId, outputHandle->type)),
1336                         "AnimateTrack failed, unexpected type %" PRIx64, outputHandle->type);
1337                     continue;
1338                 }
1339 
1340                 // spline interpolation takes three values: in-tangent, data point, and out-tangent
1341                 const size_t inputCount =
1342                     (trackHandle->interpolationMode == AnimationTrackComponent::Interpolation::SPLINE) ? 3U : 1U;
1343                 const FrameData& currentFrame = frameIndices_[trackId];
1344                 const InterpolationData interpolationData { trackHandle->interpolationMode,
1345                     currentFrame.currentFrameIndex * inputCount, currentFrame.nextFrameIndex * inputCount,
1346                     currentFrame.currentOffset, trackValues.weight };
1347 
1348                 // initial data is needed only when weight < 1 or there are multiple animations targeting the same
1349                 // property.
1350                 // identifying such tracks would save updating the initial data in AnimationSystem::Update and
1351                 // interpolating between initial and result.
1352                 AnimateTrack(
1353                     entry.property->type, interpolationData, *outputHandle, trackValues.initial, trackValues.result);
1354                 trackValues.updated = true;
1355             }
1356         }
1357     }
1358 }
1359 
ApplyResults(array_view<const uint32_t> resultIndices)1360 void AnimationSystem::ApplyResults(array_view<const uint32_t> resultIndices)
1361 {
1362     auto results = trackQuery_.GetResults();
1363     for (auto index : resultIndices) {
1364         const ComponentQuery::ResultRow& row = results[index];
1365         const auto trackId = row.components[1];
1366         auto& values = trackValues_[trackId];
1367 
1368         if (values.state < TrackState::STOPPING && values.updated) {
1369             values.updated = false;
1370             auto trackHandle = animationTrackManager_.Read(trackId);
1371             auto entry = GetEntry(*trackHandle);
1372             if (entry.component && entry.property) {
1373                 if (IPropertyHandle* targetHandle = entry.component->GetData(trackHandle->target); targetHandle) {
1374                     // special handling for IPropertyHandle*. need to fetch the property types behind the pointer and
1375                     // update targetHandle.
1376                     if (entry.property->type == PROPERTY_HANDLE_PTR_T) {
1377                         auto componentBase = ScopedHandle<const uint8_t>(targetHandle);
1378                         auto* dynamicProperties =
1379                             *Cast<IPropertyHandle* const*>(&*componentBase + entry.propertyOffset);
1380                         if (dynamicProperties) {
1381                             entry = FindDynamicProperty(*trackHandle, dynamicProperties, entry);
1382                             targetHandle = dynamicProperties;
1383                         } else {
1384                             continue;
1385                         }
1386                     }
1387                     if (entry.property->type == values.result.type) {
1388                         if (auto baseAddress = ScopedHandle<uint8_t>(targetHandle); baseAddress) {
1389                             Add(entry.property->type, &*baseAddress + entry.propertyOffset, values.result);
1390                         }
1391                     } else {
1392                         CORE_LOG_ONCE_D(to_string(Hash(trackId, entry.property->type.typeHash)),
1393                             "ApplyResults failed, type mismatch %" PRIx64, entry.property->type.typeHash);
1394                     }
1395                 }
1396             }
1397         }
1398     }
1399 }
1400 
GetEntry(const AnimationTrackComponent & track)1401 const AnimationSystem::PropertyEntry& AnimationSystem::GetEntry(const AnimationTrackComponent& track)
1402 {
1403     // Cache component manager and property lookups
1404     const auto key = Hash(track.component, track.property);
1405     auto pos = std::lower_bound(std::begin(propertyCache_), std::end(propertyCache_), key,
1406         [](const PropertyEntry& element, uint64_t value) { return element.componentAndProperty < value; });
1407     // Add new manager-propery combinations
1408     if ((pos == std::end(propertyCache_)) || (pos->componentAndProperty != key)) {
1409         pos = propertyCache_.insert(pos, PropertyEntry { key, ecs_.GetComponentManager(track.component), 0U, nullptr });
1410     }
1411     // Try to get the component manager if it's missing
1412     if (!pos->component) {
1413         pos->component = ecs_.GetComponentManager(track.component);
1414     }
1415 
1416     // Try to get the property type if it's missing and there's a valid target
1417     if (pos->component && !pos->property && EntityUtil::IsValid(track.target)) {
1418         if (IPropertyHandle* targetHandle = pos->component->GetData(track.target); targetHandle) {
1419             const auto propertyOffset = PropertyData::FindProperty(targetHandle->Owner()->MetaData(), track.property);
1420             if (propertyOffset.property) {
1421                 pos->propertyOffset = propertyOffset.offset;
1422                 pos->property = propertyOffset.property;
1423             }
1424         }
1425     }
1426     return *pos;
1427 }
1428 
InitializeInitialDataComponent(Entity trackEntity,const AnimationTrackComponent & animationTrack)1429 void AnimationSystem::InitializeInitialDataComponent(Entity trackEntity, const AnimationTrackComponent& animationTrack)
1430 {
1431     if (auto entry = GetEntry(animationTrack); entry.component && entry.property) {
1432         if (IPropertyHandle* targetHandle = entry.component->GetData(animationTrack.target); targetHandle) {
1433             initialTransformManager_.Create(trackEntity);
1434             auto dstHandle = initialTransformManager_.Write(trackEntity);
1435 
1436             auto componentBase = ScopedHandle<const uint8_t>(targetHandle);
1437             if (entry.property->type != PROPERTY_HANDLE_PTR_T) {
1438                 auto src = array_view(&*componentBase + entry.propertyOffset, entry.property->size);
1439                 CopyInitialDataComponent(*dstHandle, entry.property, src);
1440             } else {
1441                 // special handling for property handles
1442                 auto* dynamicProperties = *Cast<const IPropertyHandle* const*>(&*componentBase + entry.propertyOffset);
1443                 if (dynamicProperties) {
1444                     entry = FindDynamicProperty(animationTrack, dynamicProperties, entry);
1445                     auto dynamicPropertiesBase = ScopedHandle<const uint8_t>(dynamicProperties);
1446                     auto src = array_view(&*dynamicPropertiesBase + entry.propertyOffset, entry.property->size);
1447                     CopyInitialDataComponent(*dstHandle, entry.property, src);
1448                 }
1449             }
1450         }
1451     }
1452 }
1453 
IAnimationSystemInstance(IEcs & ecs)1454 ISystem* IAnimationSystemInstance(IEcs& ecs)
1455 {
1456     return new AnimationSystem(ecs);
1457 }
1458 
IAnimationSystemDestroy(ISystem * instance)1459 void IAnimationSystemDestroy(ISystem* instance)
1460 {
1461     delete static_cast<AnimationSystem*>(instance);
1462 }
1463 CORE3D_END_NAMESPACE()
1464