// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved. #include "Camera.h" #include "Engine/Level/SceneObjectsFactory.h" #include "Engine/Core/Math/Matrix.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::Cameras; Camera* Camera::CutSceneCamera = nullptr; ScriptingObjectReference 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(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; const Viewport viewport = GetViewport(); ProjectPoint(newWorldLocation, windowSpace, viewport); return windowSpace.X >= 0 && windowSpace.X <= viewport.Size.X && windowSpace.Y >= 0 && windowSpace.Y <= viewport.Size.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(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 (EnumHasAnyFlags(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::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.SortOrder = 0; 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(); }