2214 lines
80 KiB
C++
2214 lines
80 KiB
C++
#include "Scene/EngineEditorSceneBackend.h"
|
|
|
|
#include <XCEditor/Foundation/UIEditorRuntimeTrace.h>
|
|
#include <XCEngine/Components/AudioListenerComponent.h>
|
|
#include <XCEngine/Components/AudioSourceComponent.h>
|
|
#include <XCEngine/Components/BoxColliderComponent.h>
|
|
#include <XCEngine/Components/CameraComponent.h>
|
|
#include <XCEngine/Components/CapsuleColliderComponent.h>
|
|
#include <XCEngine/Components/ComponentFactoryRegistry.h>
|
|
#include <XCEngine/Components/GameObject.h>
|
|
#include <XCEngine/Components/LightComponent.h>
|
|
#include <XCEngine/Components/MeshFilterComponent.h>
|
|
#include <XCEngine/Components/MeshRendererComponent.h>
|
|
#include <XCEngine/Components/RigidbodyComponent.h>
|
|
#include <XCEngine/Components/SphereColliderComponent.h>
|
|
#include <XCEngine/Components/TransformComponent.h>
|
|
#include <XCEngine/Components/VolumeRendererComponent.h>
|
|
#include <XCEngine/Core/Asset/ResourceManager.h>
|
|
#include <XCEngine/Resources/Mesh/Mesh.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::LightComponent;
|
|
using ::XCEngine::Components::Scene;
|
|
using ::XCEngine::Components::SceneManager;
|
|
using ::XCEngine::Components::TransformComponent;
|
|
using ::XCEngine::Math::Quaternion;
|
|
using ::XCEngine::Math::Vector3;
|
|
using ::XCEngine::Resources::ResourceManager;
|
|
|
|
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;
|
|
}
|
|
|
|
std::string ResolveGameObjectDisplayName(const GameObject& gameObject) {
|
|
return gameObject.GetName().empty()
|
|
? std::string("GameObject")
|
|
: gameObject.GetName();
|
|
}
|
|
|
|
EditorSceneCameraProjectionType ToEditorCameraProjectionType(
|
|
::XCEngine::Components::CameraProjectionType projectionType) {
|
|
switch (projectionType) {
|
|
case ::XCEngine::Components::CameraProjectionType::Orthographic:
|
|
return EditorSceneCameraProjectionType::Orthographic;
|
|
case ::XCEngine::Components::CameraProjectionType::Perspective:
|
|
default:
|
|
return EditorSceneCameraProjectionType::Perspective;
|
|
}
|
|
}
|
|
|
|
::XCEngine::Components::CameraProjectionType ToEngineCameraProjectionType(
|
|
EditorSceneCameraProjectionType projectionType) {
|
|
switch (projectionType) {
|
|
case EditorSceneCameraProjectionType::Orthographic:
|
|
return ::XCEngine::Components::CameraProjectionType::Orthographic;
|
|
case EditorSceneCameraProjectionType::Perspective:
|
|
default:
|
|
return ::XCEngine::Components::CameraProjectionType::Perspective;
|
|
}
|
|
}
|
|
|
|
EditorSceneLightType ToEditorLightType(::XCEngine::Components::LightType lightType) {
|
|
switch (lightType) {
|
|
case ::XCEngine::Components::LightType::Point:
|
|
return EditorSceneLightType::Point;
|
|
case ::XCEngine::Components::LightType::Spot:
|
|
return EditorSceneLightType::Spot;
|
|
case ::XCEngine::Components::LightType::Directional:
|
|
default:
|
|
return EditorSceneLightType::Directional;
|
|
}
|
|
}
|
|
|
|
::XCEngine::Components::LightType ToEngineLightType(EditorSceneLightType lightType) {
|
|
switch (lightType) {
|
|
case EditorSceneLightType::Point:
|
|
return ::XCEngine::Components::LightType::Point;
|
|
case EditorSceneLightType::Spot:
|
|
return ::XCEngine::Components::LightType::Spot;
|
|
case EditorSceneLightType::Directional:
|
|
default:
|
|
return ::XCEngine::Components::LightType::Directional;
|
|
}
|
|
}
|
|
|
|
EditorSceneColliderAxis ToEditorColliderAxis(
|
|
::XCEngine::Components::ColliderAxis axis) {
|
|
switch (axis) {
|
|
case ::XCEngine::Components::ColliderAxis::X:
|
|
return EditorSceneColliderAxis::X;
|
|
case ::XCEngine::Components::ColliderAxis::Z:
|
|
return EditorSceneColliderAxis::Z;
|
|
case ::XCEngine::Components::ColliderAxis::Y:
|
|
default:
|
|
return EditorSceneColliderAxis::Y;
|
|
}
|
|
}
|
|
|
|
::XCEngine::Components::ColliderAxis ToEngineColliderAxis(
|
|
EditorSceneColliderAxis axis) {
|
|
switch (axis) {
|
|
case EditorSceneColliderAxis::X:
|
|
return ::XCEngine::Components::ColliderAxis::X;
|
|
case EditorSceneColliderAxis::Z:
|
|
return ::XCEngine::Components::ColliderAxis::Z;
|
|
case EditorSceneColliderAxis::Y:
|
|
default:
|
|
return ::XCEngine::Components::ColliderAxis::Y;
|
|
}
|
|
}
|
|
|
|
EditorScenePhysicsBodyType ToEditorPhysicsBodyType(
|
|
::XCEngine::Physics::PhysicsBodyType bodyType) {
|
|
switch (bodyType) {
|
|
case ::XCEngine::Physics::PhysicsBodyType::Static:
|
|
return EditorScenePhysicsBodyType::Static;
|
|
case ::XCEngine::Physics::PhysicsBodyType::Kinematic:
|
|
return EditorScenePhysicsBodyType::Kinematic;
|
|
case ::XCEngine::Physics::PhysicsBodyType::Dynamic:
|
|
default:
|
|
return EditorScenePhysicsBodyType::Dynamic;
|
|
}
|
|
}
|
|
|
|
::XCEngine::Physics::PhysicsBodyType ToEnginePhysicsBodyType(
|
|
EditorScenePhysicsBodyType bodyType) {
|
|
switch (bodyType) {
|
|
case EditorScenePhysicsBodyType::Static:
|
|
return ::XCEngine::Physics::PhysicsBodyType::Static;
|
|
case EditorScenePhysicsBodyType::Kinematic:
|
|
return ::XCEngine::Physics::PhysicsBodyType::Kinematic;
|
|
case EditorScenePhysicsBodyType::Dynamic:
|
|
default:
|
|
return ::XCEngine::Physics::PhysicsBodyType::Dynamic;
|
|
}
|
|
}
|
|
|
|
template <typename TView, typename TComponent>
|
|
class ComponentViewAdapterBase : public TView {
|
|
public:
|
|
ComponentViewAdapterBase(TComponent& component, const char* typeName)
|
|
: m_component(component)
|
|
, m_typeName(typeName) {}
|
|
|
|
std::string_view GetTypeName() const override {
|
|
return m_typeName;
|
|
}
|
|
|
|
protected:
|
|
TComponent& MutableComponent() const {
|
|
return m_component;
|
|
}
|
|
|
|
const TComponent& ComponentRef() const {
|
|
return m_component;
|
|
}
|
|
|
|
private:
|
|
TComponent& m_component;
|
|
const char* m_typeName = "";
|
|
};
|
|
|
|
class GenericComponentViewAdapter final : public EditorSceneComponentView {
|
|
public:
|
|
explicit GenericComponentViewAdapter(std::string typeName)
|
|
: m_typeName(std::move(typeName)) {}
|
|
|
|
std::string_view GetTypeName() const override {
|
|
return m_typeName;
|
|
}
|
|
|
|
private:
|
|
std::string m_typeName = {};
|
|
};
|
|
|
|
class TransformComponentViewAdapter final
|
|
: public ComponentViewAdapterBase<EditorSceneTransformComponentView, TransformComponent> {
|
|
public:
|
|
explicit TransformComponentViewAdapter(TransformComponent& component)
|
|
: ComponentViewAdapterBase(component, "Transform") {}
|
|
|
|
Vector3 GetLocalPosition() const override { return ComponentRef().GetLocalPosition(); }
|
|
Vector3 GetLocalEulerAngles() const override { return ComponentRef().GetLocalEulerAngles(); }
|
|
Vector3 GetLocalScale() const override { return ComponentRef().GetLocalScale(); }
|
|
};
|
|
|
|
class CameraComponentViewAdapter final
|
|
: public ComponentViewAdapterBase<
|
|
EditorSceneCameraComponentView,
|
|
::XCEngine::Components::CameraComponent> {
|
|
public:
|
|
explicit CameraComponentViewAdapter(::XCEngine::Components::CameraComponent& component)
|
|
: ComponentViewAdapterBase(component, "Camera") {}
|
|
|
|
EditorSceneCameraProjectionType GetProjectionType() const override {
|
|
return ToEditorCameraProjectionType(ComponentRef().GetProjectionType());
|
|
}
|
|
|
|
void SetProjectionType(EditorSceneCameraProjectionType projectionType) {
|
|
MutableComponent().SetProjectionType(ToEngineCameraProjectionType(projectionType));
|
|
}
|
|
|
|
float GetFieldOfView() const override { return ComponentRef().GetFieldOfView(); }
|
|
void SetFieldOfView(float fieldOfView) { MutableComponent().SetFieldOfView(fieldOfView); }
|
|
float GetOrthographicSize() const override { return ComponentRef().GetOrthographicSize(); }
|
|
void SetOrthographicSize(float orthographicSize) { MutableComponent().SetOrthographicSize(orthographicSize); }
|
|
float GetNearClipPlane() const override { return ComponentRef().GetNearClipPlane(); }
|
|
void SetNearClipPlane(float nearClipPlane) { MutableComponent().SetNearClipPlane(nearClipPlane); }
|
|
float GetFarClipPlane() const override { return ComponentRef().GetFarClipPlane(); }
|
|
void SetFarClipPlane(float farClipPlane) { MutableComponent().SetFarClipPlane(farClipPlane); }
|
|
float GetDepth() const override { return ComponentRef().GetDepth(); }
|
|
void SetDepth(float depth) { MutableComponent().SetDepth(depth); }
|
|
bool IsPrimary() const override { return ComponentRef().IsPrimary(); }
|
|
void SetPrimary(bool primary) { MutableComponent().SetPrimary(primary); }
|
|
bool IsSkyboxEnabled() const override { return ComponentRef().IsSkyboxEnabled(); }
|
|
void SetSkyboxEnabled(bool enabled) { MutableComponent().SetSkyboxEnabled(enabled); }
|
|
std::string GetSkyboxMaterialPath() const override { return ComponentRef().GetSkyboxMaterialPath(); }
|
|
void SetSkyboxMaterialPath(std::string_view assetId) { MutableComponent().SetSkyboxMaterialPath(std::string(assetId)); }
|
|
::XCEngine::Math::Color GetSkyboxTopColor() const override { return ComponentRef().GetSkyboxTopColor(); }
|
|
void SetSkyboxTopColor(const ::XCEngine::Math::Color& color) { MutableComponent().SetSkyboxTopColor(color); }
|
|
::XCEngine::Math::Color GetSkyboxHorizonColor() const override { return ComponentRef().GetSkyboxHorizonColor(); }
|
|
void SetSkyboxHorizonColor(const ::XCEngine::Math::Color& color) { MutableComponent().SetSkyboxHorizonColor(color); }
|
|
::XCEngine::Math::Color GetSkyboxBottomColor() const override { return ComponentRef().GetSkyboxBottomColor(); }
|
|
void SetSkyboxBottomColor(const ::XCEngine::Math::Color& color) { MutableComponent().SetSkyboxBottomColor(color); }
|
|
::XCEngine::Math::Color GetClearColor() const override { return ComponentRef().GetClearColor(); }
|
|
void SetClearColor(const ::XCEngine::Math::Color& color) { MutableComponent().SetClearColor(color); }
|
|
};
|
|
|
|
class LightComponentViewAdapter final
|
|
: public ComponentViewAdapterBase<EditorSceneLightComponentView, LightComponent> {
|
|
public:
|
|
explicit LightComponentViewAdapter(LightComponent& component)
|
|
: ComponentViewAdapterBase(component, "Light") {}
|
|
|
|
EditorSceneLightType GetLightType() const override {
|
|
return ToEditorLightType(ComponentRef().GetLightType());
|
|
}
|
|
|
|
void SetLightType(EditorSceneLightType lightType) {
|
|
MutableComponent().SetLightType(ToEngineLightType(lightType));
|
|
}
|
|
|
|
::XCEngine::Math::Color GetColor() const override { return ComponentRef().GetColor(); }
|
|
void SetColor(const ::XCEngine::Math::Color& color) { MutableComponent().SetColor(color); }
|
|
float GetIntensity() const override { return ComponentRef().GetIntensity(); }
|
|
void SetIntensity(float intensity) { MutableComponent().SetIntensity(intensity); }
|
|
float GetRange() const override { return ComponentRef().GetRange(); }
|
|
void SetRange(float range) { MutableComponent().SetRange(range); }
|
|
float GetSpotAngle() const override { return ComponentRef().GetSpotAngle(); }
|
|
void SetSpotAngle(float spotAngle) { MutableComponent().SetSpotAngle(spotAngle); }
|
|
bool GetCastsShadows() const override { return ComponentRef().GetCastsShadows(); }
|
|
void SetCastsShadows(bool castsShadows) { MutableComponent().SetCastsShadows(castsShadows); }
|
|
bool GetOverridesDirectionalShadowSettings() const override { return ComponentRef().GetOverridesDirectionalShadowSettings(); }
|
|
void SetOverridesDirectionalShadowSettings(bool overrides) { MutableComponent().SetOverridesDirectionalShadowSettings(overrides); }
|
|
float GetDirectionalShadowReceiverDepthBias() const override { return ComponentRef().GetDirectionalShadowReceiverDepthBias(); }
|
|
void SetDirectionalShadowReceiverDepthBias(float value) { MutableComponent().SetDirectionalShadowReceiverDepthBias(value); }
|
|
float GetDirectionalShadowNormalBiasScale() const override { return ComponentRef().GetDirectionalShadowNormalBiasScale(); }
|
|
void SetDirectionalShadowNormalBiasScale(float value) { MutableComponent().SetDirectionalShadowNormalBiasScale(value); }
|
|
float GetDirectionalShadowStrength() const override { return ComponentRef().GetDirectionalShadowStrength(); }
|
|
void SetDirectionalShadowStrength(float value) { MutableComponent().SetDirectionalShadowStrength(value); }
|
|
float GetDirectionalShadowDepthBiasFactor() const override { return ComponentRef().GetDirectionalShadowDepthBiasFactor(); }
|
|
void SetDirectionalShadowDepthBiasFactor(float value) { MutableComponent().SetDirectionalShadowDepthBiasFactor(value); }
|
|
int GetDirectionalShadowDepthBiasUnits() const override { return ComponentRef().GetDirectionalShadowDepthBiasUnits(); }
|
|
void SetDirectionalShadowDepthBiasUnits(int value) { MutableComponent().SetDirectionalShadowDepthBiasUnits(value); }
|
|
};
|
|
|
|
template <typename TView, typename TColliderComponent>
|
|
class ColliderComponentViewAdapterBase : public ComponentViewAdapterBase<TView, TColliderComponent> {
|
|
public:
|
|
ColliderComponentViewAdapterBase(TColliderComponent& component, const char* typeName)
|
|
: ComponentViewAdapterBase<TView, TColliderComponent>(component, typeName) {}
|
|
|
|
bool IsTrigger() const override { return this->ComponentRef().IsTrigger(); }
|
|
void SetTrigger(bool trigger) { this->MutableComponent().SetTrigger(trigger); }
|
|
Vector3 GetCenter() const override { return this->ComponentRef().GetCenter(); }
|
|
void SetCenter(const Vector3& center) { this->MutableComponent().SetCenter(center); }
|
|
float GetStaticFriction() const override { return this->ComponentRef().GetStaticFriction(); }
|
|
void SetStaticFriction(float value) { this->MutableComponent().SetStaticFriction(value); }
|
|
float GetDynamicFriction() const override { return this->ComponentRef().GetDynamicFriction(); }
|
|
void SetDynamicFriction(float value) { this->MutableComponent().SetDynamicFriction(value); }
|
|
float GetRestitution() const override { return this->ComponentRef().GetRestitution(); }
|
|
void SetRestitution(float value) { this->MutableComponent().SetRestitution(value); }
|
|
};
|
|
|
|
class BoxColliderComponentViewAdapter final
|
|
: public ColliderComponentViewAdapterBase<
|
|
EditorSceneBoxColliderComponentView,
|
|
::XCEngine::Components::BoxColliderComponent> {
|
|
public:
|
|
explicit BoxColliderComponentViewAdapter(::XCEngine::Components::BoxColliderComponent& component)
|
|
: ColliderComponentViewAdapterBase(component, "BoxCollider") {}
|
|
|
|
Vector3 GetSize() const override { return this->ComponentRef().GetSize(); }
|
|
void SetSize(const Vector3& size) { this->MutableComponent().SetSize(size); }
|
|
};
|
|
|
|
class CapsuleColliderComponentViewAdapter final
|
|
: public ColliderComponentViewAdapterBase<
|
|
EditorSceneCapsuleColliderComponentView,
|
|
::XCEngine::Components::CapsuleColliderComponent> {
|
|
public:
|
|
explicit CapsuleColliderComponentViewAdapter(
|
|
::XCEngine::Components::CapsuleColliderComponent& component)
|
|
: ColliderComponentViewAdapterBase(component, "CapsuleCollider") {}
|
|
|
|
float GetRadius() const override { return this->ComponentRef().GetRadius(); }
|
|
void SetRadius(float radius) { this->MutableComponent().SetRadius(radius); }
|
|
float GetHeight() const override { return this->ComponentRef().GetHeight(); }
|
|
void SetHeight(float height) { this->MutableComponent().SetHeight(height); }
|
|
EditorSceneColliderAxis GetAxis() const override {
|
|
return ToEditorColliderAxis(this->ComponentRef().GetAxis());
|
|
}
|
|
void SetAxis(EditorSceneColliderAxis axis) {
|
|
this->MutableComponent().SetAxis(ToEngineColliderAxis(axis));
|
|
}
|
|
};
|
|
|
|
class SphereColliderComponentViewAdapter final
|
|
: public ColliderComponentViewAdapterBase<
|
|
EditorSceneSphereColliderComponentView,
|
|
::XCEngine::Components::SphereColliderComponent> {
|
|
public:
|
|
explicit SphereColliderComponentViewAdapter(::XCEngine::Components::SphereColliderComponent& component)
|
|
: ColliderComponentViewAdapterBase(component, "SphereCollider") {}
|
|
|
|
float GetRadius() const override { return this->ComponentRef().GetRadius(); }
|
|
void SetRadius(float radius) { this->MutableComponent().SetRadius(radius); }
|
|
};
|
|
|
|
class AudioSourceComponentViewAdapter final
|
|
: public ComponentViewAdapterBase<
|
|
EditorSceneAudioSourceComponentView,
|
|
::XCEngine::Components::AudioSourceComponent> {
|
|
public:
|
|
explicit AudioSourceComponentViewAdapter(::XCEngine::Components::AudioSourceComponent& component)
|
|
: ComponentViewAdapterBase(component, "AudioSource") {}
|
|
|
|
std::string GetClipPath() const override { return ComponentRef().GetClipPath(); }
|
|
void ClearClip() { MutableComponent().ClearClip(); }
|
|
void SetClipPath(std::string_view assetId) { MutableComponent().SetClipPath(std::string(assetId)); }
|
|
float GetVolume() const override { return ComponentRef().GetVolume(); }
|
|
void SetVolume(float volume) { MutableComponent().SetVolume(volume); }
|
|
float GetPitch() const override { return ComponentRef().GetPitch(); }
|
|
void SetPitch(float pitch) { MutableComponent().SetPitch(pitch); }
|
|
float GetPan() const override { return ComponentRef().GetPan(); }
|
|
void SetPan(float pan) { MutableComponent().SetPan(pan); }
|
|
bool IsLooping() const override { return ComponentRef().IsLooping(); }
|
|
void SetLooping(bool looping) { MutableComponent().SetLooping(looping); }
|
|
bool IsSpatialize() const override { return ComponentRef().IsSpatialize(); }
|
|
void SetSpatialize(bool spatialize) { MutableComponent().SetSpatialize(spatialize); }
|
|
::XCEngine::Audio::Audio3DParams Get3DParams() const override { return ComponentRef().Get3DParams(); }
|
|
void Set3DParams(const ::XCEngine::Audio::Audio3DParams& params) { MutableComponent().Set3DParams(params); }
|
|
bool IsHRTFEnabled() const override { return ComponentRef().IsHRTFEnabled(); }
|
|
void SetHRTFEnabled(bool enabled) { MutableComponent().SetHRTFEnabled(enabled); }
|
|
float GetHRTFCrossFeed() const override { return ComponentRef().GetHRTFCrossFeed(); }
|
|
void SetHRTFCrossFeed(float value) { MutableComponent().SetHRTFCrossFeed(value); }
|
|
std::uint32_t GetHRTFQuality() const override { return ComponentRef().GetHRTFQuality(); }
|
|
void SetHRTFQuality(std::uint32_t quality) { MutableComponent().SetHRTFQuality(quality); }
|
|
};
|
|
|
|
class AudioListenerComponentViewAdapter final
|
|
: public ComponentViewAdapterBase<
|
|
EditorSceneAudioListenerComponentView,
|
|
::XCEngine::Components::AudioListenerComponent> {
|
|
public:
|
|
explicit AudioListenerComponentViewAdapter(
|
|
::XCEngine::Components::AudioListenerComponent& component)
|
|
: ComponentViewAdapterBase(component, "AudioListener") {}
|
|
|
|
float GetMasterVolume() const override { return ComponentRef().GetMasterVolume(); }
|
|
void SetMasterVolume(float value) { MutableComponent().SetMasterVolume(value); }
|
|
bool IsMute() const override { return ComponentRef().IsMute(); }
|
|
void SetMute(bool mute) { MutableComponent().SetMute(mute); }
|
|
float GetDopplerLevel() const override { return ComponentRef().GetDopplerLevel(); }
|
|
void SetDopplerLevel(float value) { MutableComponent().SetDopplerLevel(value); }
|
|
float GetSpeedOfSound() const override { return ComponentRef().GetSpeedOfSound(); }
|
|
void SetSpeedOfSound(float value) { MutableComponent().SetSpeedOfSound(value); }
|
|
float GetReverbLevel() const override { return ComponentRef().GetReverbLevel(); }
|
|
void SetReverbLevel(float value) { MutableComponent().SetReverbLevel(value); }
|
|
};
|
|
|
|
class MeshFilterComponentViewAdapter final
|
|
: public ComponentViewAdapterBase<
|
|
EditorSceneMeshFilterComponentView,
|
|
::XCEngine::Components::MeshFilterComponent> {
|
|
public:
|
|
explicit MeshFilterComponentViewAdapter(::XCEngine::Components::MeshFilterComponent& component)
|
|
: ComponentViewAdapterBase(component, "MeshFilter") {}
|
|
|
|
std::string GetMeshPath() const override { return ComponentRef().GetMeshPath(); }
|
|
void ClearMesh() { MutableComponent().ClearMesh(); }
|
|
void SetMeshPath(std::string_view assetId) { MutableComponent().SetMeshPath(std::string(assetId)); }
|
|
};
|
|
|
|
class MeshRendererComponentViewAdapter final
|
|
: public ComponentViewAdapterBase<
|
|
EditorSceneMeshRendererComponentView,
|
|
::XCEngine::Components::MeshRendererComponent> {
|
|
public:
|
|
explicit MeshRendererComponentViewAdapter(
|
|
::XCEngine::Components::MeshRendererComponent& component)
|
|
: ComponentViewAdapterBase(component, "MeshRenderer") {}
|
|
|
|
bool GetCastShadows() const override { return ComponentRef().GetCastShadows(); }
|
|
void SetCastShadows(bool value) { MutableComponent().SetCastShadows(value); }
|
|
bool GetReceiveShadows() const override { return ComponentRef().GetReceiveShadows(); }
|
|
void SetReceiveShadows(bool value) { MutableComponent().SetReceiveShadows(value); }
|
|
std::uint32_t GetRenderLayer() const override { return ComponentRef().GetRenderLayer(); }
|
|
void SetRenderLayer(std::uint32_t value) { MutableComponent().SetRenderLayer(value); }
|
|
std::size_t GetMaterialCount() const override { return ComponentRef().GetMaterialCount(); }
|
|
std::string GetMaterialPath(std::size_t slotIndex) const override { return ComponentRef().GetMaterialPath(slotIndex); }
|
|
void SetMaterialPath(std::size_t slotIndex, std::string_view assetId) {
|
|
MutableComponent().SetMaterialPath(slotIndex, std::string(assetId));
|
|
}
|
|
};
|
|
|
|
class RigidbodyComponentViewAdapter final
|
|
: public ComponentViewAdapterBase<
|
|
EditorSceneRigidbodyComponentView,
|
|
::XCEngine::Components::RigidbodyComponent> {
|
|
public:
|
|
explicit RigidbodyComponentViewAdapter(::XCEngine::Components::RigidbodyComponent& component)
|
|
: ComponentViewAdapterBase(component, "Rigidbody") {}
|
|
|
|
EditorScenePhysicsBodyType GetBodyType() const override {
|
|
return ToEditorPhysicsBodyType(ComponentRef().GetBodyType());
|
|
}
|
|
void SetBodyType(EditorScenePhysicsBodyType bodyType) {
|
|
MutableComponent().SetBodyType(ToEnginePhysicsBodyType(bodyType));
|
|
}
|
|
float GetMass() const override { return ComponentRef().GetMass(); }
|
|
void SetMass(float mass) { MutableComponent().SetMass(mass); }
|
|
float GetLinearDamping() const override { return ComponentRef().GetLinearDamping(); }
|
|
void SetLinearDamping(float damping) { MutableComponent().SetLinearDamping(damping); }
|
|
float GetAngularDamping() const override { return ComponentRef().GetAngularDamping(); }
|
|
void SetAngularDamping(float damping) { MutableComponent().SetAngularDamping(damping); }
|
|
bool GetUseGravity() const override { return ComponentRef().GetUseGravity(); }
|
|
void SetUseGravity(bool useGravity) { MutableComponent().SetUseGravity(useGravity); }
|
|
bool GetEnableCCD() const override { return ComponentRef().GetEnableCCD(); }
|
|
void SetEnableCCD(bool enableCCD) { MutableComponent().SetEnableCCD(enableCCD); }
|
|
};
|
|
|
|
class VolumeRendererComponentViewAdapter final
|
|
: public ComponentViewAdapterBase<
|
|
EditorSceneVolumeRendererComponentView,
|
|
::XCEngine::Components::VolumeRendererComponent> {
|
|
public:
|
|
explicit VolumeRendererComponentViewAdapter(
|
|
::XCEngine::Components::VolumeRendererComponent& component)
|
|
: ComponentViewAdapterBase(component, "VolumeRenderer") {}
|
|
|
|
std::string GetVolumeFieldPath() const override { return ComponentRef().GetVolumeFieldPath(); }
|
|
void ClearVolumeField() { MutableComponent().ClearVolumeField(); }
|
|
void SetVolumeFieldPath(std::string_view assetId) { MutableComponent().SetVolumeFieldPath(std::string(assetId)); }
|
|
std::string GetMaterialPath() const override { return ComponentRef().GetMaterialPath(); }
|
|
void ClearMaterial() { MutableComponent().ClearMaterial(); }
|
|
void SetMaterialPath(std::string_view assetId) { MutableComponent().SetMaterialPath(std::string(assetId)); }
|
|
bool GetCastShadows() const override { return ComponentRef().GetCastShadows(); }
|
|
void SetCastShadows(bool value) { MutableComponent().SetCastShadows(value); }
|
|
bool GetReceiveShadows() const override { return ComponentRef().GetReceiveShadows(); }
|
|
void SetReceiveShadows(bool value) { MutableComponent().SetReceiveShadows(value); }
|
|
};
|
|
|
|
std::shared_ptr<EditorSceneComponentView> CreateComponentView(Component& component) {
|
|
if (auto* transform = dynamic_cast<TransformComponent*>(&component); transform != nullptr) {
|
|
return std::make_shared<TransformComponentViewAdapter>(*transform);
|
|
}
|
|
if (auto* camera = dynamic_cast<::XCEngine::Components::CameraComponent*>(&component);
|
|
camera != nullptr) {
|
|
return std::make_shared<CameraComponentViewAdapter>(*camera);
|
|
}
|
|
if (auto* light = dynamic_cast<LightComponent*>(&component); light != nullptr) {
|
|
return std::make_shared<LightComponentViewAdapter>(*light);
|
|
}
|
|
if (auto* collider = dynamic_cast<::XCEngine::Components::BoxColliderComponent*>(&component);
|
|
collider != nullptr) {
|
|
return std::make_shared<BoxColliderComponentViewAdapter>(*collider);
|
|
}
|
|
if (auto* collider =
|
|
dynamic_cast<::XCEngine::Components::CapsuleColliderComponent*>(&component);
|
|
collider != nullptr) {
|
|
return std::make_shared<CapsuleColliderComponentViewAdapter>(*collider);
|
|
}
|
|
if (auto* collider =
|
|
dynamic_cast<::XCEngine::Components::SphereColliderComponent*>(&component);
|
|
collider != nullptr) {
|
|
return std::make_shared<SphereColliderComponentViewAdapter>(*collider);
|
|
}
|
|
if (auto* source = dynamic_cast<::XCEngine::Components::AudioSourceComponent*>(&component);
|
|
source != nullptr) {
|
|
return std::make_shared<AudioSourceComponentViewAdapter>(*source);
|
|
}
|
|
if (auto* listener =
|
|
dynamic_cast<::XCEngine::Components::AudioListenerComponent*>(&component);
|
|
listener != nullptr) {
|
|
return std::make_shared<AudioListenerComponentViewAdapter>(*listener);
|
|
}
|
|
if (auto* meshFilter =
|
|
dynamic_cast<::XCEngine::Components::MeshFilterComponent*>(&component);
|
|
meshFilter != nullptr) {
|
|
return std::make_shared<MeshFilterComponentViewAdapter>(*meshFilter);
|
|
}
|
|
if (auto* meshRenderer =
|
|
dynamic_cast<::XCEngine::Components::MeshRendererComponent*>(&component);
|
|
meshRenderer != nullptr) {
|
|
return std::make_shared<MeshRendererComponentViewAdapter>(*meshRenderer);
|
|
}
|
|
if (auto* rigidbody =
|
|
dynamic_cast<::XCEngine::Components::RigidbodyComponent*>(&component);
|
|
rigidbody != nullptr) {
|
|
return std::make_shared<RigidbodyComponentViewAdapter>(*rigidbody);
|
|
}
|
|
if (auto* volumeRenderer =
|
|
dynamic_cast<::XCEngine::Components::VolumeRendererComponent*>(&component);
|
|
volumeRenderer != nullptr) {
|
|
return std::make_shared<VolumeRendererComponentViewAdapter>(*volumeRenderer);
|
|
}
|
|
|
|
return std::make_shared<GenericComponentViewAdapter>(component.GetName());
|
|
}
|
|
|
|
template <typename TValue>
|
|
const TValue* TryGetMutationValue(const EditorSceneComponentMutation& mutation) {
|
|
return std::get_if<TValue>(&mutation.value);
|
|
}
|
|
|
|
template <typename TValue, typename TApply>
|
|
bool TryApplyMutationValue(
|
|
const EditorSceneComponentMutation& mutation,
|
|
std::string_view propertyPath,
|
|
TApply&& apply) {
|
|
if (mutation.propertyPath != propertyPath) {
|
|
return false;
|
|
}
|
|
|
|
const TValue* value = TryGetMutationValue<TValue>(mutation);
|
|
if (value == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
apply(*value);
|
|
return true;
|
|
}
|
|
|
|
bool TryParseMaterialSlotProperty(
|
|
std::string_view propertyPath,
|
|
std::size_t& outSlotIndex) {
|
|
constexpr std::string_view kPrefix = "material_";
|
|
if (propertyPath.size() <= kPrefix.size() ||
|
|
propertyPath.substr(0u, kPrefix.size()) != kPrefix) {
|
|
return false;
|
|
}
|
|
|
|
const std::string_view slotText = propertyPath.substr(kPrefix.size());
|
|
std::size_t slotIndex = 0u;
|
|
const char* first = slotText.data();
|
|
const char* last = slotText.data() + slotText.size();
|
|
const std::from_chars_result result =
|
|
std::from_chars(first, last, slotIndex);
|
|
if (result.ec != std::errc() || result.ptr != last) {
|
|
return false;
|
|
}
|
|
|
|
outSlotIndex = slotIndex;
|
|
return true;
|
|
}
|
|
|
|
template <typename TColliderViewAdapter>
|
|
bool ApplyColliderBaseMutation(
|
|
TColliderViewAdapter& view,
|
|
const EditorSceneComponentMutation& mutation) {
|
|
if (TryApplyMutationValue<bool>(
|
|
mutation,
|
|
"is_trigger",
|
|
[&view](bool value) { view.SetTrigger(value); })) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<Vector3>(
|
|
mutation,
|
|
"center",
|
|
[&view](const Vector3& value) { view.SetCenter(value); })) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<float>(
|
|
mutation,
|
|
"static_friction",
|
|
[&view](float value) { view.SetStaticFriction(value); })) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<float>(
|
|
mutation,
|
|
"dynamic_friction",
|
|
[&view](float value) { view.SetDynamicFriction(value); })) {
|
|
return true;
|
|
}
|
|
return TryApplyMutationValue<float>(
|
|
mutation,
|
|
"restitution",
|
|
[&view](float value) { view.SetRestitution(value); });
|
|
}
|
|
|
|
bool ApplyCameraComponentMutation(
|
|
::XCEngine::Components::CameraComponent& component,
|
|
const EditorSceneComponentMutation& mutation) {
|
|
CameraComponentViewAdapter view(component);
|
|
if (TryApplyMutationValue<EditorSceneCameraProjectionType>(
|
|
mutation,
|
|
"projection",
|
|
[&view](EditorSceneCameraProjectionType value) {
|
|
view.SetProjectionType(value);
|
|
})) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<float>(
|
|
mutation,
|
|
"field_of_view",
|
|
[&view](float value) { view.SetFieldOfView(value); })) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<float>(
|
|
mutation,
|
|
"orthographic_size",
|
|
[&view](float value) { view.SetOrthographicSize(value); })) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<float>(
|
|
mutation,
|
|
"near_clip",
|
|
[&view](float value) { view.SetNearClipPlane(value); })) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<float>(
|
|
mutation,
|
|
"far_clip",
|
|
[&view](float value) { view.SetFarClipPlane(value); })) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<float>(
|
|
mutation,
|
|
"depth",
|
|
[&view](float value) { view.SetDepth(value); })) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<bool>(
|
|
mutation,
|
|
"primary",
|
|
[&view](bool value) { view.SetPrimary(value); })) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<bool>(
|
|
mutation,
|
|
"skybox",
|
|
[&view](bool value) { view.SetSkyboxEnabled(value); })) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<std::string>(
|
|
mutation,
|
|
"skybox_material",
|
|
[&view](const std::string& value) {
|
|
view.SetSkyboxMaterialPath(value);
|
|
})) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<::XCEngine::Math::Color>(
|
|
mutation,
|
|
"skybox_top",
|
|
[&view](const ::XCEngine::Math::Color& value) {
|
|
view.SetSkyboxTopColor(value);
|
|
})) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<::XCEngine::Math::Color>(
|
|
mutation,
|
|
"skybox_horizon",
|
|
[&view](const ::XCEngine::Math::Color& value) {
|
|
view.SetSkyboxHorizonColor(value);
|
|
})) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<::XCEngine::Math::Color>(
|
|
mutation,
|
|
"skybox_bottom",
|
|
[&view](const ::XCEngine::Math::Color& value) {
|
|
view.SetSkyboxBottomColor(value);
|
|
})) {
|
|
return true;
|
|
}
|
|
return TryApplyMutationValue<::XCEngine::Math::Color>(
|
|
mutation,
|
|
"clear_color",
|
|
[&view](const ::XCEngine::Math::Color& value) {
|
|
view.SetClearColor(value);
|
|
});
|
|
}
|
|
|
|
bool ApplyLightComponentMutation(
|
|
LightComponent& component,
|
|
const EditorSceneComponentMutation& mutation) {
|
|
LightComponentViewAdapter view(component);
|
|
if (TryApplyMutationValue<EditorSceneLightType>(
|
|
mutation,
|
|
"type",
|
|
[&view](EditorSceneLightType value) { view.SetLightType(value); })) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<::XCEngine::Math::Color>(
|
|
mutation,
|
|
"color",
|
|
[&view](const ::XCEngine::Math::Color& value) {
|
|
view.SetColor(value);
|
|
})) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<float>(
|
|
mutation,
|
|
"intensity",
|
|
[&view](float value) { view.SetIntensity(value); })) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<float>(
|
|
mutation,
|
|
"range",
|
|
[&view](float value) { view.SetRange(value); })) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<float>(
|
|
mutation,
|
|
"spot_angle",
|
|
[&view](float value) { view.SetSpotAngle(value); })) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<bool>(
|
|
mutation,
|
|
"cast_shadows",
|
|
[&view](bool value) { view.SetCastsShadows(value); })) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<bool>(
|
|
mutation,
|
|
"override_shadow_params",
|
|
[&view](bool value) {
|
|
view.SetOverridesDirectionalShadowSettings(value);
|
|
})) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<float>(
|
|
mutation,
|
|
"receiver_depth_bias",
|
|
[&view](float value) {
|
|
view.SetDirectionalShadowReceiverDepthBias(value);
|
|
})) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<float>(
|
|
mutation,
|
|
"normal_bias_scale",
|
|
[&view](float value) {
|
|
view.SetDirectionalShadowNormalBiasScale(value);
|
|
})) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<float>(
|
|
mutation,
|
|
"shadow_strength",
|
|
[&view](float value) {
|
|
view.SetDirectionalShadowStrength(value);
|
|
})) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<float>(
|
|
mutation,
|
|
"depth_bias_factor",
|
|
[&view](float value) {
|
|
view.SetDirectionalShadowDepthBiasFactor(value);
|
|
})) {
|
|
return true;
|
|
}
|
|
return TryApplyMutationValue<std::int32_t>(
|
|
mutation,
|
|
"depth_bias_units",
|
|
[&view](std::int32_t value) {
|
|
view.SetDirectionalShadowDepthBiasUnits(value);
|
|
});
|
|
}
|
|
|
|
bool ApplyBoxColliderComponentMutation(
|
|
::XCEngine::Components::BoxColliderComponent& component,
|
|
const EditorSceneComponentMutation& mutation) {
|
|
BoxColliderComponentViewAdapter view(component);
|
|
if (ApplyColliderBaseMutation(view, mutation)) {
|
|
return true;
|
|
}
|
|
return TryApplyMutationValue<Vector3>(
|
|
mutation,
|
|
"size",
|
|
[&view](const Vector3& value) { view.SetSize(value); });
|
|
}
|
|
|
|
bool ApplyCapsuleColliderComponentMutation(
|
|
::XCEngine::Components::CapsuleColliderComponent& component,
|
|
const EditorSceneComponentMutation& mutation) {
|
|
CapsuleColliderComponentViewAdapter view(component);
|
|
if (ApplyColliderBaseMutation(view, mutation)) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<float>(
|
|
mutation,
|
|
"radius",
|
|
[&view](float value) { view.SetRadius(value); })) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<float>(
|
|
mutation,
|
|
"height",
|
|
[&view](float value) { view.SetHeight(value); })) {
|
|
return true;
|
|
}
|
|
return TryApplyMutationValue<EditorSceneColliderAxis>(
|
|
mutation,
|
|
"axis",
|
|
[&view](EditorSceneColliderAxis value) { view.SetAxis(value); });
|
|
}
|
|
|
|
bool ApplySphereColliderComponentMutation(
|
|
::XCEngine::Components::SphereColliderComponent& component,
|
|
const EditorSceneComponentMutation& mutation) {
|
|
SphereColliderComponentViewAdapter view(component);
|
|
if (ApplyColliderBaseMutation(view, mutation)) {
|
|
return true;
|
|
}
|
|
return TryApplyMutationValue<float>(
|
|
mutation,
|
|
"radius",
|
|
[&view](float value) { view.SetRadius(value); });
|
|
}
|
|
|
|
bool TryApplyAudioSource3DParamMutation(
|
|
AudioSourceComponentViewAdapter& view,
|
|
const EditorSceneComponentMutation& mutation,
|
|
std::string_view propertyPath,
|
|
float ::XCEngine::Audio::Audio3DParams::* member) {
|
|
return TryApplyMutationValue<float>(
|
|
mutation,
|
|
propertyPath,
|
|
[&view, member](float value) {
|
|
::XCEngine::Audio::Audio3DParams params = view.Get3DParams();
|
|
params.*member = value;
|
|
view.Set3DParams(params);
|
|
});
|
|
}
|
|
|
|
bool ApplyAudioSourceComponentMutation(
|
|
::XCEngine::Components::AudioSourceComponent& component,
|
|
const EditorSceneComponentMutation& mutation) {
|
|
AudioSourceComponentViewAdapter view(component);
|
|
if (TryApplyMutationValue<std::string>(
|
|
mutation,
|
|
"clip",
|
|
[&view](const std::string& value) {
|
|
if (value.empty()) {
|
|
view.ClearClip();
|
|
} else {
|
|
view.SetClipPath(value);
|
|
}
|
|
})) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<float>(
|
|
mutation,
|
|
"volume",
|
|
[&view](float value) { view.SetVolume(value); })) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<float>(
|
|
mutation,
|
|
"pitch",
|
|
[&view](float value) { view.SetPitch(value); })) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<float>(
|
|
mutation,
|
|
"pan",
|
|
[&view](float value) { view.SetPan(value); })) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<bool>(
|
|
mutation,
|
|
"looping",
|
|
[&view](bool value) { view.SetLooping(value); })) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<bool>(
|
|
mutation,
|
|
"spatialize",
|
|
[&view](bool value) { view.SetSpatialize(value); })) {
|
|
return true;
|
|
}
|
|
if (TryApplyAudioSource3DParamMutation(
|
|
view,
|
|
mutation,
|
|
"min_distance",
|
|
&::XCEngine::Audio::Audio3DParams::minDistance)) {
|
|
return true;
|
|
}
|
|
if (TryApplyAudioSource3DParamMutation(
|
|
view,
|
|
mutation,
|
|
"max_distance",
|
|
&::XCEngine::Audio::Audio3DParams::maxDistance)) {
|
|
return true;
|
|
}
|
|
if (TryApplyAudioSource3DParamMutation(
|
|
view,
|
|
mutation,
|
|
"pan_level",
|
|
&::XCEngine::Audio::Audio3DParams::panLevel)) {
|
|
return true;
|
|
}
|
|
if (TryApplyAudioSource3DParamMutation(
|
|
view,
|
|
mutation,
|
|
"spread",
|
|
&::XCEngine::Audio::Audio3DParams::spread)) {
|
|
return true;
|
|
}
|
|
if (TryApplyAudioSource3DParamMutation(
|
|
view,
|
|
mutation,
|
|
"reverb_send",
|
|
&::XCEngine::Audio::Audio3DParams::reverbZoneMix)) {
|
|
return true;
|
|
}
|
|
if (TryApplyAudioSource3DParamMutation(
|
|
view,
|
|
mutation,
|
|
"doppler_level",
|
|
&::XCEngine::Audio::Audio3DParams::dopplerLevel)) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<bool>(
|
|
mutation,
|
|
"use_hrtf",
|
|
[&view](bool value) { view.SetHRTFEnabled(value); })) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<float>(
|
|
mutation,
|
|
"hrtf_crossfeed",
|
|
[&view](float value) { view.SetHRTFCrossFeed(value); })) {
|
|
return true;
|
|
}
|
|
return TryApplyMutationValue<std::uint32_t>(
|
|
mutation,
|
|
"hrtf_quality",
|
|
[&view](std::uint32_t value) { view.SetHRTFQuality(value); });
|
|
}
|
|
|
|
bool ApplyAudioListenerComponentMutation(
|
|
::XCEngine::Components::AudioListenerComponent& component,
|
|
const EditorSceneComponentMutation& mutation) {
|
|
AudioListenerComponentViewAdapter view(component);
|
|
if (TryApplyMutationValue<float>(
|
|
mutation,
|
|
"master_volume",
|
|
[&view](float value) { view.SetMasterVolume(value); })) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<bool>(
|
|
mutation,
|
|
"mute",
|
|
[&view](bool value) { view.SetMute(value); })) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<float>(
|
|
mutation,
|
|
"doppler_level",
|
|
[&view](float value) { view.SetDopplerLevel(value); })) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<float>(
|
|
mutation,
|
|
"speed_of_sound",
|
|
[&view](float value) { view.SetSpeedOfSound(value); })) {
|
|
return true;
|
|
}
|
|
return TryApplyMutationValue<float>(
|
|
mutation,
|
|
"reverb_level",
|
|
[&view](float value) { view.SetReverbLevel(value); });
|
|
}
|
|
|
|
bool ApplyMeshFilterComponentMutation(
|
|
::XCEngine::Components::MeshFilterComponent& component,
|
|
const EditorSceneComponentMutation& mutation) {
|
|
MeshFilterComponentViewAdapter view(component);
|
|
return TryApplyMutationValue<std::string>(
|
|
mutation,
|
|
"mesh",
|
|
[&view](const std::string& value) {
|
|
if (value.empty()) {
|
|
view.ClearMesh();
|
|
} else {
|
|
view.SetMeshPath(value);
|
|
}
|
|
});
|
|
}
|
|
|
|
bool ApplyMeshRendererComponentMutation(
|
|
::XCEngine::Components::MeshRendererComponent& component,
|
|
const EditorSceneComponentMutation& mutation) {
|
|
MeshRendererComponentViewAdapter view(component);
|
|
if (TryApplyMutationValue<bool>(
|
|
mutation,
|
|
"cast_shadows",
|
|
[&view](bool value) { view.SetCastShadows(value); })) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<bool>(
|
|
mutation,
|
|
"receive_shadows",
|
|
[&view](bool value) { view.SetReceiveShadows(value); })) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<std::uint32_t>(
|
|
mutation,
|
|
"render_layer",
|
|
[&view](std::uint32_t value) { view.SetRenderLayer(value); })) {
|
|
return true;
|
|
}
|
|
|
|
std::size_t slotIndex = 0u;
|
|
if (!TryParseMaterialSlotProperty(mutation.propertyPath, slotIndex)) {
|
|
return false;
|
|
}
|
|
|
|
const std::string* value = TryGetMutationValue<std::string>(mutation);
|
|
if (value == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
view.SetMaterialPath(slotIndex, *value);
|
|
return true;
|
|
}
|
|
|
|
bool ApplyRigidbodyComponentMutation(
|
|
::XCEngine::Components::RigidbodyComponent& component,
|
|
const EditorSceneComponentMutation& mutation) {
|
|
RigidbodyComponentViewAdapter view(component);
|
|
if (TryApplyMutationValue<EditorScenePhysicsBodyType>(
|
|
mutation,
|
|
"body_type",
|
|
[&view](EditorScenePhysicsBodyType value) {
|
|
view.SetBodyType(value);
|
|
})) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<float>(
|
|
mutation,
|
|
"mass",
|
|
[&view](float value) { view.SetMass(value); })) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<float>(
|
|
mutation,
|
|
"linear_damping",
|
|
[&view](float value) { view.SetLinearDamping(value); })) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<float>(
|
|
mutation,
|
|
"angular_damping",
|
|
[&view](float value) { view.SetAngularDamping(value); })) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<bool>(
|
|
mutation,
|
|
"use_gravity",
|
|
[&view](bool value) { view.SetUseGravity(value); })) {
|
|
return true;
|
|
}
|
|
return TryApplyMutationValue<bool>(
|
|
mutation,
|
|
"enable_ccd",
|
|
[&view](bool value) { view.SetEnableCCD(value); });
|
|
}
|
|
|
|
bool ApplyVolumeRendererComponentMutation(
|
|
::XCEngine::Components::VolumeRendererComponent& component,
|
|
const EditorSceneComponentMutation& mutation) {
|
|
VolumeRendererComponentViewAdapter view(component);
|
|
if (TryApplyMutationValue<std::string>(
|
|
mutation,
|
|
"volume_field",
|
|
[&view](const std::string& value) {
|
|
if (value.empty()) {
|
|
view.ClearVolumeField();
|
|
} else {
|
|
view.SetVolumeFieldPath(value);
|
|
}
|
|
})) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<std::string>(
|
|
mutation,
|
|
"material",
|
|
[&view](const std::string& value) {
|
|
if (value.empty()) {
|
|
view.ClearMaterial();
|
|
} else {
|
|
view.SetMaterialPath(value);
|
|
}
|
|
})) {
|
|
return true;
|
|
}
|
|
if (TryApplyMutationValue<bool>(
|
|
mutation,
|
|
"cast_shadows",
|
|
[&view](bool value) { view.SetCastShadows(value); })) {
|
|
return true;
|
|
}
|
|
return TryApplyMutationValue<bool>(
|
|
mutation,
|
|
"receive_shadows",
|
|
[&view](bool value) { view.SetReceiveShadows(value); });
|
|
}
|
|
|
|
Component* ResolveComponent(
|
|
const GameObject& gameObject,
|
|
std::string_view componentId) {
|
|
std::string typeName = {};
|
|
std::size_t ordinal = 0u;
|
|
if (!ParseEditorComponentId(componentId, typeName, ordinal)) {
|
|
return nullptr;
|
|
}
|
|
|
|
std::size_t currentOrdinal = 0u;
|
|
for (const Component* component : gameObject.GetComponents<Component>()) {
|
|
if (component == nullptr || component->GetName() != typeName) {
|
|
continue;
|
|
}
|
|
|
|
if (currentOrdinal == ordinal) {
|
|
return const_cast<Component*>(component);
|
|
}
|
|
|
|
++currentOrdinal;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
EditorSceneComponentDescriptor BuildComponentDescriptor(
|
|
const Component& component,
|
|
std::size_t ordinal) {
|
|
EditorSceneComponentDescriptor descriptor = {};
|
|
descriptor.typeName = component.GetName();
|
|
descriptor.componentId =
|
|
BuildEditorComponentId(descriptor.typeName, ordinal);
|
|
descriptor.view = CreateComponentView(const_cast<Component&>(component));
|
|
descriptor.removable =
|
|
component.GetGameObject() != nullptr &&
|
|
component.GetGameObject()->GetTransform() != &component;
|
|
return descriptor;
|
|
}
|
|
|
|
EditorSceneComponentDescriptor ResolveComponentDescriptor(
|
|
const GameObject& gameObject,
|
|
std::string_view componentId) {
|
|
Component* component = ResolveComponent(gameObject, componentId);
|
|
if (component == nullptr) {
|
|
return {};
|
|
}
|
|
|
|
std::string typeName = {};
|
|
std::size_t ordinal = 0u;
|
|
if (!ParseEditorComponentId(componentId, typeName, ordinal)) {
|
|
return {};
|
|
}
|
|
|
|
return BuildComponentDescriptor(*component, ordinal);
|
|
}
|
|
|
|
std::size_t ResolveVisibleMaterialSlotCount(const GameObject& gameObject) {
|
|
std::size_t slotCount = 1u;
|
|
if (const auto* meshRenderer =
|
|
gameObject.GetComponent<::XCEngine::Components::MeshRendererComponent>();
|
|
meshRenderer != nullptr) {
|
|
slotCount = (std::max)(slotCount, meshRenderer->GetMaterialCount());
|
|
}
|
|
|
|
if (const auto* meshFilter =
|
|
gameObject.GetComponent<::XCEngine::Components::MeshFilterComponent>();
|
|
meshFilter != nullptr) {
|
|
::XCEngine::Resources::Mesh* mesh = meshFilter->GetMesh();
|
|
if (mesh != nullptr && mesh->IsValid()) {
|
|
slotCount = (std::max)(
|
|
slotCount,
|
|
static_cast<std::size_t>(mesh->GetMaterials().Size()));
|
|
const auto& sections = mesh->GetSections();
|
|
for (std::size_t sectionIndex = 0u;
|
|
sectionIndex < sections.Size();
|
|
++sectionIndex) {
|
|
slotCount = (std::max)(
|
|
slotCount,
|
|
static_cast<std::size_t>(sections[sectionIndex].materialID) + 1u);
|
|
}
|
|
}
|
|
}
|
|
|
|
return slotCount;
|
|
}
|
|
|
|
EditorSceneObjectSnapshot BuildObjectSnapshot(const GameObject& gameObject) {
|
|
EditorSceneObjectSnapshot snapshot = {};
|
|
snapshot.itemId = MakeEditorGameObjectItemId(gameObject.GetID());
|
|
snapshot.objectId = gameObject.GetID();
|
|
snapshot.displayName = ResolveGameObjectDisplayName(gameObject);
|
|
snapshot.visibleMaterialSlotCount = ResolveVisibleMaterialSlotCount(gameObject);
|
|
|
|
const std::vector<const Component*> components =
|
|
gameObject.GetComponents<Component>();
|
|
snapshot.componentTypeNames.reserve(components.size());
|
|
for (const Component* component : components) {
|
|
if (component == nullptr) {
|
|
continue;
|
|
}
|
|
|
|
snapshot.componentTypeNames.push_back(component->GetName());
|
|
}
|
|
|
|
return snapshot;
|
|
}
|
|
|
|
Vector3 ResolveViewportSelectionCenterWorldPosition(const GameObject& gameObject) {
|
|
const TransformComponent* transform = gameObject.GetTransform();
|
|
if (transform == nullptr) {
|
|
return Vector3::Zero();
|
|
}
|
|
|
|
if (const auto* meshFilter =
|
|
gameObject.GetComponent<::XCEngine::Components::MeshFilterComponent>();
|
|
meshFilter != nullptr) {
|
|
::XCEngine::Resources::Mesh* mesh = meshFilter->GetMesh();
|
|
if (mesh != nullptr && mesh->IsValid()) {
|
|
return transform->TransformPoint(mesh->GetBounds().center);
|
|
}
|
|
}
|
|
|
|
return transform->GetPosition();
|
|
}
|
|
|
|
std::optional<EditorSceneViewportSelectionSnapshot> BuildViewportSelectionSnapshot(
|
|
const GameObject& gameObject) {
|
|
const TransformComponent* transform = gameObject.GetTransform();
|
|
if (transform == nullptr) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
EditorSceneViewportSelectionSnapshot snapshot = {};
|
|
snapshot.valid = true;
|
|
snapshot.objectId = gameObject.GetID();
|
|
snapshot.itemId = MakeEditorGameObjectItemId(gameObject.GetID());
|
|
snapshot.displayName = ResolveGameObjectDisplayName(gameObject);
|
|
snapshot.worldPosition = transform->GetPosition();
|
|
snapshot.worldRotation = transform->GetRotation();
|
|
snapshot.localScale = transform->GetLocalScale();
|
|
snapshot.centerWorldPosition = ResolveViewportSelectionCenterWorldPosition(gameObject);
|
|
return snapshot;
|
|
}
|
|
|
|
bool PopulateViewportHelperTransform(
|
|
const GameObject& gameObject,
|
|
EditorSceneViewportHelperSnapshot& snapshot) {
|
|
const TransformComponent* transform = gameObject.GetTransform();
|
|
if (transform == nullptr || !gameObject.IsActiveInHierarchy()) {
|
|
return false;
|
|
}
|
|
|
|
snapshot.objectId = gameObject.GetID();
|
|
snapshot.worldPosition = transform->GetPosition();
|
|
snapshot.worldForward = transform->GetForward();
|
|
snapshot.worldRight = transform->GetRight();
|
|
snapshot.worldUp = transform->GetUp();
|
|
return true;
|
|
}
|
|
|
|
std::optional<EditorSceneViewportHelperSnapshot> BuildViewportCameraHelperSnapshot(
|
|
const GameObject& gameObject,
|
|
const ::XCEngine::Components::CameraComponent& camera) {
|
|
if (!camera.IsEnabled()) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
EditorSceneViewportHelperSnapshot snapshot = {};
|
|
if (!PopulateViewportHelperTransform(gameObject, snapshot)) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
snapshot.kind = EditorSceneViewportHelperKind::Camera;
|
|
snapshot.cameraProjectionType =
|
|
ToEditorCameraProjectionType(camera.GetProjectionType());
|
|
snapshot.cameraFieldOfViewDegrees = camera.GetFieldOfView();
|
|
snapshot.cameraOrthographicSize = camera.GetOrthographicSize();
|
|
snapshot.nearClipPlane = camera.GetNearClipPlane();
|
|
snapshot.farClipPlane = camera.GetFarClipPlane();
|
|
return snapshot;
|
|
}
|
|
|
|
std::optional<EditorSceneViewportHelperSnapshot> BuildViewportLightHelperSnapshot(
|
|
const GameObject& gameObject,
|
|
const LightComponent& light) {
|
|
if (!light.IsEnabled()) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
EditorSceneViewportHelperSnapshot snapshot = {};
|
|
if (!PopulateViewportHelperTransform(gameObject, snapshot)) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
switch (light.GetLightType()) {
|
|
case ::XCEngine::Components::LightType::Directional:
|
|
snapshot.kind = EditorSceneViewportHelperKind::DirectionalLight;
|
|
break;
|
|
case ::XCEngine::Components::LightType::Point:
|
|
snapshot.kind = EditorSceneViewportHelperKind::PointLight;
|
|
break;
|
|
case ::XCEngine::Components::LightType::Spot:
|
|
snapshot.kind = EditorSceneViewportHelperKind::SpotLight;
|
|
break;
|
|
default:
|
|
return std::nullopt;
|
|
}
|
|
|
|
snapshot.lightRange = light.GetRange();
|
|
snapshot.lightSpotAngle = light.GetSpotAngle();
|
|
return snapshot;
|
|
}
|
|
|
|
std::vector<EditorSceneViewportHelperSnapshot> BuildViewportHelperSnapshotsForGameObject(
|
|
const GameObject& gameObject) {
|
|
std::vector<EditorSceneViewportHelperSnapshot> snapshots = {};
|
|
|
|
if (const auto* camera =
|
|
gameObject.GetComponent<::XCEngine::Components::CameraComponent>();
|
|
camera != nullptr) {
|
|
if (const std::optional<EditorSceneViewportHelperSnapshot> snapshot =
|
|
BuildViewportCameraHelperSnapshot(gameObject, *camera);
|
|
snapshot.has_value()) {
|
|
snapshots.push_back(*snapshot);
|
|
}
|
|
}
|
|
|
|
if (const LightComponent* light = gameObject.GetComponent<LightComponent>();
|
|
light != nullptr) {
|
|
if (const std::optional<EditorSceneViewportHelperSnapshot> snapshot =
|
|
BuildViewportLightHelperSnapshot(gameObject, *light);
|
|
snapshot.has_value()) {
|
|
snapshots.push_back(*snapshot);
|
|
}
|
|
}
|
|
|
|
return snapshots;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
std::optional<EditorSceneObjectSnapshot> GetObjectSnapshot(
|
|
std::string_view itemId) const override {
|
|
const GameObject* gameObject = FindGameObject(itemId);
|
|
return gameObject != nullptr
|
|
? std::optional<EditorSceneObjectSnapshot>(BuildObjectSnapshot(*gameObject))
|
|
: std::nullopt;
|
|
}
|
|
|
|
GameObject* FindGameObject(std::string_view itemId) const {
|
|
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;
|
|
}
|
|
|
|
std::vector<EditorSceneViewportIconSnapshot> BuildViewportIconSnapshots()
|
|
const override {
|
|
std::vector<EditorSceneViewportIconSnapshot> snapshots = {};
|
|
Scene* scene = ResolvePrimaryScene(m_sceneManager);
|
|
if (scene == nullptr) {
|
|
return snapshots;
|
|
}
|
|
|
|
for (auto* camera : scene->FindObjectsOfType<::XCEngine::Components::CameraComponent>()) {
|
|
if (camera == nullptr || !camera->IsEnabled()) {
|
|
continue;
|
|
}
|
|
|
|
GameObject* gameObject = camera->GetGameObject();
|
|
TransformComponent* transform =
|
|
gameObject != nullptr ? gameObject->GetTransform() : nullptr;
|
|
if (gameObject == nullptr ||
|
|
transform == nullptr ||
|
|
!gameObject->IsActiveInHierarchy()) {
|
|
continue;
|
|
}
|
|
|
|
EditorSceneViewportIconSnapshot snapshot = {};
|
|
snapshot.entityId = gameObject->GetID();
|
|
snapshot.kind = EditorSceneViewportIconKind::Camera;
|
|
snapshot.worldPosition = transform->GetPosition();
|
|
snapshots.push_back(std::move(snapshot));
|
|
}
|
|
|
|
for (LightComponent* light : scene->FindObjectsOfType<LightComponent>()) {
|
|
if (light == nullptr || !light->IsEnabled()) {
|
|
continue;
|
|
}
|
|
|
|
GameObject* gameObject = light->GetGameObject();
|
|
TransformComponent* transform =
|
|
gameObject != nullptr ? gameObject->GetTransform() : nullptr;
|
|
if (gameObject == nullptr ||
|
|
transform == nullptr ||
|
|
!gameObject->IsActiveInHierarchy()) {
|
|
continue;
|
|
}
|
|
|
|
EditorSceneViewportIconSnapshot snapshot = {};
|
|
snapshot.entityId = gameObject->GetID();
|
|
snapshot.kind = light->GetLightType() == ::XCEngine::Components::LightType::Point
|
|
? EditorSceneViewportIconKind::PointLight
|
|
: light->GetLightType() == ::XCEngine::Components::LightType::Spot
|
|
? EditorSceneViewportIconKind::SpotLight
|
|
: EditorSceneViewportIconKind::DirectionalLight;
|
|
snapshot.worldPosition = transform->GetPosition();
|
|
snapshots.push_back(std::move(snapshot));
|
|
}
|
|
|
|
return snapshots;
|
|
}
|
|
|
|
std::optional<EditorSceneViewportSelectionSnapshot>
|
|
BuildViewportSelectionSnapshot(EditorSceneObjectId objectId) const override {
|
|
Scene* scene = ResolvePrimaryScene(m_sceneManager);
|
|
GameObject* gameObject =
|
|
scene != nullptr ? scene->FindByID(objectId) : nullptr;
|
|
return gameObject != nullptr
|
|
? ::XCEngine::UI::Editor::App::BuildViewportSelectionSnapshot(*gameObject)
|
|
: std::nullopt;
|
|
}
|
|
|
|
std::vector<EditorSceneViewportHelperSnapshot> BuildViewportHelperSnapshots(
|
|
EditorSceneObjectId objectId) const override {
|
|
Scene* scene = ResolvePrimaryScene(m_sceneManager);
|
|
GameObject* gameObject =
|
|
scene != nullptr ? scene->FindByID(objectId) : nullptr;
|
|
return gameObject != nullptr
|
|
? BuildViewportHelperSnapshotsForGameObject(*gameObject)
|
|
: std::vector<EditorSceneViewportHelperSnapshot>{};
|
|
}
|
|
|
|
bool RemoveComponent(
|
|
std::string_view itemId,
|
|
std::string_view componentId) override {
|
|
GameObject* gameObject = FindGameObject(itemId);
|
|
if (gameObject == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
Component* component = ResolveComponent(*gameObject, componentId);
|
|
const EditorSceneComponentDescriptor descriptor =
|
|
ResolveComponentDescriptor(*gameObject, componentId);
|
|
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 ApplyComponentMutation(
|
|
std::string_view itemId,
|
|
const EditorSceneComponentMutation& mutation) override {
|
|
if (!mutation.IsValid()) {
|
|
return false;
|
|
}
|
|
|
|
GameObject* gameObject = FindGameObject(itemId);
|
|
if (gameObject == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
Component* component = ResolveComponent(*gameObject, mutation.componentId);
|
|
if (component == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
if (auto* camera =
|
|
dynamic_cast<::XCEngine::Components::CameraComponent*>(component);
|
|
camera != nullptr) {
|
|
return ApplyCameraComponentMutation(*camera, mutation);
|
|
}
|
|
if (auto* light = dynamic_cast<LightComponent*>(component);
|
|
light != nullptr) {
|
|
return ApplyLightComponentMutation(*light, mutation);
|
|
}
|
|
if (auto* collider =
|
|
dynamic_cast<::XCEngine::Components::BoxColliderComponent*>(component);
|
|
collider != nullptr) {
|
|
return ApplyBoxColliderComponentMutation(*collider, mutation);
|
|
}
|
|
if (auto* collider =
|
|
dynamic_cast<::XCEngine::Components::CapsuleColliderComponent*>(component);
|
|
collider != nullptr) {
|
|
return ApplyCapsuleColliderComponentMutation(*collider, mutation);
|
|
}
|
|
if (auto* collider =
|
|
dynamic_cast<::XCEngine::Components::SphereColliderComponent*>(component);
|
|
collider != nullptr) {
|
|
return ApplySphereColliderComponentMutation(*collider, mutation);
|
|
}
|
|
if (auto* source =
|
|
dynamic_cast<::XCEngine::Components::AudioSourceComponent*>(component);
|
|
source != nullptr) {
|
|
return ApplyAudioSourceComponentMutation(*source, mutation);
|
|
}
|
|
if (auto* listener =
|
|
dynamic_cast<::XCEngine::Components::AudioListenerComponent*>(component);
|
|
listener != nullptr) {
|
|
return ApplyAudioListenerComponentMutation(*listener, mutation);
|
|
}
|
|
if (auto* meshFilter =
|
|
dynamic_cast<::XCEngine::Components::MeshFilterComponent*>(component);
|
|
meshFilter != nullptr) {
|
|
return ApplyMeshFilterComponentMutation(*meshFilter, mutation);
|
|
}
|
|
if (auto* meshRenderer =
|
|
dynamic_cast<::XCEngine::Components::MeshRendererComponent*>(component);
|
|
meshRenderer != nullptr) {
|
|
return ApplyMeshRendererComponentMutation(*meshRenderer, mutation);
|
|
}
|
|
if (auto* rigidbody =
|
|
dynamic_cast<::XCEngine::Components::RigidbodyComponent*>(component);
|
|
rigidbody != nullptr) {
|
|
return ApplyRigidbodyComponentMutation(*rigidbody, mutation);
|
|
}
|
|
if (auto* volumeRenderer =
|
|
dynamic_cast<::XCEngine::Components::VolumeRendererComponent*>(component);
|
|
volumeRenderer != nullptr) {
|
|
return ApplyVolumeRendererComponentMutation(*volumeRenderer, mutation);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
return dynamic_cast<TransformComponent*>(
|
|
ResolveComponent(*gameObject, componentId));
|
|
}
|
|
|
|
SceneManager& m_sceneManager;
|
|
ResourceManager& m_resourceManager;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
std::unique_ptr<EditorSceneBackend> CreateEngineEditorSceneBackend(
|
|
SceneManager& sceneManager,
|
|
ResourceManager& resourceManager) {
|
|
return std::make_unique<EngineEditorSceneBackend>(
|
|
sceneManager,
|
|
resourceManager);
|
|
}
|
|
|
|
} // namespace XCEngine::UI::Editor::App
|