/* * Copyright (c) 2024 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "ecs_animation.h" #include #include #include #include <3d/ecs/systems/intf_animation_system.h> #include <3d/ecs/systems/intf_node_system.h> #include #include #include "ecs_util.h" using namespace BASE_NS; using namespace CORE_NS; using namespace CORE3D_NS; using namespace META_NS; SCENE_BEGIN_NAMESPACE() namespace { bool EndsWith(string_view str, string_view postfix) { return str.size() >= postfix.size() && 0 == str.compare(str.size() - postfix.size(), postfix.size(), postfix); } template const BASE_NS::Uid GetArrayTypeUid() { return UidFromType(); } PropertyData::PropertyOffset RLock(IPropertyHandle& targetHandle, string_view property) { PropertyData pData; string path, name; auto containerHandle = ResolveContainerProperty(targetHandle, string(property), path, name); if (containerHandle) { if (auto po = pData.RLock(*containerHandle, name); po) { return po; } } if (auto po = pData.RLock(targetHandle, property); po) { return po; } return PropertyData::PropertyOffset(); } BASE_NS::string ResolvePathToAnimationRoot(IEcs& ecs, Entity root, Entity target) { BASE_NS::string result; CORE3D_NS::INodeSystem* nodeSystem = GetSystem(ecs); if (nodeSystem) { auto rootNode = nodeSystem->GetNode(root); auto node = nodeSystem->GetNode(target); if (rootNode && node) { if (rootNode->IsAncestorOf(*node)) { auto current = node; while (current) { // Found root node, break. if (current == rootNode) { break; } // Add this to path. if (result.empty()) { result = current->GetName(); } else { result = current->GetName() + "/" + result; } // Process parent next. current = current->GetParent(); } } } } return result; } void UpdateTrackTargets(IEcs& ecs, Entity animationEntity, Entity rootNode) { auto& nameManager_ = *GetManager(ecs); auto animationManager = GetManager(ecs); auto animationTrackManager = GetManager(ecs); auto& entityManager = ecs.GetEntityManager(); auto* nodeSystem = GetSystem(ecs); CORE_ASSERT(nodeSystem); if (!nodeSystem) { return; } auto* node = nodeSystem->GetNode(rootNode); CORE_ASSERT(node); if (!node) { return; } if (const ScopedHandle animationData = animationManager->Read(animationEntity); animationData) { vector targetEntities; targetEntities.reserve(animationData->tracks.size()); std::transform(animationData->tracks.begin(), animationData->tracks.end(), std::back_inserter(targetEntities), [&nameManager = nameManager_, &node](const Entity& trackEntity) { if (auto nameHandle = nameManager.Read(trackEntity); nameHandle) { if (nameHandle->name.empty()) { return node->GetEntity(); } else { if (auto targetNode = node->LookupNodeByPath(nameHandle->name); targetNode) { return targetNode->GetEntity(); } } } return Entity {}; }); if (animationData->tracks.size() == targetEntities.size()) { auto targetIt = targetEntities.begin(); for (const auto& trackEntity : animationData->tracks) { if (auto track = animationTrackManager->Write(trackEntity); track) { if (track->target) { CORE_LOG_D("AnimationTrack %s already targetted", to_hex(static_cast(track->target).id).data()); } track->target = entityManager.GetReferenceCounted(*targetIt); } ++targetIt; } } } } } // namespace IProperty::Ptr EcsTrackAnimation::Keyframes() const { return keyframes_; } size_t EcsTrackAnimation::AddKeyframe(float timestamp, const META_NS::IAny::ConstPtr& from) { // TODO: Implement. return {}; } bool EcsTrackAnimation::RemoveKeyframe(size_t index) { // TODO: Implement. return false; } void EcsTrackAnimation::RemoveAllKeyframes() { // TODO: Implement. } bool EcsTrackAnimation::Build(const META_NS::IMetadata::Ptr&) { auto& registry = META_NS::GetObjectRegistry(); auto arr = ConstructArrayProperty("Keyframes", {}, META_NS::ObjectFlagBits::NONE); keyframes_ = arr.GetProperty(); if (!keyframes_) { CORE_LOG_E("Invalid property type for EcsTrackAnimation: "); return false; } return true; } void EcsTrackAnimation::Step(const META_NS::IClock::ConstPtr& clock) { // TODO: Implement. } void EcsTrackAnimation::Start() { // TODO: Implement. META_NS::Invoke(OnStarted()); } void EcsTrackAnimation::Stop() { } void EcsTrackAnimation::Pause() { } void EcsTrackAnimation::Restart() { Stop(); Start(); } void EcsTrackAnimation::Finish() { // TODO: Implement. } void EcsTrackAnimation::Seek(float position) { // TODO: Implement. } BASE_NS::string EcsAnimation::GetName() const { return Name()->GetValue(); } bool EcsAnimation::Build(const META_NS::IMetadata::Ptr& /*data*/) { GetSelf()->SetRequiredInterfaces({ IEcsTrackAnimation::UID }); // TODO: Loop count. // TODO: Running. Name()->OnChanged()->AddHandler(MakeCallback([this] { OnNamePropertyChanged(); })); Duration()->OnChanged()->AddHandler(MakeCallback([this] { OnDurationPropertyChanged(); })); Progress()->OnChanged()->AddHandler(MakeCallback([this] { OnProgressPropertyChanged(); })); return true; } bool EcsAnimation::SetRootEntity(CORE_NS::Entity entity) { if (EntityUtil::IsValid(root_)) { // Cannot change root entity, once set. return entity == root_; } root_ = entity; return true; } CORE_NS::Entity EcsAnimation::GetRootEntity() const { return root_; } bool EcsAnimation::Retarget(CORE_NS::Entity entity) { if (ecs_) { UpdateTrackTargets(*ecs_, GetEntity(), entity); root_ = entity; return true; } return false; } void EcsAnimation::SetEntity(CORE_NS::IEcs& ecs, CORE_NS::Entity entity) { ecs_ = &ecs; entity_ = ecs_->GetEntityManager().GetReferenceCounted(entity); animationStateManager_ = nullptr; for (auto manager : ecs.GetComponentManagers()) { if (manager->GetName() == "AnimationStateComponent") { animationStateManager_ = manager; break; } } animationManager_ = GetManager(*ecs_); animationTrackManager_ = GetManager(*ecs_); animationInputManager_ = GetManager(*ecs_); animationOutputManager_ = GetManager(*ecs_); nameManager_ = GetManager(*ecs_); OnAnimationNameChanged(IEcs::ComponentListener::EventType::MODIFIED); OnAnimationChanged(IEcs::ComponentListener::EventType::MODIFIED); if (auto ecsListener = ecsListener_.lock()) { auto po = GetSelf(); ecsListener->AddEntity(entity_, po, *nameManager_); ecsListener->AddEntity(entity_, po, *animationStateManager_); ecsListener->AddEntity(entity_, po, *animationManager_); ecsListener->AddEntity(entity_, po, *animationTrackManager_); ecsListener->AddEntity(entity_, po, *animationInputManager_); } // If the animation root is not set, then try to resolve it. if (!EntityUtil::IsValid(root_)) { root_ = TryResolveAnimationRoot(); } META_ACCESS_PROPERTY(Valid)->SetValue(true); } CORE_NS::Entity EcsAnimation::TryResolveAnimationRoot() { if (!ecs_) { return CORE_NS::Entity {}; } CORE3D_NS::INodeSystem* nodeSystem = GetSystem(*ecs_); // We will try to resolve the animation root from the first animation track. auto tracks = META_NS::GetAll(GetSelf()); if (tracks.empty()) { // No way to resolve, no tracks. return {}; } IEcsTrackAnimation::Ptr track = tracks.at(0); BASE_NS::string path; if (auto nameHandle = nameManager_->Read(track->GetEntity()); nameHandle) { // The name of the track actually represents the path to the animated entity. path = nameHandle->name; } ISceneNode* node = nullptr; if (auto trackHandle = animationTrackManager_->Read(track->GetEntity()); trackHandle) { // This is the node that is being animated by the path. node = nodeSystem->GetNode(trackHandle->target); if (!node) { return {}; } // Resolve the path to parent. while (!path.empty()) { BASE_NS::string suffix = node->GetName(); if (EndsWith(path, suffix)) { // Remove name of this node from the path. path = string(path.substr(0, path.length() - suffix.length())); if (EndsWith(path, "/")) { path = string(path.substr(0, path.length() - 1)); } // Go up the tree. node = node->GetParent(); } else { // Error. node = nullptr; break; } } } if (node) { SetRootEntity(node->GetEntity()); } return node ? node->GetEntity() : Entity {}; } void EcsAnimation::AddAnimation(const IAnimation::Ptr& animation) { GetSelf()->Add(interface_pointer_cast(animation)); // ToDo: Can we rely that someone has added an listener for tracks } void EcsAnimation::RemoveAnimation(const IAnimation::Ptr& animation) { GetSelf()->Remove(interface_pointer_cast(animation)); // ToDo: In principle, the tracks should deal common listener and removing parent, might be worth checking though } vector EcsAnimation::GetAnimations() const { return META_NS::GetAll(GetSelf()); } void EcsAnimation::DoComponentEvent( IEcs::ComponentListener::EventType type, const IComponentManager& componentManager, const CORE_NS::Entity& entity) { bool isAnimationNameChange = componentManager.GetUid() == INameComponentManager::UID; bool isAnimationChange = componentManager.GetUid() == IAnimationComponentManager::UID; bool isAnimationStateChange = componentManager.GetUid() == animationStateManager_->GetUid(); bool isAnimationInputChange = componentManager.GetUid() == IAnimationInputComponentManager::UID; bool isAnimationTrackChange = componentManager.GetUid() == IAnimationTrackComponentManager::UID; if (isAnimationChange || isAnimationStateChange) { // For animation and animation state, we are interested about changes concerning entity_. if (isAnimationChange) { // Animation has changed for this entity. OnAnimationChanged(type); } else if (isAnimationStateChange) { // Animation state has changed for this entity. OnAnimationStateChanged(type); } } else if (isAnimationTrackChange) { OnAnimationTracksChanged(type, entity); } else if (isAnimationInputChange) { OnAnimationInputsChanged(type, entity); } else if (isAnimationNameChange) { OnAnimationNameChanged(type); } } void EcsAnimation::OnAnimationStateChanged(IEcs::ComponentListener::EventType event) { // This function is triggered when ECS dispatch changes at the end of the frame. if (!ecs_ || event != IEcs::ComponentListener::EventType::MODIFIED) { return; } // Propagate changes back to object's properties. auto stateHandle = animationStateManager_->GetData(entity_); const auto metaData = stateHandle->Owner()->MetaData(); for (const auto& data : metaData) { if (data.name == "time") { auto* time = static_cast( static_cast(static_cast(stateHandle->RLock()) + data.offset)); updateGuard_ = true; auto duration = GetValue(TotalDuration()).ToSecondsFloat(); if (duration > 0) { SetProgress(*time / duration); } else { SetProgress(0); } updateGuard_ = false; stateHandle->RUnlock(); break; } } } void EcsAnimation::OnAnimationNameChanged(IEcs::ComponentListener::EventType event) { // This function is triggered when ECS dispatch changes at the end of the frame. if (!ecs_ || event != IEcs::ComponentListener::EventType::MODIFIED) { return; } if (nameManager_->HasComponent(GetEntity())) { updateGuard_ = true; SetValue(Name(), nameManager_->Get(GetEntity()).name); updateGuard_ = false; } } void EcsAnimation::OnAnimationChanged(IEcs::ComponentListener::EventType event) { // This function is triggered when ECS dispatch changes at the end of the frame. if (!ecs_ || event == IEcs::ComponentListener::EventType::DESTROYED) { return; } // Propagate changes back to object's properties. auto handle = animationManager_->Read(entity_); if (handle) { updateGuard_ = true; repeatCount_ = static_cast(handle->repeatCount); // Update animation duration. SetValue(META_ACCESS_PROPERTY(Duration), TimeSpan::Seconds(handle->duration)); updateGuard_ = false; // Update animation tracks, if needed. if (IsAnimationTrackArrayModified()) { GatherAnimationTracks(); } } } void EcsAnimation::OnAnimationTracksChanged(IEcs::ComponentListener::EventType event, CORE_NS::Entity entity) { bool animationHasNewOrRemovedTracks = false; auto tracks = META_NS::GetAll(GetSelf()); // Go through all created / modified / destroyed track entities. auto iterator = std::find_if( tracks.begin(), tracks.end(), [entity](const auto& track) { return track->GetEntity() == entity; }); if (iterator != tracks.end()) { // Animation track was reported changed. if (event == IEcs::ComponentListener::EventType::MODIFIED) { // An animation track has been modified, so update it. auto index = std::distance(tracks.begin(), iterator); auto track = tracks.at(index); OnAnimationTrackChanged(*track, entity); } else { // If we have new or removed tracks, we will simply update all. animationHasNewOrRemovedTracks = true; } } // If new or removed tracks, refresh all. if (animationHasNewOrRemovedTracks) { GatherAnimationTracks(); } } void EcsAnimation::OnAnimationInputsChanged(IEcs::ComponentListener::EventType event, CORE_NS::Entity entity) { if (!ecs_) { return; } auto tracks = META_NS::GetAll(GetSelf()); // Go through all created / modified / destroyed track entities. for (size_t i = 0; i < tracks.size(); ++i) { auto track = tracks.at(i); if (auto trackHandle = animationTrackManager_->Read(track->GetEntity()); trackHandle) { if (trackHandle->timestamps == entity) { // Timestamps for this track have changed. OnAnimationTimestampsChanged(*track, trackHandle->timestamps); break; } } } } bool EcsAnimation::IsAnimationTrackArrayModified() { if (!ecs_) { return false; } auto tracks = META_NS::GetAll(GetSelf()); auto handle = animationManager_->Read(entity_); if (handle) { if (handle->tracks.size() != tracks.size()) { // The amount of tracks is different. return true; } for (size_t i = 0; i < handle->tracks.size(); ++i) { if (tracks.at(i)->GetEntity() != handle->tracks[i]) { // Track entity id has changed. return true; } } } return false; } void EcsAnimation::GatherAnimationTracks() { if (!ecs_) { return; } // Take copy of the animation tracks. auto oldTracks = META_NS::GetAll(GetSelf()); // Clear tracks. GetSelf()->RemoveAll(); // Update animation tracks. auto handle = animationManager_->Read(entity_); if (handle) { for (const auto& track : handle->tracks) { // See if we have this track stored. auto it = std::find_if(oldTracks.begin(), oldTracks.end(), [track](const auto& t) { if (t->GetEntity() == track) { return true; } return false; }); IEcsTrackAnimation::Ptr animationTrack; if (it != oldTracks.end()) { animationTrack = *it; } else { animationTrack = META_NS::GetObjectRegistry().Create(SCENE_NS::ClassId::EcsTrackAnimation); interface_pointer_cast(animationTrack) ->SetCommonListener(ecsListener_.lock()); } OnAnimationTrackChanged(*animationTrack, track); GetSelf()->Add(interface_pointer_cast(animationTrack)); } } } void EcsAnimation::OnAnimationTrackChanged(IEcsTrackAnimation& track, Entity trackEntity) { if (!ecs_ || updateGuard_) { return; } if (auto trackHandle = animationTrackManager_->Read(trackEntity); trackHandle) { auto nameComponent = nameManager_->Get(trackHandle->target.operator Entity()); track.SetEntity(trackEntity); auto named = interface_cast(&track); if (named) { SetValue(named->Name(), nameComponent.name + '.' + trackHandle->property.data()); } OnAnimationTimestampsChanged(track, trackHandle->timestamps); } } void EcsAnimation::UpdateTimestamps(IEcsTrackAnimation& track, Entity timestampEntity) { if (!ecs_) { return; } if (animationInputManager_->HasComponent(timestampEntity)) { const auto timestamps = animationInputManager_->Get(timestampEntity); const auto trackAnimation = interface_cast(&track); const auto timedAnimation = interface_cast(&track); vector times; times.reserve(timestamps.timestamps.size()); const auto duration = META_NS::GetValue(timedAnimation->Duration()).ToSecondsFloat(); for (const auto timestamp : timestamps.timestamps) { const auto offset = (duration > 0) ? (timestamp / duration) : 0.0f; times.push_back(offset); } trackAnimation->Timestamps()->SetValue(times); } } void EcsAnimation::OnAnimationTimestampsChanged(IEcsTrackAnimation& track, Entity timestampEntity) { if (!ecs_ || updateGuard_) { return; } UpdateTimestamps(track, timestampEntity); // If any of the tracks in this animation is sharing the same timestamps, then make all tracks read-only. bool hasSharedTimestamps = false; auto tracks = META_NS::GetAll(GetSelf()); for (const auto& other : tracks) { if (other->GetEntity() != track.GetEntity()) { if (auto trackHandle = animationTrackManager_->Read(other->GetEntity()); trackHandle) { if (trackHandle->timestamps == timestampEntity) { hasSharedTimestamps = true; break; } } } } if (GetValue(ReadOnly()) != hasSharedTimestamps) { META_ACCESS_PROPERTY(ReadOnly)->SetValue(hasSharedTimestamps); } } void EcsAnimation::SetDuration(uint32_t ms) { if (!ecs_) { return; } const auto newDuration = TimeSpan::Milliseconds(ms); const auto tracks = META_NS::GetAll(GetSelf()); for (const auto& track : tracks) { if (const auto trackHandle = animationTrackManager_->Read(track->GetEntity()); trackHandle) { interface_cast(track)->Duration()->SetValue(newDuration); UpdateTimestamps(*track, trackHandle->timestamps); } } META_ACCESS_PROPERTY(Duration)->SetValue(newDuration); } void EcsAnimation::AddKey(IEcsTrackAnimation::Ptr track, float time) { if (!ecs_) { return; } updateGuard_ = true; auto handle = animationTrackManager_->Read(track->GetEntity()); if (handle) { auto propertyData = GetProperty(handle->component, handle->target, handle->property); if (propertyData) { SetKeyFrameData(track->GetEntity(), time, propertyData.data); } } UpdateAnimationTrackDuration(track->GetEntity()); updateGuard_ = false; uint32_t timeMs = static_cast(time * 1000.0f); if (GetValue(Duration()).ToMilliseconds() < timeMs) { META_ACCESS_PROPERTY(Duration)->SetValue(TimeSpan::Milliseconds(timeMs)); } OnAnimationTrackChanged(*track, track->GetEntity()); } void EcsAnimation::RemoveKey(IEcsTrackAnimation::Ptr track, uint32_t index) { if (!ecs_) { return; } updateGuard_ = true; // remove timestamp at pos if (auto animationTrack = animationTrackManager_->Read(track->GetEntity()); animationTrack) { auto stampsEntity = animationTrack->timestamps.operator Entity(); auto inputData = animationInputManager_->Write(stampsEntity); vector::iterator iit = inputData->timestamps.begin(); inputData->timestamps.erase(iit + index); // remove data at pos auto dataEntity = animationTrack->data.operator Entity(); if (auto outputData = animationOutputManager_->Write(dataEntity); outputData) { auto targetEntity = animationTrack->target.operator Entity(); auto manager = ecs_->GetComponentManager(animationTrack->component); auto target = manager->GetData(targetEntity); auto poffset = RLock(*target, animationTrack->property); vector::iterator oit = outputData->data.begin(); outputData->data.erase(oit + (index * poffset.property->size), oit + (index * poffset.property->size) + poffset.property->size); } } updateGuard_ = false; OnAnimationTrackChanged(*track, track->GetEntity()); } void EcsAnimation::UpdateKey(IEcsTrackAnimation::Ptr track, uint32_t oldKeyIndex, uint32_t newKeyIndex, float time) { if (!ecs_) { return; } updateGuard_ = true; { auto animationTrack = animationTrackManager_->Read(track->GetEntity()); auto stampsEntity = animationTrack->timestamps.operator Entity(); auto inputData = animationInputManager_->Write(stampsEntity); auto dataEntity = animationTrack->data.operator Entity(); auto outputData = animationOutputManager_->Write(dataEntity); auto targetEntity = animationTrack->target.operator Entity(); vector::iterator iit = inputData->timestamps.begin(); inputData->timestamps.erase(iit + oldKeyIndex); auto manager = ecs_->GetComponentManager(animationTrack->component); auto target = manager->GetData(targetEntity); auto poffset = RLock(*target, animationTrack->property); if (poffset) { vector::iterator oit = outputData->data.begin(); vector moveData(oit + (oldKeyIndex * poffset.property->size), oit + (oldKeyIndex * poffset.property->size) + poffset.property->size); outputData->data.erase(oit + (oldKeyIndex * poffset.property->size), oit + (oldKeyIndex * poffset.property->size) + poffset.property->size); inputData->timestamps.insert(iit + newKeyIndex, time); vector::iterator noit = outputData->data.begin(); outputData->data.insert( noit + (newKeyIndex * poffset.property->size), std::begin(moveData), std::end(moveData)); } } updateGuard_ = false; uint32_t timeMs = static_cast(time * 1000.0f); if (GetValue(Duration()).ToMilliseconds() < timeMs) { META_ACCESS_PROPERTY(Duration)->SetValue(TimeSpan::Milliseconds(timeMs)); } OnAnimationTrackChanged(*track, track->GetEntity()); } void EcsAnimation::OnDestroyAnimationTrack(IEcsTrackAnimation::Ptr track) { if (ecs_) { if (auto trackHandle = animationTrackManager_->Read(track->GetEntity()); trackHandle) { // Destroy timestamps and keys. ecs_->GetEntityManager().Destroy(trackHandle->timestamps); ecs_->GetEntityManager().Destroy(trackHandle->data); } { // Remove track from animation. const auto animationHandle = animationManager_->Write(GetEntity()); auto it = std::find(animationHandle->tracks.begin(), animationHandle->tracks.end(), track->GetEntity()); if (it != animationHandle->tracks.end()) { animationHandle->tracks.erase(it); } } // Destroy track. ecs_->GetEntityManager().Destroy(track->GetEntity()); } GetSelf()->Remove(interface_pointer_cast(track)); } BASE_NS::vector EcsAnimation::GetAllRelatedEntities() const { BASE_NS::vector result; if (animationManager_) { auto animationHandle = animationManager_->Read(GetEntity()); if (animationHandle) { for (auto& track : animationHandle->tracks) { const auto trackHandle = animationTrackManager_->Read(track); if (trackHandle) { if (EntityUtil::IsValid(trackHandle->timestamps)) { result.push_back(ecs_->GetEntityManager().GetReferenceCounted(trackHandle->timestamps)); } if (EntityUtil::IsValid(trackHandle->data)) { result.push_back(ecs_->GetEntityManager().GetReferenceCounted(trackHandle->data)); } result.push_back(ecs_->GetEntityManager().GetReferenceCounted(track)); } } } // Add self. result.push_back(ecs_->GetEntityManager().GetReferenceCounted(GetEntity())); } return result; } IEcsTrackAnimation::Ptr EcsAnimation::CreateAnimationTrack( CORE_NS::Entity rootEntity, CORE_NS::Entity targetEntity, BASE_NS::string_view fullPropertyPath) { // Extract property path. auto separatorPosition = fullPropertyPath.find_first_of('.'); if (!ecs_ || separatorPosition == BASE_NS::string::npos) { return {}; } IComponentManager* componentManager { nullptr }; auto componentManagerName = fullPropertyPath.substr(0, separatorPosition); auto propertyPath = fullPropertyPath.substr(separatorPosition + 1); for (const auto& manager : ecs_->GetComponentManagers()) { if (manager->GetName() == componentManagerName) { componentManager = manager; break; } } if (!componentManager) { return {}; } updateGuard_ = true; EntityReference animationTrack; if (IPropertyHandle* targetHandle = componentManager->GetData(targetEntity); targetHandle) { if (auto po = RLock(*targetHandle, propertyPath); po) { BASE_NS::string targetName = "Unnamed"; if (nameManager_->HasComponent(targetEntity)) { targetName = nameManager_->Get(targetEntity).name; } const auto timeStamps = ecs_->GetEntityManager().CreateReferenceCounted(); { NameComponent nameComponent; nameComponent.name = "TimeStamps - " + targetName + ":" + propertyPath; nameManager_->Set(timeStamps, nameComponent); animationInputManager_->Create(timeStamps); } const auto keys = ecs_->GetEntityManager().CreateReferenceCounted(); { NameComponent nameComponent; nameComponent.name = "Keys - " + targetName + ":" + propertyPath; nameManager_->Set(keys, nameComponent); animationOutputManager_->Create(keys); auto keysHandle = animationOutputManager_->Write(keys); keysHandle->type = po.property->type; } const auto targetRef = ecs_->GetEntityManager().GetReferenceCounted(targetEntity); animationTrack = ecs_->GetEntityManager().CreateReferenceCounted(); { NameComponent nameComponent; nameComponent.name = ResolvePathToAnimationRoot(*ecs_, rootEntity, targetEntity); nameManager_->Set(animationTrack, nameComponent); animationTrackManager_->Create(animationTrack); auto trackHandle = animationTrackManager_->Write(animationTrack); trackHandle->component = componentManager->GetUid(); trackHandle->property = propertyPath; trackHandle->interpolationMode = AnimationTrackComponent::Interpolation::LINEAR; trackHandle->timestamps = timeStamps; trackHandle->data = keys; trackHandle->target = targetRef; } const auto animationHandle = animationManager_->Write(GetEntity()); animationHandle->tracks.emplace_back(animationTrack); } } updateGuard_ = false; IEcsTrackAnimation::Ptr track = META_NS::GetObjectRegistry().Create(SCENE_NS::ClassId::EcsTrackAnimation); OnAnimationTrackChanged(*track, animationTrack); GetSelf()->Add(interface_pointer_cast(track)); return track; } IEcsTrackAnimation::Ptr EcsAnimation::GetAnimationTrack(CORE_NS::Entity target, BASE_NS::string_view fullPropertyPath) { // Extract property path. auto separatorPosition = fullPropertyPath.find_first_of('.'); if (!ecs_ || separatorPosition == BASE_NS::string::npos) { return {}; } auto propertyPath = fullPropertyPath.substr(separatorPosition + 1); auto tracks = META_NS::GetAll(GetSelf()); for (auto& track : tracks) { const auto trackHandle = animationTrackManager_->Read(track->GetEntity()); if (trackHandle) { if (trackHandle->target == target && trackHandle->property == propertyPath) { return track; } } } return {}; } void EcsAnimation::DestroyAnimationTrack(IEcsTrackAnimation::Ptr track) { updateGuard_ = true; OnDestroyAnimationTrack(track); updateGuard_ = false; } void EcsAnimation::DestroyAllAnimationTracks() { updateGuard_ = true; auto container = GetSelf(); while (container->GetSize() > 0) { auto topmost = container->GetAt(0); if (auto track = interface_pointer_cast(topmost)) { OnDestroyAnimationTrack(track); } else { container->Remove(topmost); } } updateGuard_ = false; } void EcsAnimation::Destroy() { SCENE_PLUGIN_VERBOSE_LOG("Tearing down: %s", META_NS::GetValue(Name()).c_str()); if (ecs_ && entity_) { if (auto ecsListener = ecsListener_.lock()) { auto po = GetSelf(); ecsListener->RemoveEntity(entity_, po, *nameManager_); ecsListener->RemoveEntity(entity_, po, *animationStateManager_); ecsListener->RemoveEntity(entity_, po, *animationManager_); ecsListener->RemoveEntity(entity_, po, *animationTrackManager_); ecsListener->RemoveEntity(entity_, po, *animationInputManager_); } } root_ = {}; entity_ = {}; } void EcsAnimation::OnDurationPropertyChanged() { SetValue(META_ACCESS_PROPERTY(TotalDuration), GetValue(Duration()) * repeatCount_); if (!ecs_ || updateGuard_) { return; } auto handle = animationManager_->Write(entity_); if (handle) { handle->duration = GetValue(Duration()).ToSecondsFloat(); } } void EcsAnimation::OnNamePropertyChanged() { if (!ecs_ || updateGuard_) { return; } auto handle = nameManager_->Write(entity_); if (handle) { handle->name = GetValue(Name()); } } void EcsAnimation::OnProgressPropertyChanged() { if (updateGuard_) { return; } auto progress = GetValue(Progress()); auto duration = GetValue(TotalDuration()).ToMilliseconds(); SetTime(uint32_t(progress * duration)); } CORE_NS::Entity EcsAnimation::GetEntity() const { return entity_; } void EcsAnimation::SetProgress(float progress) { META_ACCESS_PROPERTY(Progress)->SetValue(Base::Math::clamp(progress, 0.0f, 1.0f)); } void EcsAnimation::SetTime(uint32_t value) { if (ecs_) { auto stateHandle = animationStateManager_->GetData(entity_); const auto metaData = stateHandle->Owner()->MetaData(); for (const auto& data : metaData) { if (data.name == "time") { auto* time = static_cast(static_cast(static_cast(stateHandle->WLock()) + data.offset)); *time = value / 1000.0f; stateHandle->WUnlock(); break; } } } } void EcsAnimation::Step(const IClock::ConstPtr& clock) { auto duration = GetValue(TotalDuration()).ToSecondsFloat(); if (duration <= 0.0f) { return; } if (!lastFrameTime_) { lastFrameTime_ = clock->GetTime(); } auto step = (clock->GetTime() - *lastFrameTime_).ToSecondsFloat(); SetProgress(GetValue(Progress()) + (step / duration)); lastFrameTime_ = clock->GetTime(); } void EcsAnimation::Start() { auto loopAnimation_ = true; if (animationManager_) { if (auto animation = animationManager_->Write(entity_); animation) { animation->state = AnimationComponent::PlaybackState::PAUSE; if (loopAnimation_) { animation->repeatCount = AnimationComponent::REPEAT_COUNT_INFINITE; } else { animation->repeatCount = 0; } } } lastFrameTime_.reset(); META_ACCESS_PROPERTY(Running)->SetValue(true); META_NS::Invoke(OnStarted()); } void EcsAnimation::Stop() { if (animationManager_) { if (auto animation = animationManager_->Write(entity_); animation) { animation->state = AnimationComponent::PlaybackState::PAUSE; } } lastFrameTime_.reset(); META_ACCESS_PROPERTY(Running)->SetValue(false); } void EcsAnimation::Pause() { if (animationManager_) { if (auto animation = animationManager_->Write(entity_); animation) { animation->state = AnimationComponent::PlaybackState::PAUSE; } } META_ACCESS_PROPERTY(Running)->SetValue(false); } void EcsAnimation::Restart() { Stop(); Start(); } void EcsAnimation::Finish() { Seek(1.0f); } void EcsAnimation::Seek(float position) { // TODO: Implement. SetProgress(position); } EcsAnimation::Data EcsAnimation::GetProperty(Uid componentUid, Entity entity, string property) const { Data data; if (ecs_) { auto cm = ecs_->GetComponentManager(componentUid); if (IPropertyHandle* targetHandle = cm->GetData(entity); targetHandle) { if (auto po = RLock(*targetHandle, property); po) { data.property = &(po.property->type); const uint8_t* src = reinterpret_cast(po.offset); data.data.resize(po.property->size); CloneData(data.data.data(), data.data.size(), src, po.property->size); } } } return data; } void EcsAnimation::SetKeyFrameData(Entity animationTrack, float timeStamp, vector valueData) { if (!ecs_) { return; } BASE_ASSERT(EntityUtil::IsValid(animationTrack)); auto trackHandle = animationTrackManager_->Read(animationTrack); if (trackHandle) { const auto timeStamps = trackHandle->timestamps; const auto keys = trackHandle->data; BASE_ASSERT(EntityUtil::IsValid(timeStamps)); BASE_ASSERT(EntityUtil::IsValid(keys)); size_t index = 0; bool replace = false; // insert/replace the timestamp // find out the position where keyframe belongs based on timestamp auto timeLineHandle = animationInputManager_->Write(timeStamps); if (timeLineHandle) { auto& data = timeLineHandle->timestamps; const size_t oldCount = data.size(); const size_t newCount = oldCount + 1; for (vector::iterator it = data.begin(); it < data.end(); it++) { if (*it == timeStamp) { replace = true; break; } if (*it > timeStamp) { break; } index++; } if (!replace) { // reserve space for one more data.reserve(newCount); vector::iterator insertIterator = data.begin() + index; data.insert(insertIterator, timeStamp); } } auto keysHandle = animationOutputManager_->Write(keys); if (keysHandle) { auto& data = keysHandle->data; const size_t oldSize = data.size(); const size_t oldCount = oldSize / valueData.size(); if (!replace) { const size_t newCount = oldCount + 1; const size_t newSize = newCount * valueData.size(); // reserve space for one more data.reserve(newSize); vector::iterator insertIterator = data.begin() + (index * valueData.size()); data.insert(insertIterator, valueData.begin(), valueData.end()); } else { auto dst = data.data() + index * valueData.size(); CloneData(dst, valueData.size(), valueData.data(), valueData.size()); } } } } void EcsAnimation::UpdateAnimationTrackDuration(Entity animationTrack) { if (ecs_) { auto duration = GetTrackDuration(animationTrack); auto animationTrackHandle = animationTrackManager_->Read(animationTrack); if (animationTrackHandle) { auto target = animationTrackHandle->target; if (EntityUtil::IsValid(target)) { if (IPropertyHandle* targetHandle = animationManager_->GetData(target); targetHandle) { PropertyData pData; if (auto po = pData.WLock(*targetHandle, "duration"); po) { float* dst = reinterpret_cast(po.offset); *dst = duration; } } } } } } float EcsAnimation::GetTrackDuration(Entity animationTrack) { BASE_ASSERT(EntityUtil::IsValid(animationTrack)); float duration = 0.0f; if (ecs_) { auto trackHandle = animationTrackManager_->Read(animationTrack); if (trackHandle) { const auto timeStamps = trackHandle->timestamps; BASE_ASSERT(EntityUtil::IsValid(timeStamps)); auto timeLineHandle = animationInputManager_->Read(timeStamps); if (timeLineHandle) { auto& stamps = timeLineHandle->timestamps; for (auto& t : stamps) { if (t > duration) { duration = t; } } } } } return duration; } /* bool EcsAnimation::Export(Serialization::IExportContext& context, Serialization::ClassPrimitive& value) const { return ObjectContainerFwd::Export(context, value); } bool EcsAnimation::Import(Serialization::IImportContext& context, const Serialization::ClassPrimitive& value) { return ObjectContainerFwd::Import(context, value); } */ void RegisterEcsAnimationObjectType() { META_NS::GetObjectRegistry().RegisterObjectType(); META_NS::GetObjectRegistry().RegisterObjectType(); } void UnregisterEcsAnimationObjectType() { META_NS::GetObjectRegistry().UnregisterObjectType(); META_NS::GetObjectRegistry().UnregisterObjectType(); } SCENE_END_NAMESPACE()