Files
XCEngine/tests/editor/test_scene_viewport_picker.cpp

183 lines
6.3 KiB
C++

#include <gtest/gtest.h>
#include "Viewport/SceneViewportPicker.h"
#include <XCEngine/Components/MeshFilterComponent.h>
#include <XCEngine/Components/MeshRendererComponent.h>
#include <XCEngine/Core/Asset/IResource.h>
#include <XCEngine/Core/Math/Quaternion.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <XCEngine/Scene/Scene.h>
#include <memory>
#include <vector>
namespace {
using XCEngine::Components::GameObject;
using XCEngine::Components::MeshFilterComponent;
using XCEngine::Components::MeshRendererComponent;
using XCEngine::Components::Scene;
using XCEngine::Editor::BuildSceneViewportRay;
using XCEngine::Editor::PickSceneViewportEntity;
using XCEngine::Editor::SceneViewportOverlayData;
using XCEngine::Editor::SceneViewportPickRequest;
using XCEngine::Math::Quaternion;
using XCEngine::Math::Ray;
using XCEngine::Math::Vector2;
using XCEngine::Math::Vector3;
using XCEngine::Resources::Mesh;
using XCEngine::Resources::MeshSection;
using XCEngine::Resources::StaticMeshVertex;
std::unique_ptr<Mesh> CreateTriangleMesh(uint64_t guid) {
auto mesh = std::make_unique<Mesh>();
XCEngine::Resources::IResource::ConstructParams params = {};
params.name = "TestTriangle";
params.path = "memory://test_triangle";
params.guid = XCEngine::Resources::ResourceGUID(guid);
mesh->Initialize(params);
const StaticMeshVertex vertices[] = {
{ Vector3(-1.0f, -1.0f, 0.0f) },
{ Vector3(1.0f, -1.0f, 0.0f) },
{ Vector3(0.0f, 1.0f, 0.0f) }
};
const uint16_t indices[] = { 0, 1, 2 };
mesh->SetVertexData(
vertices,
sizeof(vertices),
static_cast<uint32_t>(std::size(vertices)),
sizeof(StaticMeshVertex),
XCEngine::Resources::VertexAttribute::Position);
mesh->SetIndexData(indices, sizeof(indices), static_cast<uint32_t>(std::size(indices)), false);
mesh->SetBounds(XCEngine::Math::Bounds(Vector3(0.0f, 0.0f, 0.0f), Vector3(2.0f, 2.0f, 0.0f)));
MeshSection section = {};
section.baseVertex = 0;
section.vertexCount = static_cast<uint32_t>(std::size(vertices));
section.startIndex = 0;
section.indexCount = static_cast<uint32_t>(std::size(indices));
section.materialID = 0;
section.bounds = mesh->GetBounds();
mesh->AddSection(section);
return mesh;
}
class SceneViewportPickerTest : public ::testing::Test {
protected:
SceneViewportOverlayData CreateDefaultOverlay() const {
SceneViewportOverlayData overlay = {};
overlay.valid = true;
overlay.cameraPosition = Vector3::Zero();
overlay.cameraForward = Vector3::Forward();
overlay.cameraRight = Vector3::Right();
overlay.cameraUp = Vector3::Up();
overlay.verticalFovDegrees = 60.0f;
overlay.nearClipPlane = 0.03f;
overlay.farClipPlane = 2000.0f;
return overlay;
}
GameObject* CreateTriangleObject(
Scene& scene,
const std::string& name,
const Vector3& position,
const Quaternion& rotation = Quaternion::Identity(),
const Vector3& scale = Vector3::One()) {
std::unique_ptr<Mesh> mesh = CreateTriangleMesh(++m_nextMeshGuid);
Mesh* meshPtr = mesh.get();
m_meshes.push_back(std::move(mesh));
GameObject* object = scene.CreateGameObject(name);
object->GetTransform()->SetPosition(position);
object->GetTransform()->SetRotation(rotation);
object->GetTransform()->SetScale(scale);
auto* meshFilter = object->AddComponent<MeshFilterComponent>();
auto* meshRenderer = object->AddComponent<MeshRendererComponent>();
meshFilter->SetMesh(meshPtr);
meshRenderer->SetEnabled(true);
return object;
}
private:
uint64_t m_nextMeshGuid = 1;
std::vector<std::unique_ptr<Mesh>> m_meshes;
};
TEST_F(SceneViewportPickerTest, BuildSceneViewportRayPointsThroughViewportCenter) {
Ray ray;
ASSERT_TRUE(BuildSceneViewportRay(
CreateDefaultOverlay(),
Vector2(1280.0f, 720.0f),
Vector2(640.0f, 360.0f),
ray));
EXPECT_NEAR(ray.origin.x, 0.0f, 1e-4f);
EXPECT_NEAR(ray.origin.y, 0.0f, 1e-4f);
EXPECT_NEAR(ray.origin.z, 0.0f, 1e-4f);
EXPECT_NEAR(ray.direction.x, 0.0f, 1e-4f);
EXPECT_NEAR(ray.direction.y, 0.0f, 1e-4f);
EXPECT_NEAR(ray.direction.z, 1.0f, 1e-4f);
}
TEST_F(SceneViewportPickerTest, PickSceneViewportEntityReturnsNearestHit) {
Scene scene("PickerScene");
GameObject* farObject = CreateTriangleObject(scene, "Far", Vector3(0.0f, 0.0f, 6.0f));
GameObject* nearObject = CreateTriangleObject(scene, "Near", Vector3(0.0f, 0.0f, 3.0f));
SceneViewportPickRequest request = {};
request.scene = &scene;
request.overlay = CreateDefaultOverlay();
request.viewportSize = Vector2(1280.0f, 720.0f);
request.viewportPosition = Vector2(640.0f, 360.0f);
const auto result = PickSceneViewportEntity(request);
EXPECT_TRUE(result.hit);
EXPECT_EQ(result.entityId, nearObject->GetID());
EXPECT_LT(result.distanceSq, (farObject->GetTransform()->GetPosition() - request.overlay.cameraPosition).SqrMagnitude());
}
TEST_F(SceneViewportPickerTest, PickSceneViewportEntityHandlesRotatedAndScaledObjects) {
Scene scene("PickerScene");
GameObject* object = CreateTriangleObject(
scene,
"Rotated",
Vector3(0.0f, 0.0f, 4.0f),
Quaternion::FromEulerAngles(0.0f, 45.0f * XCEngine::Math::DEG_TO_RAD, 0.0f),
Vector3(2.0f, 2.0f, 2.0f));
SceneViewportPickRequest request = {};
request.scene = &scene;
request.overlay = CreateDefaultOverlay();
request.viewportSize = Vector2(1280.0f, 720.0f);
request.viewportPosition = Vector2(640.0f, 360.0f);
const auto result = PickSceneViewportEntity(request);
EXPECT_TRUE(result.hit);
EXPECT_EQ(result.entityId, object->GetID());
}
TEST_F(SceneViewportPickerTest, PickSceneViewportEntityReturnsNoHitForEmptySpace) {
Scene scene("PickerScene");
CreateTriangleObject(scene, "Offset", Vector3(3.0f, 0.0f, 4.0f));
SceneViewportPickRequest request = {};
request.scene = &scene;
request.overlay = CreateDefaultOverlay();
request.viewportSize = Vector2(1280.0f, 720.0f);
request.viewportPosition = Vector2(640.0f, 360.0f);
const auto result = PickSceneViewportEntity(request);
EXPECT_FALSE(result.hit);
EXPECT_EQ(result.entityId, 0u);
}
} // namespace