219 lines
11 KiB
C++
219 lines
11 KiB
C++
#include <filesystem>
|
|
#include <string>
|
|
|
|
#include <destrum/ObjectModel/Component.h>
|
|
#include <destrum/ObjectModel/GameObject.h>
|
|
#include <destrum/Scene/Scene.h>
|
|
#include <destrum/Scene/SceneManager.h>
|
|
#include <destrum/Serialization/ComponentFactory.h>
|
|
#include <destrum/Serialization/ComponentRegistry.h>
|
|
#include <destrum/Serialization/GlmJson.h>
|
|
#include <destrum/Serialization/SceneSerializer.h>
|
|
#include <destrum/Components/UI/Canvas.h>
|
|
#include <destrum/Components/UI/RectTransform.h>
|
|
#include <destrum/Components/UI/UIImage.h>
|
|
#include <destrum/Components/UI/UIText.h>
|
|
#include <destrum/Graphics/Managers/Renderer2D.h>
|
|
#include <destrum/Graphics/Resources/Font.h>
|
|
|
|
#include <components/CrosshairUI.h>
|
|
#include <components/FirstPersonController.h>
|
|
#include <components/WinGoal.h>
|
|
#include <components/WinScreenUI.h>
|
|
|
|
#include "TestUtils.h"
|
|
|
|
using namespace test;
|
|
|
|
namespace {
|
|
void testCanvasLayout()
|
|
{
|
|
Scene& scene = SceneManager::GetInstance().CreateScene("canvas");
|
|
GameObject* hud = scene.CreateGameObject("hud");
|
|
auto* canvas = hud->AddComponent<Canvas>();
|
|
const auto addChild = [&](const std::string& name, GameObject& parent) {
|
|
GameObject* child = scene.CreateGameObject(name);
|
|
child->GetTransform().SetParent(&parent.GetTransform(), false);
|
|
return child;
|
|
};
|
|
|
|
GameObject* panel = addChild("panel", *hud);
|
|
auto* panelRect = panel->AddComponent<RectTransform>();
|
|
panelRect->SetSize({100.0f, 50.0f});
|
|
panelRect->SetPosition({10.0f, 0.0f});
|
|
panel->AddComponent<UIImage>(glm::vec4{1.0f, 0.0f, 0.0f, 1.0f});
|
|
|
|
GameObject* fill = addChild("fill", *panel);
|
|
fill->AddComponent<UIImage>(glm::vec4{0.0f, 1.0f, 0.0f, 1.0f})->SetShape(UIImageShape::Ellipse);
|
|
|
|
GameObject* corner = addChild("corner", *hud);
|
|
auto* cornerRect = corner->AddComponent<RectTransform>();
|
|
cornerRect->SetAnchor({1.0f, 1.0f});
|
|
cornerRect->SetPivot({1.0f, 1.0f});
|
|
cornerRect->SetPosition({-20.0f, -20.0f});
|
|
cornerRect->SetSize({40.0f, 40.0f});
|
|
corner->AddComponent<UIImage>();
|
|
|
|
GameObject* hidden = addChild("hidden", *hud);
|
|
auto* hiddenRect = hidden->AddComponent<RectTransform>();
|
|
hiddenRect->SetSize({20.0f, 20.0f});
|
|
hidden->AddComponent<UIImage>();
|
|
hidden->SetActive(false);
|
|
|
|
GameObject* nested = addChild("nested", *hud);
|
|
nested->AddComponent<Canvas>();
|
|
nested->AddComponent<UIImage>();
|
|
scene.CommitPendingAdditions();
|
|
|
|
auto& r2d = Renderer2D::GetInstance();
|
|
r2d.Clear();
|
|
canvas->Draw({1920.0f, 1080.0f});
|
|
const auto& shapes = r2d.GetShapes();
|
|
check(shapes.size() == 3, "a canvas must draw its active children and skip inactive ones and nested canvases");
|
|
check(shapes[0].type == Shape2DType::Rect && shapes[0].center == glm::vec2{970.0f, 540.0f} &&
|
|
shapes[0].halfSize == glm::vec2{50.0f, 25.0f},
|
|
"a centered rect must be placed around its anchor, offset by its position");
|
|
check(shapes[1].type == Shape2DType::Ellipse && shapes[1].center == shapes[0].center &&
|
|
shapes[1].halfSize == shapes[0].halfSize,
|
|
"an object without a RectTransform must fill its parent and draw after it");
|
|
check(shapes[2].center == glm::vec2{1880.0f, 1040.0f},
|
|
"anchors and pivot must place a rect relative to the screen corner");
|
|
check(panelRect->GetScreenRect().min == glm::vec2{920.0f, 515.0f},
|
|
"the canvas must store where it drew each rect");
|
|
check(hiddenRect->GetScreenRect().size == glm::vec2{20.0f, 20.0f},
|
|
"hidden objects must still be laid out so the editor can show them");
|
|
|
|
r2d.Clear();
|
|
canvas->Draw({3840.0f, 2160.0f});
|
|
check(r2d.GetShapes().size() == 3 && r2d.GetShapes()[0].halfSize == glm::vec2{100.0f, 50.0f} &&
|
|
r2d.GetShapes()[0].center == glm::vec2{1940.0f, 1080.0f},
|
|
"sizes and offsets must scale with the screen height");
|
|
|
|
canvas->SetScaleMode(CanvasScaleMode::ConstantPixelSize);
|
|
r2d.Clear();
|
|
canvas->Draw({3840.0f, 2160.0f});
|
|
check(r2d.GetShapes()[0].halfSize == glm::vec2{50.0f, 25.0f}, "a constant pixel size canvas must not scale");
|
|
|
|
check(Canvas::FindFor(*fill) == canvas && Canvas::FindFor(*nested) == nested->GetComponent<Canvas>(),
|
|
"FindFor must return the closest canvas on the object or its parents");
|
|
check(cornerRect->GetParentRect().size == glm::vec2{3840.0f, 2160.0f},
|
|
"the canvas must store the parent rect each rect was laid out in");
|
|
|
|
cornerRect->ApplyAnchorPreset({0.0f, -1.0f});
|
|
check(cornerRect->GetAnchorMin() == glm::vec2{0.0f, 0.0f} && cornerRect->GetAnchorMax() == glm::vec2{0.0f, 1.0f} &&
|
|
cornerRect->GetPivot() == glm::vec2{0.0f, 0.5f} && cornerRect->GetPosition() == glm::vec2{0.0f} &&
|
|
cornerRect->GetSize() == glm::vec2{40.0f, 0.0f},
|
|
"an anchor preset must pin or stretch each axis and keep the size on pinned axes");
|
|
|
|
panelRect->Stretch();
|
|
check(panelRect->ComputeRect(UIRect{.min = {10.0f, 20.0f}, .size = {30.0f, 40.0f}}, 2.0f).size == glm::vec2{30.0f, 40.0f},
|
|
"a stretched rect must fill its parent");
|
|
r2d.Clear();
|
|
|
|
SceneManager::GetInstance().Destroy();
|
|
}
|
|
|
|
void testUITextPlacement()
|
|
{
|
|
Scene& scene = SceneManager::GetInstance().CreateScene("ui text");
|
|
auto* text = scene.CreateGameObject("text")->AddComponent<UIText>("hi", 20.0f);
|
|
const UIRect rect{.min = {100.0f, 100.0f}, .size = {200.0f, 100.0f}};
|
|
|
|
check(text->PlaceText(rect, {50.0f, 20.0f}) == glm::vec2{175.0f, 140.0f}, "text must be centered by default");
|
|
text->SetAlignment(UITextAlign::End, UITextAlign::Start);
|
|
check(text->PlaceText(rect, {50.0f, 20.0f}) == glm::vec2{250.0f, 100.0f}, "text must follow its alignment");
|
|
|
|
SceneManager::GetInstance().Destroy();
|
|
}
|
|
|
|
void testHudRoundTrip()
|
|
{
|
|
RegisterEngineComponents();
|
|
ComponentFactory::Register<CrosshairUI>("CrosshairUI");
|
|
ComponentFactory::Register<WinScreenUI>("WinScreenUI");
|
|
ComponentFactory::Register<WinGoal>("WinGoal");
|
|
ComponentFactory::Register<FirstPersonController>("FirstPersonController");
|
|
|
|
Scene& scene = SceneManager::GetInstance().CreateScene("hud");
|
|
GameObject* player = scene.CreateGameObject("player");
|
|
player->AddComponent<FirstPersonController>();
|
|
GameObject* goal = scene.CreateGameObject("goal");
|
|
auto* winGoal = goal->AddComponent<WinGoal>();
|
|
|
|
GameObject* hud = scene.CreateGameObject("hud");
|
|
hud->AddComponent<Canvas>()->SetReferenceHeight(720.0f);
|
|
GameObject* crosshair = scene.CreateGameObject("crosshair");
|
|
crosshair->GetTransform().SetParent(&hud->GetTransform(), false);
|
|
auto* crosshairRect = crosshair->AddComponent<RectTransform>();
|
|
crosshairRect->SetAnchors({0.25f, 0.5f}, {0.75f, 0.5f});
|
|
crosshair->AddComponent<UIImage>()->SetOutline(3.0f);
|
|
GameObject* screen = scene.CreateGameObject("screen");
|
|
screen->GetTransform().SetParent(&hud->GetTransform(), false);
|
|
auto* label = screen->AddComponent<UIText>("YOU WIN", 64.0f);
|
|
label->SetAlignment(UITextAlign::Start, UITextAlign::End);
|
|
|
|
auto* crosshairUi = hud->AddComponent<CrosshairUI>();
|
|
crosshairUi->SetPlayer(*player);
|
|
crosshairUi->SetCrosshair(*crosshair);
|
|
auto* winScreenUi = hud->AddComponent<WinScreenUI>();
|
|
winScreenUi->SetGoal(*goal);
|
|
winScreenUi->SetScreen(*screen);
|
|
scene.CommitPendingAdditions();
|
|
|
|
crosshairUi->LateUpdate(0.0f);
|
|
winScreenUi->LateUpdate(0.0f);
|
|
check(!crosshair->IsActive(), "the crosshair must hide while the player isn't possessed");
|
|
check(!screen->IsActive(), "the win screen must hide until the goal is reached");
|
|
winGoal->Activate(*player);
|
|
winScreenUi->LateUpdate(0.0f);
|
|
check(screen->IsActive(), "the win screen must show once the goal is reached");
|
|
|
|
const auto path = std::filesystem::temp_directory_path() / "destrum_hud_scene.json";
|
|
check(SceneSerializer::Save(scene, path), "the hud scene must save");
|
|
Scene& copy = SceneManager::GetInstance().CreateScene("hud copy");
|
|
check(SceneSerializer::Load(copy, path), "the hud scene must load");
|
|
std::filesystem::remove(path);
|
|
copy.CommitPendingAdditions();
|
|
|
|
GameObject* loadedHud = findByName(copy, "hud");
|
|
GameObject* loadedCrosshair = findByName(copy, "crosshair");
|
|
GameObject* loadedScreen = findByName(copy, "screen");
|
|
check(loadedHud != nullptr && loadedCrosshair != nullptr && loadedScreen != nullptr,
|
|
"the hud objects must survive a round-trip");
|
|
check(loadedCrosshair->GetTransform().GetParent() == &loadedHud->GetTransform(),
|
|
"ui objects must stay parented to their canvas");
|
|
|
|
const auto* loadedCanvas = loadedHud->GetComponent<Canvas>();
|
|
check(loadedCanvas != nullptr && loadedCanvas->GetReferenceHeight() == 720.0f, "canvas settings must round-trip");
|
|
const auto* loadedRect = loadedCrosshair->GetComponent<RectTransform>();
|
|
check(loadedRect != nullptr && loadedRect->GetAnchorMin() == glm::vec2{0.25f, 0.5f} &&
|
|
loadedRect->GetAnchorMax() == glm::vec2{0.75f, 0.5f},
|
|
"rect transforms must round-trip");
|
|
const auto* loadedImage = loadedCrosshair->GetComponent<UIImage>();
|
|
check(loadedImage != nullptr && loadedImage->GetOutline() == 3.0f, "ui images must round-trip");
|
|
const auto* loadedLabel = loadedScreen->GetComponent<UIText>();
|
|
check(loadedLabel != nullptr && loadedLabel->GetText() == "YOU WIN" && loadedLabel->GetFontSize() == 64.0f &&
|
|
loadedLabel->GetHorizontalAlign() == UITextAlign::Start &&
|
|
loadedLabel->GetVerticalAlign() == UITextAlign::End,
|
|
"ui text must round-trip");
|
|
|
|
const auto* loadedCrosshairUi = loadedHud->GetComponent<CrosshairUI>();
|
|
const auto* loadedWinScreenUi = loadedHud->GetComponent<WinScreenUI>();
|
|
check(loadedCrosshairUi != nullptr && loadedCrosshairUi->GetPlayer().Get(copy) == findByName(copy, "player") &&
|
|
loadedCrosshairUi->GetCrosshair().Get(copy) == loadedCrosshair,
|
|
"crosshair references must point at the loaded objects");
|
|
check(loadedWinScreenUi != nullptr && loadedWinScreenUi->GetGoal().Get(copy) == findByName(copy, "goal") &&
|
|
loadedWinScreenUi->GetScreen().Get(copy) == loadedScreen,
|
|
"win screen references must point at the loaded objects");
|
|
|
|
SceneManager::GetInstance().Destroy();
|
|
}
|
|
}
|
|
|
|
void RunUiTests()
|
|
{
|
|
testCanvasLayout();
|
|
testUITextPlacement();
|
|
testHudRoundTrip();
|
|
}
|