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