Files
XCEngine/tests/editor/test_scene_viewport_overlay_providers.cpp

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