183 lines
6.3 KiB
C++
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
|