Files
UnrealEngine/Engine/Plugins/Mutable/Source/CustomizableObjectEditor/Private/MuCOE/SCustomizableObjectLayoutGrid.cpp
2025-05-18 13:04:45 +08:00

1629 lines
47 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MuCOE/SCustomizableObjectLayoutGrid.h"
#include "MuCOE/SStandAloneAssetPicker.h"
#include "MuCOE/UnrealEditorPortabilityHelpers.h"
#include "BatchedElements.h"
#include "CanvasTypes.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Layout/WidgetPath.h"
#include "Rendering/SlateRenderer.h"
#include "RenderingThread.h"
#include "Styling/SlateTypes.h"
#include "UnrealClient.h"
#include "Widgets/Input/SNumericEntryBox.h"
#include "ThumbnailRendering/ThumbnailManager.h"
#include "Engine/Texture2D.h"
#include "TextureResource.h"
#include "RenderGraphBuilder.h"
#include "MuCO/LoadUtils.h"
class FExtender;
class FPaintArgs;
class FRHICommandListImmediate;
class FSlateRect;
class FWidgetStyle;
struct FSlateBrush;
#define LOCTEXT_NAMESPACE "CustomizableObjectEditor"
/** Simple representation of the backbuffer for drawing UVs. */
class FSlateCanvasRenderTarget : public FRenderTarget
{
public:
FIntPoint GetSizeXY() const override
{
return ViewRect.Size();
}
/** Sets the texture that this target renders to */
void SetRenderTargetTexture(FRDGTexture* InTexture)
{
RDGTexture = InTexture;
}
FRDGTexture* GetRenderTargetTexture(FRDGBuilder&) const override
{
return RDGTexture;
}
/** Clears the render target texture */
void ClearRenderTargetTexture()
{
RDGTexture = nullptr;
}
/** Sets the viewport rect for the render target */
void SetViewRect(const FIntRect& InViewRect)
{
ViewRect = InViewRect;
}
/** Gets the viewport rect for the render target */
const FIntRect& GetViewRect() const
{
return ViewRect;
}
/** Sets the clipping rect for the render target */
void SetClippingRect(const FIntRect& InClippingRect)
{
ClippingRect = InClippingRect;
}
/** Gets the clipping rect for the render target */
const FIntRect& GetClippingRect() const
{
return ClippingRect;
}
private:
FRDGTexture* RDGTexture = nullptr;
FIntRect ViewRect;
FIntRect ClippingRect;
};
/** Custom Slate drawing element. Holds a copy of all information required to draw UVs.
*/
class FUVCanvasDrawer : public ICustomSlateElement
{
public:
virtual ~FUVCanvasDrawer() override;
/** Set the canvas area and all required data to paint the UVs.
*
* All data will be copied.
*/
void Initialize(const FIntRect& InCanvasRect, const FIntRect& InClippingRect, const FVector2D& InOrigin, const FVector2D& InSize, const FIntPoint& InGridSize, const float InCellSize);
void InitializeDrawingData(const TArray<FVector2f>& InUVLayout, const TArray<FVector2f>& InUnassignedUVs, const TArray<FCustomizableObjectLayoutBlock>& InBlocks, const TArray<FGuid>& InSelectedBlocks);
/** Sets the layout mode to know what to draw */
void SetLayoutMode(ELayoutGridMode Mode);
private:
virtual void Draw_RenderThread(FRDGBuilder& GraphBuilder, const FDrawPassInputs& Inputs) override;
/** Basic function to draw a block in the canvas */
void DrawBlock(FBatchedElements* BatchedElements, const FHitProxyId HitProxyId, const FRect2D& BlockRect, FColor Color, UTexture2D* Mask = nullptr);
/** SlateElement initialized, can Draw during the DrawRenderThread call. */
bool Initialized = false;
/** Drawing origin. */
FVector2D Origin;
/** Drawing size. */
FVector2D Size;
/** Size of the Layout Grid */
FIntPoint GridSize = FIntPoint(0, 0);
/** Cell Size */
float CellSize = 0.0f;
/** Drawing Data. */
TArray<FVector2D> UVLayout;
TArray<FVector2D> UnassignedUVs;
TArray<FCustomizableObjectLayoutBlock> Blocks;
TArray<FGuid> SelectedBlocks;
/** Layout Mode */
ELayoutGridMode LayoutMode = ELGM_Show;
FSlateCanvasRenderTarget* RenderTarget = new FSlateCanvasRenderTarget();
/** Default colors. */
FColor SelectedBlockColor = FColor(75, 106, 230, 155);
FColor UnselectedBlockColor = FColor(230, 199, 75, 155);
FColor AutomaticBlockColor = FColor(125, 125, 125, 125);
};
void SCustomizableObjectLayoutGrid::Construct( const FArguments& InArgs )
{
GridSize = InArgs._GridSize;
Blocks = InArgs._Blocks;
UVLayout = InArgs._UVLayout;
UnassignedUVLayoutVertices = InArgs._UnassignedUVLayoutVertices;
Mode = InArgs._Mode;
BlockChangedDelegate = InArgs._OnBlockChanged;
SelectionChangedDelegate = InArgs._OnSelectionChanged;
SelectionColor = InArgs._SelectionColor;
DeleteBlocksDelegate = InArgs._OnDeleteBlocks;
AddBlockAtDelegate = InArgs._OnAddBlockAt;
OnSetBlockPriority = InArgs._OnSetBlockPriority;
OnSetReduceBlockSymmetrically = InArgs._OnSetReduceBlockSymmetrically;
OnSetReduceBlockByTwo = InArgs._OnSetReduceBlockByTwo;
OnSetBlockMask = InArgs._OnSetBlockMask;
for (int32 BufferIndex=0;BufferIndex< UE_MUTABLE_UI_DRAWBUFFERS; ++BufferIndex)
{
UVCanvasDrawers[BufferIndex] = TSharedPtr<FUVCanvasDrawer>(new FUVCanvasDrawer());
}
}
SCustomizableObjectLayoutGrid::~SCustomizableObjectLayoutGrid()
{
// UVCanvasDrawer can only be destroyed after drawing the last command
for (int32 BufferIndex = 0; BufferIndex < UE_MUTABLE_UI_DRAWBUFFERS; ++BufferIndex)
{
TSharedPtr<class FUVCanvasDrawer> UVCanvasDrawer = UVCanvasDrawers[BufferIndex];
ENQUEUE_RENDER_COMMAND(SafeDeletePreviewElement)(
[UVCanvasDrawer](FRHICommandListImmediate& RHICmdList) mutable
{
UVCanvasDrawer.Reset();
}
);
}
}
int32 SCustomizableObjectLayoutGrid::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const
{
int32 RetLayerId = SCompoundWidget::OnPaint(Args, AllottedGeometry, MyClippingRect, OutDrawElements, LayerId,InWidgetStyle, bParentEnabled );
bool bEnabled = ShouldBeEnabled( bParentEnabled );
const ESlateDrawEffect DrawEffects = bEnabled ? ESlateDrawEffect::None : ESlateDrawEffect::DisabledEffect;
// Paint inside the border only.
const FVector2D BorderPadding = FVector2D(2,2);
FPaintGeometry ForegroundPaintGeometry = AllottedGeometry.ToInflatedPaintGeometry( -BorderPadding );
const FIntPoint GridSizePoint = GridSize.Get();
const float OffsetX = BorderPadding.X;
const FVector2D AreaSize = AllottedGeometry.GetLocalSize() - 2.0f * BorderPadding;
const float GridRatio = float(GridSizePoint.X) / float(GridSizePoint.Y);
FVector2D Size;
if ( AreaSize.X/GridRatio > AreaSize.Y )
{
Size.Y = AreaSize.Y;
Size.X = AreaSize.Y*GridRatio;
}
else
{
Size.X = AreaSize.X;
Size.Y = AreaSize.X/GridRatio;
}
FVector2D OldSize = Size;
double ZoomFactor = PointOfView.GetZoomFactor();
Size *= ZoomFactor;
float AuxCellSize = Size.X / GridSizePoint.X;
// Drawing Offsets
FVector2D Offset = FVector2D((AreaSize - Size).X / 2.0f, 0.0f);
// Drawing Origin
FVector2D Origin = BorderPadding + Offset + PointOfView.PaddingAmount;
// Setting Canvas Drawing Rectangles
FSlateRect SlateCanvasRect = AllottedGeometry.GetLayoutBoundingRect();
FSlateRect ClippedCanvasRect = SlateCanvasRect.IntersectionWith(MyClippingRect);
FIntRect CanvasRect(
FMath::TruncToInt(FMath::Max(0.0f, SlateCanvasRect.Left)),
FMath::TruncToInt(FMath::Max(0.0f, SlateCanvasRect.Top)),
FMath::TruncToInt(FMath::Max(0.0f, SlateCanvasRect.Right)),
FMath::TruncToInt(FMath::Max(0.0f, SlateCanvasRect.Bottom)));
FIntRect ClippingRect(
FMath::TruncToInt(FMath::Max(0.0f, ClippedCanvasRect.Left)),
FMath::TruncToInt(FMath::Max(0.0f, ClippedCanvasRect.Top)),
FMath::TruncToInt(FMath::Max(0.0f, ClippedCanvasRect.Right)),
FMath::TruncToInt(FMath::Max(0.0f, ClippedCanvasRect.Bottom)));
TSharedPtr<class FUVCanvasDrawer> UVCanvasDrawer = UVCanvasDrawers[CurrentDrawBuffer];
ELayoutGridMode GridMode = Mode.Get();
UVCanvasDrawer->SetLayoutMode(GridMode);
UVCanvasDrawer->InitializeDrawingData(UVLayout.Get(), UnassignedUVLayoutVertices, Blocks.Get(), SelectedBlocks);
UVCanvasDrawer->Initialize(CanvasRect, ClippingRect, Origin * AllottedGeometry.Scale, Size * AllottedGeometry.Scale, GridSizePoint, AuxCellSize * AllottedGeometry.Scale);
FSlateDrawElement::MakeCustom(OutDrawElements, RetLayerId, UVCanvasDrawer);
const auto MakeYellowSquareLine = [&](const TArray<FVector2D>& Points) -> void
{
FSlateDrawElement::MakeLines(OutDrawElements, RetLayerId, AllottedGeometry.ToPaintGeometry(),
Points, ESlateDrawEffect::None, FColor(250, 230, 43, 255), true, 2.0);
};
// Drawing Multi-Selection rect
if (GridMode == ELGM_Edit && bIsSelecting)
{
TArray<FVector2D> SelectionSquarePoints;
SelectionSquarePoints.SetNum(2);
FVector2D RectMin = FVector2D(SelectionRect.Min);
FVector2D RectSize = FVector2D(SelectionRect.Size);
FVector2D TopLeft = RectMin;
FVector2D TopRight = RectMin + FVector2D(RectSize.X, 0.0f);
FVector2D BottomRight = RectMin + RectSize;
FVector2D BottomLeft = RectMin + FVector2D(0.0f, RectSize.Y);
SelectionSquarePoints[0] = TopLeft;
SelectionSquarePoints[1] = TopRight;
MakeYellowSquareLine(SelectionSquarePoints);
SelectionSquarePoints[0] = BottomRight;
MakeYellowSquareLine(SelectionSquarePoints);
SelectionSquarePoints[1] = BottomLeft;
MakeYellowSquareLine(SelectionSquarePoints);
SelectionSquarePoints[0] = TopLeft;
MakeYellowSquareLine(SelectionSquarePoints);
}
RetLayerId++;
return RetLayerId - 1;
}
void SCustomizableObjectLayoutGrid::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime )
{
// Swap the rendering buffer.
CurrentDrawBuffer = FMath::Modulo(CurrentDrawBuffer+1, UE_MUTABLE_UI_DRAWBUFFERS);
const FVector2D BorderPadding = FVector2D(2,2);
const FVector2D AreaSize = AllottedGeometry.Size - 2.0f * BorderPadding;
const float GridRatio = float(GridSize.Get().X)/float(GridSize.Get().Y);
FVector2D Size;
if ( AreaSize.X/GridRatio > AreaSize.Y )
{
Size.Y = AreaSize.Y;
Size.X = AreaSize.Y*GridRatio;
}
else
{
Size.X = AreaSize.X;
Size.Y = AreaSize.X/GridRatio;
}
FVector2D OldSize = Size;
double ZoomFactor = PointOfView.GetZoomFactor();
Size *= ZoomFactor;
CellSize = Size.X/GridSize.Get().X;
FVector2D Offset = FVector2D((AreaSize - Size).X / 2.0f, 0.0f);
FVector2D Origin = BorderPadding + Offset + PointOfView.PaddingAmount;
DrawOrigin = Origin;
BlockRects.Empty();
const TArray<FCustomizableObjectLayoutBlock>& CurrentBlocks = Blocks.Get();
for (const FCustomizableObjectLayoutBlock& Block : CurrentBlocks)
{
const FVector2f BlockMin(Block.Min);
const FVector2f BlockMax(Block.Max);
FBlockWidgetData BlockData;
BlockData.Rect.Min = FVector2f(Origin) + BlockMin * CellSize + CellSize * 0.1f;
BlockData.Rect.Size = (BlockMax - BlockMin) * CellSize - CellSize * 0.2f;
float HandleRectSize = FMath::Log2(float(GridSize.Get().X))/10.0f;
BlockData.HandleRect.Size = FVector2f(CellSize) * HandleRectSize;
BlockData.HandleRect.Min = BlockData.Rect.Min + BlockData.Rect.Size - BlockData.HandleRect.Size;
BlockRects.Add(Block.Id, BlockData);
}
// Update selection list
for (int32 SelectedBlockIndex=0; SelectedBlockIndex<SelectedBlocks.Num();)
{
bool bFound = false;
for (const FCustomizableObjectLayoutBlock& Block : CurrentBlocks)
{
if (Block.Id == SelectedBlocks[SelectedBlockIndex])
{
bFound = true;
}
}
if ( !bFound )
{
SelectedBlocks.RemoveAt(SelectedBlockIndex);
}
else
{
++SelectedBlockIndex;
}
}
if (bIsSelecting)
{
CalculateSelectionRect();
}
SCompoundWidget::Tick( AllottedGeometry, InCurrentTime, InDeltaTime );
}
FReply SCustomizableObjectLayoutGrid::OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
{
FReply Reply = FReply::Unhandled();
ELayoutGridMode GridMode = Mode.Get();
{
if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton && GridMode == ELGM_Edit)
{
bHasDragged = false;
bIsDragging = false;
bIsResizing = false;
// To know if we clicked on a block
bool ClickOnBlock = false;
FVector2D Pos = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
InitSelectionRect = Pos;
//Reset Selection Rect
SelectionRect.Size = FVector2f::Zero();
SelectionRect.Min = FVector2f(Pos);
// Handles selection must be detected on mouse down
// We also check if we click on a block
TArray<FGuid> SelectedBlockHandles;
for (const FGuid& BlockId : SelectedBlocks)
{
if (MouseOnBlock(BlockId, Pos, true))
{
SelectedBlockHandles.Add(BlockId);
}
if (MouseOnBlock(BlockId, Pos))
{
if (SelectedBlocks.Contains(BlockId))
{
ClickOnBlock = true;
}
}
}
if (SelectedBlocks.Num() && ClickOnBlock)
{
bIsDragging = true;
DragStart = Pos;
if (SelectedBlocks.Num() == 1 && SelectedBlockHandles.Contains(SelectedBlocks[0]))
{
bIsResizing = true;
}
}
}
else if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton)
{
// Mouse position
FVector2D Pos = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
FVector2D CellDelta = (Pos - DrawOrigin) / CellSize;
// Create context menu
const bool CloseAfterSelection = true;
FMenuBuilder MenuBuilder(CloseAfterSelection, NULL, TSharedPtr<FExtender>(), false, &FCoreStyle::Get(), false);
MenuBuilder.BeginSection("View", LOCTEXT("ViewActionsTitle", "View"));
{
FUIAction ResetViewAction(FExecuteAction::CreateSP(this, &SCustomizableObjectLayoutGrid::ResetView));
MenuBuilder.AddMenuEntry(LOCTEXT("ResetViewLabel", "Reset View"), LOCTEXT("ResetViewLabelTooltip", "Set the view to the unit UV space."), FSlateIcon(), ResetViewAction);
}
MenuBuilder.EndSection();
if (GridMode == ELGM_Edit)
{
MenuBuilder.BeginSection("Block Management", LOCTEXT("BlockActionsTitle", "Block Actions"));
{
if (SelectedBlocks.Num())
{
FUIAction DeleteAction(FExecuteAction::CreateSP(this, &SCustomizableObjectLayoutGrid::DeleteSelectedBlocks));
MenuBuilder.AddMenuEntry(LOCTEXT("DeleteBlocksLabel", "Delete"), LOCTEXT("DeleteBlocksTooltip", "Delete Selected Blocks"), FSlateIcon(), DeleteAction);
FUIAction DuplicateAction(FExecuteAction::CreateSP(this, &SCustomizableObjectLayoutGrid::DuplicateBlocks));
MenuBuilder.AddMenuEntry(LOCTEXT("DuplicateBlocksLabel", "Duplicate"), LOCTEXT("DuplicateBlocksTooltip", "Duplicate Selected Blocks"), FSlateIcon(), DuplicateAction);
}
else
{
FUIAction AddNewBlockAction(FExecuteAction::CreateSP(this, &SCustomizableObjectLayoutGrid::GenerateNewBlock, CellDelta));
MenuBuilder.AddMenuEntry(LOCTEXT("AddNewBlockLabel", "Add Block"), LOCTEXT("AddNewBlockTooltip", "Add New Block"), FSlateIcon(), AddNewBlockAction);
}
}
MenuBuilder.EndSection();
MenuBuilder.BeginSection("Block Properties for Fixed Layout", LOCTEXT("BlockPropertiesFixedTitle", "Block Properties for Fixed Layout"));
{
if (SelectedBlocks.Num())
{
MenuBuilder.AddWidget(
SNew(SBox)
.WidthOverride(125.0f)
.ToolTipText(LOCTEXT("SetBlockPriority_Tooltip", "Sets the block priority for a Fixed Layout Strategy."))
[
SNew(SNumericEntryBox<int32>)
.MinValue(0)
.MaxValue(INT_MAX)
.MaxSliderValue(100)
.AllowSpin(SelectedBlocks.Num() == 1)
.Value(this, &SCustomizableObjectLayoutGrid::GetBlockPriorityValue)
.UndeterminedString(LOCTEXT("MultipleValues", "Multiples Values"))
.OnValueChanged(this, &SCustomizableObjectLayoutGrid::OnBlockPriorityChanged)
.EditableTextBoxStyle(&UE_MUTABLE_GET_WIDGETSTYLE<FEditableTextBoxStyle>("NormalEditableTextBox"))
]
, FText::FromString("Block Priority"), true);
MenuBuilder.AddWidget(
SNew(SBox)
.WidthOverride(125.0f)
.ToolTipText(LOCTEXT("SetBlockSymmetry_Tooltip", "If true, this block will be reduced in both axes at the same time in a Fixed Layout Strategy."))
[
SNew(SCheckBox)
.IsChecked(this, &SCustomizableObjectLayoutGrid::GetReductionMethodBoolValue, EFRO_Symmetry)
.OnCheckStateChanged(this, &SCustomizableObjectLayoutGrid::OnReduceBlockSymmetricallyChanged)
]
, FText::FromString("Reduce Symmetrically"), true);
MenuBuilder.AddWidget(
SNew(SBox)
.WidthOverride(125.0f)
.ToolTipText(LOCTEXT("SetBlockReduceByTwo_Tooltip", "Only for Unitary reduction. If true, this option reduces each time the block by two block units."))
[
SNew(SCheckBox)
.IsChecked(this, &SCustomizableObjectLayoutGrid::GetReductionMethodBoolValue, EFRO_RedyceByTwo)
.OnCheckStateChanged(this, &SCustomizableObjectLayoutGrid::OnReduceBlockByTwoChanged)
]
, FText::FromString("Reduce by Two"), true);
}
}
MenuBuilder.EndSection();
MenuBuilder.BeginSection("Block Properties for Masks", LOCTEXT("BlockPropertiesMaskTitle", "Block Mask"));
{
if (SelectedBlocks.Num())
{
MenuBuilder.AddWidget(
SNew(SBox)
.WidthOverride(125.0f)
.ToolTipText(LOCTEXT("SetBlockMask_Tooltip", "Sets the UV mask texture for the block."))
[
SNew(SStandAloneAssetPicker)
.OnAssetSelected(this, &SCustomizableObjectLayoutGrid::OnMaskAssetSelected)
.OnGetAllowedClasses(FOnGetAllowedClasses::CreateLambda([](TArray<const UClass*>& OutClasses) {OutClasses.Add(UTexture2D::StaticClass()); }))
.InitialAsset(GetBlockMaskValue())
]
, FText::FromString("Block Mask"), true);
// TODO: Additional properties: color used for preview?
}
}
MenuBuilder.EndSection();
}
FWidgetPath WidgetPath = MouseEvent.GetEventPath() != nullptr ? *MouseEvent.GetEventPath() : FWidgetPath();
FSlateApplication::Get().PushMenu(AsShared(), WidgetPath, MenuBuilder.MakeWidget(), FSlateApplication::Get().GetCursorPos(), FPopupTransitionEffect(FPopupTransitionEffect::ContextMenu));
Reply = FReply::Handled();
}
else if (MouseEvent.GetEffectingButton() == EKeys::MiddleMouseButton)
{
bIsPadding = true;
PaddingStart = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
}
}
if (!Reply.IsEventHandled())
{
Reply = SCompoundWidget::OnMouseButtonDown(MyGeometry, MouseEvent);
}
return Reply;
}
void SCustomizableObjectLayoutGrid::CloseMenu()
{
FSlateApplication::Get().DismissAllMenus();
}
void SCustomizableObjectLayoutGrid::OnMaskAssetSelected(const FAssetData& AssetData)
{
UTexture2D* Mask = Cast<UTexture2D>(MutablePrivate::LoadObject(AssetData));
if (SelectedBlocks.Num())
{
OnSetBlockMask.ExecuteIfBound(Mask);
}
}
UTexture2D* SCustomizableObjectLayoutGrid::GetBlockMaskValue() const
{
if (SelectedBlocks.Num())
{
TArray<FCustomizableObjectLayoutBlock> CurrentSelectedBlocks;
for (const FCustomizableObjectLayoutBlock& Block : Blocks.Get())
{
if (SelectedBlocks.Contains(Block.Id))
{
CurrentSelectedBlocks.Add(Block);
}
}
UTexture2D* BlockMask = CurrentSelectedBlocks[0].Mask;
bool bSameMask = true;
for (const FCustomizableObjectLayoutBlock& Block : CurrentSelectedBlocks)
{
if (Block.Mask != BlockMask)
{
bSameMask = false;
break;
}
}
if (bSameMask)
{
return BlockMask;
}
}
return nullptr;
}
FReply SCustomizableObjectLayoutGrid::OnMouseButtonUp( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
{
FReply Reply = FReply::Unhandled();
ELayoutGridMode GridMode = Mode.Get();
if ( MouseEvent.GetEffectingButton()==EKeys::LeftMouseButton && GridMode == ELGM_Edit)
{
bIsDragging = false;
bIsResizing = false;
// Left Shif is pressed for multi selection
bool bLeftShift = MouseEvent.GetModifierKeys().IsLeftShiftDown();
// Screen to Widget Position
FVector2D Pos = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
// Selection before reset
TArray<FGuid> OldSelection = SelectedBlocks;
TArray<FGuid> OldPossibleSelection = PossibleSelectedBlocks;
PossibleSelectedBlocks.Reset();
// Reset selection if multi selection is not enabled
if (GridMode == ELGM_Edit && !bLeftShift && !bHasDragged)
{
// Only one selected block allowed in edit mode.
SelectedBlocks.Reset();
}
if (!bIsSelecting)
{
if (!bHasDragged)
{
// Backward iteration to select the block rendered in front of the rest
const TArray<FCustomizableObjectLayoutBlock>& CurrentBlocks = Blocks.Get();
for (int32 i = CurrentBlocks.Num() - 1; i > -1; --i)
{
if ( (!CurrentBlocks[i].bIsAutomatic) && MouseOnBlock(CurrentBlocks[i].Id, Pos))
{
PossibleSelectedBlocks.Add(CurrentBlocks[i].Id);
}
}
bool bSameSelection = PossibleSelectedBlocks == OldPossibleSelection;
for (int32 i = 0; i < PossibleSelectedBlocks.Num(); ++i)
{
if (bLeftShift)
{
if (PossibleSelectedBlocks.Num() == 1)
{
if (SelectedBlocks.Contains(PossibleSelectedBlocks[i]))
{
SelectedBlocks.Remove(PossibleSelectedBlocks[i]);
}
else
{
SelectedBlocks.Add(PossibleSelectedBlocks[i]);
break;
}
}
else
{
if (!SelectedBlocks.Contains(PossibleSelectedBlocks[i]))
{
SelectedBlocks.Add(PossibleSelectedBlocks[i]);
break;
}
}
}
else
{
if (OldSelection.Num() == 0)
{
SelectedBlocks.Add(PossibleSelectedBlocks[0]);
}
if (bSameSelection)
{
if (OldSelection.Contains(PossibleSelectedBlocks[i]))
{
SelectedBlocks.Remove(PossibleSelectedBlocks[i]);
if (i == PossibleSelectedBlocks.Num() - 1)
{
SelectedBlocks.Add(PossibleSelectedBlocks[0]);
break;
}
else
{
SelectedBlocks.Add(PossibleSelectedBlocks[i + 1]);
}
}
}
else
{
if (OldSelection.Contains(PossibleSelectedBlocks[i]) && PossibleSelectedBlocks.Num() > 1)
{
SelectedBlocks.Remove(PossibleSelectedBlocks[i]);
}
else
{
SelectedBlocks.AddUnique(PossibleSelectedBlocks[i]);
break;
}
}
}
}
}
}
else
{
FBox2D SelectRect(FVector2D(SelectionRect.Min), FVector2D(SelectionRect.Min + SelectionRect.Size) );
const TArray<FCustomizableObjectLayoutBlock>& CurrentBlocks = Blocks.Get();
for (int32 i = 0; i < CurrentBlocks.Num(); ++i)
{
if (CurrentBlocks[i].bIsAutomatic)
{
continue;
}
FBox2D CurrentBlock(FVector2D(BlockRects[CurrentBlocks[i].Id].Rect.Min), FVector2D(BlockRects[CurrentBlocks[i].Id].Rect.Min + BlockRects[CurrentBlocks[i].Id].Rect.Size));
if (SelectedBlocks.Contains(CurrentBlocks[i].Id))
{
if (!SelectRect.Intersect(CurrentBlock) && !bLeftShift)
{
SelectedBlocks.Remove(CurrentBlocks[i].Id);
}
}
else
{
if (SelectRect.Intersect(CurrentBlock))
{
SelectedBlocks.Add(CurrentBlocks[i].Id);
}
}
}
}
// Executing selection delegate
if (OldSelection != SelectedBlocks)
{
SelectionChangedDelegate.ExecuteIfBound(SelectedBlocks);
}
bHasDragged = false;
bIsSelecting = false;
}
else if (MouseEvent.GetEffectingButton() == EKeys::MiddleMouseButton)
{
bIsPadding = false;
}
else if (MouseEvent.GetEffectingButton() == EKeys::RightMouseButton)
{
Reply = FReply::Handled();
}
if (!Reply.IsEventHandled())
{
Reply = SCompoundWidget::OnMouseButtonUp(MyGeometry, MouseEvent);
}
return Reply;
}
FReply SCustomizableObjectLayoutGrid::OnMouseMove( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
{
CurrentMousePosition = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
ELayoutGridMode GridMode = Mode.Get();
if(MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton) && GridMode == ELGM_Edit)
{
FVector2D Pos = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
if (bIsDragging && SelectedBlocks.Num())
{
FVector2D CellDelta = (Pos - DragStart) / CellSize;
int32 CellDeltaX = CellDelta.X;
int32 CellDeltaY = CellDelta.Y;
DragStart += FVector2D(CellDeltaX * CellSize, CellDeltaY * CellSize);
if (CellDeltaX || CellDeltaY)
{
bHasDragged = true;
const TArray<FCustomizableObjectLayoutBlock>& CurrentBlocks = Blocks.Get();
if (!bIsResizing)
{
// Bounding box of all selected blocks in grid units.
FIntRect TotalBlock;
bool bFirstBlock = true;
for (const FCustomizableObjectLayoutBlock& B : CurrentBlocks)
{
FIntRect Block(B.Min, B.Max);
if (SelectedBlocks.Contains(B.Id))
{
if (bFirstBlock)
{
TotalBlock = Block;
bFirstBlock = false;
}
TotalBlock.Min.X = FMath::Min(TotalBlock.Min.X, Block.Min.X);
TotalBlock.Min.Y = FMath::Min(TotalBlock.Min.Y, Block.Min.Y);
TotalBlock.Max.X = FMath::Max(TotalBlock.Max.X, Block.Max.X);
TotalBlock.Max.Y = FMath::Max(TotalBlock.Max.Y, Block.Max.Y);
}
}
FIntPoint Grid = GridSize.Get();
FIntRect BlockMovement = TotalBlock;
// Block movement in layouts is restricted to the positive quadrant.
//BlockMovement.Min.X = FMath::Max(0, FMath::Min(TotalBlock.Min.X + CellDeltaX, Grid.X - TotalBlock.Size().X));
//BlockMovement.Min.Y = FMath::Max(0, FMath::Min(TotalBlock.Min.Y + CellDeltaY, Grid.Y - TotalBlock.Size().Y));
BlockMovement.Min.X = FMath::Max(0, TotalBlock.Min.X + CellDeltaX);
BlockMovement.Min.Y = FMath::Max(0, TotalBlock.Min.Y + CellDeltaY);
//BlockMovement.Min.X = TotalBlock.Min.X + CellDeltaX;
//BlockMovement.Min.Y = TotalBlock.Min.Y + CellDeltaY;
BlockMovement.Max = BlockMovement.Min + TotalBlock.Size();
FIntRect AddMovement = BlockMovement - TotalBlock;
for (const FCustomizableObjectLayoutBlock& B : CurrentBlocks)
{
if (SelectedBlocks.Find(B.Id) != INDEX_NONE)
{
FIntRect ResultBlock(B.Min, B.Max);
ResultBlock.Max += AddMovement.Max;
ResultBlock.Min += AddMovement.Min;
BlockChangedDelegate.ExecuteIfBound(B.Id, ResultBlock);
}
}
}
else
{
for (const FCustomizableObjectLayoutBlock& B : CurrentBlocks)
{
FIntRect Block;
for (const FGuid& Id : SelectedBlocks)
{
if (B.Id != Id)
{
continue;
}
Block.Min = B.Min;
Block.Max = B.Max;
FIntRect InitialBlock = Block;
FIntPoint Grid = GridSize.Get();
FIntPoint BlockSize = Block.Size();
// Block movement in layouts is restricted to the positive quadrant.
//Block.Max.X = FMath::Max(Block.Min.X + 1, FMath::Min(Block.Max.X + CellDeltaX, Grid.X));
//Block.Max.Y = FMath::Max(Block.Min.Y + 1, FMath::Min(Block.Max.Y + CellDeltaY, Grid.Y));
Block.Max.X = Block.Max.X + CellDeltaX;
Block.Max.Y = Block.Max.Y + CellDeltaY;
if (Block != InitialBlock)
{
BlockChangedDelegate.ExecuteIfBound(Id, Block);
}
break;
}
}
}
}
}
if (!bIsSelecting && !bIsDragging)
{
bool ClickOnBlock = false;
for (const FGuid& BlockId : SelectedBlocks)
{
if (MouseOnBlock(BlockId, Pos))
{
if (SelectedBlocks.Contains(BlockId))
{
ClickOnBlock = true;
}
}
}
int32 MovementSensitivity = 4;
FVector2D MouseDiference = InitSelectionRect - Pos;
MouseDiference = MouseDiference.GetAbs();
if (!ClickOnBlock && (MouseDiference.X > MovementSensitivity || MouseDiference.Y > MovementSensitivity))
{
bHasDragged = true;
bIsSelecting = true;
}
}
}
if (!bIsDragging && !bIsResizing && SelectedBlocks.Num()==1 && GridMode == ELGM_Edit)
{
const TArray<FCustomizableObjectLayoutBlock>& CurrentBlocks = Blocks.Get();
for (int32 i = CurrentBlocks.Num() - 1; i > -1; --i)
{
// Check for new created blocks
if (BlockRects.Contains(CurrentBlocks[i].Id) && SelectedBlocks.Contains(CurrentBlocks[i].Id))
{
FVector2D Pos = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
if (MouseOnBlock(CurrentBlocks[i].Id, Pos, true))
{
bIsResizeCursor = true;
break;
}
}
bIsResizeCursor = false;
}
}
// In case we lose focus
if (bIsPadding)
{
if (MouseEvent.IsMouseButtonDown(EKeys::MiddleMouseButton))
{
FVector2D Pos = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
PointOfView.PaddingAmount += Pos - PaddingStart;
PaddingStart = Pos;
}
else
{
bIsPadding = false;
}
}
if (!MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton))
{
bIsSelecting = false;
bIsDragging = false;
if (bIsResizing)
{
bIsResizeCursor = false;
bIsResizing = false;
}
}
return SCompoundWidget::OnMouseMove( MyGeometry, MouseEvent );
}
FReply SCustomizableObjectLayoutGrid::OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
constexpr int32 MinZoomLevel = -2;
constexpr int32 MaxZoomLevel = 3;
{
double OldZoomFactor = PointOfView.GetZoomFactor();
FVector2D UnzoomedPadding = PointOfView.PaddingAmount * (1.0f / OldZoomFactor);
if (MouseEvent.GetWheelDelta() > 0)
{
int32 NewZoomLevel = FMath::Min(PointOfView.Zoom + 1, MaxZoomLevel);
if (PointOfView.Zoom != NewZoomLevel)
{
//FVector2D GridCenter = DrawOrigin + (FVector2D((float)GridSize.Get().X, (float)GridSize.Get().Y) / 2.0f) * CellSize;
//DistanceFromOrigin = CurrentMousePosition - GridCenter;
PointOfView.Zoom = NewZoomLevel;
}
}
else
{
int32 NewZoomLevel = FMath::Max(PointOfView.Zoom - 1, MinZoomLevel);
if (PointOfView.Zoom != NewZoomLevel)
{
//DistanceFromOrigin = FVector2D::Zero();
//PaddingAmount = FVector2D::Zero();
PointOfView.Zoom = NewZoomLevel;
}
}
double NewZoomFactor = PointOfView.GetZoomFactor();
FVector2D RezoomedPadding = UnzoomedPadding * NewZoomFactor;
PointOfView.PaddingAmount = RezoomedPadding;
return FReply::Handled().SetUserFocus(SharedThis(this), EFocusCause::Mouse, true);
}
}
FReply SCustomizableObjectLayoutGrid::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
{
ELayoutGridMode GridMode = Mode.Get();
if (GridMode != ELGM_Edit)
{
return SCompoundWidget::OnKeyDown(MyGeometry, InKeyEvent);
}
if (InKeyEvent.IsLeftControlDown())
{
if (InKeyEvent.GetKey() == EKeys::D)
{
DuplicateBlocks();
}
else if (InKeyEvent.GetKey() == EKeys::N)
{
FVector2D MouseToCellPosition = (CurrentMousePosition - DrawOrigin) / CellSize;
GenerateNewBlock(MouseToCellPosition);
}
else if (InKeyEvent.GetKey() == EKeys::F)
{
SetBlockSizeToMax();
}
}
if (InKeyEvent.GetKey() == EKeys::Delete)
{
DeleteSelectedBlocks();
}
return SCompoundWidget::OnKeyDown(MyGeometry, InKeyEvent);
}
FCursorReply SCustomizableObjectLayoutGrid::OnCursorQuery(const FGeometry& MyGeometry, const FPointerEvent& CursorEvent) const
{
if (bIsResizeCursor)
{
return FCursorReply::Cursor(EMouseCursor::ResizeSouthEast);
}
else
{
return FCursorReply::Cursor(EMouseCursor::Default);
}
}
FVector2D SCustomizableObjectLayoutGrid::ComputeDesiredSize(float NotUsed) const
{
return FVector2D(200.0f, 200.0f);
}
void SCustomizableObjectLayoutGrid::SetSelectedBlock(FGuid block )
{
SelectedBlocks.Reset();
SelectedBlocks.Add( block );
}
const TArray<FGuid>& SCustomizableObjectLayoutGrid::GetSelectedBlocks() const
{
return SelectedBlocks;
}
void SCustomizableObjectLayoutGrid::DeleteSelectedBlocks()
{
DeleteBlocksDelegate.ExecuteIfBound();
}
void SCustomizableObjectLayoutGrid::ResetView()
{
PointOfView.Zoom = 1;
PointOfView.PaddingAmount = { 0,0 };
}
void SCustomizableObjectLayoutGrid::GenerateNewBlock(FVector2D MousePosition)
{
if (MousePosition.X > 0 && MousePosition.Y > 0 && MousePosition.X < GridSize.Get().X && MousePosition.Y < GridSize.Get().Y)
{
FIntPoint Min = FIntPoint(MousePosition.X, MousePosition.Y);
FIntPoint Max = Min + FIntPoint(1, 1);
AddBlockAtDelegate.ExecuteIfBound(Min, Max);
SelectedBlocks.Add(Blocks.Get().Last().Id);
}
}
void SCustomizableObjectLayoutGrid::DuplicateBlocks()
{
if (SelectedBlocks.Num())
{
for (const FCustomizableObjectLayoutBlock& Block : Blocks.Get())
{
if (SelectedBlocks.Find(Block.Id) != INDEX_NONE)
{
AddBlockAtDelegate.ExecuteIfBound(Block.Min, Block.Max);
}
}
}
}
void SCustomizableObjectLayoutGrid::SetBlockSizeToMax()
{
if (SelectedBlocks.Num())
{
for (const FCustomizableObjectLayoutBlock& Block : Blocks.Get())
{
if (SelectedBlocks.Find(Block.Id) != INDEX_NONE)
{
FIntRect FinalBlock;
FinalBlock.Min = FIntPoint(0, 0);
FinalBlock.Max = GridSize.Get();
BlockChangedDelegate.ExecuteIfBound(Block.Id, FinalBlock);
}
}
}
}
void SCustomizableObjectLayoutGrid::CalculateSelectionRect()
{
if (InitSelectionRect.X <= CurrentMousePosition.X)
{
if (InitSelectionRect.Y <= CurrentMousePosition.Y)
{
SelectionRect.Min = FVector2f(InitSelectionRect);
SelectionRect.Size = FVector2f(CurrentMousePosition - InitSelectionRect);
}
else
{
SelectionRect.Min = FVector2f(InitSelectionRect.X, CurrentMousePosition.Y);
FVector2f AuxVector(CurrentMousePosition.X, InitSelectionRect.Y);
SelectionRect.Size = AuxVector - SelectionRect.Min;
}
}
else
{
if (InitSelectionRect.Y <= CurrentMousePosition.Y)
{
SelectionRect.Min = FVector2f(CurrentMousePosition.X, InitSelectionRect.Y);
FVector2f AuxVector(InitSelectionRect.X, CurrentMousePosition.Y);
SelectionRect.Size = AuxVector - SelectionRect.Min;
}
else
{
SelectionRect.Min = FVector2f(CurrentMousePosition);
SelectionRect.Size = FVector2f(InitSelectionRect - CurrentMousePosition);
}
}
}
void SCustomizableObjectLayoutGrid::SetBlocks(const FIntPoint& InGridSize, const TArray<FCustomizableObjectLayoutBlock>& InBlocks)
{
GridSize = InGridSize;
Blocks = InBlocks;
}
bool SCustomizableObjectLayoutGrid::MouseOnBlock(FGuid BlockId, FVector2D MousePosition, bool CheckResizeBlock) const
{
FVector2f Min, Max;
if (CheckResizeBlock)
{
Min = BlockRects[BlockId].HandleRect.Min;
Max = Min + BlockRects[BlockId].HandleRect.Size;
}
else
{
Min = BlockRects[BlockId].Rect.Min;
Max = Min + BlockRects[BlockId].Rect.Size;
}
if (MousePosition.X > Min.X && MousePosition.X<Max.X && MousePosition.Y>Min.Y && MousePosition.Y < Max.Y)
{
return true;
}
return false;
}
TOptional<int32> SCustomizableObjectLayoutGrid::GetBlockPriorityValue() const
{
if (SelectedBlocks.Num())
{
TArray<FCustomizableObjectLayoutBlock> CurrentSelectedBlocks;
for (const FCustomizableObjectLayoutBlock& Block : Blocks.Get())
{
if (SelectedBlocks.Contains(Block.Id))
{
CurrentSelectedBlocks.Add(Block);
}
}
int32 BlockPriority = CurrentSelectedBlocks[0].Priority;
bool bSamePriority = true;
for (const FCustomizableObjectLayoutBlock& Block : CurrentSelectedBlocks)
{
if (Block.Priority != BlockPriority)
{
bSamePriority = false;
break;
}
}
if (bSamePriority)
{
return BlockPriority;
}
}
return TOptional<int32>();
}
ECheckBoxState SCustomizableObjectLayoutGrid::GetReductionMethodBoolValue(EFixedReductionOptions Option) const
{
if (SelectedBlocks.Num())
{
TArray<FCustomizableObjectLayoutBlock> CurrentSelectedBlocks;
// Getting all selected blocks
for (const FCustomizableObjectLayoutBlock& Block : Blocks.Get())
{
if (SelectedBlocks.Contains(Block.Id))
{
CurrentSelectedBlocks.Add(Block);
}
}
switch (Option)
{
case EFRO_Symmetry:
{
const bool bReduceBothAxes = CurrentSelectedBlocks[0].bReduceBothAxes;
// If one or more blocks have a different value than the rest of selected blocks return Undetermined
for (const FCustomizableObjectLayoutBlock& Block : CurrentSelectedBlocks)
{
if (Block.bReduceBothAxes != bReduceBothAxes)
{
return ECheckBoxState::Undetermined;
}
}
return bReduceBothAxes ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
}
case EFRO_RedyceByTwo:
{
const bool bReduceByTwo = CurrentSelectedBlocks[0].bReduceByTwo;
// If one or more blocks have a different value than the rest of selected blocks return Undetermined
for (const FCustomizableObjectLayoutBlock& Block : CurrentSelectedBlocks)
{
if (Block.bReduceByTwo != bReduceByTwo)
{
return ECheckBoxState::Undetermined;
}
}
return bReduceByTwo ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
}
default:
break;
}
}
return ECheckBoxState::Undetermined;
}
void SCustomizableObjectLayoutGrid::OnBlockPriorityChanged(int32 InValue)
{
if (SelectedBlocks.Num())
{
OnSetBlockPriority.ExecuteIfBound(InValue);
}
}
void SCustomizableObjectLayoutGrid::OnReduceBlockSymmetricallyChanged(ECheckBoxState InCheckboxState)
{
if (SelectedBlocks.Num())
{
OnSetReduceBlockSymmetrically.ExecuteIfBound(InCheckboxState == ECheckBoxState::Checked);
}
}
void SCustomizableObjectLayoutGrid::OnReduceBlockByTwoChanged(ECheckBoxState InCheckboxState)
{
if (SelectedBlocks.Num())
{
OnSetReduceBlockByTwo.ExecuteIfBound(InCheckboxState == ECheckBoxState::Checked);
}
}
// Canvas Drawer --------------------------------------------------------------
FUVCanvasDrawer::~FUVCanvasDrawer()
{
delete RenderTarget;
}
void FUVCanvasDrawer::Initialize(const FIntRect& InCanvasRect, const FIntRect& InClippingRect, const FVector2D& InOrigin, const FVector2D& InSize, const FIntPoint& InGridSize, const float InCellSize)
{
Initialized = InCanvasRect.Size().X > 0 && InCanvasRect.Size().Y > 0;
if (Initialized)
{
RenderTarget->SetViewRect(InCanvasRect);
RenderTarget->SetClippingRect(InClippingRect);
Origin = InOrigin;
Size = InSize;
CellSize = InCellSize;
GridSize = InGridSize;
}
}
void FUVCanvasDrawer::InitializeDrawingData(const TArray<FVector2f>& InUVLayout, const TArray<FVector2f>& InUnassignedUVs, const TArray<FCustomizableObjectLayoutBlock>& InBlocks, const TArray<FGuid>& InSelectedBlocks)
{
Blocks = InBlocks;
SelectedBlocks = InSelectedBlocks;
// Convert data
UVLayout.SetNum(InUVLayout.Num());
for (int32 Index = 0; Index < InUVLayout.Num(); ++Index)
{
UVLayout[Index] = FVector2D(InUVLayout[Index]);
}
UnassignedUVs.SetNum(InUnassignedUVs.Num());
for (int32 Index = 0; Index < UnassignedUVs.Num(); ++Index)
{
UnassignedUVs[Index] = FVector2D(InUnassignedUVs[Index]);
}
}
void FUVCanvasDrawer::SetLayoutMode(ELayoutGridMode Mode)
{
LayoutMode = Mode;
}
void FUVCanvasDrawer::Draw_RenderThread(FRDGBuilder& GraphBuilder, const FDrawPassInputs& Inputs)
{
if (!Initialized)
{
return;
}
RenderTarget->SetRenderTargetTexture(Inputs.OutputTexture);
FCanvas& Canvas = *GraphBuilder.AllocObject<FCanvas>(RenderTarget, nullptr, FGameTime(), GMaxRHIFeatureLevel);
Canvas.SetRenderTargetRect(RenderTarget->GetViewRect());
Canvas.SetRenderTargetScissorRect(RenderTarget->GetClippingRect());
// Number of tiles to render in each axis including the unit tile.
constexpr int32 NumTiles = 4;
// Num Lines
const uint32 NumAxisLines = 2;
const uint32 NumEdges = UVLayout.Num() / 2;
const uint32 NumUnitGridLines = GridSize.X + GridSize.Y + 2;
const uint32 NumTileLines = NumTiles + NumTiles + 2;
const uint32 NumExtendedGridLines = NumTiles * (GridSize.X + GridSize.Y) + 2;
const uint32 NumUnasignedUVs = UnassignedUVs.Num() * 4;
// Num Vertices and Triangles including blocks and unit tile quad.
const uint32 RectCount = (LayoutMode == ELayoutGridMode::ELGM_Edit) ? Blocks.Num() * 2 : Blocks.Num();
const uint32 NumVertices = RectCount*4 + 4;
const uint32 NumTriangles = RectCount*2 + 2;
const int32 NumLines = NumAxisLines + NumEdges + NumUnitGridLines + NumTileLines + NumExtendedGridLines + NumUnasignedUVs;
FBatchedElements* BatchedElements = Canvas.GetBatchedElements(FCanvas::ET_Line);
BatchedElements->AddReserveLines(NumLines);
BatchedElements->AddReserveVertices(NumVertices);
BatchedElements->AddReserveTriangles(NumTriangles, GWhiteTexture, ESimpleElementBlendMode::SE_BLEND_Translucent);
// Color Definitions
const FColor ExtendedGridLineColor = FColor(150, 150, 150, 32);
const FColor GridLineColor = FColor(150, 150, 150, 64);
const FColor TileLineColor = FColor(200, 200, 150, 48);
const FColor UVLineColor = FColor(255, 255, 255, 255);
const FColor UnassignedUVsColor = FColor::Yellow;
const FColor ResizeBlockColor = FColor(255, 96, 96, 255);
const FColor UnitTileBlockColor = FColor(96, 96, 96, 128);
const FHitProxyId HitProxyId = Canvas.GetHitProxyId();
int32 FullTilesSize = NumTiles * Size.X;
//FVector2D TilesOrigin = Origin - FVector2D(FullTilesSize / 2, FullTilesSize / 2);
FVector2D TilesOrigin = Origin;
// Create lines as pairs of points
FVector LinePoints[2];
// Unit Tile
{
FRect2D TileBlock;
TileBlock.Min = FVector2f(Origin);
TileBlock.Size = FVector2f(Size);
DrawBlock(BatchedElements, HitProxyId, TileBlock, UnitTileBlockColor, nullptr);
}
// Drawing Extended Grid
if (LayoutMode!=ELGM_ShowUVsOnly)
{
// Vertical Lines
for (int32 LineIndex = 0; LineIndex < NumTiles*GridSize.X + 1; LineIndex++)
{
LinePoints[0] = FVector(TilesOrigin.X + LineIndex * CellSize, TilesOrigin.Y, 0.0f);
LinePoints[1] = FVector(TilesOrigin.X + LineIndex * CellSize, TilesOrigin.Y + FullTilesSize, 0.0f);
BatchedElements->AddTranslucentLine(LinePoints[0], LinePoints[1], ExtendedGridLineColor, HitProxyId, 2.0f);
}
// Drawing Unit Grid Horizontal Lines
for (int32 LineIndex = 0; LineIndex < NumTiles*GridSize.Y + 1; LineIndex++)
{
LinePoints[0] = FVector(TilesOrigin.X, TilesOrigin.Y + LineIndex * CellSize, 0.0f);
LinePoints[1] = FVector(TilesOrigin.X + FullTilesSize, TilesOrigin.Y + LineIndex * CellSize, 0.0f);
BatchedElements->AddTranslucentLine(LinePoints[0], LinePoints[1], ExtendedGridLineColor, HitProxyId, 2.0f);
}
}
// Drawing Unit Grid
if (LayoutMode != ELGM_ShowUVsOnly)
{
// Vertical Lines
for (int32 LineIndex = 0; LineIndex < GridSize.X + 1; LineIndex++)
{
LinePoints[0] = FVector(Origin.X + LineIndex * CellSize, Origin.Y, 0.0f);
LinePoints[1] = FVector(Origin.X + LineIndex * CellSize, Origin.Y + Size.Y, 0.0f);
BatchedElements->AddTranslucentLine(LinePoints[0], LinePoints[1], GridLineColor, HitProxyId, 2.0f);
}
// Drawing Unit Grid Horizontal Lines
for (int32 LineIndex = 0; LineIndex < GridSize.Y + 1; LineIndex++)
{
LinePoints[0] = FVector(Origin.X, Origin.Y + LineIndex * CellSize, 0.0f);
LinePoints[1] = FVector(Origin.X + Size.X, Origin.Y + LineIndex * CellSize, 0.0f);
BatchedElements->AddTranslucentLine(LinePoints[0], LinePoints[1], GridLineColor, HitProxyId, 2.0f);
}
}
// Drawing Tiles
{
// Vertical Lines
for (int32 LineIndex = 0; LineIndex < NumTiles + 1; LineIndex++)
{
LinePoints[0] = FVector(TilesOrigin.X + LineIndex * Size.X, TilesOrigin.Y, 0.0f);
LinePoints[1] = FVector(TilesOrigin.X + LineIndex * Size.X, TilesOrigin.Y + FullTilesSize, 0.0f);
BatchedElements->AddTranslucentLine(LinePoints[0], LinePoints[1], TileLineColor, HitProxyId, 2.0f);
}
// Horizontal Lines
for (int32 LineIndex = 0; LineIndex < NumTiles + 1; LineIndex++)
{
LinePoints[0] = FVector(TilesOrigin.X, TilesOrigin.Y + LineIndex * Size.Y, 0.0f);
LinePoints[1] = FVector(TilesOrigin.X + FullTilesSize, TilesOrigin.Y + LineIndex * Size.Y, 0.0f);
BatchedElements->AddTranslucentLine(LinePoints[0], LinePoints[1], TileLineColor, HitProxyId, 2.0f);
}
}
// Axes
{
LinePoints[0] = FVector(TilesOrigin.X, Origin.Y, 0.0f);
LinePoints[1] = FVector(TilesOrigin.X + FullTilesSize, Origin.Y, 0.0f);
BatchedElements->AddTranslucentLine(LinePoints[0], LinePoints[1], FColor(255, 150, 150, 200), HitProxyId, 2.0f);
LinePoints[0] = FVector(Origin.X, TilesOrigin.Y, 0.0f);
LinePoints[1] = FVector(Origin.X, TilesOrigin.Y + FullTilesSize, 0.0f);
BatchedElements->AddTranslucentLine(LinePoints[0], LinePoints[1], FColor(150, 255, 150, 200), HitProxyId, 2.0f);
}
// Drawing UV Lines
for (uint32 LineIndex = 0; LineIndex < NumEdges; ++LineIndex)
{
LinePoints[0] = FVector(Origin + UVLayout[LineIndex * 2 + 0] * Size, 0.0f);
LinePoints[1] = FVector(Origin + UVLayout[LineIndex * 2 + 1] * Size, 0.0f);
BatchedElements->AddLine(LinePoints[0], LinePoints[1], UVLineColor, HitProxyId);
}
// Drawing Unassigned UVs
const FVector2D CrossSize = Size * 0.01;
for (const FVector2d& Vertex : UnassignedUVs)
{
LinePoints[0] = FVector(Origin + FVector2D(Vertex) * Size + FVector2D(CrossSize), 0.0f);
LinePoints[1] = FVector(Origin + FVector2D(Vertex) * Size - FVector2D(CrossSize) * FVector2D(1.0f, -1.0f), 0.0f);
BatchedElements->AddLine(LinePoints[0], LinePoints[1], UVLineColor, HitProxyId);
LinePoints[0] = FVector(Origin + FVector2D(Vertex) * Size - FVector2D(CrossSize), 0.0f);
BatchedElements->AddLine(LinePoints[0], LinePoints[1], UVLineColor, HitProxyId);
LinePoints[1] = FVector(Origin + FVector2D(Vertex) * Size + FVector2D(CrossSize) * FVector2D(1.0f, -1.0f), 0.0f);
BatchedElements->AddLine(LinePoints[0], LinePoints[1], UVLineColor, HitProxyId);
LinePoints[0] = FVector(Origin + FVector2D(Vertex) * Size + FVector2D(CrossSize), 0.0f);
BatchedElements->AddLine(LinePoints[0], LinePoints[1], UVLineColor, HitProxyId);
}
// Drawing Blocks
if (LayoutMode != ELGM_ShowUVsOnly)
{
for (const FCustomizableObjectLayoutBlock& Block : Blocks)
{
FColor BlockColor = AutomaticBlockColor;
if (!Block.bIsAutomatic)
{
BlockColor = SelectedBlocks.Contains(Block.Id) ? SelectedBlockColor : UnselectedBlockColor;
}
const FVector2f BlockMin(Block.Min);
const FVector2f BlockMax(Block.Max);
// Selection Block
FRect2D BlockRect;
BlockRect.Min = FVector2f(Origin) + BlockMin * CellSize + CellSize * 0.1f;
BlockRect.Size = (BlockMax - BlockMin) * CellSize - CellSize * 0.2f;
DrawBlock(BatchedElements, HitProxyId, BlockRect, BlockColor, Block.Mask.Get());
if (LayoutMode == ELayoutGridMode::ELGM_Edit && !Block.bIsAutomatic)
{
// Resize Block
FRect2D ResizeBlock;;
float HandleRectSize = FMath::Log2(float(GridSize.X)) / 10.0f;
ResizeBlock.Size = FVector2f(CellSize) * HandleRectSize;
ResizeBlock.Min = BlockRect.Min + BlockRect.Size - ResizeBlock.Size;
DrawBlock(BatchedElements, HitProxyId, ResizeBlock, ResizeBlockColor);
}
}
}
Canvas.Flush_RenderThread(GraphBuilder, true);
RenderTarget->ClearRenderTargetTexture();
}
void FUVCanvasDrawer::DrawBlock(FBatchedElements* BatchedElements, const FHitProxyId HitProxyId, const FRect2D& BlockRect, FColor Color, UTexture2D* Mask)
{
// Vertex positions
FVector4 Vert0(BlockRect.Min.X, BlockRect.Min.Y, 0, 1);
FVector4 Vert1(BlockRect.Min.X, BlockRect.Min.Y + BlockRect.Size.Y, 0, 1);
FVector4 Vert2(BlockRect.Min.X + BlockRect.Size.X, BlockRect.Min.Y, 0, 1);
FVector4 Vert3(BlockRect.Min.X + BlockRect.Size.X, BlockRect.Min.Y + BlockRect.Size.Y, 0, 1);
auto VertexToMaskUVs = [this](const FVector4& V)
{
FVector2D Result = ( FVector2D(V.X,V.Y) - Origin ) / (FVector2D(CellSize) * FVector2D(GridSize.X, GridSize.X));
// TODO Modulo doesn't work with cross-tile blocks: use tiling.
Result.X = FMath::Fmod(Result.X, 1.0);
Result.Y = FMath::Fmod(Result.Y, 1.0);
return Result;
};
// Brush Paint triangle
{
int32 V0 = BatchedElements->AddVertex(Vert0, VertexToMaskUVs(Vert0), Color, HitProxyId);
int32 V1 = BatchedElements->AddVertex(Vert1, VertexToMaskUVs(Vert1), Color, HitProxyId);
int32 V2 = BatchedElements->AddVertex(Vert2, VertexToMaskUVs(Vert2), Color, HitProxyId);
int32 V3 = BatchedElements->AddVertex(Vert3, VertexToMaskUVs(Vert3), Color, HitProxyId);
EBlendMode Mode = EBlendMode::BLEND_Translucent;
BatchedElements->AddTriangle(V0, V1, V2, GWhiteTexture, Mode);
BatchedElements->AddTriangle(V1, V3, V2, GWhiteTexture, Mode);
if (Mask)
{
Mode = EBlendMode::BLEND_Additive;
FTexture* Texture = Mask->GetResource();
BatchedElements->AddTriangle(V0, V1, V2, Texture, Mode);
BatchedElements->AddTriangle(V1, V3, V2, Texture, Mode);
}
}
// Drawing Outline to selected Blocks
if (Color == SelectedBlockColor)
{
BatchedElements->AddLine(Vert0, Vert1, UnselectedBlockColor, HitProxyId, 4.0f);
BatchedElements->AddLine(Vert1, Vert3, UnselectedBlockColor, HitProxyId, 4.0f);
BatchedElements->AddLine(Vert3, Vert2, UnselectedBlockColor, HitProxyId, 4.0f);
BatchedElements->AddLine(Vert2, Vert0, UnselectedBlockColor, HitProxyId, 4.0f);
}
}
#undef LOCTEXT_NAMESPACE