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