Files
2025-05-18 13:04:45 +08:00

325 lines
11 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "BaseGizmos/GizmoCircleComponent.h"
#include "BaseGizmos/GizmoRenderingUtil.h"
#include "BaseGizmos/GizmoMath.h"
#include "BaseGizmos/GizmoViewContext.h"
#include "MeshElementCollector.h"
#include "PrimitiveSceneProxy.h"
#include "PrimitiveDrawingUtils.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GizmoCircleComponent)
namespace GizmoCircleComponentLocals
{
const float RENDER_VISIBILITY_DOT_THRESHOLD = 0.05;
const float VIEW_PLANE_PARALLEL_DOT_THRESHOLD = 0.95;
template <typename SceneViewOrGizmoViewContext>
void GetVisibility(const SceneViewOrGizmoViewContext* View, const FVector& ViewDirection,
const FVector& GizmoPlaneWorldNormal, const FVector& WorldOrigin,
bool& bRenderVisibilityOut, bool& bIsViewPlaneParallelOut)
{
FVector ViewPlaneDirection = View->GetViewDirection();
bRenderVisibilityOut = FMath::Abs(ViewDirection.Dot(GizmoPlaneWorldNormal)) >= RENDER_VISIBILITY_DOT_THRESHOLD;
bIsViewPlaneParallelOut = FMath::Abs(ViewPlaneDirection.Dot(GizmoPlaneWorldNormal)) >= VIEW_PLANE_PARALLEL_DOT_THRESHOLD;
}
}
class FGizmoCircleComponentSceneProxy final : public FPrimitiveSceneProxy
{
public:
SIZE_T GetTypeHash() const override
{
static size_t UniquePointer;
return reinterpret_cast<size_t>(&UniquePointer);
}
FGizmoCircleComponentSceneProxy(const UGizmoCircleComponent* InComponent)
: FPrimitiveSceneProxy(InComponent),
Color(InComponent->Color),
Normal(InComponent->Normal),
Radius(InComponent->Radius),
Thickness(InComponent->Thickness),
NumSides(InComponent->NumSides),
bViewAligned(InComponent->bViewAligned),
bDrawFullCircle(InComponent->bDrawFullCircle),
HoverThicknessMultiplier(InComponent->HoverSizeMultiplier)
{
}
virtual void GetDynamicMeshElements(const TArray<const FSceneView*>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override
{
using namespace GizmoCircleComponentLocals;
const FMatrix& LocalToWorldMatrix = GetLocalToWorld();
FVector Origin = LocalToWorldMatrix.TransformPosition(FVector::ZeroVector);
FVector PlaneX, PlaneY;
GizmoMath::MakeNormalPlaneBasis(Normal, PlaneX, PlaneY);
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
if (VisibilityMap & (1 << ViewIndex))
{
const FSceneView* View = Views[ViewIndex];
FPrimitiveDrawInterface* PDI = Collector.GetPDI(ViewIndex);
bool bIsOrtho = !View->IsPerspectiveProjection();
FVector ViewVector = View->GetViewDirection();
float LengthScale = 1;
bool bIsViewDependent = (bExternalIsViewDependent) ? (*bExternalIsViewDependent) : false;
if (bIsViewDependent)
{
LengthScale = UE::GizmoRenderingUtil::CalculateLocalPixelToWorldScale(View, Origin);
}
double UseRadius = LengthScale * Radius;
FLinearColor BackColor = FLinearColor(0.5f, 0.5f, 0.5f);
float BackThickness = 0.5f;
float UseThickness = (bExternalHoverState != nullptr && *bExternalHoverState == true) ?
(HoverThicknessMultiplier * Thickness) : (Thickness);
if (!bIsOrtho)
{
UseThickness *= (View->FOV / 90.0f); // compensate for FOV scaling in Gizmos...
BackThickness *= (View->FOV / 90.0f); // compensate for FOV scaling in Gizmos...
}
if (bDrawFullCircle)
{
BackThickness = UseThickness;
BackColor = Color;
}
const float AngleDelta = 2.0f * PI / NumSides;
if (bViewAligned)
{
FVector WorldOrigin = LocalToWorldMatrix.TransformPosition(FVector::ZeroVector);
WorldOrigin += 0.001 * ViewVector;
FVector WorldPlaneX, WorldPlaneY;
GizmoMath::MakeNormalPlaneBasis(ViewVector, WorldPlaneX, WorldPlaneY);
FVector LastVertex = WorldOrigin + WorldPlaneX * UseRadius;
for (int32 SideIndex = 0; SideIndex < NumSides; SideIndex++)
{
float DeltaX = FMath::Cos(AngleDelta * (SideIndex + 1));
float DeltaY = FMath::Sin(AngleDelta * (SideIndex + 1));
const FVector DeltaVector = WorldPlaneX * DeltaX + WorldPlaneY * DeltaY;
const FVector Vertex = WorldOrigin + UseRadius * DeltaVector;
PDI->DrawLine(LastVertex, Vertex, Color, SDPG_Foreground, UseThickness, 0.0f, true);
LastVertex = Vertex;
}
}
else
{
FVector WorldOrigin = LocalToWorldMatrix.TransformPosition(FVector::ZeroVector);
bool bWorldAxis = (bExternalWorldLocalState) ? (*bExternalWorldLocalState) : false;
FVector WorldPlaneX = (bWorldAxis) ? PlaneX : FVector{ LocalToWorldMatrix.TransformVector(PlaneX) };
FVector WorldPlaneY = (bWorldAxis) ? PlaneY : FVector{ LocalToWorldMatrix.TransformVector(PlaneY) };
FVector PlaneWorldNormal = (bWorldAxis) ? Normal : FVector{ LocalToWorldMatrix.TransformVector(Normal) };
// direction to origin of gizmo
FVector GizmoViewDirection =
(bIsOrtho) ? (View->GetViewDirection()) : (Origin - View->ViewLocation);
GizmoViewDirection.Normalize();
bool bRenderVisibility = true;
bool bIsViewPlaneParallel = false;
if (bIsViewDependent)
{
GetVisibility(View, GizmoViewDirection, PlaneWorldNormal, WorldOrigin, bRenderVisibility, bIsViewPlaneParallel);
}
if (bRenderVisibility)
{
if (bIsViewPlaneParallel)
{
FVector LastVertex = WorldOrigin + WorldPlaneX * UseRadius;
for (int32 SideIndex = 0; SideIndex < NumSides; SideIndex++)
{
float DeltaX = FMath::Cos(AngleDelta * (SideIndex + 1));
float DeltaY = FMath::Sin(AngleDelta * (SideIndex + 1));
const FVector DeltaVector = WorldPlaneX * DeltaX + WorldPlaneY * DeltaY;
const FVector Vertex = WorldOrigin + UseRadius * DeltaVector;
PDI->DrawLine(LastVertex, Vertex, Color, SDPG_Foreground, UseThickness, 0.0f, true);
LastVertex = Vertex;
}
}
else
{
FVector LastVertex = WorldOrigin + WorldPlaneX * UseRadius;
bool bLastVisible = FVector::DotProduct(WorldPlaneX, GizmoViewDirection) < 0;
for (int32 SideIndex = 0; SideIndex < NumSides; SideIndex++)
{
float DeltaX = FMath::Cos(AngleDelta * (SideIndex + 1));
float DeltaY = FMath::Sin(AngleDelta * (SideIndex + 1));
const FVector DeltaVector = WorldPlaneX * DeltaX + WorldPlaneY * DeltaY;
const FVector Vertex = WorldOrigin + UseRadius * DeltaVector;
bool bVertexVisible = FVector::DotProduct(DeltaVector, GizmoViewDirection) < 0;
if (bLastVisible && bVertexVisible)
{
PDI->DrawLine(LastVertex, Vertex, Color, SDPG_Foreground, UseThickness, 0.0f, true);
}
else
{
PDI->DrawLine(LastVertex, Vertex, BackColor, SDPG_Foreground, BackThickness, 0.0f, true);
}
bLastVisible = bVertexVisible;
LastVertex = Vertex;
}
}
}
}
}
}
}
virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override
{
FPrimitiveViewRelevance Result;
Result.bDrawRelevance = IsShown(View);
Result.bDynamicRelevance = true;
Result.bShadowRelevance = false;
Result.bEditorPrimitiveRelevance = UseEditorCompositing(View);
Result.bRenderCustomDepth = ShouldRenderCustomDepth();
return Result;
}
virtual bool CanBeOccluded() const override
{
return false;
}
virtual uint32 GetMemoryFootprint(void) const override { return IntCastChecked<uint32>(sizeof *this + GetAllocatedSize()); }
SIZE_T GetAllocatedSize(void) const { return FPrimitiveSceneProxy::GetAllocatedSize(); }
void SetExternalHoverState(bool* HoverState)
{
bExternalHoverState = HoverState;
}
void SetExternalWorldLocalState(bool* bWorldLocalState)
{
bExternalWorldLocalState = bWorldLocalState;
}
void SetExternalIsViewDependent(bool* bExternalIsViewDependentIn)
{
bExternalIsViewDependent = bExternalIsViewDependentIn;
}
private:
FLinearColor Color;
FVector Normal;
float Radius;
float Thickness;
int NumSides;
bool bViewAligned;
bool bDrawFullCircle;
float HoverThicknessMultiplier;
// set on Component for use in ::GetDynamicMeshElements()
bool* bExternalHoverState = nullptr;
bool* bExternalWorldLocalState = nullptr;
bool* bExternalIsViewDependent = nullptr;
};
FPrimitiveSceneProxy* UGizmoCircleComponent::CreateSceneProxy()
{
FGizmoCircleComponentSceneProxy* NewProxy = new FGizmoCircleComponentSceneProxy(this);
NewProxy->SetExternalHoverState(&bHovering);
NewProxy->SetExternalWorldLocalState(&bWorld);
NewProxy->SetExternalIsViewDependent(&bIsViewDependent);
return NewProxy;
}
FBoxSphereBounds UGizmoCircleComponent::CalcBounds(const FTransform& LocalToWorld) const
{
return FBoxSphereBounds(FSphere(FVector::ZeroVector, Radius).TransformBy(LocalToWorld));
}
bool UGizmoCircleComponent::LineTraceComponent(FHitResult& OutHit, const FVector Start, const FVector End, const FCollisionQueryParams& Params)
{
using namespace GizmoCircleComponentLocals;
const FTransform& Transform = this->GetComponentToWorld();
FVector WorldOrigin = Transform.TransformPosition(FVector::ZeroVector);
FVector WorldNormal = (bWorld) ? Normal : Transform.TransformVector(Normal);
bool bRenderVisibility = true;
bool bCircleIsViewPlaneParallel = false;
float PixelToWorldScale = 1;
if (bIsViewDependent)
{
FVector ViewDirection = GizmoViewContext->IsPerspectiveProjection() ? (WorldOrigin - GizmoViewContext->ViewLocation)
: GizmoViewContext->GetViewDirection();
ViewDirection.Normalize();
GetVisibility(ToRawPtr(GizmoViewContext), ViewDirection, WorldNormal, WorldOrigin, bRenderVisibility, bCircleIsViewPlaneParallel);
if (!bRenderVisibility)
{
return false;
}
PixelToWorldScale = UE::GizmoRenderingUtil::CalculateLocalPixelToWorldScale(GizmoViewContext, WorldOrigin);
}
float LengthScale = PixelToWorldScale;
float UseRadius = LengthScale * Radius;
FRay Ray(Start, End - Start, false);
// Find the intresection with the circle plane. Note that unlike the FMath version, GizmoMath::RayPlaneIntersectionPoint()
// checks that the ray isn't parallel to the plane.
bool bIntersects;
FVector HitPos;
GizmoMath::RayPlaneIntersectionPoint(WorldOrigin, WorldNormal, Ray.Origin, Ray.Direction, bIntersects, HitPos);
if (!bIntersects || Ray.GetParameter(HitPos) > Ray.GetParameter(End))
{
return false;
}
FVector NearestCircle;
GizmoMath::ClosetPointOnCircle(HitPos, WorldOrigin, WorldNormal, UseRadius, NearestCircle);
FVector NearestRay = Ray.ClosestPoint(NearestCircle);
double Distance = FVector::Distance(NearestCircle, NearestRay);
if (Distance > PixelHitDistanceThreshold * PixelToWorldScale)
{
return false;
}
// filter out hits on "back" of sphere that circle lies on
if (bOnlyAllowFrontFacingHits && bCircleIsViewPlaneParallel == false)
{
bool bSphereIntersects = false;
FVector SphereHitPoint;
FVector RayToCirclePointDirection = (NearestCircle - Ray.Origin);
RayToCirclePointDirection.Normalize();
GizmoMath::RaySphereIntersection(
WorldOrigin, UseRadius, Ray.Origin, RayToCirclePointDirection, bSphereIntersects, SphereHitPoint);
if (bSphereIntersects)
{
if (FVector::DistSquared(SphereHitPoint, NearestCircle) > UseRadius*0.1f)
{
return false;
}
}
}
OutHit.Component = this;
OutHit.Distance = static_cast<float>(FVector::Distance(Start, NearestRay));
OutHit.ImpactPoint = NearestRay;
return true;
}