369 lines
15 KiB
C++
369 lines
15 KiB
C++
#include <gtest/gtest.h>
|
|
|
|
#include "Core/EditorContext.h"
|
|
#include "Viewport/SceneViewportOverlayBuilder.h"
|
|
#include "Viewport/SceneViewportOverlayHandleBuilder.h"
|
|
#include "Viewport/SceneViewportOverlayProviders.h"
|
|
|
|
#include <XCEngine/Components/CameraComponent.h>
|
|
#include <XCEngine/Components/LightComponent.h>
|
|
#include <XCEngine/Core/Math/Color.h>
|
|
#include <XCEngine/Core/Math/Vector3.h>
|
|
|
|
#include <algorithm>
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
namespace XCEngine::Editor {
|
|
namespace {
|
|
|
|
class MarkerOverlayProvider final : public ISceneViewportOverlayProvider {
|
|
public:
|
|
explicit MarkerOverlayProvider(float marker)
|
|
: m_marker(marker) {
|
|
}
|
|
|
|
const char* GetName() const override {
|
|
return "MarkerOverlayProvider";
|
|
}
|
|
|
|
void AppendOverlay(
|
|
const SceneViewportOverlayBuildContext& context,
|
|
SceneViewportOverlayFrameData& frameData) const override {
|
|
if (!context.IsValid()) {
|
|
return;
|
|
}
|
|
|
|
SceneViewportOverlayLinePrimitive& line = frameData.worldLines.emplace_back();
|
|
line.startWorld = Math::Vector3(m_marker, 0.0f, 0.0f);
|
|
line.endWorld = Math::Vector3(m_marker, 1.0f, 0.0f);
|
|
line.color = Math::Color::White();
|
|
}
|
|
|
|
private:
|
|
float m_marker = 0.0f;
|
|
};
|
|
|
|
SceneViewportOverlayData CreateValidOverlay() {
|
|
SceneViewportOverlayData overlay = {};
|
|
overlay.valid = true;
|
|
overlay.cameraPosition = Math::Vector3::Zero();
|
|
overlay.cameraForward = Math::Vector3::Forward();
|
|
overlay.cameraRight = Math::Vector3::Right();
|
|
overlay.cameraUp = Math::Vector3::Up();
|
|
overlay.verticalFovDegrees = 60.0f;
|
|
overlay.nearClipPlane = 0.1f;
|
|
overlay.farClipPlane = 1000.0f;
|
|
overlay.orbitDistance = 6.0f;
|
|
return overlay;
|
|
}
|
|
|
|
SceneViewportOverlayBuildContext CreateBuildContext(
|
|
EditorContext& context,
|
|
const SceneViewportOverlayData& overlay,
|
|
const std::vector<uint64_t>& selectedObjectIds,
|
|
uint32_t viewportWidth = 1280u,
|
|
uint32_t viewportHeight = 720u,
|
|
const SceneViewportTransformGizmoOverlayState* transformGizmoOverlayState = nullptr) {
|
|
return {
|
|
&context,
|
|
context.GetSceneManager().GetScene(),
|
|
&overlay,
|
|
viewportWidth,
|
|
viewportHeight,
|
|
&selectedObjectIds,
|
|
transformGizmoOverlayState
|
|
};
|
|
}
|
|
|
|
SceneViewportTransformGizmoOverlayState CreateMoveGizmoOverlayState(uint64_t entityId = 99u) {
|
|
SceneViewportTransformGizmoOverlayState state = {};
|
|
state.hasMoveGizmo = true;
|
|
state.moveEntityId = entityId;
|
|
state.moveGizmo.visible = true;
|
|
|
|
SceneViewportMoveGizmoHandleDrawData& handle = state.moveGizmo.handles[0];
|
|
handle.axis = SceneViewportGizmoAxis::X;
|
|
handle.start = Math::Vector2(24.0f, 48.0f);
|
|
handle.end = Math::Vector2(104.0f, 48.0f);
|
|
handle.color = Math::Color(0.91f, 0.09f, 0.05f, 1.0f);
|
|
handle.visible = true;
|
|
|
|
return state;
|
|
}
|
|
|
|
bool ContainsSpriteKind(
|
|
const SceneViewportOverlayFrameData& frameData,
|
|
SceneViewportOverlaySpriteTextureKind textureKind) {
|
|
return std::any_of(
|
|
frameData.worldSprites.begin(),
|
|
frameData.worldSprites.end(),
|
|
[textureKind](const SceneViewportOverlaySpritePrimitive& sprite) {
|
|
return sprite.textureKind == textureKind;
|
|
});
|
|
}
|
|
|
|
const SceneViewportOverlayLinePrimitive* FindLineStartingAt(
|
|
const SceneViewportOverlayFrameData& frameData,
|
|
const Math::Vector3& position) {
|
|
const auto matchesPosition = [&position](const SceneViewportOverlayLinePrimitive& line) {
|
|
return (line.startWorld - position).SqrMagnitude() <= 1e-4f;
|
|
};
|
|
|
|
const auto it = std::find_if(
|
|
frameData.worldLines.begin(),
|
|
frameData.worldLines.end(),
|
|
matchesPosition);
|
|
return it != frameData.worldLines.end() ? &(*it) : nullptr;
|
|
}
|
|
|
|
TEST(SceneViewportOverlayProviderRegistryTest, AppendsProvidersInRegistrationOrder) {
|
|
EditorContext context;
|
|
context.GetSceneManager().NewScene("Overlay Provider Registry");
|
|
|
|
const SceneViewportOverlayData overlay = CreateValidOverlay();
|
|
const std::vector<uint64_t> selectedObjectIds = {};
|
|
const SceneViewportOverlayBuildContext buildContext =
|
|
CreateBuildContext(context, overlay, selectedObjectIds);
|
|
|
|
SceneViewportOverlayProviderRegistry registry;
|
|
registry.AddProvider(std::make_unique<MarkerOverlayProvider>(1.0f));
|
|
registry.AddProvider(std::make_unique<MarkerOverlayProvider>(2.0f));
|
|
|
|
ASSERT_EQ(registry.GetProviderCount(), 2u);
|
|
ASSERT_NE(registry.GetProvider(0u), nullptr);
|
|
ASSERT_NE(registry.GetProvider(1u), nullptr);
|
|
|
|
SceneViewportOverlayFrameData frameData = {};
|
|
frameData.overlay = overlay;
|
|
registry.AppendOverlay(buildContext, frameData);
|
|
|
|
ASSERT_EQ(frameData.worldLines.size(), 2u);
|
|
EXPECT_FLOAT_EQ(frameData.worldLines[0].startWorld.x, 1.0f);
|
|
EXPECT_FLOAT_EQ(frameData.worldLines[1].startWorld.x, 2.0f);
|
|
}
|
|
|
|
TEST(SceneViewportOverlayProviderRegistryTest, CameraProviderBuildsSceneIconAndSelectedFrustum) {
|
|
EditorContext context;
|
|
context.GetSceneManager().NewScene("Camera Overlay Provider");
|
|
|
|
auto* cameraEntity = context.GetSceneManager().CreateEntity("SceneCamera");
|
|
ASSERT_NE(cameraEntity, nullptr);
|
|
cameraEntity->GetTransform()->SetPosition(Math::Vector3(0.0f, 0.0f, 5.0f));
|
|
|
|
auto* camera = cameraEntity->AddComponent<Components::CameraComponent>();
|
|
ASSERT_NE(camera, nullptr);
|
|
camera->SetNearClipPlane(0.3f);
|
|
camera->SetFarClipPlane(20.0f);
|
|
|
|
const SceneViewportOverlayData overlay = CreateValidOverlay();
|
|
const std::vector<uint64_t> selectedObjectIds = { cameraEntity->GetID() };
|
|
const SceneViewportOverlayBuildContext buildContext =
|
|
CreateBuildContext(context, overlay, selectedObjectIds);
|
|
|
|
auto provider = CreateSceneViewportCameraOverlayProvider();
|
|
ASSERT_NE(provider, nullptr);
|
|
|
|
SceneViewportOverlayFrameData frameData = {};
|
|
frameData.overlay = overlay;
|
|
provider->AppendOverlay(buildContext, frameData);
|
|
|
|
ASSERT_EQ(frameData.worldSprites.size(), 1u);
|
|
EXPECT_EQ(frameData.worldSprites[0].textureKind, SceneViewportOverlaySpriteTextureKind::Camera);
|
|
ASSERT_EQ(frameData.handleRecords.size(), 1u);
|
|
EXPECT_EQ(frameData.handleRecords[0].entityId, cameraEntity->GetID());
|
|
EXPECT_EQ(frameData.worldLines.size(), 12u);
|
|
}
|
|
|
|
TEST(SceneViewportOverlayProviderRegistryTest, LightProviderBuildsSceneIconAndSelectedDirectionalHelper) {
|
|
EditorContext context;
|
|
context.GetSceneManager().NewScene("Light Overlay Provider");
|
|
|
|
auto* lightEntity = context.GetSceneManager().CreateEntity("DirectionalLight");
|
|
ASSERT_NE(lightEntity, nullptr);
|
|
lightEntity->GetTransform()->SetPosition(Math::Vector3(0.0f, 0.0f, 5.0f));
|
|
|
|
auto* light = lightEntity->AddComponent<Components::LightComponent>();
|
|
ASSERT_NE(light, nullptr);
|
|
light->SetLightType(Components::LightType::Directional);
|
|
|
|
const SceneViewportOverlayData overlay = CreateValidOverlay();
|
|
const std::vector<uint64_t> selectedObjectIds = { lightEntity->GetID() };
|
|
const SceneViewportOverlayBuildContext buildContext =
|
|
CreateBuildContext(context, overlay, selectedObjectIds);
|
|
|
|
auto provider = CreateSceneViewportLightOverlayProvider();
|
|
ASSERT_NE(provider, nullptr);
|
|
|
|
SceneViewportOverlayFrameData frameData = {};
|
|
frameData.overlay = overlay;
|
|
provider->AppendOverlay(buildContext, frameData);
|
|
|
|
ASSERT_EQ(frameData.worldSprites.size(), 1u);
|
|
EXPECT_EQ(frameData.worldSprites[0].textureKind, SceneViewportOverlaySpriteTextureKind::DirectionalLight);
|
|
ASSERT_EQ(frameData.handleRecords.size(), 1u);
|
|
EXPECT_EQ(frameData.handleRecords[0].entityId, lightEntity->GetID());
|
|
EXPECT_GT(frameData.worldLines.size(), 0u);
|
|
|
|
const SceneViewportOverlayLinePrimitive* connectorLine =
|
|
FindLineStartingAt(frameData, lightEntity->GetTransform()->GetPosition());
|
|
ASSERT_NE(connectorLine, nullptr);
|
|
EXPECT_GT(connectorLine->endWorld.z, connectorLine->startWorld.z);
|
|
}
|
|
|
|
TEST(SceneViewportOverlayProviderRegistryTest, LightProviderBuildsSelectedPointLightHelper) {
|
|
EditorContext context;
|
|
context.GetSceneManager().NewScene("Point Light Overlay Provider");
|
|
|
|
auto* lightEntity = context.GetSceneManager().CreateEntity("PointLight");
|
|
ASSERT_NE(lightEntity, nullptr);
|
|
lightEntity->GetTransform()->SetPosition(Math::Vector3(0.0f, 0.0f, 12.0f));
|
|
|
|
auto* light = lightEntity->AddComponent<Components::LightComponent>();
|
|
ASSERT_NE(light, nullptr);
|
|
light->SetLightType(Components::LightType::Point);
|
|
light->SetRange(3.5f);
|
|
|
|
const SceneViewportOverlayData overlay = CreateValidOverlay();
|
|
const std::vector<uint64_t> selectedObjectIds = { lightEntity->GetID() };
|
|
const SceneViewportOverlayBuildContext buildContext =
|
|
CreateBuildContext(context, overlay, selectedObjectIds);
|
|
|
|
auto provider = CreateSceneViewportLightOverlayProvider();
|
|
ASSERT_NE(provider, nullptr);
|
|
|
|
SceneViewportOverlayFrameData frameData = {};
|
|
frameData.overlay = overlay;
|
|
provider->AppendOverlay(buildContext, frameData);
|
|
|
|
ASSERT_EQ(frameData.worldSprites.size(), 1u);
|
|
EXPECT_EQ(frameData.worldSprites[0].textureKind, SceneViewportOverlaySpriteTextureKind::PointLight);
|
|
ASSERT_EQ(frameData.handleRecords.size(), 1u);
|
|
EXPECT_EQ(frameData.handleRecords[0].entityId, lightEntity->GetID());
|
|
EXPECT_EQ(frameData.worldLines.size(), 96u);
|
|
EXPECT_NEAR(
|
|
(frameData.worldLines[0].startWorld - lightEntity->GetTransform()->GetPosition()).Magnitude(),
|
|
light->GetRange(),
|
|
1e-3f);
|
|
}
|
|
|
|
TEST(SceneViewportOverlayProviderRegistryTest, LightProviderBuildsSelectedSpotLightHelper) {
|
|
EditorContext context;
|
|
context.GetSceneManager().NewScene("Spot Light Overlay Provider");
|
|
|
|
auto* lightEntity = context.GetSceneManager().CreateEntity("SpotLight");
|
|
ASSERT_NE(lightEntity, nullptr);
|
|
lightEntity->GetTransform()->SetPosition(Math::Vector3(0.0f, 0.0f, 6.0f));
|
|
|
|
auto* light = lightEntity->AddComponent<Components::LightComponent>();
|
|
ASSERT_NE(light, nullptr);
|
|
light->SetLightType(Components::LightType::Spot);
|
|
light->SetRange(4.0f);
|
|
light->SetSpotAngle(30.0f);
|
|
|
|
const SceneViewportOverlayData overlay = CreateValidOverlay();
|
|
const std::vector<uint64_t> selectedObjectIds = { lightEntity->GetID() };
|
|
const SceneViewportOverlayBuildContext buildContext =
|
|
CreateBuildContext(context, overlay, selectedObjectIds);
|
|
|
|
auto provider = CreateSceneViewportLightOverlayProvider();
|
|
ASSERT_NE(provider, nullptr);
|
|
|
|
SceneViewportOverlayFrameData frameData = {};
|
|
frameData.overlay = overlay;
|
|
provider->AppendOverlay(buildContext, frameData);
|
|
|
|
ASSERT_EQ(frameData.worldSprites.size(), 1u);
|
|
EXPECT_EQ(frameData.worldSprites[0].textureKind, SceneViewportOverlaySpriteTextureKind::SpotLight);
|
|
ASSERT_EQ(frameData.handleRecords.size(), 1u);
|
|
EXPECT_EQ(frameData.handleRecords[0].entityId, lightEntity->GetID());
|
|
EXPECT_EQ(frameData.worldLines.size(), 37u);
|
|
|
|
const SceneViewportOverlayLinePrimitive* connectorLine =
|
|
FindLineStartingAt(frameData, lightEntity->GetTransform()->GetPosition());
|
|
ASSERT_NE(connectorLine, nullptr);
|
|
EXPECT_GT(connectorLine->endWorld.z, connectorLine->startWorld.z);
|
|
}
|
|
|
|
TEST(SceneViewportOverlayProviderRegistryTest, TransformGizmoProviderBuildsOverlayFromFormalState) {
|
|
EditorContext context;
|
|
context.GetSceneManager().NewScene("Transform Gizmo Overlay Provider");
|
|
|
|
const SceneViewportOverlayData overlay = CreateValidOverlay();
|
|
const std::vector<uint64_t> selectedObjectIds = {};
|
|
const SceneViewportTransformGizmoOverlayState gizmoState = CreateMoveGizmoOverlayState(73u);
|
|
const SceneViewportOverlayBuildContext buildContext =
|
|
CreateBuildContext(context, overlay, selectedObjectIds, 1280u, 720u, &gizmoState);
|
|
|
|
auto provider = CreateSceneViewportTransformGizmoOverlayProvider();
|
|
ASSERT_NE(provider, nullptr);
|
|
|
|
SceneViewportOverlayFrameData frameData = {};
|
|
frameData.overlay = overlay;
|
|
provider->AppendOverlay(buildContext, frameData);
|
|
|
|
ASSERT_EQ(frameData.handleRecords.size(), 1u);
|
|
EXPECT_EQ(frameData.handleRecords[0].kind, SceneViewportOverlayHandleKind::MoveAxis);
|
|
EXPECT_EQ(frameData.handleRecords[0].handleId, static_cast<uint64_t>(SceneViewportGizmoAxis::X));
|
|
EXPECT_EQ(frameData.handleRecords[0].entityId, 73u);
|
|
EXPECT_GT(frameData.screenTriangles.size(), 0u);
|
|
EXPECT_TRUE(frameData.worldLines.empty());
|
|
EXPECT_TRUE(frameData.worldSprites.empty());
|
|
}
|
|
|
|
TEST(
|
|
SceneViewportOverlayProviderRegistryTest,
|
|
OverlayBuilderUsesDefaultRegistryToAggregateCameraAndLightProviders) {
|
|
EditorContext context;
|
|
context.GetSceneManager().NewScene("Overlay Builder");
|
|
|
|
auto* cameraEntity = context.GetSceneManager().CreateEntity("SceneCamera");
|
|
ASSERT_NE(cameraEntity, nullptr);
|
|
cameraEntity->GetTransform()->SetPosition(Math::Vector3(-0.5f, 0.0f, 5.0f));
|
|
ASSERT_NE(cameraEntity->AddComponent<Components::CameraComponent>(), nullptr);
|
|
|
|
auto* lightEntity = context.GetSceneManager().CreateEntity("DirectionalLight");
|
|
ASSERT_NE(lightEntity, nullptr);
|
|
lightEntity->GetTransform()->SetPosition(Math::Vector3(0.5f, 0.0f, 5.0f));
|
|
auto* light = lightEntity->AddComponent<Components::LightComponent>();
|
|
ASSERT_NE(light, nullptr);
|
|
light->SetLightType(Components::LightType::Directional);
|
|
|
|
const SceneViewportOverlayData overlay = CreateValidOverlay();
|
|
const std::vector<uint64_t> selectedObjectIds = { cameraEntity->GetID(), lightEntity->GetID() };
|
|
|
|
SceneViewportOverlayBuilder builder;
|
|
const SceneViewportOverlayFrameData frameData =
|
|
builder.Build(context, overlay, 1280u, 720u, selectedObjectIds);
|
|
|
|
EXPECT_TRUE(frameData.overlay.valid);
|
|
EXPECT_EQ(frameData.worldSprites.size(), 2u);
|
|
EXPECT_EQ(frameData.handleRecords.size(), 2u);
|
|
EXPECT_TRUE(ContainsSpriteKind(frameData, SceneViewportOverlaySpriteTextureKind::Camera));
|
|
EXPECT_TRUE(ContainsSpriteKind(frameData, SceneViewportOverlaySpriteTextureKind::DirectionalLight));
|
|
EXPECT_GT(frameData.worldLines.size(), 12u);
|
|
}
|
|
|
|
TEST(
|
|
SceneViewportOverlayProviderRegistryTest,
|
|
OverlayBuilderAppendsFormalTransformGizmoProviderOutput) {
|
|
EditorContext context;
|
|
context.GetSceneManager().NewScene("Overlay Builder Transform Gizmo");
|
|
|
|
const SceneViewportOverlayData overlay = CreateValidOverlay();
|
|
const std::vector<uint64_t> selectedObjectIds = {};
|
|
const SceneViewportTransformGizmoOverlayState gizmoState = CreateMoveGizmoOverlayState(91u);
|
|
|
|
SceneViewportOverlayBuilder builder;
|
|
const SceneViewportOverlayFrameData frameData =
|
|
builder.Build(context, overlay, 1280u, 720u, selectedObjectIds, &gizmoState);
|
|
|
|
EXPECT_TRUE(frameData.overlay.valid);
|
|
ASSERT_EQ(frameData.handleRecords.size(), 1u);
|
|
EXPECT_EQ(frameData.handleRecords[0].entityId, 91u);
|
|
EXPECT_GT(frameData.screenTriangles.size(), 0u);
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace XCEngine::Editor
|