816 lines
25 KiB
C++
816 lines
25 KiB
C++
#include "Engine/EngineEditorServices.h"
|
|
#include "Scene/EngineEditorSceneBackend.h"
|
|
|
|
#include <XCEditor/Foundation/UIEditorRuntimeTrace.h>
|
|
#include <XCEngine/Components/ComponentFactoryRegistry.h>
|
|
#include <XCEngine/Components/GameObject.h>
|
|
#include <XCEngine/Components/TransformComponent.h>
|
|
#include <XCEngine/Core/Asset/ResourceManager.h>
|
|
#include <XCEngine/Rendering/Picking/RenderObjectIdRegistry.h>
|
|
#include <XCEngine/Scene/Scene.h>
|
|
#include <XCEngine/Scene/SceneManager.h>
|
|
|
|
#include <algorithm>
|
|
#include <charconv>
|
|
#include <filesystem>
|
|
#include <optional>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace XCEngine::UI::Editor::App {
|
|
|
|
namespace {
|
|
|
|
using ::XCEngine::Components::Component;
|
|
using ::XCEngine::Components::ComponentFactoryRegistry;
|
|
using ::XCEngine::Components::GameObject;
|
|
using ::XCEngine::Components::Scene;
|
|
using ::XCEngine::Components::SceneManager;
|
|
using ::XCEngine::Components::TransformComponent;
|
|
using ::XCEngine::Math::Quaternion;
|
|
using ::XCEngine::Math::Vector3;
|
|
using ::XCEngine::Rendering::RenderObjectId;
|
|
using ::XCEngine::Rendering::RenderObjectIdRegistry;
|
|
using ::XCEngine::Resources::ResourceManager;
|
|
using ::XCEngine::Resources::Shader;
|
|
|
|
constexpr char kComponentIdSeparator = '#';
|
|
|
|
void TraceSceneStartup(std::string message) {
|
|
::XCEngine::UI::Editor::AppendUIEditorRuntimeTrace("startup", std::move(message));
|
|
}
|
|
|
|
struct ClipboardNode {
|
|
std::string name = {};
|
|
std::string transformPayload = {};
|
|
std::vector<std::pair<std::string, std::string>> components = {};
|
|
std::vector<ClipboardNode> children = {};
|
|
};
|
|
|
|
Scene* ResolvePrimaryScene(SceneManager& sceneManager) {
|
|
if (Scene* activeScene = sceneManager.GetActiveScene();
|
|
activeScene != nullptr) {
|
|
return activeScene;
|
|
}
|
|
|
|
const std::vector<Scene*> scenes = sceneManager.GetAllScenes();
|
|
if (!scenes.empty() && scenes.front() != nullptr) {
|
|
sceneManager.SetActiveScene(scenes.front());
|
|
return scenes.front();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
std::pair<std::string, std::string> SerializeComponent(
|
|
const Component* component) {
|
|
std::ostringstream payload = {};
|
|
component->Serialize(payload);
|
|
return { component->GetName(), payload.str() };
|
|
}
|
|
|
|
ClipboardNode CopyGameObjectRecursive(const GameObject& gameObject) {
|
|
ClipboardNode node = {};
|
|
node.name = gameObject.GetName();
|
|
|
|
if (const TransformComponent* transform = gameObject.GetTransform();
|
|
transform != nullptr) {
|
|
std::ostringstream payload = {};
|
|
transform->Serialize(payload);
|
|
node.transformPayload = payload.str();
|
|
}
|
|
|
|
const auto components = gameObject.GetComponents<Component>();
|
|
for (const Component* component : components) {
|
|
if (component == nullptr || component == gameObject.GetTransform()) {
|
|
continue;
|
|
}
|
|
|
|
node.components.push_back(SerializeComponent(component));
|
|
}
|
|
|
|
for (GameObject* child : gameObject.GetChildren()) {
|
|
if (child == nullptr) {
|
|
continue;
|
|
}
|
|
|
|
node.children.push_back(CopyGameObjectRecursive(*child));
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
GameObject* PasteGameObjectRecursive(
|
|
Scene& scene,
|
|
const ClipboardNode& node,
|
|
GameObject* parent) {
|
|
GameObject* gameObject = scene.CreateGameObject(node.name, parent);
|
|
if (gameObject == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (!node.transformPayload.empty()) {
|
|
if (TransformComponent* transform = gameObject->GetTransform();
|
|
transform != nullptr) {
|
|
std::istringstream transformStream(node.transformPayload);
|
|
transform->Deserialize(transformStream);
|
|
}
|
|
}
|
|
|
|
for (const auto& componentData : node.components) {
|
|
Component* component =
|
|
ComponentFactoryRegistry::Get().CreateComponent(
|
|
gameObject,
|
|
componentData.first);
|
|
if (component == nullptr || componentData.second.empty()) {
|
|
continue;
|
|
}
|
|
|
|
std::istringstream payloadStream(componentData.second);
|
|
component->Deserialize(payloadStream);
|
|
}
|
|
|
|
for (const ClipboardNode& child : node.children) {
|
|
PasteGameObjectRecursive(scene, child, gameObject);
|
|
}
|
|
|
|
return gameObject;
|
|
}
|
|
|
|
bool WouldCreateCycle(
|
|
const GameObject& source,
|
|
const GameObject& targetParent) {
|
|
const GameObject* current = &targetParent;
|
|
while (current != nullptr) {
|
|
if (current == &source) {
|
|
return true;
|
|
}
|
|
current = current->GetParent();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
std::vector<GameObject*> ResolveSiblingSequence(
|
|
Scene& scene,
|
|
GameObject* parent) {
|
|
return parent != nullptr
|
|
? parent->GetChildren()
|
|
: scene.GetRootGameObjects();
|
|
}
|
|
|
|
std::optional<std::size_t> FindSiblingIndex(
|
|
const std::vector<GameObject*>& siblings,
|
|
const GameObject* gameObject) {
|
|
if (gameObject == nullptr) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
const auto it =
|
|
std::find(siblings.begin(), siblings.end(), gameObject);
|
|
if (it == siblings.end()) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
return static_cast<std::size_t>(std::distance(siblings.begin(), it));
|
|
}
|
|
|
|
GameObject* FindGameObjectByItemId(
|
|
SceneManager& sceneManager,
|
|
std::string_view itemId) {
|
|
Scene* scene = ResolvePrimaryScene(sceneManager);
|
|
const std::optional<EditorSceneObjectId> gameObjectId =
|
|
ParseEditorGameObjectItemId(itemId);
|
|
if (scene == nullptr || !gameObjectId.has_value()) {
|
|
return nullptr;
|
|
}
|
|
|
|
return scene->FindByID(gameObjectId.value());
|
|
}
|
|
|
|
EditorSceneHierarchyNode BuildHierarchySnapshotNodeRecursive(
|
|
const GameObject& gameObject) {
|
|
EditorSceneHierarchyNode node = {};
|
|
node.itemId = MakeEditorGameObjectItemId(gameObject.GetID());
|
|
node.displayName = gameObject.GetName().empty()
|
|
? std::string("GameObject")
|
|
: gameObject.GetName();
|
|
node.children.reserve(gameObject.GetChildCount());
|
|
for (std::size_t childIndex = 0u;
|
|
childIndex < gameObject.GetChildCount();
|
|
++childIndex) {
|
|
const GameObject* child = gameObject.GetChild(childIndex);
|
|
if (child == nullptr) {
|
|
continue;
|
|
}
|
|
|
|
node.children.push_back(BuildHierarchySnapshotNodeRecursive(*child));
|
|
}
|
|
return node;
|
|
}
|
|
|
|
std::string BuildEditorComponentId(
|
|
std::string_view typeName,
|
|
std::size_t ordinal) {
|
|
std::string componentId(typeName);
|
|
componentId.push_back(kComponentIdSeparator);
|
|
componentId += std::to_string(ordinal);
|
|
return componentId;
|
|
}
|
|
|
|
bool ParseEditorComponentId(
|
|
std::string_view componentId,
|
|
std::string& outTypeName,
|
|
std::size_t& outOrdinal) {
|
|
const std::size_t separatorIndex = componentId.find(kComponentIdSeparator);
|
|
if (separatorIndex == std::string_view::npos ||
|
|
separatorIndex == 0u ||
|
|
separatorIndex + 1u >= componentId.size()) {
|
|
return false;
|
|
}
|
|
|
|
outTypeName = std::string(componentId.substr(0u, separatorIndex));
|
|
std::size_t ordinal = 0u;
|
|
const std::string_view ordinalText =
|
|
componentId.substr(separatorIndex + 1u);
|
|
const char* first = ordinalText.data();
|
|
const char* last = ordinalText.data() + ordinalText.size();
|
|
const std::from_chars_result result =
|
|
std::from_chars(first, last, ordinal);
|
|
if (result.ec != std::errc() || result.ptr != last) {
|
|
outTypeName.clear();
|
|
return false;
|
|
}
|
|
|
|
outOrdinal = ordinal;
|
|
return true;
|
|
}
|
|
|
|
EditorSceneComponentDescriptor BuildComponentDescriptor(
|
|
const Component& component,
|
|
std::size_t ordinal) {
|
|
EditorSceneComponentDescriptor descriptor = {};
|
|
descriptor.typeName = component.GetName();
|
|
descriptor.componentId =
|
|
BuildEditorComponentId(descriptor.typeName, ordinal);
|
|
descriptor.component = &component;
|
|
descriptor.removable =
|
|
component.GetGameObject() != nullptr &&
|
|
component.GetGameObject()->GetTransform() != &component;
|
|
return descriptor;
|
|
}
|
|
|
|
EditorSceneComponentDescriptor ResolveComponentDescriptor(
|
|
const GameObject& gameObject,
|
|
std::string_view componentId) {
|
|
std::string typeName = {};
|
|
std::size_t ordinal = 0u;
|
|
if (!ParseEditorComponentId(componentId, typeName, ordinal)) {
|
|
return {};
|
|
}
|
|
|
|
std::size_t currentOrdinal = 0u;
|
|
for (const Component* component : gameObject.GetComponents<Component>()) {
|
|
if (component == nullptr || component->GetName() != typeName) {
|
|
continue;
|
|
}
|
|
|
|
if (currentOrdinal == ordinal) {
|
|
return BuildComponentDescriptor(*component, currentOrdinal);
|
|
}
|
|
|
|
++currentOrdinal;
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
bool MoveGameObjectRelativeToTarget(
|
|
SceneManager& sceneManager,
|
|
std::string_view itemId,
|
|
std::string_view targetItemId,
|
|
bool placeAfterTarget) {
|
|
Scene* scene = ResolvePrimaryScene(sceneManager);
|
|
GameObject* gameObject = FindGameObjectByItemId(sceneManager, itemId);
|
|
GameObject* target = FindGameObjectByItemId(sceneManager, targetItemId);
|
|
if (scene == nullptr ||
|
|
gameObject == nullptr ||
|
|
target == nullptr ||
|
|
gameObject == target) {
|
|
return false;
|
|
}
|
|
|
|
GameObject* targetParent = target->GetParent();
|
|
if (targetParent != nullptr &&
|
|
WouldCreateCycle(*gameObject, *targetParent)) {
|
|
return false;
|
|
}
|
|
|
|
const std::vector<GameObject*> siblings =
|
|
ResolveSiblingSequence(*scene, targetParent);
|
|
const std::optional<std::size_t> targetIndex =
|
|
FindSiblingIndex(siblings, target);
|
|
if (!targetIndex.has_value()) {
|
|
return false;
|
|
}
|
|
|
|
std::size_t finalIndex =
|
|
targetIndex.value() + (placeAfterTarget ? 1u : 0u);
|
|
if (gameObject->GetParent() == targetParent) {
|
|
const std::optional<std::size_t> sourceIndex =
|
|
FindSiblingIndex(siblings, gameObject);
|
|
if (sourceIndex.has_value()) {
|
|
if ((!placeAfterTarget && sourceIndex.value() < targetIndex.value()) ||
|
|
(placeAfterTarget && sourceIndex.value() <= targetIndex.value())) {
|
|
--finalIndex;
|
|
}
|
|
|
|
if (finalIndex == sourceIndex.value()) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
gameObject->SetParent(targetParent, finalIndex, true);
|
|
return true;
|
|
}
|
|
|
|
class EngineEditorSceneBackend final : public EditorSceneBackend {
|
|
public:
|
|
EngineEditorSceneBackend(
|
|
SceneManager& sceneManager,
|
|
ResourceManager& resourceManager)
|
|
: m_sceneManager(sceneManager)
|
|
, m_resourceManager(resourceManager) {}
|
|
|
|
EditorStartupSceneResult EnsureStartupScene(
|
|
const std::filesystem::path& projectRoot) override {
|
|
EditorStartupSceneResult result = {};
|
|
TraceSceneStartup("EnsureStartupScene begin projectRoot=" + projectRoot.string());
|
|
if (projectRoot.empty()) {
|
|
return result;
|
|
}
|
|
|
|
m_resourceManager.SetResourceRoot(projectRoot.string().c_str());
|
|
TraceSceneStartup("ResourceManager::SetResourceRoot complete");
|
|
|
|
if (Scene* activeScene = ResolvePrimaryScene(m_sceneManager);
|
|
activeScene != nullptr) {
|
|
result.ready = true;
|
|
result.sceneName = activeScene->GetName();
|
|
TraceSceneStartup("EnsureStartupScene reused active scene=" + result.sceneName);
|
|
return result;
|
|
}
|
|
|
|
const std::filesystem::path startupScenePath =
|
|
(projectRoot / "Assets" / "Scenes" / "Main.xc").lexically_normal();
|
|
if (std::filesystem::exists(startupScenePath) &&
|
|
std::filesystem::is_regular_file(startupScenePath)) {
|
|
TraceSceneStartup(
|
|
"SceneManager::LoadScene begin path=" + startupScenePath.string());
|
|
{
|
|
ResourceManager::ScopedDeferredSceneLoad deferredSceneLoad(
|
|
m_resourceManager);
|
|
m_sceneManager.LoadScene(startupScenePath.string());
|
|
}
|
|
TraceSceneStartup("SceneManager::LoadScene end");
|
|
Scene* loadedScene =
|
|
m_sceneManager.GetScene(startupScenePath.stem().string());
|
|
if (loadedScene == nullptr) {
|
|
loadedScene = ResolvePrimaryScene(m_sceneManager);
|
|
} else {
|
|
m_sceneManager.SetActiveScene(loadedScene);
|
|
}
|
|
|
|
if (loadedScene != nullptr) {
|
|
result.ready = true;
|
|
result.loadedFromDisk = true;
|
|
result.scenePath = startupScenePath;
|
|
result.sceneName = loadedScene->GetName();
|
|
TraceSceneStartup(
|
|
"EnsureStartupScene loaded scene=" + result.sceneName);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
if (Scene* scene = m_sceneManager.CreateScene("Main");
|
|
scene != nullptr) {
|
|
m_sceneManager.SetActiveScene(scene);
|
|
result.ready = true;
|
|
result.sceneName = scene->GetName();
|
|
TraceSceneStartup(
|
|
"EnsureStartupScene created scene=" + result.sceneName);
|
|
}
|
|
|
|
TraceSceneStartup(
|
|
std::string("EnsureStartupScene end ready=") +
|
|
(result.ready ? "1" : "0"));
|
|
return result;
|
|
}
|
|
|
|
Scene* GetActiveScene() const override {
|
|
return ResolvePrimaryScene(m_sceneManager);
|
|
}
|
|
|
|
EditorSceneHierarchySnapshot BuildHierarchySnapshot() const override {
|
|
EditorSceneHierarchySnapshot snapshot = {};
|
|
Scene* scene = ResolvePrimaryScene(m_sceneManager);
|
|
if (scene == nullptr) {
|
|
return snapshot;
|
|
}
|
|
|
|
const std::vector<GameObject*> roots = scene->GetRootGameObjects();
|
|
snapshot.roots.reserve(roots.size());
|
|
for (const GameObject* root : roots) {
|
|
if (root == nullptr) {
|
|
continue;
|
|
}
|
|
|
|
snapshot.roots.push_back(BuildHierarchySnapshotNodeRecursive(*root));
|
|
}
|
|
return snapshot;
|
|
}
|
|
|
|
bool OpenSceneAsset(const std::filesystem::path& scenePath) override {
|
|
if (scenePath.empty()) {
|
|
return false;
|
|
}
|
|
|
|
std::error_code errorCode = {};
|
|
if (!std::filesystem::exists(scenePath, errorCode) ||
|
|
errorCode ||
|
|
!std::filesystem::is_regular_file(scenePath, errorCode)) {
|
|
return false;
|
|
}
|
|
|
|
{
|
|
ResourceManager::ScopedDeferredSceneLoad deferredSceneLoad(
|
|
m_resourceManager);
|
|
m_sceneManager.LoadScene(scenePath.string());
|
|
}
|
|
Scene* loadedScene = m_sceneManager.GetScene(scenePath.stem().string());
|
|
if (loadedScene == nullptr) {
|
|
loadedScene = ResolvePrimaryScene(m_sceneManager);
|
|
} else {
|
|
m_sceneManager.SetActiveScene(loadedScene);
|
|
}
|
|
|
|
return loadedScene != nullptr;
|
|
}
|
|
|
|
GameObject* FindGameObject(std::string_view itemId) const override {
|
|
return FindGameObjectByItemId(m_sceneManager, itemId);
|
|
}
|
|
|
|
bool AddComponent(
|
|
std::string_view itemId,
|
|
std::string_view componentTypeName) override {
|
|
if (componentTypeName.empty()) {
|
|
return false;
|
|
}
|
|
|
|
GameObject* gameObject = FindGameObject(itemId);
|
|
if (gameObject == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
Component* addedComponent =
|
|
ComponentFactoryRegistry::Get().CreateComponent(
|
|
gameObject,
|
|
std::string(componentTypeName));
|
|
return addedComponent != nullptr;
|
|
}
|
|
|
|
std::vector<EditorSceneComponentDescriptor> GetComponents(
|
|
std::string_view itemId) const override {
|
|
std::vector<EditorSceneComponentDescriptor> descriptors = {};
|
|
const GameObject* gameObject = FindGameObject(itemId);
|
|
if (gameObject == nullptr) {
|
|
return descriptors;
|
|
}
|
|
|
|
const std::vector<const Component*> components =
|
|
gameObject->GetComponents<Component>();
|
|
descriptors.reserve(components.size());
|
|
std::unordered_map<std::string, std::size_t> ordinalsByType = {};
|
|
for (const Component* component : components) {
|
|
if (component == nullptr) {
|
|
continue;
|
|
}
|
|
|
|
const std::string typeName = component->GetName();
|
|
const std::size_t ordinal = ordinalsByType[typeName];
|
|
descriptors.push_back(BuildComponentDescriptor(*component, ordinal));
|
|
ordinalsByType[typeName] = ordinal + 1u;
|
|
}
|
|
|
|
return descriptors;
|
|
}
|
|
|
|
bool RemoveComponent(
|
|
std::string_view itemId,
|
|
std::string_view componentId) override {
|
|
GameObject* gameObject = FindGameObject(itemId);
|
|
if (gameObject == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
const EditorSceneComponentDescriptor descriptor =
|
|
ResolveComponentDescriptor(*gameObject, componentId);
|
|
Component* component =
|
|
const_cast<Component*>(descriptor.component);
|
|
if (!descriptor.IsValid() ||
|
|
!descriptor.removable ||
|
|
component == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
return gameObject->RemoveComponent(component);
|
|
}
|
|
|
|
bool SetTransformLocalPosition(
|
|
std::string_view itemId,
|
|
std::string_view componentId,
|
|
const Vector3& position) override {
|
|
TransformComponent* transform =
|
|
ResolveTransformComponent(itemId, componentId);
|
|
if (transform == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
transform->SetLocalPosition(position);
|
|
return true;
|
|
}
|
|
|
|
bool SetTransformLocalEulerAngles(
|
|
std::string_view itemId,
|
|
std::string_view componentId,
|
|
const Vector3& eulerAngles) override {
|
|
TransformComponent* transform =
|
|
ResolveTransformComponent(itemId, componentId);
|
|
if (transform == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
transform->SetLocalEulerAngles(eulerAngles);
|
|
return true;
|
|
}
|
|
|
|
bool SetTransformLocalScale(
|
|
std::string_view itemId,
|
|
std::string_view componentId,
|
|
const Vector3& scale) override {
|
|
TransformComponent* transform =
|
|
ResolveTransformComponent(itemId, componentId);
|
|
if (transform == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
transform->SetLocalScale(scale);
|
|
return true;
|
|
}
|
|
|
|
bool MutateComponent(
|
|
std::string_view itemId,
|
|
std::string_view componentId,
|
|
const std::function<bool(Component&)>& mutation) override {
|
|
if (!mutation) {
|
|
return false;
|
|
}
|
|
|
|
GameObject* gameObject = FindGameObject(itemId);
|
|
if (gameObject == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
const EditorSceneComponentDescriptor descriptor =
|
|
ResolveComponentDescriptor(*gameObject, componentId);
|
|
Component* component =
|
|
const_cast<Component*>(descriptor.component);
|
|
return descriptor.IsValid() &&
|
|
component != nullptr &&
|
|
mutation(*component);
|
|
}
|
|
|
|
bool QueryWorldTransform(
|
|
EditorSceneObjectId objectId,
|
|
Vector3& outPosition,
|
|
Quaternion& outRotation,
|
|
Vector3& outScale) const override {
|
|
Scene* scene = ResolvePrimaryScene(m_sceneManager);
|
|
GameObject* gameObject =
|
|
scene != nullptr ? scene->FindByID(objectId) : nullptr;
|
|
const TransformComponent* transform =
|
|
gameObject != nullptr ? gameObject->GetTransform() : nullptr;
|
|
if (transform == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
outPosition = transform->GetPosition();
|
|
outRotation = transform->GetRotation();
|
|
outScale = transform->GetScale();
|
|
return true;
|
|
}
|
|
|
|
bool SetWorldTransform(
|
|
EditorSceneObjectId objectId,
|
|
const Vector3& position,
|
|
const Quaternion& rotation,
|
|
const Vector3& scale) override {
|
|
TransformComponent* transform = ResolveTransformComponent(objectId);
|
|
if (transform == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
transform->SetPosition(position);
|
|
transform->SetRotation(rotation);
|
|
transform->SetScale(scale);
|
|
return true;
|
|
}
|
|
|
|
bool SetWorldPositionRotation(
|
|
EditorSceneObjectId objectId,
|
|
const Vector3& position,
|
|
const Quaternion& rotation) override {
|
|
TransformComponent* transform = ResolveTransformComponent(objectId);
|
|
if (transform == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
transform->SetPosition(position);
|
|
transform->SetRotation(rotation);
|
|
return true;
|
|
}
|
|
|
|
bool SetObjectLocalScale(
|
|
EditorSceneObjectId objectId,
|
|
const Vector3& localScale) override {
|
|
TransformComponent* transform = ResolveTransformComponent(objectId);
|
|
if (transform == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
transform->SetLocalScale(localScale);
|
|
return true;
|
|
}
|
|
|
|
bool RenameGameObject(
|
|
std::string_view itemId,
|
|
std::string_view newName) override {
|
|
GameObject* gameObject = FindGameObject(itemId);
|
|
if (gameObject == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
gameObject->SetName(std::string(newName));
|
|
return true;
|
|
}
|
|
|
|
bool DeleteGameObject(std::string_view itemId) override {
|
|
Scene* scene = ResolvePrimaryScene(m_sceneManager);
|
|
GameObject* gameObject = FindGameObject(itemId);
|
|
if (scene == nullptr || gameObject == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
scene->DestroyGameObject(gameObject);
|
|
return true;
|
|
}
|
|
|
|
std::string DuplicateGameObject(std::string_view itemId) override {
|
|
Scene* scene = ResolvePrimaryScene(m_sceneManager);
|
|
GameObject* gameObject = FindGameObject(itemId);
|
|
if (scene == nullptr || gameObject == nullptr) {
|
|
return {};
|
|
}
|
|
|
|
const ClipboardNode clipboard = CopyGameObjectRecursive(*gameObject);
|
|
GameObject* duplicate =
|
|
PasteGameObjectRecursive(*scene, clipboard, gameObject->GetParent());
|
|
return duplicate != nullptr
|
|
? MakeEditorGameObjectItemId(duplicate->GetID())
|
|
: std::string();
|
|
}
|
|
|
|
bool ReparentGameObject(
|
|
std::string_view itemId,
|
|
std::string_view parentItemId) override {
|
|
GameObject* gameObject = FindGameObject(itemId);
|
|
GameObject* newParent = FindGameObject(parentItemId);
|
|
if (gameObject == nullptr || newParent == nullptr ||
|
|
gameObject == newParent ||
|
|
gameObject->GetParent() == newParent ||
|
|
WouldCreateCycle(*gameObject, *newParent)) {
|
|
return false;
|
|
}
|
|
|
|
gameObject->SetParent(newParent);
|
|
return true;
|
|
}
|
|
|
|
bool MoveGameObjectBefore(
|
|
std::string_view itemId,
|
|
std::string_view targetItemId) override {
|
|
return MoveGameObjectRelativeToTarget(
|
|
m_sceneManager,
|
|
itemId,
|
|
targetItemId,
|
|
false);
|
|
}
|
|
|
|
bool MoveGameObjectAfter(
|
|
std::string_view itemId,
|
|
std::string_view targetItemId) override {
|
|
return MoveGameObjectRelativeToTarget(
|
|
m_sceneManager,
|
|
itemId,
|
|
targetItemId,
|
|
true);
|
|
}
|
|
|
|
bool MoveGameObjectToRoot(std::string_view itemId) override {
|
|
GameObject* gameObject = FindGameObject(itemId);
|
|
if (gameObject == nullptr || gameObject->GetParent() == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
gameObject->SetParent(nullptr);
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
TransformComponent* ResolveTransformComponent(EditorSceneObjectId objectId) const {
|
|
Scene* scene = ResolvePrimaryScene(m_sceneManager);
|
|
GameObject* gameObject =
|
|
scene != nullptr ? scene->FindByID(objectId) : nullptr;
|
|
return gameObject != nullptr ? gameObject->GetTransform() : nullptr;
|
|
}
|
|
|
|
TransformComponent* ResolveTransformComponent(
|
|
std::string_view itemId,
|
|
std::string_view componentId) const {
|
|
GameObject* gameObject = FindGameObject(itemId);
|
|
if (gameObject == nullptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
const EditorSceneComponentDescriptor descriptor =
|
|
ResolveComponentDescriptor(*gameObject, componentId);
|
|
return const_cast<TransformComponent*>(
|
|
dynamic_cast<const TransformComponent*>(descriptor.component));
|
|
}
|
|
|
|
SceneManager& m_sceneManager;
|
|
ResourceManager& m_resourceManager;
|
|
};
|
|
|
|
class EngineEditorServices final : public EditorEngineServices {
|
|
public:
|
|
void UpdateAsyncLoads() override {
|
|
ResourceManager::Get().UpdateAsyncLoads();
|
|
}
|
|
|
|
void Shutdown() override {
|
|
ResourceManager::Get().Shutdown();
|
|
}
|
|
|
|
std::unique_ptr<EditorSceneBackend> CreateSceneBackend() override {
|
|
return CreateEngineEditorSceneBackend(
|
|
SceneManager::Get(),
|
|
ResourceManager::Get());
|
|
}
|
|
|
|
::XCEngine::Resources::ResourceHandle<Shader> LoadShader(
|
|
const ::XCEngine::Containers::String& path) override {
|
|
return ResourceManager::Get().Load<Shader>(path);
|
|
}
|
|
|
|
bool TryResolveRenderObjectId(
|
|
RenderObjectId renderObjectId,
|
|
std::uint64_t& outRuntimeObjectId) const override {
|
|
return RenderObjectIdRegistry::Get().TryResolveRuntimeObjectId(
|
|
renderObjectId,
|
|
outRuntimeObjectId);
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
std::unique_ptr<EditorSceneBackend> CreateEngineEditorSceneBackend(
|
|
SceneManager& sceneManager,
|
|
ResourceManager& resourceManager) {
|
|
return std::make_unique<EngineEditorSceneBackend>(
|
|
sceneManager,
|
|
resourceManager);
|
|
}
|
|
|
|
std::unique_ptr<EditorEngineServices> CreateEngineEditorServices() {
|
|
return std::make_unique<EngineEditorServices>();
|
|
}
|
|
|
|
} // namespace XCEngine::UI::Editor::App
|