// Copyright Voulz 2021-2025. All Rights Reserved. #include "ArchVisSceneViewExtension.h" #include "ArchVisCineCameraComponent.h" #include "ArchVisSubsystem.h" #include "ArchVisToolsDefinitions.h" #include "SceneView.h" #include "Engine/LocalPlayer.h" #include "GameFramework/PlayerController.h" #if WITH_EDITOR #include "LevelEditorViewport.h" #endif FArchVisSceneViewExtension::FArchVisSceneViewExtension(const FAutoRegister& AutoRegister, UArchVisSubsystem* InArchVisSubsystem) : FSceneViewExtensionBase(AutoRegister), ArchVisSubsystem(InArchVisSubsystem) { UE_LOG(LogArchVisTools, Log, TEXT("FArchVisSceneViewExtension: Custom SceneViewExtension registered")); } void FArchVisSceneViewExtension::SetupViewFamily(FSceneViewFamily& InViewFamily) { } void FArchVisSceneViewExtension::SetupView(FSceneViewFamily& InViewFamily, FSceneView& InView) { UArchVisCineCameraComponent* ArchVisComponent = InView.ViewActor ? InView.ViewActor->FindComponentByClass() : nullptr; #if WITH_EDITOR if (!IsValid(ArchVisComponent)) { if (const FLevelEditorViewportClient* ViewportClient = static_cast(InView.SceneViewInitOptions.ViewElementDrawer)) { ArchVisComponent = Cast(ViewportClient->GetCameraComponentForView()); } } #endif if (!IsValid(ArchVisComponent)) { return; } InView.PreviousViewTransform.Reset(); const UWorld* World = InViewFamily.Scene ? InViewFamily.Scene->GetWorld() : nullptr; FMinimalViewInfo CurrentPOV; CurrentPOV.Location = InView.ViewLocation; CurrentPOV.Rotation = InView.ViewRotation; CurrentPOV.FOV = InView.FOV; FMinimalViewInfo ViewInfo = GetCorrectedViewInfo(World, ArchVisComponent, InView.UnconstrainedViewRect.Size(), CurrentPOV); // as per ULocalPlayer::GetProjectionData and UMoviePipelineImagePassBase::GetSceneViewForSampleState FSceneViewProjectionData& ProjectionData = InView.SceneViewInitOptions; ProjectionData.ViewOrigin = ViewInfo.Location; ProjectionData.ViewRotationMatrix = FInverseRotationMatrix(ViewInfo.Rotation) * FMatrix( FPlane(0, 0, 1, 0), FPlane(1, 0, 0, 0), FPlane(0, 1, 0, 0), FPlane(0, 0, 0, 1)); const FIntRect ConstrainedViewRect = CalculateViewExtents(ViewInfo.AspectRatio, InView.SceneViewInitOptions.GetViewRect()); //this only affects the ProjectionData which is InView.SceneViewInitOptions FMinimalViewInfo::CalculateProjectionMatrixGivenViewRectangle(ViewInfo, ViewInfo.AspectRatioAxisConstraint.Get(AspectRatio_MaintainXFOV), ConstrainedViewRect, ProjectionData); InView.UpdateProjectionMatrix(ProjectionData.ProjectionMatrix); } FMinimalViewInfo FArchVisSceneViewExtension::GetCorrectedViewInfo(const UWorld* World, UArchVisCineCameraComponent* ArchVisComponent, const FIntPoint& UnconstrainedViewSize, const FMinimalViewInfo& CurrentViewInfo) { if (!ArchVisComponent) { return {}; } // --- Then we find the cached ViewInfo that we will adjust to regenerate the Projection Matrix; FMinimalViewInfo ViewInfo; ArchVisComponent->GetCameraView(0.f, ViewInfo); // The current issue is that a Camera Shake applied via the camera manager has been baked in the CurrentViewInfo in APlayerCameraManager::ApplyCameraModifiers and UCameraModifier_CameraShake::ModifyCamera on Tick // So to keep the adjustments made by the camera shakes, we reuse the position, rotation and FOV of the CurrentViewInfo, inspired by UE::MovieScene::FEvaluateCameraShake::EvaluateCameraComponentShake ViewInfo.Location = CurrentViewInfo.Location; ViewInfo.Rotation = CurrentViewInfo.Rotation; ViewInfo.FOV = CurrentViewInfo.FOV; // We fix the aspect ratio constraint if (!ViewInfo.bConstrainAspectRatio) { #if WITH_EDITOR if (!ViewInfo.AspectRatioAxisConstraint.IsSet()) { ViewInfo.AspectRatioAxisConstraint = GetDefault()->AspectRatioAxisConstraint; } #endif if (!ViewInfo.AspectRatioAxisConstraint.IsSet() || ViewInfo.AspectRatioAxisConstraint == EAspectRatioAxisConstraint::AspectRatio_MajorAxisFOV) { ViewInfo.AspectRatioAxisConstraint = UnconstrainedViewSize.X >= UnconstrainedViewSize.Y ? AspectRatio_MaintainXFOV : AspectRatio_MaintainYFOV; } } float UnconstrainedAspectRatio = static_cast(UnconstrainedViewSize.X) / static_cast(UnconstrainedViewSize.Y); float ViewportAspectRatio = ViewInfo.bConstrainAspectRatio ? ViewInfo.AspectRatio : UnconstrainedAspectRatio; // At this point, InView.ViewRotation contains the already adjusted rotation, and ViewInfo.Rotation the original View rotation // --- We are now ready to compute the automatic correction if (ArchVisComponent->ProjectionMode == ECameraProjectionMode::Perspective && ArchVisComponent->bCorrectPerspective) { float HorFov = ArchVisComponent->GetHorizontalFieldOfView(); // The Angle of the Horizontal FOV for the Constrained Viewport float VertFov = ArchVisComponent->GetVerticalFieldOfView(); // The Angle of the Vertical FOV for the Constrained Viewport float Camera_Width, Camera_Height; if (ViewportAspectRatio > UnconstrainedAspectRatio || (ViewportAspectRatio == UnconstrainedAspectRatio && ViewInfo.AspectRatioAxisConstraint.Get(AspectRatio_MaintainXFOV) == AspectRatio_MaintainXFOV)) { // because the FOV returned by GetHorizontalFieldOfView is the FOV of the unconstrained viewport, we can use it to find the height of the viewport // only when the constrained viewport would have the same width as the unconstrained viewport (black bands top and bottom) Camera_Width = FMath::Tan(FMath::DegreesToRadians(HorFov) / 2) * 2; Camera_Height = Camera_Width / ViewportAspectRatio; // (bIsMRQWorld ? UnconstrainedAspectRatio : ViewportAspectRatio); } else { // because the FOV returned by GetVerticalFieldOfView is the FOV of the unconstrained viewport, we can use it to find the width of the viewport // only when the constrained viewport would have the same height as the unconstrained viewport (black bands left and right) Camera_Height = FMath::Tan(FMath::DegreesToRadians(VertFov) / 2) * 2; Camera_Width = Camera_Height * ViewportAspectRatio; //(bIsMRQWorld ? UnconstrainedAspectRatio : ViewportAspectRatio);; } float MaxPitch = FMath::Clamp(FMath::Abs(ArchVisComponent->MaxPitch), 0.0F, 89.99F); float Target_Pitch = FMath::DegreesToRadians(FMath::Clamp(ViewInfo.Rotation.Pitch, -MaxPitch, MaxPitch)); //ensure the pitch is not 90 degrees float Final_Camera_Angle = Target_Pitch * (1 - ArchVisComponent->CorrectionStrength); // apply the strength to get the real angle of the final camera float Vert_Dist_Final_Camera = FMath::Tan(Final_Camera_Angle); // *hor_dist; // vertical distance of the final camera rotation // float Camera_Width = FMath::Tan(FMath::DegreesToRadians(HorFov) / 2) * 2; // float Camera_Height = FMath::Tan(FMath::DegreesToRadians(VertFov) / 2) * 2; // float CameraAspectRatio = Camera_Width / Camera_Height; // float Camera_Height = Camera_Width / ViewportAspectRatio; //TODO: need to handle the constraint type float Screen_Height = Camera_Height; // ViewInfo.bConstrainAspectRatio ? Camera_Height : Camera_Width / ViewportAspectRatio; float Vert_Dist_To_Move = FMath::Tan(Target_Pitch - Final_Camera_Angle); // *hor_dist; // distance we should move vertically to see the same point float Ratio_To_Move = Vert_Dist_To_Move / Screen_Height * 2; // the ratio of the screen we will need to offset. A *2 is applied as a projection offset of 1.0 moves the projection half ViewInfo.Rotation.Pitch = FMath::RadiansToDegrees(Final_Camera_Angle); // UE_LOG(LogTemp, Log, TEXT("ViewportAspectRatio: %f"), ViewportAspectRatio) // UE_LOG(LogTemp, Log, TEXT("HorFov: %f"), HorFov) // UE_LOG(LogTemp, Log, TEXT("VertFov: %f"), VertFov) // UE_LOG(LogTemp, Log, TEXT("Target_Pitch: %f"), Target_Pitch) // UE_LOG(LogTemp, Log, TEXT("Final_Camera_Angle: %f"), Final_Camera_Angle) // UE_LOG(LogTemp, Log, TEXT("Vert_Dist_Final_Camera: %f"), Vert_Dist_Final_Camera) // UE_LOG(LogTemp, Log, TEXT("Camera_Width: %f"), Camera_Width) // UE_LOG(LogTemp, Log, TEXT("Camera_Height: %f"), Camera_Height) // // UE_LOG(LogTemp, Log, TEXT("CameraAspectRatio: %f"), CameraAspectRatio) // UE_LOG(LogTemp, Log, TEXT("Screen_Height: %f"), Screen_Height) // UE_LOG(LogTemp, Log, TEXT("Vert_Dist_To_Move: %f"), Vert_Dist_To_Move) // UE_LOG(LogTemp, Log, TEXT("Ratio_To_Move: %f"), Ratio_To_Move) // UE_LOG(LogTemp, Log, TEXT("Pitch: %f"), ViewInfo.Rotation.Pitch) FVector2D FinalProjectionOffset; if (ArchVisComponent->bAllowRoll && ViewInfo.Rotation.Roll != 0.0f) { // FinalProjectionOffset = (FVector2D(0, Ratio_To_Move) + ViewInfo.OffCenterProjectionOffset).GetRotated(ViewInfo.Rotation.Roll); // FinalProjectionOffset.X /= ViewportAspectRatio; FinalProjectionOffset = FVector2D(0, Ratio_To_Move).GetRotated(ViewInfo.Rotation.Roll); if (!UArchVisSubsystem::IsMoviePipelineWorld(World)) { FinalProjectionOffset.X /= ViewportAspectRatio; //to compensate as an offset of 1 moves by one half screen, 1 horizontal and 1 vertical are not the same // This is not the case in Movie Pipeline as it works differently } FinalProjectionOffset += ViewInfo.OffCenterProjectionOffset; } else { FinalProjectionOffset = FVector2D(0, Ratio_To_Move) + ViewInfo.OffCenterProjectionOffset; } ViewInfo.OffCenterProjectionOffset = FinalProjectionOffset; } if (!ArchVisComponent->bAllowRoll) { ViewInfo.Rotation.Roll = 0; } return ViewInfo; } FIntRect FArchVisSceneViewExtension::CalculateViewExtents(float AspectRatio, const FIntRect& ViewRect) { // Adjustment of FViewport::CalculateViewExtents to remove the need from the FViewport FIntRect Result = ViewRect; const float CurrentSizeX = ViewRect.Width(); const float CurrentSizeY = ViewRect.Height(); const float DesiredAspectRatio = CurrentSizeX / CurrentSizeY; // the viewport's SizeX/SizeY may not always match the GetDesiredAspectRatio(), so adjust the requested AspectRatio to compensate const float AdjustedAspectRatio = AspectRatio; // / (GetDesiredAspectRatio() / ((float)GetSizeXY().X / (float)GetSizeXY().Y)); // If desired, enforce a particular aspect ratio for the render of the scene. // Results in black bars at top/bottom etc. const float AspectRatioDifference = AdjustedAspectRatio - (CurrentSizeX / CurrentSizeY); if( FMath::Abs( AspectRatioDifference ) > 0.01f ) { // If desired aspect ratio is bigger than current - we need black bars on top and bottom. if( AspectRatioDifference > 0.0f ) { // Calculate desired Y size. const int32 NewSizeY = FMath::Max(1, FMath::RoundToInt( CurrentSizeX / AdjustedAspectRatio ) ); Result.Min.Y = FMath::RoundToInt( 0.5f * (CurrentSizeY - NewSizeY) ); Result.Max.Y = Result.Min.Y + NewSizeY; Result.Min.Y += ViewRect.Min.Y; Result.Max.Y += ViewRect.Min.Y; } // Otherwise - will place bars on the sides. else { const int32 NewSizeX = FMath::Max(1, FMath::RoundToInt( CurrentSizeY * AdjustedAspectRatio ) ); Result.Min.X = FMath::RoundToInt( 0.5f * (CurrentSizeX - NewSizeX) ); Result.Max.X = Result.Min.X + NewSizeX; Result.Min.X += ViewRect.Min.X; Result.Max.X += ViewRect.Min.X; } } return Result; } bool FArchVisSceneViewExtension::IsActiveThisFrame_Internal(const FSceneViewExtensionContext& Context) const { // Always enabled unless specifically requested via UArchVisMoviePipelineDeferredPass::AddViewExtensions return !Context.IsA(FArchVisSceneViewExtensionContextDisableOnce{}); }