diff --git a/Source/StevesUEHelpers/Private/StevesDebugRenderSceneProxy.cpp b/Source/StevesUEHelpers/Private/StevesDebugRenderSceneProxy.cpp new file mode 100644 index 0000000..e50a319 --- /dev/null +++ b/Source/StevesUEHelpers/Private/StevesDebugRenderSceneProxy.cpp @@ -0,0 +1,49 @@ +// Copyright 2020 Old Doorways Ltd + + +#include "StevesDebugRenderSceneProxy.h" + + +void FStevesDebugRenderSceneProxy::GetDynamicMeshElements(const TArray& Views, + const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const +{ + FDebugRenderSceneProxy::GetDynamicMeshElements(Views, ViewFamily, VisibilityMap, Collector); + + + for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) + { + if (VisibilityMap & (1 << ViewIndex)) + { + const FSceneView* View = Views[ViewIndex]; + FPrimitiveDrawInterface* PDI = Collector.GetPDI(ViewIndex); + + // Draw Circles + for (const auto& C : Circles) + { + DrawCircle(PDI, C.Centre, C.X, C.Y, C.Color, C.Radius, C.NumSegments, SDPG_World, C.Thickness, 0, C.Thickness > 0); + } + + // Draw Arcs + for (const auto& C : Arcs) + { + ::DrawArc(PDI, + C.Centre, + C.X, C.Y, + C.MinAngle, C.MaxAngle, + C.Radius, C.NumSegments, + C.Color, SDPG_Foreground); + } + } + } +} + +FPrimitiveViewRelevance FStevesDebugRenderSceneProxy::GetViewRelevance(const FSceneView* View) const +{ + // More useful defaults than FDebugRenderSceneProxy + FPrimitiveViewRelevance Result; + Result.bDrawRelevance = IsShown(View); + Result.bDynamicRelevance = true; + Result.bShadowRelevance = false; + Result.bEditorPrimitiveRelevance = UseEditorCompositing(View); + return Result; +} diff --git a/Source/StevesUEHelpers/Private/StevesEditorVisComponent.cpp b/Source/StevesUEHelpers/Private/StevesEditorVisComponent.cpp new file mode 100644 index 0000000..3c0ee83 --- /dev/null +++ b/Source/StevesUEHelpers/Private/StevesEditorVisComponent.cpp @@ -0,0 +1,125 @@ +// Copyright 2020 Old Doorways Ltd + + +#include "StevesEditorVisComponent.h" +#include "StevesDebugRenderSceneProxy.h" + +UStevesEditorVisComponent::UStevesEditorVisComponent(const FObjectInitializer& ObjectInitializer) + : UPrimitiveComponent(ObjectInitializer) +{ + // set up some constants + PrimaryComponentTick.bCanEverTick = false; + SetCastShadow(false); +#if WITH_EDITORONLY_DATA + // Note: this makes this component invisible on level instances, not sure why + SetIsVisualizationComponent(true); +#endif + SetHiddenInGame(true); + bVisibleInReflectionCaptures = false; + bVisibleInRayTracing = false; + bVisibleInRealTimeSkyCaptures = false; + AlwaysLoadOnClient = false; + bIsEditorOnly = true; + +} + +FPrimitiveSceneProxy* UStevesEditorVisComponent::CreateSceneProxy() +{ + auto Ret = new FStevesDebugRenderSceneProxy(this); + + const FTransform& XForm = GetComponentTransform(); + for (auto& L : Lines) + { + Ret->Lines.Add(FDebugRenderSceneProxy::FDebugLine(XForm.TransformPosition(L.Start), + XForm.TransformPosition(L.End), L.Colour)); + } + for (auto& A : Arrows) + { + Ret->ArrowLines.Add(FDebugRenderSceneProxy::FArrowLine(XForm.TransformPosition(A.Start), + XForm.TransformPosition(A.End), A.Colour)); + } + for (auto& C : Circles) + { + FQuat WorldRot = XForm.TransformRotation(C.Rotation.Quaternion()); + Ret->Circles.Add(FStevesDebugRenderSceneProxy::FDebugCircle( + XForm.TransformPosition(C.Location), + WorldRot.GetForwardVector(), WorldRot.GetRightVector(), + XForm.GetMaximumAxisScale() * C.Radius, + C.NumSegments, C.Colour + )); + } + for (auto& Arc : Arcs) + { + FQuat WorldRot = XForm.TransformRotation(Arc.Rotation.Quaternion()); + Ret->Arcs.Add(FStevesDebugRenderSceneProxy::FDebugArc( + XForm.TransformPosition(Arc.Location), + WorldRot.GetForwardVector(), WorldRot.GetRightVector(), + Arc.MinAngle, Arc.MaxAngle, + XForm.GetMaximumAxisScale() * Arc.Radius, + Arc.NumSegments, Arc.Colour + )); + } + for (auto& S : Spheres) + { + Ret->Spheres.Add(FStevesDebugRenderSceneProxy::FSphere( + XForm.GetMaximumAxisScale() * S.Radius, + XForm.TransformPosition(S.Location), + S.Colour + )); + } + for (auto& Box : Boxes) + { + FVector HalfSize = Box.Size * 0.5f; + FBox DBox(-HalfSize, HalfSize); + // Apply local rotation first then parent transform + FTransform CombinedXForm = FTransform(Box.Rotation, Box.Location) * XForm; + Ret->Boxes.Add(FStevesDebugRenderSceneProxy::FDebugBox( + DBox, Box.Colour, CombinedXForm)); + } + + return Ret; + +} + +FBoxSphereBounds UStevesEditorVisComponent::CalcBounds(const FTransform& LocalToWorld) const +{ + FBoxSphereBounds B = Super::CalcBounds(LocalToWorld); + + // Now we need to merge in all components + for (auto& L : Lines) + { + // Re-centre the origin of the line to make box extents + FVector Extents = L.Start.GetAbs().ComponentMax(L.End.GetAbs()); + B = B + FBoxSphereBounds(FVector::ZeroVector, Extents, Extents.GetMax()); + } + for (auto& A : Arrows) + { + // Re-centre the origin of the line to make box extents + FVector Extents = A.Start.GetAbs().ComponentMax(A.End.GetAbs()); + B = B + FBoxSphereBounds(FVector::ZeroVector, Extents, Extents.GetMax()); + } + for (auto& C : Circles) + { + B = B + FBoxSphereBounds(C.Location, FVector(C.Radius), C.Radius); + } + for (auto& Arc : Arcs) + { + // Just use the entire circle for simplicity + B = B + FBoxSphereBounds(Arc.Location, FVector(Arc.Radius), Arc.Radius); + } + for (auto& S : Spheres) + { + B = B + FBoxSphereBounds(S.Location, FVector(S.Radius), S.Radius); + } + for (auto& Box : Boxes) + { + FVector HalfSize = Box.Size * 0.5f; + FBox DBox(-HalfSize, HalfSize); + // Apply local rotation only, world is done later + FTransform BoxXForm = FTransform(Box.Rotation, Box.Location); + DBox = DBox.TransformBy(BoxXForm); + B = B + FBoxSphereBounds(DBox); + } + return B.TransformBy(LocalToWorld); +} + diff --git a/Source/StevesUEHelpers/Public/StevesDebugRenderSceneProxy.h b/Source/StevesUEHelpers/Public/StevesDebugRenderSceneProxy.h new file mode 100644 index 0000000..c802841 --- /dev/null +++ b/Source/StevesUEHelpers/Public/StevesDebugRenderSceneProxy.h @@ -0,0 +1,75 @@ +// Copyright 2020 Old Doorways Ltd + +#pragma once + +#include "CoreMinimal.h" +#include "DebugRenderSceneProxy.h" + +/** + * An extension to FDebugRenderSceneProxy to support other shapes, e.g. circles and arcs + */ +class FStevesDebugRenderSceneProxy : public FDebugRenderSceneProxy +{ +public: + FStevesDebugRenderSceneProxy(const UPrimitiveComponent* InComponent) + : FDebugRenderSceneProxy(InComponent) + { + } + + STEVESUEHELPERS_API virtual void GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, + uint32 VisibilityMap, FMeshElementCollector& Collector) const override; + + STEVESUEHELPERS_API virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override; + + struct FDebugCircle + { + FDebugCircle(const FVector& InCentre, const FVector& InX, const FVector& InY, float InRadius, int InNumSegments, + const FColor& InColor, float InThickness = 0) : + Centre(InCentre), + X(InX), + Y(InY), + Radius(InRadius), + NumSegments(InNumSegments), + Color(InColor), + Thickness(InThickness) + { + } + + FVector Centre; + FVector X; + FVector Y; + float Radius; + int NumSegments; + FColor Color; + float Thickness; + }; + + /// An arc which is a section of a circle + struct FDebugArc + { + FDebugArc(const FVector& InCentre, const FVector& InX, const FVector& InY, float InMinAngle, float InMaxAngle, + float InRadius, int InNumSegments, const FColor& InColor) : + Centre(InCentre), + X(InX), + Y(InY), + MinAngle(InMinAngle), + MaxAngle(InMaxAngle), + Radius(InRadius), + NumSegments(InNumSegments), + Color(InColor) + { + } + + FVector Centre; + FVector X; + FVector Y; + float MinAngle; + float MaxAngle; + float Radius; + int NumSegments; + FColor Color; + }; + + TArray Circles; + TArray Arcs; +}; diff --git a/Source/StevesUEHelpers/Public/StevesEditorVisComponent.h b/Source/StevesUEHelpers/Public/StevesEditorVisComponent.h new file mode 100644 index 0000000..297863a --- /dev/null +++ b/Source/StevesUEHelpers/Public/StevesEditorVisComponent.h @@ -0,0 +1,237 @@ +// Copyright 2020 Old Doorways Ltd + +#pragma once + +#include "CoreMinimal.h" +#include "Components/PrimitiveComponent.h" +#include "StevesEditorVisComponent.generated.h" + + +USTRUCT(BlueprintType) +struct FStevesEditorVisLine +{ + GENERATED_BODY() + + /// Start location relative to component + UPROPERTY(EditAnywhere) + FVector Start; + /// End location relative to component + UPROPERTY(EditAnywhere) + FVector End; + /// The colour of the line render + UPROPERTY(EditAnywhere) + FColor Colour; + + FStevesEditorVisLine(const FVector& InStart, const FVector& InEnd, + const FColor& InColour) + : Start(InStart), + End(InEnd), + Colour(Colour) + { + } + + FStevesEditorVisLine(): + Start(FVector::ZeroVector), + End(FVector(100, 0, 0)), + Colour(FColor::White) + { + } +}; + +USTRUCT(BlueprintType) +struct FStevesEditorVisCircle +{ + GENERATED_BODY() + + /// Location relative to component + UPROPERTY(EditAnywhere) + FVector Location; + /// Rotation relative to component; circles will be rendered in the X/Y plane + UPROPERTY(EditAnywhere) + FRotator Rotation; + /// Circle radius + UPROPERTY(EditAnywhere) + float Radius; + /// The number of line segments to render the circle with + UPROPERTY(EditAnywhere) + int NumSegments; + /// The colour of the line render + UPROPERTY(EditAnywhere) + FColor Colour; + + FStevesEditorVisCircle(const FVector& InLocation, const FRotator& InRotation, float InRadius, int InNumSegments, + const FColor& InColour) + : Location(InLocation), + Rotation(InRotation), + Radius(InRadius), + NumSegments(InNumSegments), + Colour(InColour) + { + } + + FStevesEditorVisCircle(): + Location(FVector::ZeroVector), + Rotation(FRotator::ZeroRotator), + Radius(50), NumSegments(12), + Colour(FColor::White) + { + } +}; + +USTRUCT(BlueprintType) +struct FStevesEditorVisArc +{ + GENERATED_BODY() + + /// Location relative to component + UPROPERTY(EditAnywhere) + FVector Location; + /// Rotation relative to component; arcs will be rendered in the X/Y plane + UPROPERTY(EditAnywhere) + FRotator Rotation; + /// Minimum angle to render arc from + UPROPERTY(EditAnywhere) + float MinAngle; + /// Maximum angle to render arc to + UPROPERTY(EditAnywhere) + float MaxAngle; + /// Circle radius + UPROPERTY(EditAnywhere) + float Radius; + /// The number of line segments to render the circle with + UPROPERTY(EditAnywhere) + int NumSegments; + /// The colour of the line render + UPROPERTY(EditAnywhere) + FColor Colour; + + FStevesEditorVisArc(const FVector& InLocation, const FRotator& InRotation, float InMinAngle, float InMaxAngle, + float InRadius, int InNumSegments, + const FColor& InColour, float InThickness) + : Location(InLocation), + Rotation(InRotation), + MinAngle(InMinAngle), + MaxAngle(InMaxAngle), + Radius(InRadius), + NumSegments(InNumSegments), + Colour(InColour) + { + } + + FStevesEditorVisArc(): + Location(FVector::ZeroVector), + Rotation(FRotator::ZeroRotator), + MinAngle(0), + MaxAngle(180), + Radius(50), NumSegments(12), + Colour(FColor::White) + { + } +}; + +USTRUCT(BlueprintType) +struct FStevesEditorVisSphere +{ + GENERATED_BODY() + + /// Location relative to component + UPROPERTY(EditAnywhere) + FVector Location; + /// Sphere radius + UPROPERTY(EditAnywhere) + float Radius; + /// The colour of the line render + UPROPERTY(EditAnywhere) + FColor Colour; + + FStevesEditorVisSphere(const FVector& InLocation, float InRadius, const FColor& InColour) : + Location(InLocation), + Radius(InRadius), + Colour(InColour) + { + } + + FStevesEditorVisSphere(): + Location(FVector::ZeroVector), + Radius(50), + Colour(FColor::White) + { + } +}; + +USTRUCT(BlueprintType) +struct FStevesEditorVisBox +{ + GENERATED_BODY() + + /// Location relative to component + UPROPERTY(EditAnywhere) + FVector Location; + /// Size of box in each axis + UPROPERTY(EditAnywhere) + FVector Size; + /// Rotation relative to component + UPROPERTY(EditAnywhere) + FRotator Rotation; + /// The colour of the line render + UPROPERTY(EditAnywhere) + FColor Colour; + + FStevesEditorVisBox(const FVector& InLocation, const FVector& InSize, const FRotator& InRot, + const FColor& InColour) : + Location(InLocation), + Size(InSize), + Rotation(InRot), + Colour(InColour) + { + } + + FStevesEditorVisBox(): + Location(FVector::ZeroVector), + Size(FVector(50, 50, 50)), + Rotation(FRotator::ZeroRotator), + Colour(FColor::White) + { + } +}; + + +/** + * + * A component that solely exists to provide arbitrary editor visualisation when not selected. + * FComponentVisualizer can only display visualisation when selected. + * To display vis on an unselected object, you need a UPrimitiveComponent, and sometimes you don't want/need one of those + * in your actor. Instead, add UStevesEditorVisComponent at construction of your actor, or registration of another component, + * but only in a WITH_EDITOR block. Then, get nice visualisation of your actor/component without making more invasive changes + * to your code. + * + * If you want to, you can add this to your Blueprints too. This component automatically marks itself as "visualisation + * only" so shouldn't have a runtime impact. + */ +UCLASS(Blueprintable, ClassGroup="Utility", hidecategories=(Collision,Physics,Object,LOD,Lighting,TextureStreaming), + meta=(DisplayName="Steves Editor Visualisation", BlueprintSpawnableComponent)) +class STEVESUEHELPERS_API UStevesEditorVisComponent : public UPrimitiveComponent +{ + GENERATED_BODY() +public: + /// Circles to render + UPROPERTY(EditAnywhere, BlueprintReadWrite) + TArray Lines; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + TArray Arrows; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + TArray Circles; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + TArray Arcs; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + TArray Spheres; + UPROPERTY(EditAnywhere, BlueprintReadWrite) + TArray Boxes; + + UStevesEditorVisComponent(const FObjectInitializer& ObjectInitializer); + + virtual FPrimitiveSceneProxy* CreateSceneProxy() override; + virtual FBoxSphereBounds CalcBounds(const FTransform& LocalToWorld) const override; + /// Needs to update on transform since proxy is detached + virtual bool ShouldRecreateProxyOnUpdateTransform() const override { return true; } +};