424 lines
11 KiB
C++
424 lines
11 KiB
C++
// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
|
|
|
|
#include "Camera.h"
|
|
#include "Engine/Level/SceneObjectsFactory.h"
|
|
#include "Engine/Core/Math/Viewport.h"
|
|
#include "Engine/Content/Assets/Model.h"
|
|
#include "Engine/Content/Content.h"
|
|
#include "Engine/Serialization/Serialization.h"
|
|
#if USE_EDITOR
|
|
#include "Editor/Editor.h"
|
|
#include "Editor/Managed/ManagedEditor.h"
|
|
#include "Engine/Renderer/DrawCall.h"
|
|
#include "Engine/Graphics/RenderTask.h"
|
|
#include "Engine/Level/Scene/SceneRendering.h"
|
|
#else
|
|
#include "Engine/Engine/Engine.h"
|
|
#include "Engine/Platform/Window.h"
|
|
#endif
|
|
|
|
Array<Camera*> Camera::Cameras;
|
|
Camera* Camera::CutSceneCamera = nullptr;
|
|
ScriptingObjectReference<Camera> Camera::OverrideMainCamera;
|
|
|
|
Camera* Camera::GetMainCamera()
|
|
{
|
|
Camera* overrideMainCamera = OverrideMainCamera.Get();
|
|
if (overrideMainCamera)
|
|
return overrideMainCamera;
|
|
if (CutSceneCamera)
|
|
return CutSceneCamera;
|
|
return Cameras.HasItems() ? Cameras.First() : nullptr;
|
|
}
|
|
|
|
Camera::Camera(const SpawnParams& params)
|
|
: Actor(params)
|
|
, _usePerspective(true)
|
|
, _fov(60.0f)
|
|
, _customAspectRatio(0.0f)
|
|
, _near(10.0f)
|
|
, _far(40000.0f)
|
|
, _orthoScale(1.0f)
|
|
{
|
|
#if USE_EDITOR
|
|
_previewModel.Loaded.Bind<Camera, &Camera::OnPreviewModelLoaded>(this);
|
|
#endif
|
|
}
|
|
|
|
void Camera::SetUsePerspective(bool value)
|
|
{
|
|
if (_usePerspective != value)
|
|
{
|
|
_usePerspective = value;
|
|
UpdateCache();
|
|
}
|
|
}
|
|
|
|
void Camera::SetFieldOfView(float value)
|
|
{
|
|
value = Math::Clamp(value, 1.0f, 179.9f);
|
|
if (Math::NotNearEqual(_fov, value))
|
|
{
|
|
_fov = value;
|
|
UpdateCache();
|
|
}
|
|
}
|
|
|
|
void Camera::SetCustomAspectRatio(float value)
|
|
{
|
|
value = Math::Clamp(value, 0.0f, 100.0f);
|
|
if (_customAspectRatio != value)
|
|
{
|
|
_customAspectRatio = value;
|
|
UpdateCache();
|
|
}
|
|
}
|
|
|
|
void Camera::SetNearPlane(float value)
|
|
{
|
|
value = Math::Clamp(value, 0.001f, _far - 1.0f);
|
|
if (Math::NotNearEqual(_near, value))
|
|
{
|
|
_near = value;
|
|
UpdateCache();
|
|
}
|
|
}
|
|
|
|
void Camera::SetFarPlane(float value)
|
|
{
|
|
value = Math::Max(value, _near + 1.0f);
|
|
if (Math::NotNearEqual(_far, value))
|
|
{
|
|
_far = value;
|
|
UpdateCache();
|
|
}
|
|
}
|
|
|
|
void Camera::SetOrthographicScale(float value)
|
|
{
|
|
value = Math::Clamp(value, 0.0001f, 1000000.0f);
|
|
if (Math::NotNearEqual(_orthoScale, value))
|
|
{
|
|
_orthoScale = value;
|
|
UpdateCache();
|
|
}
|
|
}
|
|
|
|
void Camera::ProjectPoint(const Vector3& worldSpaceLocation, Float2& gameWindowSpaceLocation) const
|
|
{
|
|
ProjectPoint(worldSpaceLocation, gameWindowSpaceLocation, GetViewport());
|
|
}
|
|
|
|
void Camera::ProjectPoint(const Vector3& worldSpaceLocation, Float2& cameraViewportSpaceLocation, const Viewport& viewport) const
|
|
{
|
|
Matrix v, p, vp;
|
|
GetMatrices(v, p, viewport);
|
|
Matrix::Multiply(v, p, vp);
|
|
Vector3 clipSpaceLocation;
|
|
Vector3::Transform(worldSpaceLocation, vp, clipSpaceLocation);
|
|
viewport.Project(worldSpaceLocation, vp, clipSpaceLocation);
|
|
cameraViewportSpaceLocation = Float2(clipSpaceLocation);
|
|
}
|
|
|
|
bool Camera::IsPointOnView(const Vector3& worldSpaceLocation) const
|
|
{
|
|
Vector3 cameraUp = GetTransform().GetUp();
|
|
Vector3 cameraForward = GetTransform().GetForward();
|
|
Vector3 directionToPosition = (worldSpaceLocation - GetPosition()).GetNormalized();
|
|
|
|
if (Vector3::Dot(cameraForward, directionToPosition) < 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Quaternion lookAt = Quaternion::LookRotation(directionToPosition, cameraUp);
|
|
Vector3 lookAtDirection = lookAt * Vector3::Forward;
|
|
Vector3 newWorldLocation = GetPosition() + lookAtDirection;
|
|
|
|
Float2 windowSpace = Float2();
|
|
Float2 screenSize = GetViewport().Size;
|
|
|
|
ProjectPoint(newWorldLocation, windowSpace);
|
|
|
|
return (windowSpace.X >= 0 && windowSpace.X <= screenSize.X) &&
|
|
(windowSpace.Y >= 0 && windowSpace.Y <= screenSize.Y);
|
|
}
|
|
|
|
Ray Camera::ConvertMouseToRay(const Float2& mousePosition) const
|
|
{
|
|
return ConvertMouseToRay(mousePosition, GetViewport());
|
|
}
|
|
|
|
Ray Camera::ConvertMouseToRay(const Float2& mousePosition, const Viewport& viewport) const
|
|
{
|
|
#if 1
|
|
// Gather camera properties
|
|
Matrix v, p, ivp;
|
|
GetMatrices(v, p, viewport);
|
|
Matrix::Multiply(v, p, ivp);
|
|
ivp.Invert();
|
|
|
|
// Create near and far points
|
|
Vector3 nearPoint(mousePosition, 0.0f);
|
|
Vector3 farPoint(mousePosition, 1.0f);
|
|
viewport.Unproject(nearPoint, ivp, nearPoint);
|
|
viewport.Unproject(farPoint, ivp, farPoint);
|
|
|
|
// Create direction vector
|
|
Vector3 direction = farPoint - nearPoint;
|
|
direction.Normalize();
|
|
|
|
return Ray(nearPoint, direction);
|
|
#else
|
|
// Create near and far points
|
|
Vector3 nearPoint, farPoint;
|
|
Matrix ivp;
|
|
_frustum.GetInvMatrix(&ivp);
|
|
viewport.Unproject(Vector3(mousePosition, 0.0f), ivp, nearPoint);
|
|
viewport.Unproject(Vector3(mousePosition, 1.0f), ivp, farPoint);
|
|
|
|
// Create direction vector
|
|
Vector3 direction = farPoint - nearPoint;
|
|
direction.Normalize();
|
|
|
|
// Return result
|
|
return Ray(nearPoint, direction);
|
|
#endif
|
|
}
|
|
|
|
Viewport Camera::GetViewport() const
|
|
{
|
|
Viewport result = Viewport(Float2::Zero);
|
|
|
|
#if USE_EDITOR
|
|
// Editor
|
|
if (Editor::Managed)
|
|
result.Size = Editor::Managed->GetGameWindowSize();
|
|
#else
|
|
// game
|
|
auto mainWin = Engine::MainWindow;
|
|
if (mainWin)
|
|
{
|
|
const auto size = mainWin->GetClientSize();
|
|
result.Size = size;
|
|
}
|
|
#endif
|
|
|
|
// Fallback to the default value
|
|
if (result.Width <= ZeroTolerance)
|
|
result.Size = Float2(1280, 720);
|
|
|
|
return result;
|
|
}
|
|
|
|
void Camera::GetMatrices(Matrix& view, Matrix& projection) const
|
|
{
|
|
GetMatrices(view, projection, GetViewport(), Vector3::Zero);
|
|
}
|
|
|
|
void Camera::GetMatrices(Matrix& view, Matrix& projection, const Viewport& viewport) const
|
|
{
|
|
GetMatrices(view, projection, viewport, Vector3::Zero);
|
|
}
|
|
|
|
void Camera::GetMatrices(Matrix& view, Matrix& projection, const Viewport& viewport, const Vector3& origin) const
|
|
{
|
|
// Create projection matrix
|
|
if (_usePerspective)
|
|
{
|
|
const float aspect = _customAspectRatio <= 0.0f ? viewport.GetAspectRatio() : _customAspectRatio;
|
|
Matrix::PerspectiveFov(_fov * DegreesToRadians, aspect, _near, _far, projection);
|
|
}
|
|
else
|
|
{
|
|
Matrix::Ortho(viewport.Width * _orthoScale, viewport.Height * _orthoScale, _near, _far, projection);
|
|
}
|
|
|
|
// Create view matrix
|
|
const Float3 direction = GetDirection();
|
|
const Float3 position = _transform.Translation - origin;
|
|
const Float3 target = position + direction;
|
|
Float3 up;
|
|
Float3::Transform(Float3::Up, GetOrientation(), up);
|
|
Matrix::LookAt(position, target, up, view);
|
|
}
|
|
|
|
#if USE_EDITOR
|
|
|
|
void Camera::OnPreviewModelLoaded()
|
|
{
|
|
_previewModelBuffer.Setup(_previewModel.Get());
|
|
|
|
UpdateCache();
|
|
}
|
|
|
|
void Camera::BeginPlay(SceneBeginData* data)
|
|
{
|
|
_previewModel = Content::LoadAsyncInternal<Model>(TEXT("Editor/Camera/O_Camera"));
|
|
|
|
// Base
|
|
Actor::BeginPlay(data);
|
|
}
|
|
|
|
BoundingBox Camera::GetEditorBox() const
|
|
{
|
|
const Vector3 size(100);
|
|
const Vector3 pos = _transform.Translation + _transform.Orientation * Vector3::Forward * 30.0f;
|
|
return BoundingBox(pos - size, pos + size);
|
|
}
|
|
|
|
bool Camera::IntersectsItselfEditor(const Ray& ray, Real& distance)
|
|
{
|
|
return _previewModelBox.Intersects(ray, distance);
|
|
}
|
|
|
|
bool Camera::HasContentLoaded() const
|
|
{
|
|
return _previewModel == nullptr || _previewModel->IsLoaded();
|
|
}
|
|
|
|
void Camera::Draw(RenderContext& renderContext)
|
|
{
|
|
if (renderContext.View.Flags & ViewFlags::EditorSprites
|
|
&& _previewModel
|
|
&& _previewModel->IsLoaded())
|
|
{
|
|
Matrix rot, tmp, world;
|
|
renderContext.View.GetWorldMatrix(_transform, tmp);
|
|
Matrix::RotationY(PI * -0.5f, rot);
|
|
Matrix::Multiply(rot, tmp, world);
|
|
GeometryDrawStateData drawState;
|
|
Mesh::DrawInfo draw;
|
|
draw.Buffer = &_previewModelBuffer;
|
|
draw.World = &world;
|
|
draw.DrawState = &drawState;
|
|
draw.Lightmap = nullptr;
|
|
draw.LightmapUVs = nullptr;
|
|
draw.Flags = StaticFlags::Transform;
|
|
draw.DrawModes = (DrawPass)((DrawPass::Depth | DrawPass::GBuffer | DrawPass::Forward) & renderContext.View.Pass);
|
|
BoundingSphere::FromBox(_previewModelBox, draw.Bounds);
|
|
draw.Bounds.Center -= renderContext.View.Origin;
|
|
draw.PerInstanceRandom = GetPerInstanceRandom();
|
|
draw.LODBias = 0;
|
|
draw.ForcedLOD = -1;
|
|
draw.VertexColors = nullptr;
|
|
if (draw.DrawModes != DrawPass::None)
|
|
{
|
|
_previewModel->Draw(renderContext, draw);
|
|
}
|
|
}
|
|
}
|
|
|
|
#include "Engine/Debug/DebugDraw.h"
|
|
|
|
void Camera::OnDebugDrawSelected()
|
|
{
|
|
DEBUG_DRAW_WIRE_FRUSTUM(_frustum, Color::White, 0, true);
|
|
|
|
// Base
|
|
Actor::OnDebugDrawSelected();
|
|
}
|
|
|
|
#endif
|
|
|
|
void Camera::UpdateCache()
|
|
{
|
|
// Calculate view and projection matrices
|
|
Matrix view, projection;
|
|
GetMatrices(view, projection);
|
|
|
|
// Update frustum and bounding box
|
|
_frustum.SetMatrix(view, projection);
|
|
_frustum.GetBox(_box);
|
|
BoundingSphere::FromBox(_box, _sphere);
|
|
|
|
#if USE_EDITOR
|
|
|
|
// Update editor preview model cache
|
|
Matrix rot, tmp, world;
|
|
_transform.GetWorld(tmp);
|
|
Matrix::RotationY(PI * -0.5f, rot);
|
|
Matrix::Multiply(rot, tmp, world);
|
|
|
|
// Calculate snap box for preview model
|
|
if (_previewModel && _previewModel->IsLoaded())
|
|
{
|
|
_previewModelBox = _previewModel->GetBox(world);
|
|
}
|
|
else
|
|
{
|
|
Vector3 min(-10.0f), max(10.0f);
|
|
min = Vector3::Transform(min, world);
|
|
max = Vector3::Transform(max, world);
|
|
_previewModelBox = BoundingBox(min, max);
|
|
}
|
|
|
|
// Extend culling bounding box
|
|
BoundingBox::Merge(_box, _previewModelBox, _box);
|
|
BoundingSphere::FromBox(_box, _sphere);
|
|
|
|
#endif
|
|
}
|
|
|
|
void Camera::Serialize(SerializeStream& stream, const void* otherObj)
|
|
{
|
|
// Base
|
|
Actor::Serialize(stream, otherObj);
|
|
|
|
SERIALIZE_GET_OTHER_OBJ(Camera);
|
|
|
|
SERIALIZE_MEMBER(UsePerspective, _usePerspective);
|
|
SERIALIZE_MEMBER(FOV, _fov);
|
|
SERIALIZE_MEMBER(CustomAspectRatio, _customAspectRatio);
|
|
SERIALIZE_MEMBER(Near, _near);
|
|
SERIALIZE_MEMBER(Far, _far);
|
|
SERIALIZE_MEMBER(OrthoScale, _orthoScale);
|
|
SERIALIZE(RenderLayersMask);
|
|
}
|
|
|
|
void Camera::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier)
|
|
{
|
|
// Base
|
|
Actor::Deserialize(stream, modifier);
|
|
|
|
DESERIALIZE_MEMBER(UsePerspective, _usePerspective);
|
|
DESERIALIZE_MEMBER(FOV, _fov);
|
|
DESERIALIZE_MEMBER(CustomAspectRatio, _customAspectRatio);
|
|
DESERIALIZE_MEMBER(Near, _near);
|
|
DESERIALIZE_MEMBER(Far, _far);
|
|
DESERIALIZE_MEMBER(OrthoScale, _orthoScale);
|
|
DESERIALIZE(RenderLayersMask);
|
|
}
|
|
|
|
void Camera::OnEnable()
|
|
{
|
|
Cameras.Add(this);
|
|
#if USE_EDITOR
|
|
GetSceneRendering()->AddActor(this, _sceneRenderingKey);
|
|
#endif
|
|
|
|
// Base
|
|
Actor::OnEnable();
|
|
}
|
|
|
|
void Camera::OnDisable()
|
|
{
|
|
#if USE_EDITOR
|
|
GetSceneRendering()->RemoveActor(this, _sceneRenderingKey);
|
|
#endif
|
|
Cameras.Remove(this);
|
|
if (CutSceneCamera == this)
|
|
CutSceneCamera = nullptr;
|
|
|
|
// Base
|
|
Actor::OnDisable();
|
|
}
|
|
|
|
void Camera::OnTransformChanged()
|
|
{
|
|
// Base
|
|
Actor::OnTransformChanged();
|
|
|
|
UpdateCache();
|
|
}
|