Introduce scene viewport overlay providers
This commit is contained in:
@@ -1004,3 +1004,45 @@ ISceneViewportGizmoProvider
|
|||||||
- 合并 `SceneViewportOverlayFrameData`
|
- 合并 `SceneViewportOverlayFrameData`
|
||||||
|
|
||||||
这一步完成后,当前 SceneView gizmo 系统才算真正进入“可持续扩展”的第二阶段。
|
这一步完成后,当前 SceneView gizmo 系统才算真正进入“可持续扩展”的第二阶段。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. 进度更新 2026-04-03 Phase 3A 已完成
|
||||||
|
|
||||||
|
本次已完成 `Phase 3A: Overlay Provider Registry 落地`,且已经通过代码、测试与 editor 编译验证。
|
||||||
|
|
||||||
|
已落地内容:
|
||||||
|
|
||||||
|
1. 新增 `SceneViewportOverlayBuildContext`
|
||||||
|
2. 新增 `ISceneViewportOverlayProvider`
|
||||||
|
3. 新增 `SceneViewportOverlayProviderRegistry`
|
||||||
|
4. 新增默认 provider:
|
||||||
|
- `SceneViewportCameraOverlayProvider`
|
||||||
|
- `SceneViewportLightOverlayProvider`
|
||||||
|
5. `SceneViewportOverlayBuilder` 已从大单体逻辑改为 provider 聚合器
|
||||||
|
6. `ViewportHostService` 已改为持有 `SceneViewportOverlayBuilder` 实例,而不是依赖静态构建入口
|
||||||
|
|
||||||
|
本阶段行为保持不变的部分:
|
||||||
|
|
||||||
|
- `BuildSceneViewEditorOverlayContentSignature(...)` 未改
|
||||||
|
- `SceneViewPanel` 的 transient transform gizmo 注入链路未改
|
||||||
|
- orientation gizmo / HUD 分层未改
|
||||||
|
- grid / outline pass 未改
|
||||||
|
|
||||||
|
新增验证:
|
||||||
|
|
||||||
|
1. `SceneViewportOverlayProviderRegistryTest.AppendsProvidersInRegistrationOrder`
|
||||||
|
2. `SceneViewportOverlayProviderRegistryTest.CameraProviderBuildsSceneIconAndSelectedFrustum`
|
||||||
|
3. `SceneViewportOverlayProviderRegistryTest.LightProviderBuildsSceneIconAndSelectedDirectionalHelper`
|
||||||
|
4. `SceneViewportOverlayProviderRegistryTest.OverlayBuilderUsesDefaultRegistryToAggregateCameraAndLightProviders`
|
||||||
|
|
||||||
|
已执行验证:
|
||||||
|
|
||||||
|
- `cmake --build build --config Debug --target editor_tests -- /p:BuildProjectReferences=false`
|
||||||
|
- `build/tests/Editor/Debug/editor_tests.exe --gtest_filter=SceneViewportOverlayProviderRegistryTest.*:ViewportRenderFlowUtilsTest.*`
|
||||||
|
- `cmake --build build --config Debug --target XCEditor`
|
||||||
|
|
||||||
|
阶段结论:
|
||||||
|
|
||||||
|
- Phase 3A 已完成并可作为后续 `Phase 3B: Transform Gizmo 并入正式 Provider 体系` 的稳定起点
|
||||||
|
- 当前 `camera/light overlay` 的职责边界已经从单体 builder 中抽离,但 `transform gizmo` 仍然保留在 transient 注入链路,需要下一阶段继续收口
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ add_executable(${PROJECT_NAME} WIN32
|
|||||||
src/Viewport/SceneViewportScaleGizmo.cpp
|
src/Viewport/SceneViewportScaleGizmo.cpp
|
||||||
src/Viewport/SceneViewportOrientationGizmo.cpp
|
src/Viewport/SceneViewportOrientationGizmo.cpp
|
||||||
src/Viewport/SceneViewportOverlayBuilder.cpp
|
src/Viewport/SceneViewportOverlayBuilder.cpp
|
||||||
|
src/Viewport/SceneViewportOverlayProviders.cpp
|
||||||
src/Viewport/SceneViewportOverlayRenderer.cpp
|
src/Viewport/SceneViewportOverlayRenderer.cpp
|
||||||
src/Viewport/Passes/SceneViewportEditorOverlayPass.cpp
|
src/Viewport/Passes/SceneViewportEditorOverlayPass.cpp
|
||||||
src/Viewport/Passes/SceneViewportGridPass.cpp
|
src/Viewport/Passes/SceneViewportGridPass.cpp
|
||||||
|
|||||||
@@ -2,392 +2,29 @@
|
|||||||
|
|
||||||
#include "Core/IEditorContext.h"
|
#include "Core/IEditorContext.h"
|
||||||
#include "Core/ISceneManager.h"
|
#include "Core/ISceneManager.h"
|
||||||
#include "SceneViewportOverlayHandleBuilder.h"
|
|
||||||
#include "SceneViewportMath.h"
|
|
||||||
|
|
||||||
#include <XCEngine/Components/CameraComponent.h>
|
|
||||||
#include <XCEngine/Components/GameObject.h>
|
|
||||||
#include <XCEngine/Components/LightComponent.h>
|
|
||||||
#include <XCEngine/Components/TransformComponent.h>
|
|
||||||
#include <XCEngine/Core/Math/Rect.h>
|
|
||||||
#include <XCEngine/Scene/Scene.h>
|
#include <XCEngine/Scene/Scene.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <utility>
|
||||||
#include <array>
|
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
namespace XCEngine {
|
namespace XCEngine {
|
||||||
namespace Editor {
|
namespace Editor {
|
||||||
|
|
||||||
namespace {
|
SceneViewportOverlayBuilder::SceneViewportOverlayBuilder()
|
||||||
|
: m_providerRegistry(BuildDefaultSceneViewportOverlayProviderRegistry()) {
|
||||||
bool CanBuildOverlayForGameObject(const Components::GameObject* gameObject) {
|
|
||||||
return gameObject != nullptr &&
|
|
||||||
gameObject->GetTransform() != nullptr &&
|
|
||||||
gameObject->IsActiveInHierarchy();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float ResolveCameraAspect(
|
SceneViewportOverlayBuilder::SceneViewportOverlayBuilder(
|
||||||
const Components::CameraComponent& camera,
|
SceneViewportOverlayProviderRegistry providerRegistry)
|
||||||
uint32_t viewportWidth,
|
: m_providerRegistry(std::move(providerRegistry)) {
|
||||||
uint32_t viewportHeight) {
|
|
||||||
const Math::Rect viewportRect = camera.GetViewportRect();
|
|
||||||
const float resolvedWidth = static_cast<float>(viewportWidth) *
|
|
||||||
(viewportRect.width > Math::EPSILON ? viewportRect.width : 1.0f);
|
|
||||||
const float resolvedHeight = static_cast<float>(viewportHeight) *
|
|
||||||
(viewportRect.height > Math::EPSILON ? viewportRect.height : 1.0f);
|
|
||||||
return resolvedHeight > Math::EPSILON
|
|
||||||
? resolvedWidth / resolvedHeight
|
|
||||||
: 1.0f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float ComputeWorldUnitsPerPixel(
|
|
||||||
const SceneViewportOverlayData& overlay,
|
|
||||||
const Math::Vector3& worldPoint,
|
|
||||||
uint32_t viewportHeight) {
|
|
||||||
if (!overlay.valid || viewportHeight <= 1u) {
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Math::Vector3 cameraForward = overlay.cameraForward.Normalized();
|
|
||||||
const float depth = Math::Vector3::Dot(worldPoint - overlay.cameraPosition, cameraForward);
|
|
||||||
if (depth <= Math::EPSILON) {
|
|
||||||
return 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 2.0f * depth * std::tan(overlay.verticalFovDegrees * Math::DEG_TO_RAD * 0.5f) /
|
|
||||||
static_cast<float>(viewportHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AppendWorldLine(
|
|
||||||
SceneViewportOverlayFrameData& frameData,
|
|
||||||
const Math::Vector3& startWorld,
|
|
||||||
const Math::Vector3& endWorld,
|
|
||||||
const Math::Color& color,
|
|
||||||
float thicknessPixels,
|
|
||||||
SceneViewportOverlayDepthMode depthMode) {
|
|
||||||
SceneViewportOverlayLinePrimitive& line = frameData.worldLines.emplace_back();
|
|
||||||
line.startWorld = startWorld;
|
|
||||||
line.endWorld = endWorld;
|
|
||||||
line.color = color;
|
|
||||||
line.thicknessPixels = thicknessPixels;
|
|
||||||
line.depthMode = depthMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AppendWorldSprite(
|
|
||||||
SceneViewportOverlayFrameData& frameData,
|
|
||||||
const Math::Vector3& worldPosition,
|
|
||||||
const Math::Vector2& sizePixels,
|
|
||||||
const Math::Color& tintColor,
|
|
||||||
float sortDepth,
|
|
||||||
uint64_t entityId,
|
|
||||||
SceneViewportOverlaySpriteTextureKind textureKind,
|
|
||||||
SceneViewportOverlayDepthMode depthMode) {
|
|
||||||
if (entityId == 0 || sizePixels.x <= Math::EPSILON || sizePixels.y <= Math::EPSILON) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SceneViewportOverlaySpritePrimitive& sprite = frameData.worldSprites.emplace_back();
|
|
||||||
sprite.worldPosition = worldPosition;
|
|
||||||
sprite.sizePixels = sizePixels;
|
|
||||||
sprite.tintColor = tintColor;
|
|
||||||
sprite.sortDepth = sortDepth;
|
|
||||||
sprite.entityId = entityId;
|
|
||||||
sprite.textureKind = textureKind;
|
|
||||||
sprite.depthMode = depthMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AppendHandleRecord(
|
|
||||||
SceneViewportOverlayFrameData& frameData,
|
|
||||||
SceneViewportOverlayHandleKind kind,
|
|
||||||
uint64_t handleId,
|
|
||||||
uint64_t entityId,
|
|
||||||
const Math::Vector3& worldPosition,
|
|
||||||
const Math::Vector2& sizePixels,
|
|
||||||
float sortDepth) {
|
|
||||||
if (kind == SceneViewportOverlayHandleKind::None ||
|
|
||||||
handleId == 0 ||
|
|
||||||
entityId == 0 ||
|
|
||||||
sizePixels.x <= Math::EPSILON ||
|
|
||||||
sizePixels.y <= Math::EPSILON) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SceneViewportOverlayHandleRecord& handleRecord = frameData.handleRecords.emplace_back();
|
|
||||||
handleRecord.kind = kind;
|
|
||||||
handleRecord.handleId = handleId;
|
|
||||||
handleRecord.entityId = entityId;
|
|
||||||
handleRecord.shape = SceneViewportOverlayHandleShape::WorldRect;
|
|
||||||
handleRecord.priority = Detail::kSceneViewportHandlePrioritySceneIcon;
|
|
||||||
handleRecord.worldPosition = worldPosition;
|
|
||||||
handleRecord.sizePixels = sizePixels;
|
|
||||||
handleRecord.sortDepth = sortDepth;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AppendSceneIconOverlay(
|
|
||||||
SceneViewportOverlayFrameData& frameData,
|
|
||||||
const SceneViewportOverlayData& overlay,
|
|
||||||
uint32_t viewportWidth,
|
|
||||||
uint32_t viewportHeight,
|
|
||||||
const Components::GameObject& gameObject,
|
|
||||||
const Math::Vector2& sizePixels,
|
|
||||||
SceneViewportOverlaySpriteTextureKind textureKind) {
|
|
||||||
const Components::TransformComponent* transform = gameObject.GetTransform();
|
|
||||||
if (transform == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SceneViewportProjectedPoint projectedPoint = ProjectSceneViewportWorldPoint(
|
|
||||||
overlay,
|
|
||||||
static_cast<float>(viewportWidth),
|
|
||||||
static_cast<float>(viewportHeight),
|
|
||||||
transform->GetPosition());
|
|
||||||
if (!projectedPoint.visible) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
AppendWorldSprite(
|
|
||||||
frameData,
|
|
||||||
transform->GetPosition(),
|
|
||||||
sizePixels,
|
|
||||||
Math::Color::White(),
|
|
||||||
projectedPoint.ndcDepth,
|
|
||||||
gameObject.GetID(),
|
|
||||||
textureKind,
|
|
||||||
SceneViewportOverlayDepthMode::AlwaysOnTop);
|
|
||||||
AppendHandleRecord(
|
|
||||||
frameData,
|
|
||||||
SceneViewportOverlayHandleKind::SceneIcon,
|
|
||||||
gameObject.GetID(),
|
|
||||||
gameObject.GetID(),
|
|
||||||
transform->GetPosition(),
|
|
||||||
sizePixels,
|
|
||||||
projectedPoint.ndcDepth);
|
|
||||||
}
|
|
||||||
|
|
||||||
void AppendCameraFrustumOverlay(
|
|
||||||
SceneViewportOverlayFrameData& frameData,
|
|
||||||
const Components::CameraComponent& camera,
|
|
||||||
const Components::GameObject& gameObject,
|
|
||||||
uint32_t viewportWidth,
|
|
||||||
uint32_t viewportHeight) {
|
|
||||||
const Components::TransformComponent* transform = gameObject.GetTransform();
|
|
||||||
if (transform == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Math::Vector3 position = transform->GetPosition();
|
|
||||||
const Math::Vector3 forward = transform->GetForward().Normalized();
|
|
||||||
const Math::Vector3 right = transform->GetRight().Normalized();
|
|
||||||
const Math::Vector3 up = transform->GetUp().Normalized();
|
|
||||||
if (forward.SqrMagnitude() <= Math::EPSILON ||
|
|
||||||
right.SqrMagnitude() <= Math::EPSILON ||
|
|
||||||
up.SqrMagnitude() <= Math::EPSILON) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const float nearClip = (std::max)(camera.GetNearClipPlane(), 0.01f);
|
|
||||||
const float farClip = (std::max)(camera.GetFarClipPlane(), nearClip + 0.01f);
|
|
||||||
const float aspect = ResolveCameraAspect(camera, viewportWidth, viewportHeight);
|
|
||||||
|
|
||||||
float nearHalfHeight = 0.0f;
|
|
||||||
float nearHalfWidth = 0.0f;
|
|
||||||
float farHalfHeight = 0.0f;
|
|
||||||
float farHalfWidth = 0.0f;
|
|
||||||
if (camera.GetProjectionType() == Components::CameraProjectionType::Perspective) {
|
|
||||||
const float halfFovRadians =
|
|
||||||
std::clamp(camera.GetFieldOfView(), 1.0f, 179.0f) * Math::DEG_TO_RAD * 0.5f;
|
|
||||||
nearHalfHeight = std::tan(halfFovRadians) * nearClip;
|
|
||||||
nearHalfWidth = nearHalfHeight * aspect;
|
|
||||||
farHalfHeight = std::tan(halfFovRadians) * farClip;
|
|
||||||
farHalfWidth = farHalfHeight * aspect;
|
|
||||||
} else {
|
|
||||||
const float halfHeight = (std::max)(camera.GetOrthographicSize(), 0.01f);
|
|
||||||
const float halfWidth = halfHeight * aspect;
|
|
||||||
nearHalfHeight = halfHeight;
|
|
||||||
nearHalfWidth = halfWidth;
|
|
||||||
farHalfHeight = halfHeight;
|
|
||||||
farHalfWidth = halfWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Math::Vector3 nearCenter = position + forward * nearClip;
|
|
||||||
const Math::Vector3 farCenter = position + forward * farClip;
|
|
||||||
const std::array<Math::Vector3, 8> corners = {{
|
|
||||||
nearCenter + up * nearHalfHeight - right * nearHalfWidth,
|
|
||||||
nearCenter + up * nearHalfHeight + right * nearHalfWidth,
|
|
||||||
nearCenter - up * nearHalfHeight + right * nearHalfWidth,
|
|
||||||
nearCenter - up * nearHalfHeight - right * nearHalfWidth,
|
|
||||||
farCenter + up * farHalfHeight - right * farHalfWidth,
|
|
||||||
farCenter + up * farHalfHeight + right * farHalfWidth,
|
|
||||||
farCenter - up * farHalfHeight + right * farHalfWidth,
|
|
||||||
farCenter - up * farHalfHeight - right * farHalfWidth
|
|
||||||
}};
|
|
||||||
|
|
||||||
static constexpr std::array<std::pair<size_t, size_t>, 12> kFrustumEdges = {{
|
|
||||||
{ 0u, 1u }, { 1u, 2u }, { 2u, 3u }, { 3u, 0u },
|
|
||||||
{ 4u, 5u }, { 5u, 6u }, { 6u, 7u }, { 7u, 4u },
|
|
||||||
{ 0u, 4u }, { 1u, 5u }, { 2u, 6u }, { 3u, 7u }
|
|
||||||
}};
|
|
||||||
constexpr Math::Color kFrustumColor(1.0f, 1.0f, 1.0f, 1.0f);
|
|
||||||
|
|
||||||
for (const auto& edge : kFrustumEdges) {
|
|
||||||
AppendWorldLine(
|
|
||||||
frameData,
|
|
||||||
corners[edge.first],
|
|
||||||
corners[edge.second],
|
|
||||||
kFrustumColor,
|
|
||||||
1.6f,
|
|
||||||
SceneViewportOverlayDepthMode::AlwaysOnTop);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AppendDirectionalLightOverlay(
|
|
||||||
SceneViewportOverlayFrameData& frameData,
|
|
||||||
const Components::GameObject& gameObject,
|
|
||||||
const SceneViewportOverlayData& overlay,
|
|
||||||
uint32_t viewportHeight) {
|
|
||||||
const Components::TransformComponent* transform = gameObject.GetTransform();
|
|
||||||
if (transform == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Math::Vector3 position = transform->GetPosition();
|
|
||||||
const Math::Vector3 lightDirection = (transform->GetForward() * -1.0f).Normalized();
|
|
||||||
const Math::Vector3 right = transform->GetRight().Normalized();
|
|
||||||
const Math::Vector3 up = transform->GetUp().Normalized();
|
|
||||||
if (lightDirection.SqrMagnitude() <= Math::EPSILON ||
|
|
||||||
right.SqrMagnitude() <= Math::EPSILON ||
|
|
||||||
up.SqrMagnitude() <= Math::EPSILON) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const float worldUnitsPerPixel = ComputeWorldUnitsPerPixel(overlay, position, viewportHeight);
|
|
||||||
if (worldUnitsPerPixel <= Math::EPSILON) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr Math::Color kDirectionalLightColor(1.0f, 0.92f, 0.24f, 1.0f);
|
|
||||||
constexpr float kLineThickness = 1.8f;
|
|
||||||
constexpr size_t kRingSegmentCount = 32;
|
|
||||||
constexpr std::array<float, 6> kRayAngles = {{
|
|
||||||
0.0f,
|
|
||||||
Math::PI / 3.0f,
|
|
||||||
Math::PI * 2.0f / 3.0f,
|
|
||||||
Math::PI,
|
|
||||||
Math::PI * 4.0f / 3.0f,
|
|
||||||
Math::PI * 5.0f / 3.0f
|
|
||||||
}};
|
|
||||||
|
|
||||||
const float ringRadius = worldUnitsPerPixel * 26.0f;
|
|
||||||
const float ringOffset = worldUnitsPerPixel * 54.0f;
|
|
||||||
const float innerRayRadius = ringRadius * 0.52f;
|
|
||||||
const float rayLength = worldUnitsPerPixel * 96.0f;
|
|
||||||
const Math::Vector3 ringCenter = position + lightDirection * ringOffset;
|
|
||||||
|
|
||||||
for (size_t segmentIndex = 0; segmentIndex < kRingSegmentCount; ++segmentIndex) {
|
|
||||||
const float angle0 =
|
|
||||||
static_cast<float>(segmentIndex) / static_cast<float>(kRingSegmentCount) * Math::PI * 2.0f;
|
|
||||||
const float angle1 =
|
|
||||||
static_cast<float>(segmentIndex + 1u) / static_cast<float>(kRingSegmentCount) * Math::PI * 2.0f;
|
|
||||||
const Math::Vector3 p0 =
|
|
||||||
ringCenter + right * std::cos(angle0) * ringRadius + up * std::sin(angle0) * ringRadius;
|
|
||||||
const Math::Vector3 p1 =
|
|
||||||
ringCenter + right * std::cos(angle1) * ringRadius + up * std::sin(angle1) * ringRadius;
|
|
||||||
AppendWorldLine(
|
|
||||||
frameData,
|
|
||||||
p0,
|
|
||||||
p1,
|
|
||||||
kDirectionalLightColor,
|
|
||||||
kLineThickness,
|
|
||||||
SceneViewportOverlayDepthMode::AlwaysOnTop);
|
|
||||||
}
|
|
||||||
|
|
||||||
AppendWorldLine(
|
|
||||||
frameData,
|
|
||||||
position,
|
|
||||||
ringCenter,
|
|
||||||
kDirectionalLightColor,
|
|
||||||
kLineThickness,
|
|
||||||
SceneViewportOverlayDepthMode::AlwaysOnTop);
|
|
||||||
AppendWorldLine(
|
|
||||||
frameData,
|
|
||||||
ringCenter,
|
|
||||||
ringCenter + lightDirection * rayLength,
|
|
||||||
kDirectionalLightColor,
|
|
||||||
kLineThickness,
|
|
||||||
SceneViewportOverlayDepthMode::AlwaysOnTop);
|
|
||||||
|
|
||||||
for (float angle : kRayAngles) {
|
|
||||||
const Math::Vector3 rayStart =
|
|
||||||
ringCenter + right * std::cos(angle) * innerRayRadius + up * std::sin(angle) * innerRayRadius;
|
|
||||||
AppendWorldLine(
|
|
||||||
frameData,
|
|
||||||
rayStart,
|
|
||||||
rayStart + lightDirection * rayLength,
|
|
||||||
kDirectionalLightColor,
|
|
||||||
kLineThickness,
|
|
||||||
SceneViewportOverlayDepthMode::AlwaysOnTop);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AppendSceneObjectIconOverlays(
|
|
||||||
SceneViewportOverlayFrameData& frameData,
|
|
||||||
const Components::Scene& scene,
|
|
||||||
const SceneViewportOverlayData& overlay,
|
|
||||||
uint32_t viewportWidth,
|
|
||||||
uint32_t viewportHeight) {
|
|
||||||
constexpr Math::Vector2 kCameraIconSize(90.0f, 90.0f);
|
|
||||||
constexpr Math::Vector2 kLightIconSize(100.0f, 100.0f);
|
|
||||||
|
|
||||||
for (Components::CameraComponent* camera : scene.FindObjectsOfType<Components::CameraComponent>()) {
|
|
||||||
if (camera == nullptr || !camera->IsEnabled()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Components::GameObject* gameObject = camera->GetGameObject();
|
|
||||||
if (!CanBuildOverlayForGameObject(gameObject)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
AppendSceneIconOverlay(
|
|
||||||
frameData,
|
|
||||||
overlay,
|
|
||||||
viewportWidth,
|
|
||||||
viewportHeight,
|
|
||||||
*gameObject,
|
|
||||||
kCameraIconSize,
|
|
||||||
SceneViewportOverlaySpriteTextureKind::Camera);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Components::LightComponent* light : scene.FindObjectsOfType<Components::LightComponent>()) {
|
|
||||||
if (light == nullptr || !light->IsEnabled()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Components::GameObject* gameObject = light->GetGameObject();
|
|
||||||
if (!CanBuildOverlayForGameObject(gameObject)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
AppendSceneIconOverlay(
|
|
||||||
frameData,
|
|
||||||
overlay,
|
|
||||||
viewportWidth,
|
|
||||||
viewportHeight,
|
|
||||||
*gameObject,
|
|
||||||
kLightIconSize,
|
|
||||||
SceneViewportOverlaySpriteTextureKind::Light);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
SceneViewportOverlayFrameData SceneViewportOverlayBuilder::Build(
|
SceneViewportOverlayFrameData SceneViewportOverlayBuilder::Build(
|
||||||
IEditorContext& context,
|
IEditorContext& context,
|
||||||
const SceneViewportOverlayData& overlay,
|
const SceneViewportOverlayData& overlay,
|
||||||
uint32_t viewportWidth,
|
uint32_t viewportWidth,
|
||||||
uint32_t viewportHeight,
|
uint32_t viewportHeight,
|
||||||
const std::vector<uint64_t>& selectedObjectIds) {
|
const std::vector<uint64_t>& selectedObjectIds) const {
|
||||||
SceneViewportOverlayFrameData frameData = {};
|
SceneViewportOverlayFrameData frameData = {};
|
||||||
frameData.overlay = overlay;
|
frameData.overlay = overlay;
|
||||||
if (!overlay.valid || viewportWidth == 0u || viewportHeight == 0u) {
|
if (!overlay.valid || viewportWidth == 0u || viewportHeight == 0u) {
|
||||||
@@ -399,31 +36,15 @@ SceneViewportOverlayFrameData SceneViewportOverlayBuilder::Build(
|
|||||||
return frameData;
|
return frameData;
|
||||||
}
|
}
|
||||||
|
|
||||||
AppendSceneObjectIconOverlays(frameData, *scene, overlay, viewportWidth, viewportHeight);
|
const SceneViewportOverlayBuildContext buildContext = {
|
||||||
|
&context,
|
||||||
for (uint64_t entityId : selectedObjectIds) {
|
scene,
|
||||||
if (entityId == 0) {
|
&overlay,
|
||||||
continue;
|
viewportWidth,
|
||||||
}
|
viewportHeight,
|
||||||
|
&selectedObjectIds
|
||||||
Components::GameObject* gameObject = context.GetSceneManager().GetEntity(entityId);
|
};
|
||||||
if (!CanBuildOverlayForGameObject(gameObject)) {
|
m_providerRegistry.AppendOverlay(buildContext, frameData);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Components::CameraComponent* camera = gameObject->GetComponent<Components::CameraComponent>();
|
|
||||||
camera != nullptr && camera->IsEnabled()) {
|
|
||||||
AppendCameraFrustumOverlay(frameData, *camera, *gameObject, viewportWidth, viewportHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Components::LightComponent* light = gameObject->GetComponent<Components::LightComponent>();
|
|
||||||
light != nullptr &&
|
|
||||||
light->IsEnabled() &&
|
|
||||||
light->GetLightType() == Components::LightType::Directional) {
|
|
||||||
AppendDirectionalLightOverlay(frameData, *gameObject, overlay, viewportHeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return frameData;
|
return frameData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "IViewportHostService.h"
|
#include "IViewportHostService.h"
|
||||||
#include "SceneViewportEditorOverlayData.h"
|
#include "SceneViewportOverlayProviders.h"
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -13,12 +13,26 @@ class IEditorContext;
|
|||||||
|
|
||||||
class SceneViewportOverlayBuilder {
|
class SceneViewportOverlayBuilder {
|
||||||
public:
|
public:
|
||||||
static SceneViewportOverlayFrameData Build(
|
SceneViewportOverlayBuilder();
|
||||||
|
explicit SceneViewportOverlayBuilder(SceneViewportOverlayProviderRegistry providerRegistry);
|
||||||
|
|
||||||
|
SceneViewportOverlayFrameData Build(
|
||||||
IEditorContext& context,
|
IEditorContext& context,
|
||||||
const SceneViewportOverlayData& overlay,
|
const SceneViewportOverlayData& overlay,
|
||||||
uint32_t viewportWidth,
|
uint32_t viewportWidth,
|
||||||
uint32_t viewportHeight,
|
uint32_t viewportHeight,
|
||||||
const std::vector<uint64_t>& selectedObjectIds);
|
const std::vector<uint64_t>& selectedObjectIds) const;
|
||||||
|
|
||||||
|
const SceneViewportOverlayProviderRegistry& GetProviderRegistry() const {
|
||||||
|
return m_providerRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
|
SceneViewportOverlayProviderRegistry& GetProviderRegistry() {
|
||||||
|
return m_providerRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
SceneViewportOverlayProviderRegistry m_providerRegistry = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Editor
|
} // namespace Editor
|
||||||
|
|||||||
502
editor/src/Viewport/SceneViewportOverlayProviders.cpp
Normal file
502
editor/src/Viewport/SceneViewportOverlayProviders.cpp
Normal file
@@ -0,0 +1,502 @@
|
|||||||
|
#include "SceneViewportOverlayProviders.h"
|
||||||
|
|
||||||
|
#include <XCEngine/Components/CameraComponent.h>
|
||||||
|
#include <XCEngine/Components/GameObject.h>
|
||||||
|
#include <XCEngine/Components/LightComponent.h>
|
||||||
|
#include <XCEngine/Components/TransformComponent.h>
|
||||||
|
#include <XCEngine/Core/Math/Rect.h>
|
||||||
|
#include <XCEngine/Scene/Scene.h>
|
||||||
|
|
||||||
|
#include "SceneViewportOverlayHandleBuilder.h"
|
||||||
|
#include "SceneViewportMath.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Editor {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
bool CanBuildOverlayForGameObject(const Components::GameObject* gameObject) {
|
||||||
|
return gameObject != nullptr &&
|
||||||
|
gameObject->GetTransform() != nullptr &&
|
||||||
|
gameObject->IsActiveInHierarchy();
|
||||||
|
}
|
||||||
|
|
||||||
|
float ResolveCameraAspect(
|
||||||
|
const Components::CameraComponent& camera,
|
||||||
|
uint32_t viewportWidth,
|
||||||
|
uint32_t viewportHeight) {
|
||||||
|
const Math::Rect viewportRect = camera.GetViewportRect();
|
||||||
|
const float resolvedWidth = static_cast<float>(viewportWidth) *
|
||||||
|
(viewportRect.width > Math::EPSILON ? viewportRect.width : 1.0f);
|
||||||
|
const float resolvedHeight = static_cast<float>(viewportHeight) *
|
||||||
|
(viewportRect.height > Math::EPSILON ? viewportRect.height : 1.0f);
|
||||||
|
return resolvedHeight > Math::EPSILON
|
||||||
|
? resolvedWidth / resolvedHeight
|
||||||
|
: 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float ComputeWorldUnitsPerPixel(
|
||||||
|
const SceneViewportOverlayData& overlay,
|
||||||
|
const Math::Vector3& worldPoint,
|
||||||
|
uint32_t viewportHeight) {
|
||||||
|
if (!overlay.valid || viewportHeight <= 1u) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Math::Vector3 cameraForward = overlay.cameraForward.Normalized();
|
||||||
|
const float depth = Math::Vector3::Dot(worldPoint - overlay.cameraPosition, cameraForward);
|
||||||
|
if (depth <= Math::EPSILON) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 2.0f * depth * std::tan(overlay.verticalFovDegrees * Math::DEG_TO_RAD * 0.5f) /
|
||||||
|
static_cast<float>(viewportHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppendWorldLine(
|
||||||
|
SceneViewportOverlayFrameData& frameData,
|
||||||
|
const Math::Vector3& startWorld,
|
||||||
|
const Math::Vector3& endWorld,
|
||||||
|
const Math::Color& color,
|
||||||
|
float thicknessPixels,
|
||||||
|
SceneViewportOverlayDepthMode depthMode) {
|
||||||
|
SceneViewportOverlayLinePrimitive& line = frameData.worldLines.emplace_back();
|
||||||
|
line.startWorld = startWorld;
|
||||||
|
line.endWorld = endWorld;
|
||||||
|
line.color = color;
|
||||||
|
line.thicknessPixels = thicknessPixels;
|
||||||
|
line.depthMode = depthMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppendWorldSprite(
|
||||||
|
SceneViewportOverlayFrameData& frameData,
|
||||||
|
const Math::Vector3& worldPosition,
|
||||||
|
const Math::Vector2& sizePixels,
|
||||||
|
const Math::Color& tintColor,
|
||||||
|
float sortDepth,
|
||||||
|
uint64_t entityId,
|
||||||
|
SceneViewportOverlaySpriteTextureKind textureKind,
|
||||||
|
SceneViewportOverlayDepthMode depthMode) {
|
||||||
|
if (entityId == 0 || sizePixels.x <= Math::EPSILON || sizePixels.y <= Math::EPSILON) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SceneViewportOverlaySpritePrimitive& sprite = frameData.worldSprites.emplace_back();
|
||||||
|
sprite.worldPosition = worldPosition;
|
||||||
|
sprite.sizePixels = sizePixels;
|
||||||
|
sprite.tintColor = tintColor;
|
||||||
|
sprite.sortDepth = sortDepth;
|
||||||
|
sprite.entityId = entityId;
|
||||||
|
sprite.textureKind = textureKind;
|
||||||
|
sprite.depthMode = depthMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppendHandleRecord(
|
||||||
|
SceneViewportOverlayFrameData& frameData,
|
||||||
|
SceneViewportOverlayHandleKind kind,
|
||||||
|
uint64_t handleId,
|
||||||
|
uint64_t entityId,
|
||||||
|
const Math::Vector3& worldPosition,
|
||||||
|
const Math::Vector2& sizePixels,
|
||||||
|
float sortDepth) {
|
||||||
|
if (kind == SceneViewportOverlayHandleKind::None ||
|
||||||
|
handleId == 0 ||
|
||||||
|
entityId == 0 ||
|
||||||
|
sizePixels.x <= Math::EPSILON ||
|
||||||
|
sizePixels.y <= Math::EPSILON) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SceneViewportOverlayHandleRecord& handleRecord = frameData.handleRecords.emplace_back();
|
||||||
|
handleRecord.kind = kind;
|
||||||
|
handleRecord.handleId = handleId;
|
||||||
|
handleRecord.entityId = entityId;
|
||||||
|
handleRecord.shape = SceneViewportOverlayHandleShape::WorldRect;
|
||||||
|
handleRecord.priority = Detail::kSceneViewportHandlePrioritySceneIcon;
|
||||||
|
handleRecord.worldPosition = worldPosition;
|
||||||
|
handleRecord.sizePixels = sizePixels;
|
||||||
|
handleRecord.sortDepth = sortDepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppendSceneIconOverlay(
|
||||||
|
SceneViewportOverlayFrameData& frameData,
|
||||||
|
const SceneViewportOverlayData& overlay,
|
||||||
|
uint32_t viewportWidth,
|
||||||
|
uint32_t viewportHeight,
|
||||||
|
const Components::GameObject& gameObject,
|
||||||
|
const Math::Vector2& sizePixels,
|
||||||
|
SceneViewportOverlaySpriteTextureKind textureKind) {
|
||||||
|
const Components::TransformComponent* transform = gameObject.GetTransform();
|
||||||
|
if (transform == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SceneViewportProjectedPoint projectedPoint = ProjectSceneViewportWorldPoint(
|
||||||
|
overlay,
|
||||||
|
static_cast<float>(viewportWidth),
|
||||||
|
static_cast<float>(viewportHeight),
|
||||||
|
transform->GetPosition());
|
||||||
|
if (!projectedPoint.visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppendWorldSprite(
|
||||||
|
frameData,
|
||||||
|
transform->GetPosition(),
|
||||||
|
sizePixels,
|
||||||
|
Math::Color::White(),
|
||||||
|
projectedPoint.ndcDepth,
|
||||||
|
gameObject.GetID(),
|
||||||
|
textureKind,
|
||||||
|
SceneViewportOverlayDepthMode::AlwaysOnTop);
|
||||||
|
AppendHandleRecord(
|
||||||
|
frameData,
|
||||||
|
SceneViewportOverlayHandleKind::SceneIcon,
|
||||||
|
gameObject.GetID(),
|
||||||
|
gameObject.GetID(),
|
||||||
|
transform->GetPosition(),
|
||||||
|
sizePixels,
|
||||||
|
projectedPoint.ndcDepth);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppendCameraFrustumOverlay(
|
||||||
|
SceneViewportOverlayFrameData& frameData,
|
||||||
|
const Components::CameraComponent& camera,
|
||||||
|
const Components::GameObject& gameObject,
|
||||||
|
uint32_t viewportWidth,
|
||||||
|
uint32_t viewportHeight) {
|
||||||
|
const Components::TransformComponent* transform = gameObject.GetTransform();
|
||||||
|
if (transform == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Math::Vector3 position = transform->GetPosition();
|
||||||
|
const Math::Vector3 forward = transform->GetForward().Normalized();
|
||||||
|
const Math::Vector3 right = transform->GetRight().Normalized();
|
||||||
|
const Math::Vector3 up = transform->GetUp().Normalized();
|
||||||
|
if (forward.SqrMagnitude() <= Math::EPSILON ||
|
||||||
|
right.SqrMagnitude() <= Math::EPSILON ||
|
||||||
|
up.SqrMagnitude() <= Math::EPSILON) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float nearClip = (std::max)(camera.GetNearClipPlane(), 0.01f);
|
||||||
|
const float farClip = (std::max)(camera.GetFarClipPlane(), nearClip + 0.01f);
|
||||||
|
const float aspect = ResolveCameraAspect(camera, viewportWidth, viewportHeight);
|
||||||
|
|
||||||
|
float nearHalfHeight = 0.0f;
|
||||||
|
float nearHalfWidth = 0.0f;
|
||||||
|
float farHalfHeight = 0.0f;
|
||||||
|
float farHalfWidth = 0.0f;
|
||||||
|
if (camera.GetProjectionType() == Components::CameraProjectionType::Perspective) {
|
||||||
|
const float halfFovRadians =
|
||||||
|
std::clamp(camera.GetFieldOfView(), 1.0f, 179.0f) * Math::DEG_TO_RAD * 0.5f;
|
||||||
|
nearHalfHeight = std::tan(halfFovRadians) * nearClip;
|
||||||
|
nearHalfWidth = nearHalfHeight * aspect;
|
||||||
|
farHalfHeight = std::tan(halfFovRadians) * farClip;
|
||||||
|
farHalfWidth = farHalfHeight * aspect;
|
||||||
|
} else {
|
||||||
|
const float halfHeight = (std::max)(camera.GetOrthographicSize(), 0.01f);
|
||||||
|
const float halfWidth = halfHeight * aspect;
|
||||||
|
nearHalfHeight = halfHeight;
|
||||||
|
nearHalfWidth = halfWidth;
|
||||||
|
farHalfHeight = halfHeight;
|
||||||
|
farHalfWidth = halfWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Math::Vector3 nearCenter = position + forward * nearClip;
|
||||||
|
const Math::Vector3 farCenter = position + forward * farClip;
|
||||||
|
const std::array<Math::Vector3, 8> corners = {{
|
||||||
|
nearCenter + up * nearHalfHeight - right * nearHalfWidth,
|
||||||
|
nearCenter + up * nearHalfHeight + right * nearHalfWidth,
|
||||||
|
nearCenter - up * nearHalfHeight + right * nearHalfWidth,
|
||||||
|
nearCenter - up * nearHalfHeight - right * nearHalfWidth,
|
||||||
|
farCenter + up * farHalfHeight - right * farHalfWidth,
|
||||||
|
farCenter + up * farHalfHeight + right * farHalfWidth,
|
||||||
|
farCenter - up * farHalfHeight + right * farHalfWidth,
|
||||||
|
farCenter - up * farHalfHeight - right * farHalfWidth
|
||||||
|
}};
|
||||||
|
|
||||||
|
static constexpr std::array<std::pair<size_t, size_t>, 12> kFrustumEdges = {{
|
||||||
|
{ 0u, 1u }, { 1u, 2u }, { 2u, 3u }, { 3u, 0u },
|
||||||
|
{ 4u, 5u }, { 5u, 6u }, { 6u, 7u }, { 7u, 4u },
|
||||||
|
{ 0u, 4u }, { 1u, 5u }, { 2u, 6u }, { 3u, 7u }
|
||||||
|
}};
|
||||||
|
constexpr Math::Color kFrustumColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||||
|
|
||||||
|
for (const auto& edge : kFrustumEdges) {
|
||||||
|
AppendWorldLine(
|
||||||
|
frameData,
|
||||||
|
corners[edge.first],
|
||||||
|
corners[edge.second],
|
||||||
|
kFrustumColor,
|
||||||
|
1.6f,
|
||||||
|
SceneViewportOverlayDepthMode::AlwaysOnTop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppendDirectionalLightOverlay(
|
||||||
|
SceneViewportOverlayFrameData& frameData,
|
||||||
|
const Components::GameObject& gameObject,
|
||||||
|
const SceneViewportOverlayData& overlay,
|
||||||
|
uint32_t viewportHeight) {
|
||||||
|
const Components::TransformComponent* transform = gameObject.GetTransform();
|
||||||
|
if (transform == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Math::Vector3 position = transform->GetPosition();
|
||||||
|
const Math::Vector3 lightDirection = (transform->GetForward() * -1.0f).Normalized();
|
||||||
|
const Math::Vector3 right = transform->GetRight().Normalized();
|
||||||
|
const Math::Vector3 up = transform->GetUp().Normalized();
|
||||||
|
if (lightDirection.SqrMagnitude() <= Math::EPSILON ||
|
||||||
|
right.SqrMagnitude() <= Math::EPSILON ||
|
||||||
|
up.SqrMagnitude() <= Math::EPSILON) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float worldUnitsPerPixel = ComputeWorldUnitsPerPixel(overlay, position, viewportHeight);
|
||||||
|
if (worldUnitsPerPixel <= Math::EPSILON) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr Math::Color kDirectionalLightColor(1.0f, 0.92f, 0.24f, 1.0f);
|
||||||
|
constexpr float kLineThickness = 1.8f;
|
||||||
|
constexpr size_t kRingSegmentCount = 32u;
|
||||||
|
constexpr std::array<float, 6> kRayAngles = {{
|
||||||
|
0.0f,
|
||||||
|
Math::PI / 3.0f,
|
||||||
|
Math::PI * 2.0f / 3.0f,
|
||||||
|
Math::PI,
|
||||||
|
Math::PI * 4.0f / 3.0f,
|
||||||
|
Math::PI * 5.0f / 3.0f
|
||||||
|
}};
|
||||||
|
|
||||||
|
const float ringRadius = worldUnitsPerPixel * 26.0f;
|
||||||
|
const float ringOffset = worldUnitsPerPixel * 54.0f;
|
||||||
|
const float innerRayRadius = ringRadius * 0.52f;
|
||||||
|
const float rayLength = worldUnitsPerPixel * 96.0f;
|
||||||
|
const Math::Vector3 ringCenter = position + lightDirection * ringOffset;
|
||||||
|
|
||||||
|
for (size_t segmentIndex = 0; segmentIndex < kRingSegmentCount; ++segmentIndex) {
|
||||||
|
const float angle0 =
|
||||||
|
static_cast<float>(segmentIndex) / static_cast<float>(kRingSegmentCount) * Math::PI * 2.0f;
|
||||||
|
const float angle1 =
|
||||||
|
static_cast<float>(segmentIndex + 1u) / static_cast<float>(kRingSegmentCount) * Math::PI * 2.0f;
|
||||||
|
const Math::Vector3 p0 =
|
||||||
|
ringCenter + right * std::cos(angle0) * ringRadius + up * std::sin(angle0) * ringRadius;
|
||||||
|
const Math::Vector3 p1 =
|
||||||
|
ringCenter + right * std::cos(angle1) * ringRadius + up * std::sin(angle1) * ringRadius;
|
||||||
|
AppendWorldLine(
|
||||||
|
frameData,
|
||||||
|
p0,
|
||||||
|
p1,
|
||||||
|
kDirectionalLightColor,
|
||||||
|
kLineThickness,
|
||||||
|
SceneViewportOverlayDepthMode::AlwaysOnTop);
|
||||||
|
}
|
||||||
|
|
||||||
|
AppendWorldLine(
|
||||||
|
frameData,
|
||||||
|
position,
|
||||||
|
ringCenter,
|
||||||
|
kDirectionalLightColor,
|
||||||
|
kLineThickness,
|
||||||
|
SceneViewportOverlayDepthMode::AlwaysOnTop);
|
||||||
|
AppendWorldLine(
|
||||||
|
frameData,
|
||||||
|
ringCenter,
|
||||||
|
ringCenter + lightDirection * rayLength,
|
||||||
|
kDirectionalLightColor,
|
||||||
|
kLineThickness,
|
||||||
|
SceneViewportOverlayDepthMode::AlwaysOnTop);
|
||||||
|
|
||||||
|
for (float angle : kRayAngles) {
|
||||||
|
const Math::Vector3 rayStart =
|
||||||
|
ringCenter + right * std::cos(angle) * innerRayRadius + up * std::sin(angle) * innerRayRadius;
|
||||||
|
AppendWorldLine(
|
||||||
|
frameData,
|
||||||
|
rayStart,
|
||||||
|
rayStart + lightDirection * rayLength,
|
||||||
|
kDirectionalLightColor,
|
||||||
|
kLineThickness,
|
||||||
|
SceneViewportOverlayDepthMode::AlwaysOnTop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SceneViewportCameraOverlayProvider final : public ISceneViewportOverlayProvider {
|
||||||
|
public:
|
||||||
|
const char* GetName() const override {
|
||||||
|
return "SceneViewportCameraOverlayProvider";
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppendOverlay(
|
||||||
|
const SceneViewportOverlayBuildContext& context,
|
||||||
|
SceneViewportOverlayFrameData& frameData) const override {
|
||||||
|
if (!context.IsValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr Math::Vector2 kCameraIconSize(90.0f, 90.0f);
|
||||||
|
|
||||||
|
for (Components::CameraComponent* camera : context.scene->FindObjectsOfType<Components::CameraComponent>()) {
|
||||||
|
if (camera == nullptr || !camera->IsEnabled()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Components::GameObject* gameObject = camera->GetGameObject();
|
||||||
|
if (!CanBuildOverlayForGameObject(gameObject)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppendSceneIconOverlay(
|
||||||
|
frameData,
|
||||||
|
*context.overlay,
|
||||||
|
context.viewportWidth,
|
||||||
|
context.viewportHeight,
|
||||||
|
*gameObject,
|
||||||
|
kCameraIconSize,
|
||||||
|
SceneViewportOverlaySpriteTextureKind::Camera);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint64_t entityId : *context.selectedObjectIds) {
|
||||||
|
if (entityId == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Components::GameObject* gameObject = context.scene->FindByID(entityId);
|
||||||
|
if (!CanBuildOverlayForGameObject(gameObject)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Components::CameraComponent* camera = gameObject->GetComponent<Components::CameraComponent>();
|
||||||
|
if (camera == nullptr || !camera->IsEnabled()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppendCameraFrustumOverlay(
|
||||||
|
frameData,
|
||||||
|
*camera,
|
||||||
|
*gameObject,
|
||||||
|
context.viewportWidth,
|
||||||
|
context.viewportHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class SceneViewportLightOverlayProvider final : public ISceneViewportOverlayProvider {
|
||||||
|
public:
|
||||||
|
const char* GetName() const override {
|
||||||
|
return "SceneViewportLightOverlayProvider";
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppendOverlay(
|
||||||
|
const SceneViewportOverlayBuildContext& context,
|
||||||
|
SceneViewportOverlayFrameData& frameData) const override {
|
||||||
|
if (!context.IsValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr Math::Vector2 kLightIconSize(100.0f, 100.0f);
|
||||||
|
|
||||||
|
for (Components::LightComponent* light : context.scene->FindObjectsOfType<Components::LightComponent>()) {
|
||||||
|
if (light == nullptr || !light->IsEnabled()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Components::GameObject* gameObject = light->GetGameObject();
|
||||||
|
if (!CanBuildOverlayForGameObject(gameObject)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppendSceneIconOverlay(
|
||||||
|
frameData,
|
||||||
|
*context.overlay,
|
||||||
|
context.viewportWidth,
|
||||||
|
context.viewportHeight,
|
||||||
|
*gameObject,
|
||||||
|
kLightIconSize,
|
||||||
|
SceneViewportOverlaySpriteTextureKind::Light);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint64_t entityId : *context.selectedObjectIds) {
|
||||||
|
if (entityId == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Components::GameObject* gameObject = context.scene->FindByID(entityId);
|
||||||
|
if (!CanBuildOverlayForGameObject(gameObject)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Components::LightComponent* light = gameObject->GetComponent<Components::LightComponent>();
|
||||||
|
if (light == nullptr ||
|
||||||
|
!light->IsEnabled() ||
|
||||||
|
light->GetLightType() != Components::LightType::Directional) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppendDirectionalLightOverlay(
|
||||||
|
frameData,
|
||||||
|
*gameObject,
|
||||||
|
*context.overlay,
|
||||||
|
context.viewportHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void SceneViewportOverlayProviderRegistry::AddProvider(
|
||||||
|
std::unique_ptr<ISceneViewportOverlayProvider> provider) {
|
||||||
|
if (provider == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_providers.emplace_back(std::move(provider));
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t SceneViewportOverlayProviderRegistry::GetProviderCount() const {
|
||||||
|
return m_providers.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
const ISceneViewportOverlayProvider* SceneViewportOverlayProviderRegistry::GetProvider(size_t index) const {
|
||||||
|
return index < m_providers.size() ? m_providers[index].get() : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneViewportOverlayProviderRegistry::AppendOverlay(
|
||||||
|
const SceneViewportOverlayBuildContext& context,
|
||||||
|
SceneViewportOverlayFrameData& frameData) const {
|
||||||
|
if (!context.IsValid()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& provider : m_providers) {
|
||||||
|
if (provider != nullptr) {
|
||||||
|
provider->AppendOverlay(context, frameData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<ISceneViewportOverlayProvider> CreateSceneViewportCameraOverlayProvider() {
|
||||||
|
return std::make_unique<SceneViewportCameraOverlayProvider>();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<ISceneViewportOverlayProvider> CreateSceneViewportLightOverlayProvider() {
|
||||||
|
return std::make_unique<SceneViewportLightOverlayProvider>();
|
||||||
|
}
|
||||||
|
|
||||||
|
SceneViewportOverlayProviderRegistry BuildDefaultSceneViewportOverlayProviderRegistry() {
|
||||||
|
SceneViewportOverlayProviderRegistry registry;
|
||||||
|
registry.AddProvider(CreateSceneViewportCameraOverlayProvider());
|
||||||
|
registry.AddProvider(CreateSceneViewportLightOverlayProvider());
|
||||||
|
return registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Editor
|
||||||
|
} // namespace XCEngine
|
||||||
66
editor/src/Viewport/SceneViewportOverlayProviders.h
Normal file
66
editor/src/Viewport/SceneViewportOverlayProviders.h
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "SceneViewportEditorOverlayData.h"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace XCEngine {
|
||||||
|
namespace Components {
|
||||||
|
class Scene;
|
||||||
|
} // namespace Components
|
||||||
|
|
||||||
|
namespace Editor {
|
||||||
|
|
||||||
|
class IEditorContext;
|
||||||
|
|
||||||
|
struct SceneViewportOverlayBuildContext {
|
||||||
|
IEditorContext* editorContext = nullptr;
|
||||||
|
const Components::Scene* scene = nullptr;
|
||||||
|
const SceneViewportOverlayData* overlay = nullptr;
|
||||||
|
uint32_t viewportWidth = 0u;
|
||||||
|
uint32_t viewportHeight = 0u;
|
||||||
|
const std::vector<uint64_t>* selectedObjectIds = nullptr;
|
||||||
|
|
||||||
|
bool IsValid() const {
|
||||||
|
return editorContext != nullptr &&
|
||||||
|
scene != nullptr &&
|
||||||
|
overlay != nullptr &&
|
||||||
|
overlay->valid &&
|
||||||
|
viewportWidth > 0u &&
|
||||||
|
viewportHeight > 0u &&
|
||||||
|
selectedObjectIds != nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class ISceneViewportOverlayProvider {
|
||||||
|
public:
|
||||||
|
virtual ~ISceneViewportOverlayProvider() = default;
|
||||||
|
|
||||||
|
virtual const char* GetName() const = 0;
|
||||||
|
virtual void AppendOverlay(
|
||||||
|
const SceneViewportOverlayBuildContext& context,
|
||||||
|
SceneViewportOverlayFrameData& frameData) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SceneViewportOverlayProviderRegistry {
|
||||||
|
public:
|
||||||
|
void AddProvider(std::unique_ptr<ISceneViewportOverlayProvider> provider);
|
||||||
|
size_t GetProviderCount() const;
|
||||||
|
const ISceneViewportOverlayProvider* GetProvider(size_t index) const;
|
||||||
|
void AppendOverlay(
|
||||||
|
const SceneViewportOverlayBuildContext& context,
|
||||||
|
SceneViewportOverlayFrameData& frameData) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<std::unique_ptr<ISceneViewportOverlayProvider>> m_providers = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<ISceneViewportOverlayProvider> CreateSceneViewportCameraOverlayProvider();
|
||||||
|
std::unique_ptr<ISceneViewportOverlayProvider> CreateSceneViewportLightOverlayProvider();
|
||||||
|
SceneViewportOverlayProviderRegistry BuildDefaultSceneViewportOverlayProviderRegistry();
|
||||||
|
|
||||||
|
} // namespace Editor
|
||||||
|
} // namespace XCEngine
|
||||||
@@ -595,7 +595,7 @@ private:
|
|||||||
m_sceneViewEditorOverlayFrameData = {};
|
m_sceneViewEditorOverlayFrameData = {};
|
||||||
m_sceneViewEditorOverlayFrameData.overlay = overlay;
|
m_sceneViewEditorOverlayFrameData.overlay = overlay;
|
||||||
if (scene != nullptr && overlay.valid && viewportWidth > 0u && viewportHeight > 0u) {
|
if (scene != nullptr && overlay.valid && viewportWidth > 0u && viewportHeight > 0u) {
|
||||||
m_sceneViewEditorOverlayFrameData = SceneViewportOverlayBuilder::Build(
|
m_sceneViewEditorOverlayFrameData = m_sceneViewportOverlayBuilder.Build(
|
||||||
context,
|
context,
|
||||||
overlay,
|
overlay,
|
||||||
viewportWidth,
|
viewportWidth,
|
||||||
@@ -873,6 +873,7 @@ private:
|
|||||||
UI::ImGuiBackendBridge* m_backend = nullptr;
|
UI::ImGuiBackendBridge* m_backend = nullptr;
|
||||||
RHI::RHIDevice* m_device = nullptr;
|
RHI::RHIDevice* m_device = nullptr;
|
||||||
std::unique_ptr<Rendering::SceneRenderer> m_sceneRenderer;
|
std::unique_ptr<Rendering::SceneRenderer> m_sceneRenderer;
|
||||||
|
SceneViewportOverlayBuilder m_sceneViewportOverlayBuilder = {};
|
||||||
Rendering::RenderContext m_sceneViewLastRenderContext = {};
|
Rendering::RenderContext m_sceneViewLastRenderContext = {};
|
||||||
std::array<ViewportEntry, 2> m_entries = {};
|
std::array<ViewportEntry, 2> m_entries = {};
|
||||||
SceneViewCameraState m_sceneViewCamera;
|
SceneViewCameraState m_sceneViewCamera;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ project(XCEngine_EditorTests)
|
|||||||
|
|
||||||
set(EDITOR_TEST_SOURCES
|
set(EDITOR_TEST_SOURCES
|
||||||
test_action_routing.cpp
|
test_action_routing.cpp
|
||||||
|
test_application_asset_cache_stub.cpp
|
||||||
test_play_session_controller.cpp
|
test_play_session_controller.cpp
|
||||||
test_scene_viewport_camera_controller.cpp
|
test_scene_viewport_camera_controller.cpp
|
||||||
test_scene_viewport_move_gizmo.cpp
|
test_scene_viewport_move_gizmo.cpp
|
||||||
@@ -12,6 +13,7 @@ set(EDITOR_TEST_SOURCES
|
|||||||
test_scene_viewport_picker.cpp
|
test_scene_viewport_picker.cpp
|
||||||
test_scene_viewport_shader_paths.cpp
|
test_scene_viewport_shader_paths.cpp
|
||||||
test_scene_viewport_overlay_renderer.cpp
|
test_scene_viewport_overlay_renderer.cpp
|
||||||
|
test_scene_viewport_overlay_providers.cpp
|
||||||
test_script_component_editor_utils.cpp
|
test_script_component_editor_utils.cpp
|
||||||
test_viewport_host_surface_utils.cpp
|
test_viewport_host_surface_utils.cpp
|
||||||
test_viewport_object_id_picker.cpp
|
test_viewport_object_id_picker.cpp
|
||||||
@@ -31,6 +33,8 @@ set(EDITOR_TEST_SOURCES
|
|||||||
${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportMoveGizmo.cpp
|
${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportMoveGizmo.cpp
|
||||||
${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportRotateGizmo.cpp
|
${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportRotateGizmo.cpp
|
||||||
${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportScaleGizmo.cpp
|
${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportScaleGizmo.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportOverlayBuilder.cpp
|
||||||
|
${CMAKE_SOURCE_DIR}/editor/src/Viewport/SceneViewportOverlayProviders.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
if(XCENGINE_ENABLE_MONO_SCRIPTING AND TARGET xcengine_managed_assemblies)
|
if(XCENGINE_ENABLE_MONO_SCRIPTING AND TARGET xcengine_managed_assemblies)
|
||||||
|
|||||||
45
tests/editor/test_application_asset_cache_stub.cpp
Normal file
45
tests/editor/test_application_asset_cache_stub.cpp
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#include "Application.h"
|
||||||
|
|
||||||
|
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||||
|
#ifdef XCENGINE_ENABLE_MONO_SCRIPTING
|
||||||
|
#include <XCEngine/Scripting/Mono/MonoScriptRuntime.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace XCEngine::Editor {
|
||||||
|
|
||||||
|
Application& Application::Get() {
|
||||||
|
alignas(Application) static unsigned char storage[sizeof(Application)] = {};
|
||||||
|
return *reinterpret_cast<Application*>(storage);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Application::CanReimportProjectAsset(const std::string& assetPath) const {
|
||||||
|
if (assetPath.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ::XCEngine::Resources::ResourceManager::Get().CanReimportProjectAsset(assetPath.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Application::ReimportProjectAsset(const std::string& assetPath) {
|
||||||
|
if (assetPath.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& resourceManager = ::XCEngine::Resources::ResourceManager::Get();
|
||||||
|
resourceManager.Initialize();
|
||||||
|
return resourceManager.ReimportProjectAsset(assetPath.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Application::ReimportAllProjectAssets() {
|
||||||
|
auto& resourceManager = ::XCEngine::Resources::ResourceManager::Get();
|
||||||
|
resourceManager.Initialize();
|
||||||
|
return resourceManager.RebuildProjectAssetCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Application::ClearProjectLibrary() {
|
||||||
|
auto& resourceManager = ::XCEngine::Resources::ResourceManager::Get();
|
||||||
|
resourceManager.Initialize();
|
||||||
|
return resourceManager.ClearProjectLibraryCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace XCEngine::Editor
|
||||||
210
tests/editor/test_scene_viewport_overlay_providers.cpp
Normal file
210
tests/editor/test_scene_viewport_overlay_providers.cpp
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "Core/EditorContext.h"
|
||||||
|
#include "Viewport/SceneViewportOverlayBuilder.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) {
|
||||||
|
return {
|
||||||
|
&context,
|
||||||
|
context.GetSceneManager().GetScene(),
|
||||||
|
&overlay,
|
||||||
|
viewportWidth,
|
||||||
|
viewportHeight,
|
||||||
|
&selectedObjectIds
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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::Light);
|
||||||
|
ASSERT_EQ(frameData.handleRecords.size(), 1u);
|
||||||
|
EXPECT_EQ(frameData.handleRecords[0].entityId, lightEntity->GetID());
|
||||||
|
EXPECT_GT(frameData.worldLines.size(), 0u);
|
||||||
|
}
|
||||||
|
|
||||||
|
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::Light));
|
||||||
|
EXPECT_GT(frameData.worldLines.size(), 12u);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace XCEngine::Editor
|
||||||
Reference in New Issue
Block a user