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 #include "ecs_animation.h"
16
17 #include <algorithm>
18 #include <PropertyTools/property_api_impl.inl>
19 #include <PropertyTools/property_data.h>
20
21 #include <3d/ecs/systems/intf_animation_system.h>
22 #include <3d/ecs/systems/intf_node_system.h>
23 #include <core/intf_engine.h>
24
25 #include <meta/api/make_callback.h>
26
27 #include "ecs_util.h"
28
29 using namespace BASE_NS;
30 using namespace CORE_NS;
31 using namespace CORE3D_NS;
32 using namespace META_NS;
33
34 SCENE_BEGIN_NAMESPACE()
35
36 namespace {
37
EndsWith(string_view str,string_view postfix)38 bool EndsWith(string_view str, string_view postfix)
39 {
40 return str.size() >= postfix.size() && 0 == str.compare(str.size() - postfix.size(), postfix.size(), postfix);
41 }
42
43 template<typename type>
GetArrayTypeUid()44 const BASE_NS::Uid GetArrayTypeUid()
45 {
46 return UidFromType<type[]>();
47 }
48
RLock(IPropertyHandle & targetHandle,string_view property)49 PropertyData::PropertyOffset RLock(IPropertyHandle& targetHandle, string_view property)
50 {
51 PropertyData pData;
52
53 string path, name;
54 auto containerHandle = ResolveContainerProperty(targetHandle, string(property), path, name);
55 if (containerHandle) {
56 if (auto po = pData.RLock(*containerHandle, name); po) {
57 return po;
58 }
59 }
60
61 if (auto po = pData.RLock(targetHandle, property); po) {
62 return po;
63 }
64
65 return PropertyData::PropertyOffset();
66 }
67
ResolvePathToAnimationRoot(IEcs & ecs,Entity root,Entity target)68 BASE_NS::string ResolvePathToAnimationRoot(IEcs& ecs, Entity root, Entity target)
69 {
70 BASE_NS::string result;
71
72 CORE3D_NS::INodeSystem* nodeSystem = GetSystem<INodeSystem>(ecs);
73 if (nodeSystem) {
74 auto rootNode = nodeSystem->GetNode(root);
75 auto node = nodeSystem->GetNode(target);
76 if (rootNode && node) {
77 if (rootNode->IsAncestorOf(*node)) {
78 auto current = node;
79 while (current) {
80 // Found root node, break.
81 if (current == rootNode) {
82 break;
83 }
84
85 // Add this to path.
86 if (result.empty()) {
87 result = current->GetName();
88 } else {
89 result = current->GetName() + "/" + result;
90 }
91
92 // Process parent next.
93 current = current->GetParent();
94 }
95 }
96 }
97 }
98
99 return result;
100 }
101
UpdateTrackTargets(IEcs & ecs,Entity animationEntity,Entity rootNode)102 void UpdateTrackTargets(IEcs& ecs, Entity animationEntity, Entity rootNode)
103 {
104 auto& nameManager_ = *GetManager<INameComponentManager>(ecs);
105 auto animationManager = GetManager<IAnimationComponentManager>(ecs);
106 auto animationTrackManager = GetManager<IAnimationTrackComponentManager>(ecs);
107 auto& entityManager = ecs.GetEntityManager();
108
109 auto* nodeSystem = GetSystem<INodeSystem>(ecs);
110 CORE_ASSERT(nodeSystem);
111 if (!nodeSystem) {
112 return;
113 }
114 auto* node = nodeSystem->GetNode(rootNode);
115 CORE_ASSERT(node);
116 if (!node) {
117 return;
118 }
119
120 if (const ScopedHandle<const AnimationComponent> animationData = animationManager->Read(animationEntity);
121 animationData) {
122 vector<Entity> targetEntities;
123 targetEntities.reserve(animationData->tracks.size());
124 std::transform(animationData->tracks.begin(), animationData->tracks.end(), std::back_inserter(targetEntities),
125 [&nameManager = nameManager_, &node](const Entity& trackEntity) {
126 if (auto nameHandle = nameManager.Read(trackEntity); nameHandle) {
127 if (nameHandle->name.empty()) {
128 return node->GetEntity();
129 } else {
130 if (auto targetNode = node->LookupNodeByPath(nameHandle->name); targetNode) {
131 return targetNode->GetEntity();
132 }
133 }
134 }
135 return Entity {};
136 });
137 if (animationData->tracks.size() == targetEntities.size()) {
138 auto targetIt = targetEntities.begin();
139 for (const auto& trackEntity : animationData->tracks) {
140 if (auto track = animationTrackManager->Write(trackEntity); track) {
141 if (track->target) {
142 CORE_LOG_D("AnimationTrack %s already targetted",
143 to_hex(static_cast<const Entity&>(track->target).id).data());
144 }
145 track->target = entityManager.GetReferenceCounted(*targetIt);
146 }
147 ++targetIt;
148 }
149 }
150 }
151 }
152 } // namespace
153
Keyframes() const154 IProperty::Ptr EcsTrackAnimation::Keyframes() const
155 {
156 return keyframes_;
157 }
158
AddKeyframe(float timestamp,const META_NS::IAny::ConstPtr & from)159 size_t EcsTrackAnimation::AddKeyframe(float timestamp, const META_NS::IAny::ConstPtr& from)
160 {
161 // TODO: Implement.
162 return {};
163 }
164
RemoveKeyframe(size_t index)165 bool EcsTrackAnimation::RemoveKeyframe(size_t index)
166 {
167 // TODO: Implement.
168 return false;
169 }
RemoveAllKeyframes()170 void EcsTrackAnimation::RemoveAllKeyframes()
171 {
172 // TODO: Implement.
173 }
174
Build(const META_NS::IMetadata::Ptr &)175 bool EcsTrackAnimation::Build(const META_NS::IMetadata::Ptr&)
176 {
177 auto& registry = META_NS::GetObjectRegistry();
178
179 auto arr = ConstructArrayProperty<float>("Keyframes", {}, META_NS::ObjectFlagBits::NONE);
180 keyframes_ = arr.GetProperty();
181 if (!keyframes_) {
182 CORE_LOG_E("Invalid property type for EcsTrackAnimation: <float>");
183 return false;
184 }
185 return true;
186 }
187
Step(const META_NS::IClock::ConstPtr & clock)188 void EcsTrackAnimation::Step(const META_NS::IClock::ConstPtr& clock)
189 {
190 // TODO: Implement.
191 }
192
Start()193 void EcsTrackAnimation::Start()
194 {
195 // TODO: Implement.
196 META_NS::Invoke<META_NS::IOnChanged>(OnStarted());
197 }
198
Stop()199 void EcsTrackAnimation::Stop()
200 {
201 }
202
Pause()203 void EcsTrackAnimation::Pause()
204 {
205 }
206
Restart()207 void EcsTrackAnimation::Restart()
208 {
209 Stop();
210 Start();
211 }
212
Finish()213 void EcsTrackAnimation::Finish()
214 {
215 // TODO: Implement.
216 }
217
Seek(float position)218 void EcsTrackAnimation::Seek(float position)
219 {
220 // TODO: Implement.
221 }
222
GetName() const223 BASE_NS::string EcsAnimation::GetName() const
224 {
225 return Name()->GetValue();
226 }
227
Build(const META_NS::IMetadata::Ptr &)228 bool EcsAnimation::Build(const META_NS::IMetadata::Ptr& /*data*/)
229 {
230 GetSelf<META_NS::IRequiredInterfaces>()->SetRequiredInterfaces({ IEcsTrackAnimation::UID });
231
232 // TODO: Loop count.
233 // TODO: Running.
234 Name()->OnChanged()->AddHandler(MakeCallback<IOnChanged>([this] { OnNamePropertyChanged(); }));
235 Duration()->OnChanged()->AddHandler(MakeCallback<IOnChanged>([this] { OnDurationPropertyChanged(); }));
236 Progress()->OnChanged()->AddHandler(MakeCallback<IOnChanged>([this] { OnProgressPropertyChanged(); }));
237
238 return true;
239 }
240
SetRootEntity(CORE_NS::Entity entity)241 bool EcsAnimation::SetRootEntity(CORE_NS::Entity entity)
242 {
243 if (EntityUtil::IsValid(root_)) {
244 // Cannot change root entity, once set.
245 return entity == root_;
246 }
247
248 root_ = entity;
249 return true;
250 }
251
GetRootEntity() const252 CORE_NS::Entity EcsAnimation::GetRootEntity() const
253 {
254 return root_;
255 }
256
Retarget(CORE_NS::Entity entity)257 bool EcsAnimation::Retarget(CORE_NS::Entity entity)
258 {
259 if (ecs_) {
260 UpdateTrackTargets(*ecs_, GetEntity(), entity);
261 root_ = entity;
262
263 return true;
264 }
265 return false;
266 }
267
SetEntity(CORE_NS::IEcs & ecs,CORE_NS::Entity entity)268 void EcsAnimation::SetEntity(CORE_NS::IEcs& ecs, CORE_NS::Entity entity)
269 {
270 ecs_ = &ecs;
271 entity_ = ecs_->GetEntityManager().GetReferenceCounted(entity);
272 animationStateManager_ = nullptr;
273
274 for (auto manager : ecs.GetComponentManagers()) {
275 if (manager->GetName() == "AnimationStateComponent") {
276 animationStateManager_ = manager;
277 break;
278 }
279 }
280
281 animationManager_ = GetManager<IAnimationComponentManager>(*ecs_);
282 animationTrackManager_ = GetManager<IAnimationTrackComponentManager>(*ecs_);
283 animationInputManager_ = GetManager<IAnimationInputComponentManager>(*ecs_);
284 animationOutputManager_ = GetManager<IAnimationOutputComponentManager>(*ecs_);
285 nameManager_ = GetManager<INameComponentManager>(*ecs_);
286
287 OnAnimationNameChanged(IEcs::ComponentListener::EventType::MODIFIED);
288 OnAnimationChanged(IEcs::ComponentListener::EventType::MODIFIED);
289
290 if (auto ecsListener = ecsListener_.lock()) {
291 auto po = GetSelf<SCENE_NS::IEcsProxyObject>();
292 ecsListener->AddEntity(entity_, po, *nameManager_);
293 ecsListener->AddEntity(entity_, po, *animationStateManager_);
294 ecsListener->AddEntity(entity_, po, *animationManager_);
295 ecsListener->AddEntity(entity_, po, *animationTrackManager_);
296 ecsListener->AddEntity(entity_, po, *animationInputManager_);
297 }
298
299 // If the animation root is not set, then try to resolve it.
300 if (!EntityUtil::IsValid(root_)) {
301 root_ = TryResolveAnimationRoot();
302 }
303
304 META_ACCESS_PROPERTY(Valid)->SetValue(true);
305 }
306
TryResolveAnimationRoot()307 CORE_NS::Entity EcsAnimation::TryResolveAnimationRoot()
308 {
309 if (!ecs_) {
310 return CORE_NS::Entity {};
311 }
312
313 CORE3D_NS::INodeSystem* nodeSystem = GetSystem<INodeSystem>(*ecs_);
314
315 // We will try to resolve the animation root from the first animation track.
316 auto tracks = META_NS::GetAll<IEcsTrackAnimation>(GetSelf<META_NS::IContainer>());
317 if (tracks.empty()) {
318 // No way to resolve, no tracks.
319 return {};
320 }
321
322 IEcsTrackAnimation::Ptr track = tracks.at(0);
323
324 BASE_NS::string path;
325 if (auto nameHandle = nameManager_->Read(track->GetEntity()); nameHandle) {
326 // The name of the track actually represents the path to the animated entity.
327 path = nameHandle->name;
328 }
329
330 ISceneNode* node = nullptr;
331 if (auto trackHandle = animationTrackManager_->Read(track->GetEntity()); trackHandle) {
332 // This is the node that is being animated by the path.
333 node = nodeSystem->GetNode(trackHandle->target);
334 if (!node) {
335 return {};
336 }
337
338 // Resolve the path to parent.
339 while (!path.empty()) {
340 BASE_NS::string suffix = node->GetName();
341 if (EndsWith(path, suffix)) {
342 // Remove name of this node from the path.
343 path = string(path.substr(0, path.length() - suffix.length()));
344 if (EndsWith(path, "/")) {
345 path = string(path.substr(0, path.length() - 1));
346 }
347 // Go up the tree.
348 node = node->GetParent();
349 } else {
350 // Error.
351 node = nullptr;
352 break;
353 }
354 }
355 }
356
357 if (node) {
358 SetRootEntity(node->GetEntity());
359 }
360
361 return node ? node->GetEntity() : Entity {};
362 }
363
AddAnimation(const IAnimation::Ptr & animation)364 void EcsAnimation::AddAnimation(const IAnimation::Ptr& animation)
365 {
366 GetSelf<META_NS::IContainer>()->Add(interface_pointer_cast<IObject>(animation));
367 // ToDo: Can we rely that someone has added an listener for tracks
368 }
369
RemoveAnimation(const IAnimation::Ptr & animation)370 void EcsAnimation::RemoveAnimation(const IAnimation::Ptr& animation)
371 {
372 GetSelf<META_NS::IContainer>()->Remove(interface_pointer_cast<IObject>(animation));
373 // ToDo: In principle, the tracks should deal common listener and removing parent, might be worth checking though
374 }
375
GetAnimations() const376 vector<IAnimation::Ptr> EcsAnimation::GetAnimations() const
377 {
378 return META_NS::GetAll<IAnimation>(GetSelf<META_NS::IContainer>());
379 }
380
DoComponentEvent(IEcs::ComponentListener::EventType type,const IComponentManager & componentManager,const CORE_NS::Entity & entity)381 void EcsAnimation::DoComponentEvent(
382 IEcs::ComponentListener::EventType type, const IComponentManager& componentManager, const CORE_NS::Entity& entity)
383 {
384 bool isAnimationNameChange = componentManager.GetUid() == INameComponentManager::UID;
385 bool isAnimationChange = componentManager.GetUid() == IAnimationComponentManager::UID;
386 bool isAnimationStateChange = componentManager.GetUid() == animationStateManager_->GetUid();
387 bool isAnimationInputChange = componentManager.GetUid() == IAnimationInputComponentManager::UID;
388 bool isAnimationTrackChange = componentManager.GetUid() == IAnimationTrackComponentManager::UID;
389
390 if (isAnimationChange || isAnimationStateChange) {
391 // For animation and animation state, we are interested about changes concerning entity_.
392
393 if (isAnimationChange) {
394 // Animation has changed for this entity.
395 OnAnimationChanged(type);
396 } else if (isAnimationStateChange) {
397 // Animation state has changed for this entity.
398 OnAnimationStateChanged(type);
399 }
400 } else if (isAnimationTrackChange) {
401 OnAnimationTracksChanged(type, entity);
402 } else if (isAnimationInputChange) {
403 OnAnimationInputsChanged(type, entity);
404 } else if (isAnimationNameChange) {
405 OnAnimationNameChanged(type);
406 }
407 }
408
OnAnimationStateChanged(IEcs::ComponentListener::EventType event)409 void EcsAnimation::OnAnimationStateChanged(IEcs::ComponentListener::EventType event)
410 {
411 // This function is triggered when ECS dispatch changes at the end of the frame.
412 if (!ecs_ || event != IEcs::ComponentListener::EventType::MODIFIED) {
413 return;
414 }
415
416 // Propagate changes back to object's properties.
417 auto stateHandle = animationStateManager_->GetData(entity_);
418 const auto metaData = stateHandle->Owner()->MetaData();
419 for (const auto& data : metaData) {
420 if (data.name == "time") {
421 auto* time = static_cast<const float*>(
422 static_cast<const void*>(static_cast<const uint8_t*>(stateHandle->RLock()) + data.offset));
423
424 updateGuard_ = true;
425 auto duration = GetValue(TotalDuration()).ToSecondsFloat();
426 if (duration > 0) {
427 SetProgress(*time / duration);
428 } else {
429 SetProgress(0);
430 }
431 updateGuard_ = false;
432
433 stateHandle->RUnlock();
434 break;
435 }
436 }
437 }
438
OnAnimationNameChanged(IEcs::ComponentListener::EventType event)439 void EcsAnimation::OnAnimationNameChanged(IEcs::ComponentListener::EventType event)
440 {
441 // This function is triggered when ECS dispatch changes at the end of the frame.
442 if (!ecs_ || event != IEcs::ComponentListener::EventType::MODIFIED) {
443 return;
444 }
445
446 if (nameManager_->HasComponent(GetEntity())) {
447 updateGuard_ = true;
448 SetValue(Name(), nameManager_->Get(GetEntity()).name);
449 updateGuard_ = false;
450 }
451 }
452
OnAnimationChanged(IEcs::ComponentListener::EventType event)453 void EcsAnimation::OnAnimationChanged(IEcs::ComponentListener::EventType event)
454 {
455 // This function is triggered when ECS dispatch changes at the end of the frame.
456 if (!ecs_ || event == IEcs::ComponentListener::EventType::DESTROYED) {
457 return;
458 }
459
460 // Propagate changes back to object's properties.
461 auto handle = animationManager_->Read(entity_);
462 if (handle) {
463 updateGuard_ = true;
464 repeatCount_ = static_cast<int32_t>(handle->repeatCount);
465 // Update animation duration.
466 SetValue(META_ACCESS_PROPERTY(Duration), TimeSpan::Seconds(handle->duration));
467 updateGuard_ = false;
468
469 // Update animation tracks, if needed.
470 if (IsAnimationTrackArrayModified()) {
471 GatherAnimationTracks();
472 }
473 }
474 }
475
OnAnimationTracksChanged(IEcs::ComponentListener::EventType event,CORE_NS::Entity entity)476 void EcsAnimation::OnAnimationTracksChanged(IEcs::ComponentListener::EventType event, CORE_NS::Entity entity)
477 {
478 bool animationHasNewOrRemovedTracks = false;
479
480 auto tracks = META_NS::GetAll<IEcsTrackAnimation>(GetSelf<META_NS::IContainer>());
481
482 // Go through all created / modified / destroyed track entities.
483 auto iterator = std::find_if(
484 tracks.begin(), tracks.end(), [entity](const auto& track) { return track->GetEntity() == entity; });
485 if (iterator != tracks.end()) {
486 // Animation track was reported changed.
487 if (event == IEcs::ComponentListener::EventType::MODIFIED) {
488 // An animation track has been modified, so update it.
489 auto index = std::distance(tracks.begin(), iterator);
490
491 auto track = tracks.at(index);
492 OnAnimationTrackChanged(*track, entity);
493
494 } else {
495 // If we have new or removed tracks, we will simply update all.
496 animationHasNewOrRemovedTracks = true;
497 }
498 }
499
500 // If new or removed tracks, refresh all.
501 if (animationHasNewOrRemovedTracks) {
502 GatherAnimationTracks();
503 }
504 }
505
OnAnimationInputsChanged(IEcs::ComponentListener::EventType event,CORE_NS::Entity entity)506 void EcsAnimation::OnAnimationInputsChanged(IEcs::ComponentListener::EventType event, CORE_NS::Entity entity)
507 {
508 if (!ecs_) {
509 return;
510 }
511
512 auto tracks = META_NS::GetAll<IEcsTrackAnimation>(GetSelf<META_NS::IContainer>());
513 // Go through all created / modified / destroyed track entities.
514 for (size_t i = 0; i < tracks.size(); ++i) {
515 auto track = tracks.at(i);
516 if (auto trackHandle = animationTrackManager_->Read(track->GetEntity()); trackHandle) {
517 if (trackHandle->timestamps == entity) {
518 // Timestamps for this track have changed.
519 OnAnimationTimestampsChanged(*track, trackHandle->timestamps);
520 break;
521 }
522 }
523 }
524 }
525
IsAnimationTrackArrayModified()526 bool EcsAnimation::IsAnimationTrackArrayModified()
527 {
528 if (!ecs_) {
529 return false;
530 }
531
532 auto tracks = META_NS::GetAll<IEcsTrackAnimation>(GetSelf<META_NS::IContainer>());
533 auto handle = animationManager_->Read(entity_);
534 if (handle) {
535 if (handle->tracks.size() != tracks.size()) {
536 // The amount of tracks is different.
537 return true;
538 }
539
540 for (size_t i = 0; i < handle->tracks.size(); ++i) {
541 if (tracks.at(i)->GetEntity() != handle->tracks[i]) {
542 // Track entity id has changed.
543 return true;
544 }
545 }
546 }
547
548 return false;
549 }
550
GatherAnimationTracks()551 void EcsAnimation::GatherAnimationTracks()
552 {
553 if (!ecs_) {
554 return;
555 }
556
557 // Take copy of the animation tracks.
558 auto oldTracks = META_NS::GetAll<IEcsTrackAnimation>(GetSelf<META_NS::IContainer>());
559
560 // Clear tracks.
561 GetSelf<META_NS::IContainer>()->RemoveAll();
562
563 // Update animation tracks.
564 auto handle = animationManager_->Read(entity_);
565 if (handle) {
566 for (const auto& track : handle->tracks) {
567 // See if we have this track stored.
568 auto it = std::find_if(oldTracks.begin(), oldTracks.end(), [track](const auto& t) {
569 if (t->GetEntity() == track) {
570 return true;
571 }
572 return false;
573 });
574
575 IEcsTrackAnimation::Ptr animationTrack;
576 if (it != oldTracks.end()) {
577 animationTrack = *it;
578 } else {
579 animationTrack =
580 META_NS::GetObjectRegistry().Create<IEcsTrackAnimation>(SCENE_NS::ClassId::EcsTrackAnimation);
581 interface_pointer_cast<SCENE_NS::IEcsProxyObject>(animationTrack)
582 ->SetCommonListener(ecsListener_.lock());
583 }
584
585 OnAnimationTrackChanged(*animationTrack, track);
586 GetSelf<META_NS::IContainer>()->Add(interface_pointer_cast<IObject>(animationTrack));
587 }
588 }
589 }
590
OnAnimationTrackChanged(IEcsTrackAnimation & track,Entity trackEntity)591 void EcsAnimation::OnAnimationTrackChanged(IEcsTrackAnimation& track, Entity trackEntity)
592 {
593 if (!ecs_ || updateGuard_) {
594 return;
595 }
596
597 if (auto trackHandle = animationTrackManager_->Read(trackEntity); trackHandle) {
598 auto nameComponent = nameManager_->Get(trackHandle->target.operator Entity());
599
600 track.SetEntity(trackEntity);
601
602 auto named = interface_cast<INamed>(&track);
603 if (named) {
604 SetValue(named->Name(), nameComponent.name + '.' + trackHandle->property.data());
605 }
606 OnAnimationTimestampsChanged(track, trackHandle->timestamps);
607 }
608 }
609
UpdateTimestamps(IEcsTrackAnimation & track,Entity timestampEntity)610 void EcsAnimation::UpdateTimestamps(IEcsTrackAnimation& track, Entity timestampEntity)
611 {
612 if (!ecs_) {
613 return;
614 }
615
616 if (animationInputManager_->HasComponent(timestampEntity)) {
617 const auto timestamps = animationInputManager_->Get(timestampEntity);
618 const auto trackAnimation = interface_cast<ITrackAnimation>(&track);
619 const auto timedAnimation = interface_cast<ITimedAnimation>(&track);
620
621 vector<float> times;
622 times.reserve(timestamps.timestamps.size());
623
624 const auto duration = META_NS::GetValue(timedAnimation->Duration()).ToSecondsFloat();
625
626 for (const auto timestamp : timestamps.timestamps) {
627 const auto offset = (duration > 0) ? (timestamp / duration) : 0.0f;
628 times.push_back(offset);
629 }
630
631 trackAnimation->Timestamps()->SetValue(times);
632 }
633 }
OnAnimationTimestampsChanged(IEcsTrackAnimation & track,Entity timestampEntity)634 void EcsAnimation::OnAnimationTimestampsChanged(IEcsTrackAnimation& track, Entity timestampEntity)
635 {
636 if (!ecs_ || updateGuard_) {
637 return;
638 }
639
640 UpdateTimestamps(track, timestampEntity);
641
642 // If any of the tracks in this animation is sharing the same timestamps, then make all tracks read-only.
643 bool hasSharedTimestamps = false;
644
645 auto tracks = META_NS::GetAll<IEcsTrackAnimation>(GetSelf<META_NS::IContainer>());
646 for (const auto& other : tracks) {
647 if (other->GetEntity() != track.GetEntity()) {
648 if (auto trackHandle = animationTrackManager_->Read(other->GetEntity()); trackHandle) {
649 if (trackHandle->timestamps == timestampEntity) {
650 hasSharedTimestamps = true;
651 break;
652 }
653 }
654 }
655 }
656
657 if (GetValue(ReadOnly()) != hasSharedTimestamps) {
658 META_ACCESS_PROPERTY(ReadOnly)->SetValue(hasSharedTimestamps);
659 }
660 }
661
SetDuration(uint32_t ms)662 void EcsAnimation::SetDuration(uint32_t ms)
663 {
664 if (!ecs_) {
665 return;
666 }
667
668 const auto newDuration = TimeSpan::Milliseconds(ms);
669 const auto tracks = META_NS::GetAll<IEcsTrackAnimation>(GetSelf<META_NS::IContainer>());
670 for (const auto& track : tracks) {
671 if (const auto trackHandle = animationTrackManager_->Read(track->GetEntity()); trackHandle) {
672 interface_cast<META_NS::ITimedAnimation>(track)->Duration()->SetValue(newDuration);
673 UpdateTimestamps(*track, trackHandle->timestamps);
674 }
675 }
676
677 META_ACCESS_PROPERTY(Duration)->SetValue(newDuration);
678 }
679
AddKey(IEcsTrackAnimation::Ptr track,float time)680 void EcsAnimation::AddKey(IEcsTrackAnimation::Ptr track, float time)
681 {
682 if (!ecs_) {
683 return;
684 }
685
686 updateGuard_ = true;
687
688 auto handle = animationTrackManager_->Read(track->GetEntity());
689 if (handle) {
690 auto propertyData = GetProperty(handle->component, handle->target, handle->property);
691 if (propertyData) {
692 SetKeyFrameData(track->GetEntity(), time, propertyData.data);
693 }
694 }
695
696 UpdateAnimationTrackDuration(track->GetEntity());
697
698 updateGuard_ = false;
699
700 uint32_t timeMs = static_cast<uint32_t>(time * 1000.0f);
701 if (GetValue(Duration()).ToMilliseconds() < timeMs) {
702 META_ACCESS_PROPERTY(Duration)->SetValue(TimeSpan::Milliseconds(timeMs));
703 }
704
705 OnAnimationTrackChanged(*track, track->GetEntity());
706 }
707
RemoveKey(IEcsTrackAnimation::Ptr track,uint32_t index)708 void EcsAnimation::RemoveKey(IEcsTrackAnimation::Ptr track, uint32_t index)
709 {
710 if (!ecs_) {
711 return;
712 }
713
714 updateGuard_ = true;
715
716 // remove timestamp at pos
717 if (auto animationTrack = animationTrackManager_->Read(track->GetEntity()); animationTrack) {
718 auto stampsEntity = animationTrack->timestamps.operator Entity();
719 auto inputData = animationInputManager_->Write(stampsEntity);
720 vector<float>::iterator iit = inputData->timestamps.begin();
721 inputData->timestamps.erase(iit + index);
722 // remove data at pos
723 auto dataEntity = animationTrack->data.operator Entity();
724 if (auto outputData = animationOutputManager_->Write(dataEntity); outputData) {
725 auto targetEntity = animationTrack->target.operator Entity();
726
727 auto manager = ecs_->GetComponentManager(animationTrack->component);
728
729 auto target = manager->GetData(targetEntity);
730 auto poffset = RLock(*target, animationTrack->property);
731 vector<uint8_t>::iterator oit = outputData->data.begin();
732 outputData->data.erase(oit + (index * poffset.property->size),
733 oit + (index * poffset.property->size) + poffset.property->size);
734 }
735 }
736
737 updateGuard_ = false;
738
739 OnAnimationTrackChanged(*track, track->GetEntity());
740 }
741
UpdateKey(IEcsTrackAnimation::Ptr track,uint32_t oldKeyIndex,uint32_t newKeyIndex,float time)742 void EcsAnimation::UpdateKey(IEcsTrackAnimation::Ptr track, uint32_t oldKeyIndex, uint32_t newKeyIndex, float time)
743 {
744 if (!ecs_) {
745 return;
746 }
747
748 updateGuard_ = true;
749
750 {
751 auto animationTrack = animationTrackManager_->Read(track->GetEntity());
752 auto stampsEntity = animationTrack->timestamps.operator Entity();
753 auto inputData = animationInputManager_->Write(stampsEntity);
754
755 auto dataEntity = animationTrack->data.operator Entity();
756 auto outputData = animationOutputManager_->Write(dataEntity);
757 auto targetEntity = animationTrack->target.operator Entity();
758
759 vector<float>::iterator iit = inputData->timestamps.begin();
760 inputData->timestamps.erase(iit + oldKeyIndex);
761
762 auto manager = ecs_->GetComponentManager(animationTrack->component);
763
764 auto target = manager->GetData(targetEntity);
765 auto poffset = RLock(*target, animationTrack->property);
766 if (poffset) {
767 vector<uint8_t>::iterator oit = outputData->data.begin();
768
769 vector<uint8_t> moveData(oit + (oldKeyIndex * poffset.property->size),
770 oit + (oldKeyIndex * poffset.property->size) + poffset.property->size);
771
772 outputData->data.erase(oit + (oldKeyIndex * poffset.property->size),
773 oit + (oldKeyIndex * poffset.property->size) + poffset.property->size);
774
775 inputData->timestamps.insert(iit + newKeyIndex, time);
776
777 vector<uint8_t>::iterator noit = outputData->data.begin();
778 outputData->data.insert(
779 noit + (newKeyIndex * poffset.property->size), std::begin(moveData), std::end(moveData));
780 }
781 }
782
783 updateGuard_ = false;
784
785 uint32_t timeMs = static_cast<uint32_t>(time * 1000.0f);
786 if (GetValue(Duration()).ToMilliseconds() < timeMs) {
787 META_ACCESS_PROPERTY(Duration)->SetValue(TimeSpan::Milliseconds(timeMs));
788 }
789
790 OnAnimationTrackChanged(*track, track->GetEntity());
791 }
792
OnDestroyAnimationTrack(IEcsTrackAnimation::Ptr track)793 void EcsAnimation::OnDestroyAnimationTrack(IEcsTrackAnimation::Ptr track)
794 {
795 if (ecs_) {
796 if (auto trackHandle = animationTrackManager_->Read(track->GetEntity()); trackHandle) {
797 // Destroy timestamps and keys.
798 ecs_->GetEntityManager().Destroy(trackHandle->timestamps);
799 ecs_->GetEntityManager().Destroy(trackHandle->data);
800 }
801
802 {
803 // Remove track from animation.
804 const auto animationHandle = animationManager_->Write(GetEntity());
805 auto it = std::find(animationHandle->tracks.begin(), animationHandle->tracks.end(), track->GetEntity());
806 if (it != animationHandle->tracks.end()) {
807 animationHandle->tracks.erase(it);
808 }
809 }
810
811 // Destroy track.
812 ecs_->GetEntityManager().Destroy(track->GetEntity());
813 }
814
815 GetSelf<META_NS::IContainer>()->Remove(interface_pointer_cast<IObject>(track));
816 }
817
GetAllRelatedEntities() const818 BASE_NS::vector<CORE_NS::EntityReference> EcsAnimation::GetAllRelatedEntities() const
819 {
820 BASE_NS::vector<CORE_NS::EntityReference> result;
821
822 if (animationManager_) {
823 auto animationHandle = animationManager_->Read(GetEntity());
824 if (animationHandle) {
825 for (auto& track : animationHandle->tracks) {
826 const auto trackHandle = animationTrackManager_->Read(track);
827 if (trackHandle) {
828 if (EntityUtil::IsValid(trackHandle->timestamps)) {
829 result.push_back(ecs_->GetEntityManager().GetReferenceCounted(trackHandle->timestamps));
830 }
831
832 if (EntityUtil::IsValid(trackHandle->data)) {
833 result.push_back(ecs_->GetEntityManager().GetReferenceCounted(trackHandle->data));
834 }
835
836 result.push_back(ecs_->GetEntityManager().GetReferenceCounted(track));
837 }
838 }
839 }
840
841 // Add self.
842 result.push_back(ecs_->GetEntityManager().GetReferenceCounted(GetEntity()));
843 }
844
845 return result;
846 }
847
CreateAnimationTrack(CORE_NS::Entity rootEntity,CORE_NS::Entity targetEntity,BASE_NS::string_view fullPropertyPath)848 IEcsTrackAnimation::Ptr EcsAnimation::CreateAnimationTrack(
849 CORE_NS::Entity rootEntity, CORE_NS::Entity targetEntity, BASE_NS::string_view fullPropertyPath)
850 {
851 // Extract property path.
852 auto separatorPosition = fullPropertyPath.find_first_of('.');
853 if (!ecs_ || separatorPosition == BASE_NS::string::npos) {
854 return {};
855 }
856
857 IComponentManager* componentManager { nullptr };
858 auto componentManagerName = fullPropertyPath.substr(0, separatorPosition);
859 auto propertyPath = fullPropertyPath.substr(separatorPosition + 1);
860
861 for (const auto& manager : ecs_->GetComponentManagers()) {
862 if (manager->GetName() == componentManagerName) {
863 componentManager = manager;
864 break;
865 }
866 }
867
868 if (!componentManager) {
869 return {};
870 }
871
872 updateGuard_ = true;
873
874 EntityReference animationTrack;
875
876 if (IPropertyHandle* targetHandle = componentManager->GetData(targetEntity); targetHandle) {
877 if (auto po = RLock(*targetHandle, propertyPath); po) {
878 BASE_NS::string targetName = "Unnamed";
879 if (nameManager_->HasComponent(targetEntity)) {
880 targetName = nameManager_->Get(targetEntity).name;
881 }
882
883 const auto timeStamps = ecs_->GetEntityManager().CreateReferenceCounted();
884 {
885 NameComponent nameComponent;
886 nameComponent.name = "TimeStamps - " + targetName + ":" + propertyPath;
887 nameManager_->Set(timeStamps, nameComponent);
888
889 animationInputManager_->Create(timeStamps);
890 }
891
892 const auto keys = ecs_->GetEntityManager().CreateReferenceCounted();
893 {
894 NameComponent nameComponent;
895 nameComponent.name = "Keys - " + targetName + ":" + propertyPath;
896 nameManager_->Set(keys, nameComponent);
897
898 animationOutputManager_->Create(keys);
899 auto keysHandle = animationOutputManager_->Write(keys);
900 keysHandle->type = po.property->type;
901 }
902
903 const auto targetRef = ecs_->GetEntityManager().GetReferenceCounted(targetEntity);
904 animationTrack = ecs_->GetEntityManager().CreateReferenceCounted();
905 {
906 NameComponent nameComponent;
907 nameComponent.name = ResolvePathToAnimationRoot(*ecs_, rootEntity, targetEntity);
908 nameManager_->Set(animationTrack, nameComponent);
909
910 animationTrackManager_->Create(animationTrack);
911 auto trackHandle = animationTrackManager_->Write(animationTrack);
912 trackHandle->component = componentManager->GetUid();
913 trackHandle->property = propertyPath;
914 trackHandle->interpolationMode = AnimationTrackComponent::Interpolation::LINEAR;
915 trackHandle->timestamps = timeStamps;
916 trackHandle->data = keys;
917 trackHandle->target = targetRef;
918 }
919
920 const auto animationHandle = animationManager_->Write(GetEntity());
921 animationHandle->tracks.emplace_back(animationTrack);
922 }
923 }
924
925 updateGuard_ = false;
926
927 IEcsTrackAnimation::Ptr track =
928 META_NS::GetObjectRegistry().Create<IEcsTrackAnimation>(SCENE_NS::ClassId::EcsTrackAnimation);
929 OnAnimationTrackChanged(*track, animationTrack);
930
931 GetSelf<META_NS::IContainer>()->Add(interface_pointer_cast<IObject>(track));
932
933 return track;
934 }
935
GetAnimationTrack(CORE_NS::Entity target,BASE_NS::string_view fullPropertyPath)936 IEcsTrackAnimation::Ptr EcsAnimation::GetAnimationTrack(CORE_NS::Entity target, BASE_NS::string_view fullPropertyPath)
937 {
938 // Extract property path.
939 auto separatorPosition = fullPropertyPath.find_first_of('.');
940 if (!ecs_ || separatorPosition == BASE_NS::string::npos) {
941 return {};
942 }
943
944 auto propertyPath = fullPropertyPath.substr(separatorPosition + 1);
945
946 auto tracks = META_NS::GetAll<IEcsTrackAnimation>(GetSelf<META_NS::IContainer>());
947 for (auto& track : tracks) {
948 const auto trackHandle = animationTrackManager_->Read(track->GetEntity());
949 if (trackHandle) {
950 if (trackHandle->target == target && trackHandle->property == propertyPath) {
951 return track;
952 }
953 }
954 }
955
956 return {};
957 }
958
DestroyAnimationTrack(IEcsTrackAnimation::Ptr track)959 void EcsAnimation::DestroyAnimationTrack(IEcsTrackAnimation::Ptr track)
960 {
961 updateGuard_ = true;
962 OnDestroyAnimationTrack(track);
963 updateGuard_ = false;
964 }
965
DestroyAllAnimationTracks()966 void EcsAnimation::DestroyAllAnimationTracks()
967 {
968 updateGuard_ = true;
969 auto container = GetSelf<META_NS::IContainer>();
970 while (container->GetSize() > 0) {
971 auto topmost = container->GetAt(0);
972 if (auto track = interface_pointer_cast<IEcsTrackAnimation>(topmost)) {
973 OnDestroyAnimationTrack(track);
974 } else {
975 container->Remove(topmost);
976 }
977 }
978 updateGuard_ = false;
979 }
980
Destroy()981 void EcsAnimation::Destroy()
982 {
983 SCENE_PLUGIN_VERBOSE_LOG("Tearing down: %s", META_NS::GetValue(Name()).c_str());
984
985 if (ecs_ && entity_) {
986 if (auto ecsListener = ecsListener_.lock()) {
987 auto po = GetSelf<SCENE_NS::IEcsProxyObject>();
988 ecsListener->RemoveEntity(entity_, po, *nameManager_);
989 ecsListener->RemoveEntity(entity_, po, *animationStateManager_);
990 ecsListener->RemoveEntity(entity_, po, *animationManager_);
991 ecsListener->RemoveEntity(entity_, po, *animationTrackManager_);
992 ecsListener->RemoveEntity(entity_, po, *animationInputManager_);
993 }
994 }
995
996 root_ = {};
997 entity_ = {};
998 }
999
OnDurationPropertyChanged()1000 void EcsAnimation::OnDurationPropertyChanged()
1001 {
1002 SetValue(META_ACCESS_PROPERTY(TotalDuration), GetValue(Duration()) * repeatCount_);
1003 if (!ecs_ || updateGuard_) {
1004 return;
1005 }
1006
1007 auto handle = animationManager_->Write(entity_);
1008 if (handle) {
1009 handle->duration = GetValue(Duration()).ToSecondsFloat();
1010 }
1011 }
1012
OnNamePropertyChanged()1013 void EcsAnimation::OnNamePropertyChanged()
1014 {
1015 if (!ecs_ || updateGuard_) {
1016 return;
1017 }
1018
1019 auto handle = nameManager_->Write(entity_);
1020 if (handle) {
1021 handle->name = GetValue(Name());
1022 }
1023 }
1024
OnProgressPropertyChanged()1025 void EcsAnimation::OnProgressPropertyChanged()
1026 {
1027 if (updateGuard_) {
1028 return;
1029 }
1030
1031 auto progress = GetValue(Progress());
1032 auto duration = GetValue(TotalDuration()).ToMilliseconds();
1033 SetTime(uint32_t(progress * duration));
1034 }
1035
GetEntity() const1036 CORE_NS::Entity EcsAnimation::GetEntity() const
1037 {
1038 return entity_;
1039 }
1040
SetProgress(float progress)1041 void EcsAnimation::SetProgress(float progress)
1042 {
1043 META_ACCESS_PROPERTY(Progress)->SetValue(Base::Math::clamp(progress, 0.0f, 1.0f));
1044 }
1045
SetTime(uint32_t value)1046 void EcsAnimation::SetTime(uint32_t value)
1047 {
1048 if (ecs_) {
1049 auto stateHandle = animationStateManager_->GetData(entity_);
1050 const auto metaData = stateHandle->Owner()->MetaData();
1051 for (const auto& data : metaData) {
1052 if (data.name == "time") {
1053 auto* time =
1054 static_cast<float*>(static_cast<void*>(static_cast<uint8_t*>(stateHandle->WLock()) + data.offset));
1055 *time = value / 1000.0f;
1056 stateHandle->WUnlock();
1057 break;
1058 }
1059 }
1060 }
1061 }
1062
Step(const IClock::ConstPtr & clock)1063 void EcsAnimation::Step(const IClock::ConstPtr& clock)
1064 {
1065 auto duration = GetValue(TotalDuration()).ToSecondsFloat();
1066 if (duration <= 0.0f) {
1067 return;
1068 }
1069
1070 if (!lastFrameTime_) {
1071 lastFrameTime_ = clock->GetTime();
1072 }
1073
1074 auto step = (clock->GetTime() - *lastFrameTime_).ToSecondsFloat();
1075 SetProgress(GetValue(Progress()) + (step / duration));
1076
1077 lastFrameTime_ = clock->GetTime();
1078 }
1079
Start()1080 void EcsAnimation::Start()
1081 {
1082 auto loopAnimation_ = true;
1083
1084 if (animationManager_) {
1085 if (auto animation = animationManager_->Write(entity_); animation) {
1086 animation->state = AnimationComponent::PlaybackState::PAUSE;
1087 if (loopAnimation_) {
1088 animation->repeatCount = AnimationComponent::REPEAT_COUNT_INFINITE;
1089 } else {
1090 animation->repeatCount = 0;
1091 }
1092 }
1093 }
1094
1095 lastFrameTime_.reset();
1096 META_ACCESS_PROPERTY(Running)->SetValue(true);
1097 META_NS::Invoke<META_NS::IOnChanged>(OnStarted());
1098 }
1099
Stop()1100 void EcsAnimation::Stop()
1101 {
1102 if (animationManager_) {
1103 if (auto animation = animationManager_->Write(entity_); animation) {
1104 animation->state = AnimationComponent::PlaybackState::PAUSE;
1105 }
1106 }
1107 lastFrameTime_.reset();
1108 META_ACCESS_PROPERTY(Running)->SetValue(false);
1109 }
1110
Pause()1111 void EcsAnimation::Pause()
1112 {
1113 if (animationManager_) {
1114 if (auto animation = animationManager_->Write(entity_); animation) {
1115 animation->state = AnimationComponent::PlaybackState::PAUSE;
1116 }
1117 }
1118 META_ACCESS_PROPERTY(Running)->SetValue(false);
1119 }
1120
Restart()1121 void EcsAnimation::Restart()
1122 {
1123 Stop();
1124 Start();
1125 }
1126
Finish()1127 void EcsAnimation::Finish()
1128 {
1129 Seek(1.0f);
1130 }
1131
Seek(float position)1132 void EcsAnimation::Seek(float position)
1133 {
1134 // TODO: Implement.
1135 SetProgress(position);
1136 }
1137
GetProperty(Uid componentUid,Entity entity,string property) const1138 EcsAnimation::Data EcsAnimation::GetProperty(Uid componentUid, Entity entity, string property) const
1139 {
1140 Data data;
1141 if (ecs_) {
1142 auto cm = ecs_->GetComponentManager(componentUid);
1143 if (IPropertyHandle* targetHandle = cm->GetData(entity); targetHandle) {
1144 if (auto po = RLock(*targetHandle, property); po) {
1145 data.property = &(po.property->type);
1146 const uint8_t* src = reinterpret_cast<uint8_t*>(po.offset);
1147 data.data.resize(po.property->size);
1148 CloneData(data.data.data(), data.data.size(), src, po.property->size);
1149 }
1150 }
1151 }
1152 return data;
1153 }
1154
SetKeyFrameData(Entity animationTrack,float timeStamp,vector<uint8_t> valueData)1155 void EcsAnimation::SetKeyFrameData(Entity animationTrack, float timeStamp, vector<uint8_t> valueData)
1156 {
1157 if (!ecs_) {
1158 return;
1159 }
1160
1161 BASE_ASSERT(EntityUtil::IsValid(animationTrack));
1162 auto trackHandle = animationTrackManager_->Read(animationTrack);
1163 if (trackHandle) {
1164 const auto timeStamps = trackHandle->timestamps;
1165 const auto keys = trackHandle->data;
1166 BASE_ASSERT(EntityUtil::IsValid(timeStamps));
1167 BASE_ASSERT(EntityUtil::IsValid(keys));
1168
1169 size_t index = 0;
1170 bool replace = false;
1171 // insert/replace the timestamp
1172 // find out the position where keyframe belongs based on timestamp
1173 auto timeLineHandle = animationInputManager_->Write(timeStamps);
1174 if (timeLineHandle) {
1175 auto& data = timeLineHandle->timestamps;
1176 const size_t oldCount = data.size();
1177 const size_t newCount = oldCount + 1;
1178
1179 for (vector<float>::iterator it = data.begin(); it < data.end(); it++) {
1180 if (*it == timeStamp) {
1181 replace = true;
1182 break;
1183 }
1184 if (*it > timeStamp) {
1185 break;
1186 }
1187 index++;
1188 }
1189
1190 if (!replace) {
1191 // reserve space for one more
1192 data.reserve(newCount);
1193 vector<float>::iterator insertIterator = data.begin() + index;
1194 data.insert(insertIterator, timeStamp);
1195 }
1196 }
1197
1198 auto keysHandle = animationOutputManager_->Write(keys);
1199 if (keysHandle) {
1200 auto& data = keysHandle->data;
1201 const size_t oldSize = data.size();
1202 const size_t oldCount = oldSize / valueData.size();
1203
1204 if (!replace) {
1205 const size_t newCount = oldCount + 1;
1206 const size_t newSize = newCount * valueData.size();
1207 // reserve space for one more
1208 data.reserve(newSize);
1209 vector<unsigned char>::iterator insertIterator = data.begin() + (index * valueData.size());
1210 data.insert(insertIterator, valueData.begin(), valueData.end());
1211 } else {
1212 auto dst = data.data() + index * valueData.size();
1213 CloneData(dst, valueData.size(), valueData.data(), valueData.size());
1214 }
1215 }
1216 }
1217 }
1218
UpdateAnimationTrackDuration(Entity animationTrack)1219 void EcsAnimation::UpdateAnimationTrackDuration(Entity animationTrack)
1220 {
1221 if (ecs_) {
1222 auto duration = GetTrackDuration(animationTrack);
1223 auto animationTrackHandle = animationTrackManager_->Read(animationTrack);
1224 if (animationTrackHandle) {
1225 auto target = animationTrackHandle->target;
1226 if (EntityUtil::IsValid(target)) {
1227 if (IPropertyHandle* targetHandle = animationManager_->GetData(target); targetHandle) {
1228 PropertyData pData;
1229 if (auto po = pData.WLock(*targetHandle, "duration"); po) {
1230 float* dst = reinterpret_cast<float*>(po.offset);
1231 *dst = duration;
1232 }
1233 }
1234 }
1235 }
1236 }
1237 }
1238
GetTrackDuration(Entity animationTrack)1239 float EcsAnimation::GetTrackDuration(Entity animationTrack)
1240 {
1241 BASE_ASSERT(EntityUtil::IsValid(animationTrack));
1242
1243 float duration = 0.0f;
1244
1245 if (ecs_) {
1246 auto trackHandle = animationTrackManager_->Read(animationTrack);
1247 if (trackHandle) {
1248 const auto timeStamps = trackHandle->timestamps;
1249 BASE_ASSERT(EntityUtil::IsValid(timeStamps));
1250
1251 auto timeLineHandle = animationInputManager_->Read(timeStamps);
1252 if (timeLineHandle) {
1253 auto& stamps = timeLineHandle->timestamps;
1254 for (auto& t : stamps) {
1255 if (t > duration) {
1256 duration = t;
1257 }
1258 }
1259 }
1260 }
1261 }
1262
1263 return duration;
1264 }
1265 /*
1266 bool EcsAnimation::Export(Serialization::IExportContext& context, Serialization::ClassPrimitive& value) const
1267 {
1268 return ObjectContainerFwd::Export(context, value);
1269 }
1270
1271 bool EcsAnimation::Import(Serialization::IImportContext& context, const Serialization::ClassPrimitive& value)
1272 {
1273 return ObjectContainerFwd::Import(context, value);
1274 }
1275 */
RegisterEcsAnimationObjectType()1276 void RegisterEcsAnimationObjectType()
1277 {
1278 META_NS::GetObjectRegistry().RegisterObjectType<EcsTrackAnimation>();
1279 META_NS::GetObjectRegistry().RegisterObjectType<EcsAnimation>();
1280 }
1281
UnregisterEcsAnimationObjectType()1282 void UnregisterEcsAnimationObjectType()
1283 {
1284 META_NS::GetObjectRegistry().UnregisterObjectType<EcsTrackAnimation>();
1285 META_NS::GetObjectRegistry().UnregisterObjectType<EcsAnimation>();
1286 }
1287
1288 SCENE_END_NAMESPACE()
1289