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