Files
FlaxEngine/Source/Engine/Level/Actor.cpp
2024-05-31 22:12:03 +02:00

1924 lines
48 KiB
C++

// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#include "Actor.h"
#include "ActorsCache.h"
#include "Level.h"
#include "SceneObjectsFactory.h"
#include "Scene/Scene.h"
#include "Prefabs/Prefab.h"
#include "Prefabs/PrefabManager.h"
#include "Engine/Core/Log.h"
#include "Engine/Scripting/Script.h"
#include "Engine/Scripting/ManagedCLR/MClass.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Content/Content.h"
#include "Engine/Core/Cache.h"
#include "Engine/Core/Collections/CollectionPoolCache.h"
#include "Engine/Debug/Exceptions/JsonParseException.h"
#include "Engine/Graphics/RenderTask.h"
#include "Engine/Graphics/RenderView.h"
#include "Engine/Physics/Physics.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Scripting/Scripting.h"
#include "Engine/Serialization/ISerializeModifier.h"
#include "Engine/Serialization/Serialization.h"
#include "Engine/Serialization/JsonWriters.h"
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Serialization/MemoryWriteStream.h"
#if USE_EDITOR
#include "Editor/Editor.h"
#define CHECK_EXECUTE_IN_EDITOR if (Editor::IsPlayMode || script->_executeInEditor)
#else
#define CHECK_EXECUTE_IN_EDITOR
#endif
#define ACTOR_ORIENTATION_EPSILON 0.000000001f
namespace
{
Actor* GetChildByPrefabObjectId(Actor* a, const Guid& prefabObjectId)
{
Actor* result = nullptr;
for (int32 i = 0; i < a->Children.Count(); i++)
{
if (a->Children[i]->GetPrefabObjectID() == prefabObjectId)
{
result = a->Children[i];
break;
}
}
return result;
}
Script* GetScriptByPrefabObjectId(Actor* a, const Guid& prefabObjectId)
{
Script* result = nullptr;
for (int32 i = 0; i < a->Scripts.Count(); i++)
{
if (a->Scripts[i]->GetPrefabObjectID() == prefabObjectId)
{
result = a->Scripts[i];
break;
}
}
return result;
}
}
Actor::Actor(const SpawnParams& params)
: SceneObject(params)
, _isActive(true)
, _isActiveInHierarchy(true)
, _isPrefabRoot(false)
, _isEnabled(false)
, _layer(0)
, _staticFlags(StaticFlags::FullyStatic)
, _localTransform(Transform::Identity)
, _transform(Transform::Identity)
, _sphere(BoundingSphere::Empty)
, _box(BoundingBox::Zero)
, _scene(nullptr)
, _physicsScene(nullptr)
, HideFlags(HideFlags::None)
{
_drawNoCulling = 0;
_drawCategory = 0;
}
SceneRendering* Actor::GetSceneRendering() const
{
return &GetScene()->Rendering;
}
void Actor::SetSceneInHierarchy(Scene* scene)
{
_scene = scene;
for (int32 i = 0; i < Children.Count(); i++)
{
Children[i]->SetSceneInHierarchy(scene);
}
}
void Actor::OnEnableInHierarchy()
{
if (IsActiveInHierarchy() && GetScene() && !_isEnabled)
{
OnEnable();
for (int32 i = 0; i < Children.Count(); i++)
{
Children[i]->OnEnableInHierarchy();
}
}
}
void Actor::OnDisableInHierarchy()
{
if (IsActiveInHierarchy() && GetScene() && _isEnabled)
{
for (int32 i = 0; i < Children.Count(); i++)
{
Children[i]->OnDisableInHierarchy();
}
OnDisable();
}
}
bool Actor::IsSubClassOf(const Actor* object, const MClass* klass)
{
return object->GetClass()->IsSubClassOf(klass);
}
bool Actor::IsSubClassOf(const Script* object, const MClass* klass)
{
return object->GetClass()->IsSubClassOf(klass);
}
String Actor::ToString() const
{
auto& type = GetType();
return String::Format(TEXT("{0} ({1}; {2})"), _name, type.ToString(), _id);
}
void Actor::OnDeleteObject()
{
// Check if actor is still in game (eg. user deletes actor object via Object.Delete)
if (IsDuringPlay())
{
// Check if parent is still during game (eg. user removes child actor but rest is still in game)
const bool isParentInPlay = _parent && _parent->IsDuringPlay();
if (isParentInPlay)
{
// Call event on object removed from the game (only from th top object, don't call it for children of the tree)
Level::callActorEvent(Level::ActorEventType::OnActorDeleted, this, nullptr);
}
// Note: endPlay will remove managed instance
EndPlay();
if (isParentInPlay)
{
// Unlink from the parent
_parent->Children.RemoveKeepOrder(this);
_parent = nullptr;
_scene = nullptr;
}
}
else if (_parent)
{
// Unlink from the parent
_parent->Children.RemoveKeepOrder(this);
_parent = nullptr;
_scene = nullptr;
}
// Ensure to exit gameplay in a valid way
ASSERT(!IsDuringPlay());
#if BUILD_DEBUG || BUILD_DEVELOPMENT
ASSERT(!_isEnabled);
#endif
// Fire event
Deleted(this);
// Delete children
#if BUILD_DEBUG
int32 callsCheck = Children.Count();
#endif
for (int32 i = 0; i < Children.Count(); i++)
{
auto e = Children[i];
ASSERT(e->_parent == this);
e->_parent = nullptr;
e->DeleteObject();
}
#if BUILD_DEBUG
ASSERT(callsCheck == Children.Count());
#endif
Children.Clear();
// Delete scripts
#if BUILD_DEBUG
callsCheck = Scripts.Count();
#endif
for (int32 i = 0; i < Scripts.Count(); i++)
{
auto script = Scripts[i];
ASSERT(script->_parent == this);
if (script->_wasAwakeCalled)
{
script->_wasAwakeCalled = false;
CHECK_EXECUTE_IN_EDITOR
{
script->OnDestroy();
}
}
script->_parent = nullptr;
script->DeleteObject();
}
#if BUILD_DEBUG
ASSERT(callsCheck == Scripts.Count());
#endif
Scripts.Clear();
// Cleanup prefab link
if (_isPrefabRoot)
{
_isPrefabRoot = 0;
#if USE_EDITOR
ScopeLock lock(PrefabManager::PrefabsReferencesLocker);
PrefabManager::PrefabsReferences[_prefabID].Remove(this);
#endif
}
_prefabID = Guid::Empty;
_prefabObjectID = Guid::Empty;
// Base
SceneObject::OnDeleteObject();
}
const Guid& Actor::GetSceneObjectId() const
{
return GetID();
}
void Actor::SetParent(Actor* value, bool worldPositionsStays, bool canBreakPrefabLink)
{
if (_parent == value)
return;
#if USE_EDITOR || !BUILD_RELEASE
if (Is<Scene>())
{
LOG(Error, "Cannot change parent of the Scene. Use Level to manage scenes.");
return;
}
#endif
// Peek the previous state
const Transform prevTransform = _transform;
const bool wasActiveInTree = IsActiveInHierarchy();
const auto prevParent = _parent;
const auto prevScene = _scene;
const auto newScene = value ? value->_scene : nullptr;
// Detect it actor is not in a game but new parent is already in a game (we should spawn it)
const bool isBeingSpawned = !IsDuringPlay() && newScene && value->IsDuringPlay();
// Actors system doesn't support editing scene hierarchy from multiple threads
if (!IsInMainThread() && (IsDuringPlay() || isBeingSpawned))
{
LOG(Error, "Editing scene hierarchy is only allowed on a main thread.");
return;
}
// Handle changing scene (unregister from it)
const bool isSceneChanging = prevScene != newScene;
if (prevScene && isSceneChanging && wasActiveInTree)
{
OnDisableInHierarchy();
}
Level::ScenesLock.Lock();
// Unlink from the old one
if (_parent)
{
_parent->Children.RemoveKeepOrder(this);
}
// Set value
_parent = value;
// Link to the new one
if (_parent)
{
_parent->Children.Add(this);
}
// Sync scene change if need to
if (isSceneChanging)
{
SetSceneInHierarchy(newScene);
}
Level::ScenesLock.Unlock();
// Cache flag
_isActiveInHierarchy = _isActive && (_parent == nullptr || _parent->IsActiveInHierarchy());
// Break prefab link for non-root prefab instance objects
if (HasPrefabLink() && !_isPrefabRoot && IsDuringPlay() && canBreakPrefabLink)
{
BreakPrefabLink();
}
// Update the transform
if (worldPositionsStays)
{
if (_parent)
_parent->_transform.WorldToLocal(prevTransform, _localTransform);
else
_localTransform = prevTransform;
}
// Fire events
OnParentChanged();
if (wasActiveInTree != IsActiveInHierarchy())
OnActiveInTreeChanged();
OnTransformChanged();
if (newScene && isSceneChanging && !isBeingSpawned && IsActiveInHierarchy())
{
// Handle scene changing c.d. (register to the new one)
OnEnableInHierarchy();
}
//if (_isDuringPlay)
if (!isBeingSpawned)
Level::callActorEvent(Level::ActorEventType::OnActorParentChanged, this, prevParent);
// Spawn
if (isBeingSpawned)
{
ASSERT(_parent != nullptr && GetScene() != nullptr);
// Fire events
InitializeHierarchy();
{
SceneBeginData beginData;
BeginPlay(&beginData);
beginData.OnDone();
}
Level::callActorEvent(Level::ActorEventType::OnActorSpawned, this, nullptr);
}
}
void Actor::SetParent(Actor* value, bool canBreakPrefabLink)
{
SetParent(value, false, canBreakPrefabLink);
}
int32 Actor::GetOrderInParent() const
{
return _parent ? _parent->Children.Find((Actor*)this) : INVALID_INDEX;
}
void Actor::SetOrderInParent(int32 index)
{
if (!_parent)
return;
// Cache data
auto& parentChildren = _parent->Children;
const int32 currentIndex = parentChildren.Find(this);
ASSERT(currentIndex != INVALID_INDEX);
// Check if index will change
if (currentIndex != index)
{
parentChildren.RemoveAtKeepOrder(currentIndex);
// Check if index is invalid
if (index < 0 || index >= parentChildren.Count())
{
// Append at the end
parentChildren.Add(this);
}
else
{
// Change order
parentChildren.Insert(index, this);
}
// Fire event
OnOrderInParentChanged();
}
}
Actor* Actor::GetChild(int32 index) const
{
CHECK_RETURN(index >= 0 && index < Children.Count(), nullptr);
return Children[index];
}
Actor* Actor::GetChild(const StringView& name) const
{
for (int32 i = 0; i < Children.Count(); i++)
{
auto e = Children.Get()[i];
if (e->GetName() == name)
return e;
}
return nullptr;
}
Actor* Actor::GetChild(const MClass* type) const
{
CHECK_RETURN(type, nullptr);
for (auto child : Children)
{
if (child->GetClass()->IsSubClassOf(type))
return child;
}
return nullptr;
}
Array<Actor*> Actor::GetChildren(const MClass* type) const
{
Array<Actor*> result;
for (auto child : Children)
if (child->GetClass()->IsSubClassOf(type))
result.Add(child);
return result;
}
void Actor::DestroyChildren(float timeLeft)
{
PROFILE_CPU();
Array<Actor*> children = Children;
const bool useGameTime = timeLeft > ZeroTolerance;
for (Actor* child : children)
{
child->SetParent(nullptr, false, false);
child->DeleteObject(timeLeft, useGameTime);
}
}
const String& Actor::GetLayerName() const
{
return Level::Layers[_layer];
}
void Actor::SetLayerName(const StringView& value)
{
for (int32 i = 0; i < 32; i++)
{
if (Level::Layers[i] == value)
{
SetLayer(i);
return;
}
}
LOG(Warning, "Unknown layer name '{0}'", value);
}
void Actor::SetLayerNameRecursive(const StringView& value)
{
for (int32 i = 0; i < 32; i++)
{
if (Level::Layers[i] == value)
{
SetLayerRecursive(i);
return;
}
}
LOG(Warning, "Unknown layer name '{0}'", value);
}
bool Actor::HasTag() const
{
return Tags.Count() != 0;
}
bool Actor::HasTag(const Tag& tag) const
{
return Tags.Contains(tag);
}
bool Actor::HasTag(const StringView& tag) const
{
return Tags.Contains(tag);
}
void Actor::AddTag(const Tag& tag)
{
Tags.AddUnique(tag);
}
void Actor::AddTagRecursive(const Tag& tag)
{
for (const auto& child : Children)
child->AddTagRecursive(tag);
Tags.AddUnique(tag);
}
void Actor::RemoveTag(const Tag& tag)
{
Tags.Remove(tag);
}
PRAGMA_DISABLE_DEPRECATION_WARNINGS
const String& Actor::GetTag() const
{
return Tags.Count() != 0 ? Tags[0].ToString() : String::Empty;
}
void Actor::SetTag(const StringView& value)
{
const Tag tag = Tags::Get(value);
Tags.Set(&tag, 1);
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS
void Actor::SetLayer(int32 layerIndex)
{
layerIndex = Math::Clamp(layerIndex, 0, 31);
if (layerIndex == _layer)
return;
_layer = layerIndex;
OnLayerChanged();
}
void Actor::SetLayerRecursive(int32 layerIndex)
{
layerIndex = Math::Clamp(layerIndex, 0, 31);
for (const auto& child : Children)
child->SetLayerRecursive(layerIndex);
if (layerIndex == _layer)
return;
_layer = layerIndex;
OnLayerChanged();
}
void Actor::SetName(const StringView& value)
{
if (_name == value)
return;
_name = value;
if (GetScene())
Level::callActorEvent(Level::ActorEventType::OnActorNameChanged, this, nullptr);
}
Script* Actor::GetScript(int32 index) const
{
CHECK_RETURN(index >= 0 && index < Scripts.Count(), nullptr);
return Scripts[index];
}
Script* Actor::GetScript(const MClass* type) const
{
CHECK_RETURN(type, nullptr);
if (type->IsInterface())
{
for (auto script : Scripts)
{
if (script->GetClass()->HasInterface(type))
return script;
}
}
else
{
for (auto script : Scripts)
{
if (script->GetClass()->IsSubClassOf(type))
return script;
}
}
return nullptr;
}
Array<Script*> Actor::GetScripts(const MClass* type) const
{
Array<Script*> result;
if (type->IsInterface())
{
for (auto script : Scripts)
if (script->GetClass()->HasInterface(type))
result.Add(script);
}
else
{
for (auto script : Scripts)
if (script->GetClass()->IsSubClassOf(type))
result.Add(script);
}
return result;
}
void Actor::SetIsActive(bool value)
{
if (value != GetIsActive())
{
_isActive = value;
OnActiveChanged();
}
}
void Actor::SetStaticFlags(StaticFlags value)
{
if (_staticFlags == value)
return;
_staticFlags = value;
OnStaticFlagsChanged();
}
void Actor::SetTransform(const Transform& value)
{
CHECK(!value.IsNanOrInfinity());
if (!(Vector3::NearEqual(_transform.Translation, value.Translation) && Quaternion::NearEqual(_transform.Orientation, value.Orientation, ACTOR_ORIENTATION_EPSILON) && Float3::NearEqual(_transform.Scale, value.Scale)))
{
if (_parent)
_parent->_transform.WorldToLocal(value, _localTransform);
else
_localTransform = value;
OnTransformChanged();
}
}
void Actor::SetPosition(const Vector3& value)
{
CHECK(!value.IsNanOrInfinity());
if (!Vector3::NearEqual(_transform.Translation, value))
{
if (_parent)
_localTransform.Translation = _parent->_transform.WorldToLocal(value);
else
_localTransform.Translation = value;
OnTransformChanged();
}
}
void Actor::SetOrientation(const Quaternion& value)
{
CHECK(!value.IsNanOrInfinity());
if (!Quaternion::NearEqual(_transform.Orientation, value, ACTOR_ORIENTATION_EPSILON))
{
if (_parent)
_parent->_transform.WorldToLocal(value, _localTransform.Orientation);
else
_localTransform.Orientation = value;
OnTransformChanged();
}
}
void Actor::SetScale(const Float3& value)
{
CHECK(!value.IsNanOrInfinity());
if (!Float3::NearEqual(_transform.Scale, value))
{
if (_parent)
Float3::Divide(value, _parent->_transform.Scale, _localTransform.Scale);
else
_localTransform.Scale = value;
OnTransformChanged();
}
}
Matrix Actor::GetRotation() const
{
Matrix result;
Matrix::RotationQuaternion(_transform.Orientation, result);
return result;
}
void Actor::SetRotation(const Matrix& value)
{
Quaternion orientation;
Quaternion::RotationMatrix(value, orientation);
SetOrientation(orientation);
}
void Actor::SetDirection(const Float3& value)
{
CHECK(!value.IsNanOrInfinity());
Quaternion orientation;
if (Float3::Dot(value, Float3::Up) >= 0.999f)
{
Quaternion::RotationAxis(Float3::Left, PI_HALF, orientation);
}
else
{
const Float3 right = Float3::Cross(value, Float3::Up);
const Float3 up = Float3::Cross(right, value);
Quaternion::LookRotation(value, up, orientation);
}
SetOrientation(orientation);
}
void Actor::ResetLocalTransform()
{
SetLocalTransform(Transform::Identity);
}
void Actor::SetLocalTransform(const Transform& value)
{
CHECK(!value.IsNanOrInfinity());
if (!(Vector3::NearEqual(_localTransform.Translation, value.Translation) && Quaternion::NearEqual(_localTransform.Orientation, value.Orientation, ACTOR_ORIENTATION_EPSILON) && Float3::NearEqual(_localTransform.Scale, value.Scale)))
{
_localTransform = value;
OnTransformChanged();
}
}
void Actor::SetLocalPosition(const Vector3& value)
{
CHECK(!value.IsNanOrInfinity());
if (!Vector3::NearEqual(_localTransform.Translation, value))
{
_localTransform.Translation = value;
OnTransformChanged();
}
}
void Actor::SetLocalOrientation(const Quaternion& value)
{
CHECK(!value.IsNanOrInfinity());
Quaternion v = value;
v.Normalize();
if (!Quaternion::NearEqual(_localTransform.Orientation, v, ACTOR_ORIENTATION_EPSILON))
{
_localTransform.Orientation = v;
OnTransformChanged();
}
}
void Actor::SetLocalScale(const Float3& value)
{
CHECK(!value.IsNanOrInfinity());
if (!Float3::NearEqual(_localTransform.Scale, value))
{
_localTransform.Scale = value;
OnTransformChanged();
}
}
void Actor::AddMovement(const Vector3& translation, const Quaternion& rotation)
{
Transform t;
t.Translation = _transform.Translation + translation;
t.Orientation = _transform.Orientation * rotation;
t.Scale = _transform.Scale;
SetTransform(t);
}
void Actor::GetWorldToLocalMatrix(Matrix& worldToLocal) const
{
GetLocalToWorldMatrix(worldToLocal);
worldToLocal.Invert();
}
void Actor::GetLocalToWorldMatrix(Matrix& localToWorld) const
{
#if 0
_transform.GetWorld(localToWorld);
#else
_localTransform.GetWorld(localToWorld);
if (_parent)
{
Matrix parentToWorld;
_parent->GetLocalToWorldMatrix(parentToWorld);
localToWorld = localToWorld * parentToWorld;
}
#endif
}
void Actor::LinkPrefab(const Guid& prefabId, const Guid& prefabObjectId)
{
ASSERT(prefabId.IsValid());
#if USE_EDITOR
if (_isPrefabRoot)
{
ScopeLock lock(PrefabManager::PrefabsReferencesLocker);
PrefabManager::PrefabsReferences[_prefabID].Remove(this);
}
#endif
// Link
_prefabID = prefabId;
_prefabObjectID = prefabObjectId;
_isPrefabRoot = 0;
if (_prefabID.IsValid() && _prefabObjectID.IsValid())
{
auto prefab = Content::LoadAsync<Prefab>(_prefabID);
if (prefab == nullptr || prefab->WaitForLoaded())
{
_prefabID = Guid::Empty;
_prefabObjectID = Guid::Empty;
LOG(Warning, "Failed to load prefab linked to the actor.");
}
else if (prefab->GetRootObjectId() == _prefabObjectID)
{
_isPrefabRoot = 1;
#if USE_EDITOR
ScopeLock lock(PrefabManager::PrefabsReferencesLocker);
PrefabManager::PrefabsReferences[_prefabID].Add(this);
#endif
}
}
}
void Actor::BreakPrefabLink()
{
#if USE_EDITOR
if (_isPrefabRoot)
{
ScopeLock lock(PrefabManager::PrefabsReferencesLocker);
PrefabManager::PrefabsReferences[_prefabID].Remove(this);
}
#endif
// Invalidate link
_prefabID = Guid::Empty;
_prefabObjectID = Guid::Empty;
_isPrefabRoot = 0;
// Do for scripts
for (int32 i = 0; i < Scripts.Count(); i++)
{
Scripts[i]->BreakPrefabLink();
}
// Do for children
for (int32 i = 0; i < Children.Count(); i++)
{
Children[i]->BreakPrefabLink();
}
}
void Actor::Initialize()
{
#if ENABLE_ASSERTION
CHECK(!IsDuringPlay());
#endif
// Cache
if (_parent)
_scene = _parent->GetScene();
_isActiveInHierarchy = _isActive && (_parent == nullptr || _parent->IsActiveInHierarchy());
// Use lazy creation for the managed instance, just register the object
if (!IsRegistered())
RegisterObject();
}
void Actor::BeginPlay(SceneBeginData* data)
{
#if ENABLE_ASSERTION
CHECK(!IsDuringPlay());
#endif
// Set flag
Flags |= ObjectFlags::IsDuringPlay;
OnBeginPlay();
// Update scripts
for (int32 i = 0; i < Scripts.Count(); i++)
{
auto e = Scripts.Get()[i];
if (!e->IsDuringPlay())
e->BeginPlay(data);
}
// Update children
for (int32 i = 0; i < Children.Count(); i++)
{
auto e = Children.Get()[i];
if (!e->IsDuringPlay())
e->BeginPlay(data);
}
// Fire events for scripting
if (IsActiveInHierarchy() && GetScene() && !_isEnabled)
{
OnEnable();
}
}
void Actor::EndPlay()
{
#if ENABLE_ASSERTION
CHECK(IsDuringPlay());
#endif
// Fire event for scripting
if (IsActiveInHierarchy() && GetScene())
{
ASSERT(GetScene());
OnDisable();
}
for (auto* script : Scripts)
{
if (script->_wasAwakeCalled)
{
script->_wasAwakeCalled = false;
CHECK_EXECUTE_IN_EDITOR
{
script->OnDestroy();
}
}
}
OnEndPlay();
// Clear flag
Flags &= ~ObjectFlags::IsDuringPlay;
// Call event deeper
for (int32 i = 0; i < Children.Count(); i++)
{
auto e = Children.Get()[i];
if (e->IsDuringPlay())
e->EndPlay();
}
// Inform attached scripts
for (int32 i = 0; i < Scripts.Count(); i++)
{
auto e = Scripts.Get()[i];
if (e->IsDuringPlay())
e->EndPlay();
}
// Cleanup managed object
//DestroyManaged();
if (IsRegistered())
UnregisterObject();
}
void Actor::Serialize(SerializeStream& stream, const void* otherObj)
{
// Base
SceneObject::Serialize(stream, otherObj);
SERIALIZE_GET_OTHER_OBJ(Actor);
const bool isPrefabDiff = other && HasPrefabLink();
SERIALIZE_BIT_MEMBER(IsActive, _isActive);
SERIALIZE_MEMBER(Name, _name);
SERIALIZE_MEMBER(Transform, _localTransform);
SERIALIZE_MEMBER(StaticFlags, _staticFlags);
SERIALIZE(HideFlags);
SERIALIZE_MEMBER(Layer, _layer);
if (!other || Tags != other->Tags)
{
if (Tags.Count() == 1)
{
stream.JKEY("Tag");
stream.String(Tags.Get()->ToString());
}
else
{
stream.JKEY("Tags");
stream.StartArray();
for (auto& tag : Tags)
stream.String(tag.ToString());
stream.EndArray();
}
}
if (isPrefabDiff)
{
// Prefab object instance may have removed child objects (actors/scripts)
// Scene deserialization by default adds missing objects to synchronize changes applied to prefab but not applied to scene
// In order to handle removed objects per instance we need to save the ids of the prefab object ids that are not used by this object
bool hasRemovedObjects = false;
for (int32 i = 0; i < other->Children.Count(); i++)
{
const Guid prefabObjectId = other->Children[i]->GetPrefabObjectID();
if (GetChildByPrefabObjectId(this, prefabObjectId) == nullptr)
{
if (!hasRemovedObjects)
{
hasRemovedObjects = true;
stream.JKEY("RemovedObjects");
stream.StartArray();
}
stream.Guid(prefabObjectId);
}
}
for (int32 i = 0; i < other->Scripts.Count(); i++)
{
const Guid prefabObjectId = other->Scripts[i]->GetPrefabObjectID();
if (GetScriptByPrefabObjectId(this, prefabObjectId) == nullptr)
{
if (!hasRemovedObjects)
{
hasRemovedObjects = true;
stream.JKEY("RemovedObjects");
stream.StartArray();
}
stream.Guid(prefabObjectId);
}
}
if (hasRemovedObjects)
{
stream.EndArray();
}
}
}
void Actor::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
{
// Base
SceneObject::Deserialize(stream, modifier);
DESERIALIZE_BIT_MEMBER(IsActive, _isActive);
DESERIALIZE_MEMBER(StaticFlags, _staticFlags);
DESERIALIZE(HideFlags);
DESERIALIZE_MEMBER(Layer, _layer);
DESERIALIZE_MEMBER(Name, _name);
DESERIALIZE_MEMBER(Transform, _localTransform);
{
const auto member = SERIALIZE_FIND_MEMBER(stream, "ParentID");
if (member != stream.MemberEnd())
{
Guid parentId;
Serialization::Deserialize(member->value, parentId, modifier);
const auto parent = Scripting::FindObject<Actor>(parentId);
if (_parent != parent)
{
if (IsDuringPlay())
{
SetParent(parent, false, false);
}
else
{
if (_parent)
_parent->Children.RemoveKeepOrder(this);
_parent = parent;
if (_parent)
_parent->Children.Add(this);
OnParentChanged();
}
}
else if (!parent && parentId.IsValid())
{
if (_prefabObjectID.IsValid())
LOG(Warning, "Missing parent actor {0} for \'{1}\', prefab object {2}", parentId, ToString(), _prefabObjectID);
else
LOG(Warning, "Missing parent actor {0} for \'{1}\'", parentId, ToString());
}
}
}
// StaticFlags update - added StaticFlags::Navigation
// [Deprecated on 17.05.2020, expires on 17.05.2021]
if (modifier->EngineBuild < 6178 && (int32)_staticFlags == (1 + 2 + 4))
_staticFlags |= StaticFlags::Navigation;
// StaticFlags update - added StaticFlags::Shadow
// [Deprecated on 17.05.2020, expires on 17.05.2021]
if (modifier->EngineBuild < 6601 && (int32)_staticFlags == (1 + 2 + 4 + 8))
_staticFlags |= StaticFlags::Shadow;
const auto tag = stream.FindMember("Tag");
if (tag != stream.MemberEnd())
{
if (tag->value.IsString() && tag->value.GetStringLength())
{
Tags.Clear();
Tags.Add(Tags::Get(tag->value.GetText()));
}
}
else
{
const auto tags = stream.FindMember("Tags");
if (tags != stream.MemberEnd() && tags->value.IsArray())
{
Tags.Clear();
for (rapidjson::SizeType i = 0; i < tags->value.Size(); i++)
{
auto& e = tags->value[i];
if (e.IsString() && e.GetStringLength())
Tags.Add(Tags::Get(e.GetText()));
}
}
}
{
const auto member = stream.FindMember("PrefabID");
if (member != stream.MemberEnd())
{
#if USE_EDITOR
if (_isPrefabRoot)
{
ScopeLock lock(PrefabManager::PrefabsReferencesLocker);
PrefabManager::PrefabsReferences[_prefabID].Remove(this);
}
#endif
Serialization::Deserialize(member->value, _prefabID, modifier);
_isPrefabRoot = 0;
auto prefab = Content::LoadAsync<Prefab>(_prefabID);
if (prefab == nullptr || prefab->WaitForLoaded())
{
_prefabID = Guid::Empty;
_prefabObjectID = Guid::Empty;
LOG(Warning, "Failed to load prefab linked to the actor on load.");
}
else if (prefab->GetRootObjectId() == _prefabObjectID)
{
_isPrefabRoot = 1;
#if USE_EDITOR
ScopeLock lock(PrefabManager::PrefabsReferencesLocker);
PrefabManager::PrefabsReferences[_prefabID].Add(this);
#endif
}
}
}
}
void Actor::OnEnable()
{
#if ENABLE_ASSERTION
CHECK(!_isEnabled);
#endif
_isEnabled = true;
for (int32 i = 0; i < Scripts.Count(); i++)
{
auto script = Scripts[i];
if (script->GetEnabled() && !script->_wasStartCalled)
script->Start();
}
for (int32 i = 0; i < Scripts.Count(); i++)
{
auto script = Scripts[i];
if (script->GetEnabled() && !script->_wasEnableCalled)
script->Enable();
}
}
void Actor::OnDisable()
{
#if ENABLE_ASSERTION
CHECK(_isEnabled);
#endif
_isEnabled = false;
for (int32 i = Scripts.Count() - 1; i >= 0; i--)
{
auto script = Scripts[i];
if (script->GetEnabled() && script->_wasEnableCalled)
script->Disable();
}
}
void Actor::OnParentChanged()
{
}
void Actor::OnTransformChanged()
{
ASSERT_LOW_LAYER(!_localTransform.IsNanOrInfinity());
if (_parent)
{
_parent->_transform.LocalToWorld(_localTransform, _transform);
}
else
{
_transform = _localTransform;
}
for (auto child : Children)
{
child->OnTransformChanged();
}
}
void Actor::OnActiveChanged()
{
const bool wasActiveInTree = IsActiveInHierarchy();
_isActiveInHierarchy = _isActive && (_parent == nullptr || _parent->IsActiveInHierarchy());
if (wasActiveInTree != IsActiveInHierarchy())
OnActiveInTreeChanged();
//if (GetScene())
Level::callActorEvent(Level::ActorEventType::OnActorActiveChanged, this, nullptr);
}
void Actor::OnActiveInTreeChanged()
{
if (IsDuringPlay() && GetScene())
{
if (IsActiveInHierarchy())
{
if (!_isEnabled)
OnEnable();
}
else if (_isEnabled)
{
OnDisable();
}
}
for (auto child : Children)
{
if (child->GetIsActive() && child->_isActiveInHierarchy != _isActiveInHierarchy)
{
child->_isActiveInHierarchy = _isActiveInHierarchy;
child->OnActiveInTreeChanged();
}
}
}
void Actor::OnOrderInParentChanged()
{
//if (GetScene())
Level::callActorEvent(Level::ActorEventType::OnActorOrderInParentChanged, this, nullptr);
}
void Actor::OnStaticFlagsChanged()
{
}
void Actor::OnLayerChanged()
{
}
BoundingBox Actor::GetBoxWithChildren() const
{
BoundingBox result = GetBox();
for (int32 i = 0; i < Children.Count(); i++)
{
BoundingBox::Merge(result, Children[i]->GetBoxWithChildren(), result);
}
return result;
}
#if USE_EDITOR
BoundingBox Actor::GetEditorBox() const
{
return GetBox();
}
BoundingBox Actor::GetEditorBoxChildren() const
{
BoundingBox result = GetEditorBox();
for (int32 i = 0; i < Children.Count(); i++)
{
BoundingBox::Merge(result, Children[i]->GetEditorBoxChildren(), result);
}
return result;
}
#endif
bool Actor::HasContentLoaded() const
{
return true;
}
void Actor::UnregisterObjectHierarchy()
{
if (IsRegistered())
UnregisterObject();
for (int32 i = 0; i < Scripts.Count(); i++)
{
if (Scripts[i]->IsRegistered())
Scripts[i]->UnregisterObject();
}
for (int32 i = 0; i < Children.Count(); i++)
{
Children[i]->UnregisterObjectHierarchy();
}
}
void Actor::InitializeHierarchy()
{
Initialize();
for (int32 i = 0; i < Scripts.Count(); i++)
Scripts[i]->Initialize();
for (int32 i = 0; i < Children.Count(); i++)
Children[i]->InitializeHierarchy();
}
void Actor::Draw(RenderContext& renderContext)
{
}
void Actor::Draw(RenderContextBatch& renderContextBatch)
{
// Default impl calls single-context
for (RenderContext& renderContext : renderContextBatch.Contexts)
Draw(renderContext);
}
#if USE_EDITOR
void Actor::OnDebugDraw()
{
for (auto* script : Scripts)
if (script->GetEnabled())
script->OnDebugDraw();
}
void Actor::OnDebugDrawSelected()
{
for (auto* script : Scripts)
if (script->GetEnabled())
script->OnDebugDrawSelected();
}
#endif
void Actor::ChangeScriptOrder(Script* script, int32 newIndex)
{
int32 oldIndex = Scripts.Find(script);
ASSERT(oldIndex != INVALID_INDEX);
if (oldIndex == newIndex)
return;
Scripts.RemoveAtKeepOrder(oldIndex);
// Check if index is invalid
if (newIndex < 0 || newIndex >= Scripts.Count())
{
// Append at the end
Scripts.Add(script);
}
else
{
// Change order
Scripts.Insert(newIndex, script);
}
}
Script* Actor::GetScriptByID(const Guid& id) const
{
Script* result = nullptr;
for (int32 i = 0; i < Scripts.Count(); i++)
{
if (Scripts[i]->GetID() == id)
{
result = Scripts[i];
break;
}
}
return result;
}
bool Actor::IsPrefabRoot() const
{
return _isPrefabRoot != 0;
}
Actor* Actor::GetPrefabRoot()
{
if (!HasPrefabLink())
return nullptr;
Actor* result = this;
while (result && !result->IsPrefabRoot())
result = result->GetParent();
return result;
}
Actor* Actor::FindActor(const StringView& name) const
{
Actor* result = nullptr;
if (_name == name)
{
result = const_cast<Actor*>(this);
}
else
{
for (int32 i = 0; i < Children.Count(); i++)
{
result = Children[i]->FindActor(name);
if (result)
break;
}
}
return result;
}
Actor* Actor::FindActor(const MClass* type, bool activeOnly) const
{
CHECK_RETURN(type, nullptr);
if (activeOnly && !_isActive)
return nullptr;
if (GetClass()->IsSubClassOf(type))
return const_cast<Actor*>(this);
for (auto child : Children)
{
const auto actor = child->FindActor(type, activeOnly);
if (actor)
return actor;
}
return nullptr;
}
Actor* Actor::FindActor(const MClass* type, const StringView& name) const
{
CHECK_RETURN(type, nullptr);
if (GetClass()->IsSubClassOf(type) && _name == name)
return const_cast<Actor*>(this);
for (auto child : Children)
{
const auto actor = child->FindActor(type, name);
if (actor)
return actor;
}
return nullptr;
}
Actor* Actor::FindActor(const MClass* type, const Tag& tag, bool activeOnly) const
{
CHECK_RETURN(type, nullptr);
if (activeOnly && !_isActive)
return nullptr;
if (GetClass()->IsSubClassOf(type) && HasTag(tag))
return const_cast<Actor*>(this);
for (auto child : Children)
{
const auto actor = child->FindActor(type, tag, activeOnly);
if (actor)
return actor;
}
return nullptr;
}
Script* Actor::FindScript(const MClass* type) const
{
CHECK_RETURN(type, nullptr);
for (auto script : Scripts)
{
if (script->GetClass()->IsSubClassOf(type) || script->GetClass()->HasInterface(type))
return script;
}
for (auto child : Children)
{
const auto script = child->FindScript(type);
if (script)
return script;
}
return nullptr;
}
bool Actor::HasActorInHierarchy(Actor* a) const
{
bool result = false;
if (Children.Contains(a))
{
result = true;
}
else
{
for (int32 i = 0; i < Children.Count(); i++)
{
result = Children[i]->HasActorInHierarchy(a);
if (result)
break;
}
}
return result;
}
bool Actor::HasActorInChildren(Actor* a) const
{
return Children.Contains(a);
}
bool Actor::IntersectsItself(const Ray& ray, Real& distance, Vector3& normal)
{
return _box.Intersects(ray, distance, normal);
}
Actor* Actor::Intersects(const Ray& ray, Real& distance, Vector3& normal)
{
if (!_isActive)
return nullptr;
// Check itself
bool result = IntersectsItself(ray, distance, normal);
Actor* minTarget = result ? (Actor*)this : nullptr;
Real minDistance = result ? distance : MAX_Real;
Vector3 minDistanceNormal = result ? normal : Vector3::Up;
// Check all children
for (int32 i = 0; i < Children.Count(); i++)
{
const auto target = Children[i]->Intersects(ray, distance, normal);
if (target && minDistance > distance)
{
minDistanceNormal = normal;
minDistance = distance;
minTarget = target;
result = true;
}
}
distance = minDistance;
normal = minDistanceNormal;
return minTarget;
}
void Actor::LookAt(const Vector3& worldPos)
{
const Quaternion orientation = LookingAt(worldPos);
SetOrientation(orientation);
}
void Actor::LookAt(const Vector3& worldPos, const Vector3& worldUp)
{
const Quaternion orientation = LookingAt(worldPos, worldUp);
SetOrientation(orientation);
}
Quaternion Actor::LookingAt(const Vector3& worldPos) const
{
const Vector3 direction = worldPos - _transform.Translation;
if (direction.LengthSquared() < ZeroTolerance)
return _parent->GetOrientation();
const Float3 newForward = Vector3::Normalize(direction);
const Float3 oldForward = _transform.Orientation * Vector3::Forward;
Quaternion orientation;
if ((newForward + oldForward).LengthSquared() < 0.00005f)
{
// 180 degree turn (infinite possible rotation axes)
// Default to yaw i.e. use current Up
orientation = Quaternion(-_transform.Orientation.Y, -_transform.Orientation.Z, _transform.Orientation.W, _transform.Orientation.X);
}
else
{
// Derive shortest arc to new direction
Quaternion rotQuat;
Quaternion::GetRotationFromTo(oldForward, newForward, rotQuat, Float3::Zero);
orientation = rotQuat * _transform.Orientation;
}
return orientation;
}
Quaternion Actor::LookingAt(const Vector3& worldPos, const Vector3& worldUp) const
{
const Vector3 direction = worldPos - _transform.Translation;
if (direction.LengthSquared() < ZeroTolerance)
return _parent->GetOrientation();
const Float3 forward = Vector3::Normalize(direction);
const Float3 up = Vector3::Normalize(worldUp);
if (Math::IsOne(Float3::Dot(forward, up)))
{
return LookingAt(worldPos);
}
Quaternion orientation;
Quaternion::LookRotation(direction, up, orientation);
return orientation;
}
void WriteObjectToBytes(SceneObject* obj, rapidjson_flax::StringBuffer& buffer, MemoryWriteStream& output)
{
// Create JSON
CompactJsonWriter writer(buffer);
writer.SceneObject(obj);
// Write json to output
// TODO: maybe compress json or use binary serialization
output.WriteInt32((int32)buffer.GetSize());
output.WriteBytes((byte*)buffer.GetString(), (int32)buffer.GetSize());
// Store order in parent. Makes life easier for editor to sync objects order on undo/redo actions.
output.WriteInt32(obj->GetOrderInParent());
// Reuse string buffer
buffer.Clear();
}
bool Actor::ToBytes(const Array<Actor*>& actors, MemoryWriteStream& output)
{
PROFILE_CPU();
if (actors.IsEmpty())
{
// Cannot serialize empty list
return true;
}
// Collect object ids that exist in the serialized data to allow references mapping later
Array<Guid> ids(Math::RoundUpToPowerOf2(actors.Count() * 2));
for (int32 i = 0; i < actors.Count(); i++)
{
// By default we collect actors and scripts (they are ManagedObjects recognized by the id)
auto actor = actors[i];
if (!actor)
continue;
ids.Add(actor->GetID());
for (int32 j = 0; j < actor->Scripts.Count(); j++)
{
const auto script = actor->Scripts[j];
ids.Add(script->GetID());
}
}
// Header
output.WriteInt32(FLAXENGINE_VERSION_BUILD);
// Serialized objects ids (for references mapping)
output.WriteArray(ids);
// Objects data
rapidjson_flax::StringBuffer buffer;
for (int32 i = 0; i < actors.Count(); i++)
{
Actor* actor = actors[i];
if (!actor)
continue;
WriteObjectToBytes(actor, buffer, output);
for (int32 j = 0; j < actor->Scripts.Count(); j++)
{
Script* script = actor->Scripts[j];
WriteObjectToBytes(script, buffer, output);
}
}
return false;
}
Array<byte> Actor::ToBytes(const Array<Actor*>& actors)
{
Array<byte> data;
MemoryWriteStream stream(Math::Min(actors.Count() * 256, 10 * 1024 * 1024));
if (!ToBytes(actors, stream))
{
data.Set(stream.GetHandle(), stream.GetPosition());
}
return data;
}
bool Actor::FromBytes(const Span<byte>& data, Array<Actor*>& output, ISerializeModifier* modifier)
{
PROFILE_CPU();
output.Clear();
ASSERT(modifier);
if (data.Length() <= 0)
return true;
MemoryReadStream stream(data.Get(), data.Length());
// Header
int32 engineBuild;
stream.ReadInt32(&engineBuild);
if (engineBuild > FLAXENGINE_VERSION_BUILD || engineBuild < 6165)
{
LOG(Warning, "Unsupported actors data version.");
return true;
}
// Serialized objects ids (for references mapping)
Array<Guid> ids;
stream.ReadArray(&ids);
int32 objectsCount = ids.Count();
if (objectsCount < 0)
return true;
// Prepare
Array<int32> order;
order.Resize(objectsCount);
modifier->EngineBuild = engineBuild;
CollectionPoolCache<ActorsCache::SceneObjectsListType>::ScopeCache sceneObjects = ActorsCache::SceneObjectsListCache.Get();
sceneObjects->Resize(objectsCount);
SceneObjectsFactory::Context context(modifier);
// Deserialize objects
Scripting::ObjectsLookupIdMapping.Set(&modifier->IdsMapping);
auto startPos = stream.GetPosition();
for (int32 i = 0; i < objectsCount; i++)
{
// Buffer
int32 bufferSize;
stream.ReadInt32(&bufferSize);
const char* buffer = (const char*)stream.GetPositionHandle();
stream.Move(bufferSize);
// Order in parent
int32 orderInParent;
stream.ReadInt32(&orderInParent);
order[i] = orderInParent;
// Load JSON
rapidjson_flax::Document document;
{
PROFILE_CPU_NAMED("Json.Parse");
document.Parse(buffer, bufferSize);
}
if (document.HasParseError())
{
Log::JsonParseException(document.GetParseError(), document.GetErrorOffset());
return true;
}
// Create object
auto obj = SceneObjectsFactory::Spawn(context, document);
sceneObjects->At(i) = obj;
if (obj == nullptr)
{
LOG(Warning, "Cannot create object.");
continue;
}
obj->RegisterObject();
// Add to results
Actor* actor = dynamic_cast<Actor*>(obj);
if (actor)
{
output.Add(actor);
}
}
// TODO: optimize this to call json parsing only once per-object instead of twice (spawn + load)
stream.SetPosition(startPos);
for (int32 i = 0; i < objectsCount; i++)
{
// Buffer
int32 bufferSize;
stream.ReadInt32(&bufferSize);
const char* buffer = (const char*)stream.GetPositionHandle();
stream.Move(bufferSize);
// Order in parent
int32 orderInParent;
stream.ReadInt32(&orderInParent);
// Load JSON
rapidjson_flax::Document document;
{
PROFILE_CPU_NAMED("Json.Parse");
document.Parse(buffer, bufferSize);
}
if (document.HasParseError())
{
Log::JsonParseException(document.GetParseError(), document.GetErrorOffset());
return true;
}
// Deserialize object
auto obj = sceneObjects->At(i);
if (obj)
SceneObjectsFactory::Deserialize(context, obj, document);
else
SceneObjectsFactory::HandleObjectDeserializationError(document);
}
Scripting::ObjectsLookupIdMapping.Set(nullptr);
// Update objects order
//for (int32 i = 0; i < objectsCount; i++)
{
//SceneObject* obj = sceneObjects->At(i);
// TODO: remove order from saved data?
//obj->SetOrderInParent(order[i]);
}
// Call events (only for parents because they will propagate events down the tree)
CollectionPoolCache<ActorsCache::ActorsListType>::ScopeCache parents = ActorsCache::ActorsListCache.Get();
parents->EnsureCapacity(output.Count());
Level::ConstructParentActorsTreeList(output, *parents);
for (int32 i = 0; i < parents->Count(); i++)
{
// Break prefab links for actors from prefab but no a root ones (eg. when user duplicates a sub-prefab actor but not a root one)
Actor* actor = parents->At(i);
if (actor->HasPrefabLink() && !actor->IsPrefabRoot())
{
actor->BreakPrefabLink();
}
}
for (int32 i = 0; i < parents->Count(); i++)
{
parents->At(i)->InitializeHierarchy();
}
for (int32 i = 0; i < parents->Count(); i++)
{
Actor* actor = parents->At(i);
actor->OnTransformChanged();
}
// Initialize actor that are spawned to scene or create managed instanced for others
for (int32 i = 0; i < parents->Count(); i++)
{
Actor* actor = parents->At(i);
if (!actor->GetScene())
continue;
// Add to game
SceneBeginData beginData;
actor->BeginPlay(&beginData);
beginData.OnDone();
Level::callActorEvent(Level::ActorEventType::OnActorSpawned, actor, nullptr);
}
return false;
}
Array<Actor*> Actor::FromBytes(const Span<byte>& data)
{
Array<Actor*> output;
auto modifier = Cache::ISerializeModifier.Get();
FromBytes(data, output, modifier.Value);
return output;
}
Array<Actor*> Actor::FromBytes(const Span<byte>& data, const Dictionary<Guid, Guid>& idsMapping)
{
Array<Actor*> output;
auto modifier = Cache::ISerializeModifier.Get();
modifier->IdsMapping = idsMapping;
FromBytes(data, output, modifier.Value);
return output;
}
Array<Guid> Actor::TryGetSerializedObjectsIds(const Span<byte>& data)
{
PROFILE_CPU();
Array<Guid> result;
if (data.Length() > 0)
{
MemoryReadStream stream(data.Get(), data.Length());
// Header
int32 engineBuild;
stream.ReadInt32(&engineBuild);
if (engineBuild <= FLAXENGINE_VERSION_BUILD && engineBuild >= 6165)
{
// Serialized objects ids (for references mapping)
stream.ReadArray(&result);
}
}
return result;
}
String Actor::ToJson()
{
PROFILE_CPU();
rapidjson_flax::StringBuffer buffer;
CompactJsonWriter writer(buffer);
writer.SceneObject(this);
String result;
const char* c = buffer.GetString();
result.SetUTF8(c, (int32)buffer.GetSize());
return result;
}
void Actor::FromJson(const StringAnsiView& json)
{
PROFILE_CPU();
// Load JSON
rapidjson_flax::Document document;
{
PROFILE_CPU_NAMED("Json.Parse");
document.Parse(json.Get(), json.Length());
}
if (document.HasParseError())
{
Log::JsonParseException(document.GetParseError(), document.GetErrorOffset());
return;
}
// Deserialize object
auto modifier = Cache::ISerializeModifier.Get();
Scripting::ObjectsLookupIdMapping.Set(&modifier.Value->IdsMapping);
Deserialize(document, &*modifier);
Scripting::ObjectsLookupIdMapping.Set(nullptr);
OnTransformChanged();
}
void Actor::SetPhysicsScene(PhysicsScene* scene)
{
CHECK(scene);
const auto previous = GetPhysicsScene();
_physicsScene = scene;
if (previous != _physicsScene)
{
OnPhysicsSceneChanged(previous);
// cascade
for (auto child : Children)
child->SetPhysicsScene(scene);
}
}
PhysicsScene* Actor::GetPhysicsScene() const
{
return _physicsScene ? _physicsScene : Physics::DefaultScene;
}