// Copyright Epic Games, Inc. All Rights Reserved. #include "ToolSceneQueriesUtil.h" #include "Engine/World.h" #include "VectorUtil.h" #include "Quaternion.h" #include "GameFramework/Actor.h" #include "Components/PrimitiveComponent.h" #include "SceneQueries/SceneSnappingManager.h" static double VISUAL_ANGLE_SNAP_THRESHOLD_DEG = 1.0; double ToolSceneQueriesUtil::GetDefaultVisualAngleSnapThreshD() { return VISUAL_ANGLE_SNAP_THRESHOLD_DEG; } bool ToolSceneQueriesUtil::PointSnapQuery(const UInteractiveTool* Tool, const FVector3d& Point1, const FVector3d& Point2, double VisualAngleThreshold) { IToolsContextQueriesAPI* QueryAPI = Tool->GetToolManager()->GetContextQueriesAPI(); FViewCameraState CameraState; QueryAPI->GetCurrentViewState(CameraState); return PointSnapQuery(CameraState, Point1, Point2, VisualAngleThreshold); } bool ToolSceneQueriesUtil::PointSnapQuery(const FViewCameraState& CameraState, const FVector3d& Point1, const FVector3d& Point2, double VisualAngleThreshold) { if (!CameraState.bIsOrthographic) { double UseThreshold = (VisualAngleThreshold <= 0) ? GetDefaultVisualAngleSnapThreshD() : VisualAngleThreshold; UseThreshold *= CameraState.GetFOVAngleNormalizationFactor(); double VisualAngle = VectorUtil::OpeningAngleD(Point1, Point2, (FVector3d)CameraState.Position); return FMathd::Abs(VisualAngle) < UseThreshold; } else { // Whereas in perspective mode we can compare the angle difference to the camera, we can't do that in ortho mode, since the camera isn't a point // but a plane. Instead we need to project into the camera plane and measure distance here. To be analogous to our tolerance in perspective mode, // where we divide the FOV into 90 visual angle degrees, we divide the plane into 90 segments and use the same tolerance. double AngleThreshold = (VisualAngleThreshold <= 0) ? GetDefaultVisualAngleSnapThreshD() : VisualAngleThreshold; double OrthoThreshold = AngleThreshold * CameraState.OrthoWorldCoordinateWidth / 90.0; FVector3d ViewPlaneNormal = (FVector3d)CameraState.Orientation.GetForwardVector(); FVector3d DistanceVector = Point1 - Point2; // Project the vector into the plane and check its length DistanceVector = DistanceVector - (DistanceVector).Dot(ViewPlaneNormal) * ViewPlaneNormal; return DistanceVector.SquaredLength() < (OrthoThreshold * OrthoThreshold); } } double ToolSceneQueriesUtil::PointSnapMetric(const FViewCameraState& CameraState, const FVector3d& Point1, const FVector3d& Point2) { if (!CameraState.bIsOrthographic) { double VisualAngle = VectorUtil::OpeningAngleD(Point1, Point2, (FVector3d)CameraState.Position); // To go from a world space angle to a 90 degree division of the view, we divide by TrueFOVDegrees/90 (our normalization factor) VisualAngle /= CameraState.GetFOVAngleNormalizationFactor(); return FMathd::Abs(VisualAngle); } else { FVector3d ViewPlaneNormal = (FVector3d)CameraState.Orientation.GetForwardVector(); // Get projected distance in the plane FVector3d DistanceVector = Point1 - Point2; DistanceVector = DistanceVector - (DistanceVector).Dot(ViewPlaneNormal) * ViewPlaneNormal; // We have one visual angle degree correspond to the width of the viewport divided by 90, so we divide by width/90. return DistanceVector.Length() * 90.0 / CameraState.OrthoWorldCoordinateWidth; } } double ToolSceneQueriesUtil::CalculateViewVisualAngleD(const UInteractiveTool* Tool, const FVector3d& Point1, const FVector3d& Point2) { IToolsContextQueriesAPI* QueryAPI = Tool->GetToolManager()->GetContextQueriesAPI(); FViewCameraState CameraState; QueryAPI->GetCurrentViewState(CameraState); return CalculateViewVisualAngleD(CameraState, Point1, Point2); } double ToolSceneQueriesUtil::CalculateViewVisualAngleD(const FViewCameraState& CameraState, const FVector3d& Point1, const FVector3d& Point2) { double VisualAngle = VectorUtil::OpeningAngleD(Point1, Point2, (FVector3d)CameraState.Position); return FMathd::Abs(VisualAngle); } double ToolSceneQueriesUtil::CalculateNormalizedViewVisualAngleD(const FViewCameraState& CameraState, const FVector3d& Point1, const FVector3d& Point2) { double VisualAngle = VectorUtil::OpeningAngleD(Point1, Point2, (FVector3d)CameraState.Position); double FOVNormalization = CameraState.GetFOVAngleNormalizationFactor(); return FMathd::Abs(VisualAngle) / FOVNormalization; } double ToolSceneQueriesUtil::CalculateDimensionFromVisualAngleD(const UInteractiveTool* Tool, const FVector3d& Point, double TargetVisualAngleDeg) { IToolsContextQueriesAPI* QueryAPI = Tool->GetToolManager()->GetContextQueriesAPI(); FViewCameraState CameraState; QueryAPI->GetCurrentViewState(CameraState); return CalculateDimensionFromVisualAngleD(CameraState, Point, TargetVisualAngleDeg); } double ToolSceneQueriesUtil::CalculateDimensionFromVisualAngleD(const FViewCameraState& CameraState, const FVector3d& Point, double TargetVisualAngleDeg) { FVector3d EyePos = (FVector3d)CameraState.Position; FVector3d PointVec = Point - EyePos; TargetVisualAngleDeg *= CameraState.GetFOVAngleNormalizationFactor(); FVector3d RotPointPos = EyePos + FQuaterniond((FVector3d)CameraState.Up(), TargetVisualAngleDeg, true)*PointVec; double ActualAngleDeg = CalculateViewVisualAngleD(CameraState, Point, RotPointPos); return Distance(Point, RotPointPos) * (TargetVisualAngleDeg/ActualAngleDeg); } bool ToolSceneQueriesUtil::IsPointVisible(const FViewCameraState& CameraState, const FVector3d& Point) { if (CameraState.bIsOrthographic == false) { FVector3d PointDir = (Point - (FVector3d)CameraState.Position); //@todo should use view frustum here! if (PointDir.Dot((FVector3d)CameraState.Forward()) < 0.25) // ballpark estimate { return false; } } else { // @todo probably not always true but it's not exactly clear how ortho camera is configured... return true; } return true; } bool ToolSceneQueriesUtil::FindSceneSnapPoint(const UInteractiveTool* Tool, const FVector3d& Point, FVector3d& SnapPointOut, bool bVertices, bool bEdges, double VisualAngleThreshold, FSnapGeometry* SnapGeometry, FVector* DebugTriangleOut) { FFindSceneSnapPointParams Params; Params.SnappingManager = USceneSnappingManager::Find(Tool->GetToolManager()); Tool->GetToolManager()->GetContextQueriesAPI()->GetCurrentViewState(Params.CameraState); Params.Point = &Point; Params.SnapPointOut = &SnapPointOut; Params.bVertices = bVertices; Params.bEdges = bEdges; Params.VisualAngleThreshold = VisualAngleThreshold; Params.SnapGeometryOut = SnapGeometry; Params.DebugTriangleOut = DebugTriangleOut; return FindSceneSnapPoint(Params); } bool ToolSceneQueriesUtil::FindSceneSnapPoint(FFindSceneSnapPointParams& Params) { double UseThreshold = (Params.VisualAngleThreshold <= 0) ? GetDefaultVisualAngleSnapThreshD() : Params.VisualAngleThreshold; if (!Params.SnappingManager) { return false; } UseThreshold *= Params.CameraState.GetFOVAngleNormalizationFactor(); FSceneSnapQueryRequest Request; Request.RequestType = ESceneSnapQueryType::Position; Request.TargetTypes = ESceneSnapQueryTargetType::None; if (Params.bVertices) { Request.TargetTypes |= ESceneSnapQueryTargetType::MeshVertex; } if (Params.bEdges) { Request.TargetTypes |= ESceneSnapQueryTargetType::MeshEdge; } Request.Position = (FVector)*Params.Point; Request.VisualAngleThresholdDegrees = UseThreshold; Request.ComponentsToIgnore = Params.ComponentsToIgnore; Request.InvisibleComponentsToInclude = Params.InvisibleComponentsToInclude; TArray Results; if (Params.SnappingManager->ExecuteSceneSnapQuery(Request, Results)) { *Params.SnapPointOut = (FVector3d)Results[0].Position; if (Params.SnapGeometryOut != nullptr) { int iSnap = Results[0].TriSnapIndex; Params.SnapGeometryOut->Points[0] = (FVector3d)Results[0].TriVertices[iSnap]; Params.SnapGeometryOut->PointCount = 1; if (Results[0].TargetType == ESceneSnapQueryTargetType::MeshEdge) { Params.SnapGeometryOut->Points[1] = (FVector3d)Results[0].TriVertices[(iSnap+1)%3]; Params.SnapGeometryOut->PointCount = 2; } } if (Params.DebugTriangleOut != nullptr) { Params.DebugTriangleOut[0] = Results[0].TriVertices[0]; Params.DebugTriangleOut[1] = Results[0].TriVertices[1]; Params.DebugTriangleOut[2] = Results[0].TriVertices[2]; } return true; } return false; } bool ToolSceneQueriesUtil::FindWorldGridSnapPoint(const UInteractiveTool* Tool, const FVector3d& Point, FVector3d& GridSnapPointOut) { USceneSnappingManager* SnapManager = USceneSnappingManager::Find(Tool->GetToolManager()); if (!SnapManager) { return false; } FSceneSnapQueryRequest Request; Request.RequestType = ESceneSnapQueryType::Position; Request.TargetTypes = ESceneSnapQueryTargetType::Grid; Request.Position = (FVector)Point; TArray Results; if ( SnapManager->ExecuteSceneSnapQuery(Request, Results) ) { GridSnapPointOut = (FVector3d)Results[0].Position; return true; }; return false; } double ToolSceneQueriesUtil::SnapDistanceToWorldGridSize(const UInteractiveTool* Tool, const double Distance) { IToolsContextQueriesAPI* QueryAPI = Tool->GetToolManager()->GetContextQueriesAPI(); FToolContextSnappingConfiguration SnapConfig = QueryAPI->GetCurrentSnappingSettings(); if (QueryAPI->GetCurrentSnappingSettings().bEnablePositionGridSnapping) { double DX = SnapConfig.PositionGridDimensions.GetMax(); if (DX > 0.0) { int N = FMath::RoundToInt(Distance / DX); return N * DX; } } return Distance; } bool ToolSceneQueriesUtil::IsVisibleObjectHit(const FHitResult& HitResult) { AActor* Actor = HitResult.GetActor(); if (Actor != nullptr) { #if WITH_EDITOR if (Actor->IsHidden() || Actor->IsHiddenEd()) { return false; } #else if (Actor->IsHidden()) { return false; } #endif } UPrimitiveComponent* Component = HitResult.GetComponent(); if (Component != nullptr) { #if WITH_EDITOR if (Component->IsVisible() == false && Component->IsVisibleInEditor() == false) { return false; } #else if (Component->IsVisible() == false) { return false; } #endif } return true; } bool ToolSceneQueriesUtil::FindNearestVisibleObjectHit(UWorld* World, FHitResult& HitResultOut, const FVector& Start, const FVector& End, const TArray* IgnoreComponents, const TArray* InvisibleComponentsToInclude) { FCollisionObjectQueryParams ObjectQueryParams(FCollisionObjectQueryParams::AllObjects); FCollisionQueryParams QueryParams = FCollisionQueryParams::DefaultQueryParam; QueryParams.bTraceComplex = true; TArray OutHits; if (World->LineTraceMultiByObjectType(OutHits, Start, End, ObjectQueryParams, QueryParams) == false) { return false; } float NearestVisible = TNumericLimits::Max(); for (const FHitResult& CurResult : OutHits) { if (CurResult.Distance < NearestVisible) { if (IsVisibleObjectHit(CurResult) || (InvisibleComponentsToInclude && InvisibleComponentsToInclude->Contains(CurResult.GetComponent()))) { if (IgnoreComponents == nullptr || IgnoreComponents->Contains(CurResult.GetComponent()) == false) { HitResultOut = CurResult; NearestVisible = CurResult.Distance; } } } } return NearestVisible < TNumericLimits::Max(); } bool ToolSceneQueriesUtil::FindNearestVisibleObjectHit(UWorld* World, FHitResult& HitResultOut, const FRay& Ray, const TArray* IgnoreComponents, const TArray* InvisibleComponentsToInclude) { return FindNearestVisibleObjectHit(World, HitResultOut, Ray.Origin, Ray.PointAt(HALF_WORLD_MAX), IgnoreComponents, InvisibleComponentsToInclude); } bool ToolSceneQueriesUtil::FindNearestVisibleObjectHit(USceneSnappingManager* SnapManager, FHitResult& HitResultOut, const FRay& Ray, const TArray* IgnoreComponents, const TArray* InvisibleComponentsToInclude) { if (!SnapManager) { return false; } FSceneHitQueryRequest Request; Request.WorldRay = (FRay3d)Ray; Request.bWantHitGeometryInfo = false; Request.VisibilityFilter.ComponentsToIgnore = IgnoreComponents; Request.VisibilityFilter.InvisibleComponentsToInclude = InvisibleComponentsToInclude; FSceneHitQueryResult Result; if ( SnapManager->ExecuteSceneHitQuery(Request, Result) ) { HitResultOut = Result.HitResult; return true; }; return false; } bool ToolSceneQueriesUtil::FindNearestVisibleObjectHit(const UInteractiveTool* Tool, FHitResult& HitResultOut, const FVector& Start, const FVector& End, const TArray* IgnoreComponents, const TArray* InvisibleComponentsToInclude) { FRay WorldRay(Start, (End - Start), false); return FindNearestVisibleObjectHit(USceneSnappingManager::Find(Tool->GetToolManager()), HitResultOut, WorldRay, IgnoreComponents, InvisibleComponentsToInclude); } bool ToolSceneQueriesUtil::FindNearestVisibleObjectHit(const UInteractiveTool* Tool, FHitResult& HitResultOut, const FRay& Ray, const TArray* IgnoreComponents, const TArray* InvisibleComponentsToInclude) { return FindNearestVisibleObjectHit(USceneSnappingManager::Find(Tool->GetToolManager()), HitResultOut, Ray, IgnoreComponents, InvisibleComponentsToInclude); }