【UE5】ComponentVisualizerを活用してデータを視覚化

UE5には Component Visualizer という機能(クラス)があります。
Component Visualizerを活用すると、コンポーネントの値を視覚化することができます。視覚化されるUIはランタイムでは描画されずエディタ上で編集時にのみ描画されます。また、その視覚化されたUIをマウスで操作し、値を編集することも可能です。

エンジン内部では、ライトの範囲表示やSplineの線など様々な箇所の描画で使用されています。
今回はそのComponent Visualizerの使用方法について紹介します。

 


0. 準備

プラグインの作成

Component Visualizerはクラスのロードタイミングがデフォルトのままでは使えず、専用のモジュールでロードタイミングを変更する必要があります。そのため、まずはComponent Visualizerを使用するために専用のプラグインを作成します。

プラグインを作成したら、早速 ".uplugin" を開き、"LoadingPhase" を "PostEngineInit" に変更します。

"Modules": [
	{
		"Name": "MyComponentVisualizer",
		"Type": "Runtime",
		"LoadingPhase": "PostEngineInit"
	}
]

独自のComponent Visualizerを作成

FComponentVisualizer を継承した独自のクラスを作成します。

#include "ComponentVisualizer.h"

class FMyComponentVisualizer : public FComponentVisualizer
{
};

"ComponentVisualizer.h"をインクルードするために、"Build.cs" に "UnrealEd" モジュールを追加します。

PrivateDependencyModuleNames.AddRange(
	new string[]
	{
		"CoreUObject",
		"Engine",
		"Slate",
		"SlateCore",
		"UnrealEd",	// 追加
	}
);

Component Visualizerに登録

Component Visualizerを使用するにはコンポーネントを登録する必要があります。
まずはActorComponentを継承した独自のコンポーネントを作成します。

UCLASS(ClassGroup = Custom, meta = (BlueprintSpawnableComponent))
class MYCOMPONENTVISUALIZER_API UMyActorComponent : public UActorComponent
{
	GENERATED_BODY()

public:
	UMyActorComponent();

protected:
	virtual void BeginPlay() override;

public:
	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;
};

次にStartupModule関数で独自のComponent Visualizerに作成したコンポーネントを登録、ShutdownModule関数で登録を解除する処理を追加します。

#include "MyComponentVisualizer.h"

#include "MyActorComponent.h"
#include "UnrealEdGlobals.h"
#include "Editor/UnrealEdEngine.h"

#define LOCTEXT_NAMESPACE "FMyComponentVisualizerModule"

void FMyComponentVisualizerModule::StartupModule()
{
	TSharedPtr<FMyComponentVisualizer> visualizer = MakeShareable(new FMyComponentVisualizer);
	GUnrealEd->RegisterComponentVisualizer(UMyActorComponent::StaticClass()->GetFName(), visualizer);
	visualizer->OnRegister();
}

void FMyComponentVisualizerModule::ShutdownModule()
{
	GUnrealEd->UnregisterComponentVisualizer(UMyActorComponent::StaticClass()->GetFName());
}

#undef LOCTEXT_NAMESPACE
	
IMPLEMENT_MODULE(FMyComponentVisualizerModule, MyComponentVisualizer)

描画の基本

ここまでできたら準備完了です。
FComponentVisualizer の関数をオーバーライドしてデータを描画する処理を書いていきます。

基本的には DrawVisualization関数で図形を描画し、DrawVisualizationHUD関数でHUD上に文字やテクスチャを描画します。
DrawVisualization関数では、引数のFPrimitiveDrawInterfaceからDraw系関数を呼ぶことで線やスプライトなどを描画できます。
DrawVisualizationHUD関数では、引数のFCanvasから文字などを描画できます。

1. 点の描画

点を描画するには、DrawPoint関数を呼び出します。
MyActorComponentにFVectorの配列を用意して、配列の要素に点を描画するようにしたいと思います。まずは配列の変数を用意します。

UPROPERTY(EditInstanceOnly)
TArray<FVector> Points;

DrawVisualization関数で引数のComponentをキャストして、MyActorComponentのPointsごとにDrawPoint関数で点を描画します。

void FMyComponentVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI)
{
	if (const UMyActorComponent* myComp = Cast<const UMyActorComponent>(Component))
	{
		// 点の描画
		for (int32 i = 0; i < myComp->Points.Num(); i++)
		{
			PDI->DrawPoint(myComp->Points[i], FColor::Red, 10.f, SDPG_Foreground);
		}
	}
}

DrawPointの引数で点の色や描画方法などを指定します。

引数名 説明
1 Position 点の座標。
2 Color 点の色。
3 PointSize 点のサイズ。
4 DepthPriorityGroup 描画方法。
SDPG_Foreground: 常に前面に描画されます。
SDPG_World: ワールド座標を基準に描画されます。

Tips: コンポーネント選択時のみ描画

デフォルトではコンポーネントの親のアクターを選択時に描画されますが、ShouldShowForSelectedSubcomponents関数をオーバーライドし、falseを返すことでComponent Visualizerに登録されたコンポーネントを選択時にのみ描画されるようになります。


2. 線の描画

線を描画するには、DrawLine関数を呼び出します。
点と同じようにPointsの要素間に線を描画するようにDrawVisualization関数に描画処理を追加します。

void FMyComponentVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI)
{
	if (const UMyActorComponent* myComp = Cast<const UMyActorComponent>(Component))
	{
		const int32 n = myComp->Points.Num();
		
		// 点の描画
		for (int32 i = 0; i < n; i++)
		{
			PDI->DrawPoint(myComp->Points[i], FColor::Red, 10.f, SDPG_Foreground);
		}

		// 線の描画
		if (n >= 2)
		{
			for (int32 i = n-1; i >= 1; i--)
			{
				PDI->DrawLine(myComp->Points[i-1], myComp->Points[i], FColor::Cyan, SDPG_Foreground, 2.f, 0.f, true);
			}
		}
	}
}

DrawLineの引数で線の色や描画方法などを指定します。

引数名 説明
1 Start 始点。
2 End 終点。
3 Color 線の色。
4 DepthPriorityGroup 描画方法。
SDPG_Foreground: 常に前面に描画されます。
SDPG_World: ワールド座標を基準に描画されます。
5 Thickness 線の太さ。
6 DepthBias 深度のバイアス。
7 bScreenSpace trueの場合、スクリーンスペースで描画されるためカメラが離れても同じ見た目で描画されます。(デフォルトはfalse)

DrawLine関数をDrawTranslucentLine関数に変更すると半透明の線を描画できます。アルファ値は第三引数のColorで指定します。

3. スプライトの描画

スプライト(テクスチャ)を描画するには、DrawSprite関数を呼び出します。
まずはMyActorComponentにUTexture2Dの変数を用意します。

UPROPERTY(EditInstanceOnly)
TObjectPtr<UTexture2D> Sprite;

用意したSprite変数をDrawSprite関数の引数として渡し、アクターの座標に描画します。

void FMyComponentVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI)
{
	if (const UMyActorComponent* myComp = Cast<const UMyActorComponent>(Component))
	{
		if (myComp->Sprite)
		{
			PDI->DrawSprite(myComp->GetOwner()->GetActorLocation(), 10.f, 10.f, myComp->Sprite->GetResource(), FColor::White, SDPG_Foreground, 0.f, 0.f, 0.f, 0.f);
		}
	}
}

DrawSpriteの引数でテクスチャや描画方法などを指定します。

引数名 説明
1 Position 点の座標。
2 SizeX スプライトのXサイズ。
3 SizeY スプライトのYサイズ。
4 Sprite スプライト(テクスチャ)。
5 Color スプライトの色。
6 DepthPriorityGroup 描画方法。
SDPG_Foreground: 常に前面に描画されます。
SDPG_World: ワールド座標を基準に描画されます。
7~10 U, UL, V, VL スプライトのUV。
11 BlendMode ブレンドモード。デフォルトではMasked。
12 OpacityMaskRefVal ブレンドモードがMasked時のOpacity値。



4. ワイヤーフレームの描画

DrawWire系の関数でワイヤーフレームの図形を描画できます。

関数名 描画される図形
DrawWireSphere
DrawWireSphereAutoSides
辺の数は自動で設定されます。
DrawWireCylinder 円筒
DrawWireCapsule カプセル
DrawWireChoppedCone カプセル
上下の球の半径を個別に設定できます。
DrawWireCone 円錐
DrawWireSphereCappedCone 円錐
キャップ上に円弧がある円錐を描画します。
DrawOrientedWireBox 立方体
DrawDirectionalArrow 矢印
DrawConnectedArrow 矢印
DrawWireStar 米印
DrawDashedLine 点線
DrawWireDiamond ひし形
DrawCoordinateSystem 座標系の描画。


5. テキストの描画

テキストを描画するには、DrawVisualizationHUD関数で引数のFCanvasからDrawShadowedString関数DrawShadowedText関数を呼び出します。

「1」「2」で描画した点(Points変数)の位置にテキストを描画する処理を書きました。描画の前にワールド座標からスクリーン座標への変換なども行っています。

#include "CanvasTypes.h"

void FMyComponentVisualizer::DrawVisualizationHUD(const UActorComponent* Component, const FViewport* Viewport, const FSceneView* View, FCanvas* Canvas)
{
	if (const UMyActorComponent* myComp = Cast<const UMyActorComponent>(Component))
	{
		// スクリーン位置の計算
		const FIntRect rect = Canvas->GetViewRect();
		const float halfX = rect.Width() / 2.f;
		const float halfY = rect.Height() / 2.f;
		const float offsetY = 12.f;
		
		for (int32 i = 0; i < myComp->Points.Num(); i++)
		{
			// ワールド座標からスクリーン座標に変換
			const FVector pos = FVector(View->Project(myComp->Points[i]));
			const float drawX = FMath::FloorToFloat(halfX + pos.X * halfX);
			const float drawY = FMath::FloorToFloat(halfY + -1.f * pos.Y * halfY);

			// 描画
			Canvas->DrawShadowedString(drawX, (drawY + offsetY), *FString::Printf(TEXT("Points[%d]"), i), GEngine->GetLargeFont(), FColor::White, FColor::Black);
		}
	}
}

DrawShadowedStringの引数で描画する位置や文字色などを指定します。

引数名 説明
1 StartX テキストの座標X。
2 StartY テキストの座標Y。
3 Text 描画する文字列。
4 Font フォント。
5 Color 文字の色。
6 ShadowColor 影の色。



6. マニュピレータでの制御

Component Visualizerは描画だけではなく、描画した図形をビューポート上で編集することもできます。
今回は「1」「2」で記述した点と線を描画し、操作したい点をマニピュレーターで制御できるようにしたいと思います。

VisProxyの作成

まずは入力の受信に必要なHComponentVisProxyというクラスを継承した独自のクラスを作成します。

struct HMyComponentVisProxy : public HComponentVisProxy
{
	DECLARE_HIT_PROXY();
	
	HMyComponentVisProxy(const UActorComponent* InComponent, int32 InPointIndex)
	: HComponentVisProxy(InComponent, HPP_Wireframe)
	, PointIndex(InPointIndex)
	{
	}
	
	int32 PointIndex;
};

PointIndex変数は点の制御のために用意しています。
コンストラクタはPointIndexを代入できるように拡張しています。

.cppファイルの頭にIMPLEMENT_HIT_PROXYマクロを記述します。(おまじないだと思ってください。)

IMPLEMENT_HIT_PROXY(HMyComponentVisProxy, HComponentVisProxy);

次にDrawVisualization関数に描画の処理を記述します。
点の描画の前後にSetHitProxyを記述します。これだけで入力を受け取れるようになります。
線や別の図形を操作したい場合も同じようにSetHitProxyで挟めば入力を受け取れるようになります。

void FMyComponentVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI)
{
	if (const UMyActorComponent* myComp = Cast<const UMyActorComponent>(Component))
	{
		const int32 n = myComp->Points.Num();
		
		for (int32 i = 0; i < n; i++)
		{
			// 線の描画
			if (i < n-1)
			{
				PDI->DrawLine(myComp->Points[i], myComp->Points[i+1], FColor::Cyan, SDPG_World, 2.f, 0.f, true);
			}
			
			// 点の描画
			PDI->SetHitProxy(new HMyComponentVisProxy(Component, i));
			PDI->DrawPoint(myComp->Points[i], FColor::Red, 15.f, SDPG_World);
			PDI->SetHitProxy(NULL);
		}
	}
}

入力の受信

まずはFMyComponentVisualizerに変数を用意します。また、各関数をオーバーライドしておきます。

class FMyComponentVisualizer : public FComponentVisualizer
{
public:
	//~ Begin FComponentVisualizer Interface
	virtual void DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) override;
	virtual bool VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) override;
	virtual bool GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const override;
	virtual bool HandleInputDelta(FEditorViewportClient* ViewportClient, FViewport* Viewport, FVector& DeltaTranslate, FRotator& DeltaRotate, FVector& DeltaScale) override;
	virtual UActorComponent* GetEditedComponent() const override;
	//~ End FComponentVisualizer Interface

	TObjectPtr<UMyActorComponent> CurrentComponent;	// 追加
	int32 SelectPointIndex;	// 追加
};

オーバーライドしたVisProxyHandleClick関数に点をクリックした時の処理を記述します。
クリックした際にコンポーネントと選択中の点のインデックス番号を保存しておきます。

bool FMyComponentVisualizer::VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click)
{
	if (VisProxy && VisProxy->Component.IsValid())
	{
		const HMyComponentVisProxy* Proxy = (HMyComponentVisProxy*)VisProxy;
		CurrentComponent = (UMyActorComponent*)Proxy->Component.Get();	// コンポーネントの保存
		SelectPointIndex = Proxy->PointIndex;	// 現在選択中のインデックス番号の保存

		return true;
	}
	
	return false;
}

保存したコンポーネントは、GetEditedComponent関数で渡してあげます。

UActorComponent* FMyComponentVisualizer::GetEditedComponent() const
{
	return CurrentComponent;
}

マニピュレータの描画

GetWidgetLocation関数で引数のOutLocationに選択中の点の座標を代入します。

bool FMyComponentVisualizer::GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const
{
	if (CurrentComponent)
	{
		OutLocation = CurrentComponent->Points[SelectPointIndex];
		return true;
	}
	
	return false;
}

これでクリックした際にマニピュレータが表示されるようになりました。但しマニピュレータを操作しても図形は変化しません。
最後にHandleInputDelta関数で操作した時の差分をPoints変数に加算してあげます。

bool FMyComponentVisualizer::HandleInputDelta(FEditorViewportClient* ViewportClient, FViewport* Viewport, FVector& DeltaTranslate, FRotator& DeltalRotate, FVector& DeltaScale)
{
	if (CurrentComponent)
	{
		if (!DeltaTranslate.IsZero())
		{
			CurrentComponent->Points[SelectPointIndex] += DeltaTranslate;
		}
		return true;
	}
	
	return false;
}

マニピュレータで点を操作できるようになりました。

Tips: Handle系関数でイベントの受信

今回はマニピュレータでの操作だったのでHandleInputDelta関数で処理しましたが、他にもHandle系関数はいくつか用意されており、様々なタイミングでイベントを受け取ることができます。
例えば、下記のようにHandleInputKey関数を用いるとキー入力時に処理をすることができます。

bool FMyComponentVisualizer::HandleInputKey(FEditorViewportClient* ViewportClient, FViewport* Viewport, FKey Key, EInputEvent Event)
{
	if (Key == EKeys::Delete)
	{
		// 削除処理
	}
}


7. コンテキストメニューの追加

Component Visualizerではコンテキストメニューの仕組みも用意されています。
点を右クリックした時にPoints変数に要素を追加するメニューを追加したいと思います。

コマンド用クラスの用意

.cppファイルの頭でコマンド用クラスを定義します。

#define LOCTEXT_NAMESPACE "FMyVisualizerCommands"
class FMyVisualizerCommands : public TCommands<FMyVisualizerCommands>
{
public:
	FMyVisualizerCommands() : TCommands<FMyVisualizerCommands>
	(
		"MyVisualizerCommands",
		LOCTEXT("MyVisualizerCommands", "My Component Visualizer"),
		NAME_None,
		FAppStyle::GetAppStyleSetName()
	){}

	virtual void RegisterCommands() override
	{
		UI_COMMAND(AddPointCommand, "Add Point", "Add the point.", EUserInterfaceActionType::Button, FInputGesture());
	}
	
	TSharedPtr<FUICommandInfo> AddPointCommand;
};
#undef LOCTEXT_NAMESPACE

コマンドクラスはComponent Visualizer固有のクラスではなく、Slateを書いたことがある方は馴染みがあるかと思います。
クラスの定義のみですので特別なことはしていませんが、UI_COMMANDマクロの第四引数でEUserInterfaceActionTypeを変えることでチェックボックスラジオボタンにすることができます。今回はベーシックなボタンにしておきます。

次にFMyComponentVisualizerにコンストラクタや関数を追加します。

class FMyComponentVisualizer : public FComponentVisualizer
{
public:
	FMyComponentVisualizer();	// 追加
	virtual ~FMyComponentVisualizer();	// 追加
	
	//~ Begin FComponentVisualizer Interface
	virtual void OnRegister() override;	// 追加
	virtual TSharedPtr<SWidget> GenerateContextMenu() const override;	// 追加
	virtual void DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) override;
	virtual void DrawVisualizationHUD(const UActorComponent* Component, const FViewport* Viewport, const FSceneView* View, FCanvas* Canvas) override;
	virtual bool VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) override;
	virtual bool GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const override;
	virtual bool HandleInputDelta(FEditorViewportClient* ViewportClient, FViewport* Viewport, FVector& DeltaTranslate, FRotator& DeltaRotate, FVector& DeltaScale) override;
	virtual UActorComponent* GetEditedComponent() const override;
	//~ End FComponentVisualizer Interface

	void OnAddPoint();	// 追加:ボタン押下時に実行される関数

	TSharedPtr<FUICommandList> MyActions;	// 追加
	
	TObjectPtr<UMyActorComponent> CurrentComponent;
	int32 SelectPointIndex;
};

初期化処理やコマンドの登録などを行います。

FMyComponentVisualizer::FMyComponentVisualizer()
	: FComponentVisualizer()
	, SelectPointIndex(INDEX_NONE)
{
	FMyVisualizerCommands::Register();
	MyActions = MakeShareable(new FUICommandList);
}

FMyComponentVisualizer::~FMyComponentVisualizer()
{
	FMyVisualizerCommands::Unregister();
}

void FMyComponentVisualizer::OnRegister()
{
	const auto& Commands = FMyVisualizerCommands::Get();

	MyActions->MapAction(
		Commands.AddPointCommand,
		FExecuteAction::CreateSP(this, &FMyComponentVisualizer::OnAddPoint),
		FCanExecuteAction());
}

コンテキストメニューからポイントの追加

GenerateContextMenu関数でメニューの登録を行い、OnAddPoint関数では選択中の点の後に要素を追加するようにしました。

TSharedPtr<SWidget> FMyComponentVisualizer::GenerateContextMenu() const
{
	FMenuBuilder builder(true, MyActions);
	{
		builder.BeginSection("My Actions");
		{
			builder.AddMenuEntry(FMyVisualizerCommands::Get().AddPointCommand);
		}
		builder.EndSection();
	}
	
	return builder.MakeWidget();
}

void FMyComponentVisualizer::OnAddPoint()
{
	// 選択中の点の後に要素を追加
	CurrentComponent->Points.Insert(FVector::Zero(), SelectPointIndex+1);
}

右クリックのコンテキストメニューから要素を追加できるようになりました。



まとめ

いかがでしたでしょうか?
Component VisualizerはC++での実装が必須で場合によってはSlateも触るため実装難易度は高いかと思いますが、使いこなせれば強力なツールとなりうると思います。

最後に本記事で使用したコード全体を記述しておきます。

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "ComponentVisualizer.h"
#include "MyActorComponent.generated.h"

class UMyActorComponent;

struct HMyComponentVisProxy : public HComponentVisProxy
{
	DECLARE_HIT_PROXY();
	
	HMyComponentVisProxy(const UActorComponent* InComponent, int32 InPointIndex)
	: HComponentVisProxy(InComponent, HPP_Wireframe)
	, PointIndex(InPointIndex)
	{
	}
	
	int32 PointIndex;
};

class FMyComponentVisualizer : public FComponentVisualizer
{
public:
	FMyComponentVisualizer();
	virtual ~FMyComponentVisualizer();
	
	//~ Begin FComponentVisualizer Interface
	virtual bool ShouldShowForSelectedSubcomponents(const UActorComponent* Component) override;
	virtual void OnRegister() override;
	virtual TSharedPtr<SWidget> GenerateContextMenu() const override;
	virtual void DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI) override;
	virtual void DrawVisualizationHUD(const UActorComponent* Component, const FViewport* Viewport, const FSceneView* View, FCanvas* Canvas) override;
	virtual bool VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click) override;
	virtual bool GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const override;
	virtual bool HandleInputDelta(FEditorViewportClient* ViewportClient, FViewport* Viewport, FVector& DeltaTranslate, FRotator& DeltaRotate, FVector& DeltaScale) override;
	virtual UActorComponent* GetEditedComponent() const override;
	//~ End FComponentVisualizer Interface

	void OnAddPoint();

	TSharedPtr<FUICommandList> MyActions;
	
	TObjectPtr<UMyActorComponent> CurrentComponent;
	int32 SelectPointIndex;
};

UCLASS(ClassGroup = Custom, meta = (BlueprintSpawnableComponent))
class MYCOMPONENTVISUALIZER_API UMyActorComponent : public UActorComponent
{
	GENERATED_BODY()

public:
	UMyActorComponent();

protected:
	virtual void BeginPlay() override;

public:
	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;

public:
	UPROPERTY(EditInstanceOnly)
	TArray<FVector> Points;
};
#include "MyActorComponent.h"
#include "CanvasTypes.h"

IMPLEMENT_HIT_PROXY(HMyComponentVisProxy, HComponentVisProxy);

#define LOCTEXT_NAMESPACE "FMyVisualizerCommands"
class FMyVisualizerCommands : public TCommands<FMyVisualizerCommands>
{
public:
	FMyVisualizerCommands() : TCommands<FMyVisualizerCommands>
	(
		"MyVisualizerCommands",
		LOCTEXT("MyVisualizerCommands", "My Component Visualizer"),
		NAME_None,
		FAppStyle::GetAppStyleSetName()
	){}

	virtual void RegisterCommands() override
	{
		UI_COMMAND(AddPointCommand, "Add Point", "Add the point.", EUserInterfaceActionType::Button, FInputGesture());
	}
	
	TSharedPtr<FUICommandInfo> AddPointCommand;
};
#undef LOCTEXT_NAMESPACE

FMyComponentVisualizer::FMyComponentVisualizer()
	: FComponentVisualizer()
	, SelectPointIndex(INDEX_NONE)
{
	FMyVisualizerCommands::Register();
	MyActions = MakeShareable(new FUICommandList);
}

FMyComponentVisualizer::~FMyComponentVisualizer()
{
	FMyVisualizerCommands::Unregister();
}

bool FMyComponentVisualizer::ShouldShowForSelectedSubcomponents(const UActorComponent* Component)
{
	return false;
}

void FMyComponentVisualizer::OnRegister()
{
	const auto& Commands = FMyVisualizerCommands::Get();

	MyActions->MapAction(
		Commands.AddPointCommand,
		FExecuteAction::CreateSP(this, &FMyComponentVisualizer::OnAddPoint),
		FCanExecuteAction());
}

TSharedPtr<SWidget> FMyComponentVisualizer::GenerateContextMenu() const
{
	FMenuBuilder builder(true, MyActions);
	{
		builder.BeginSection("My Actions");
		{
			builder.AddMenuEntry(FMyVisualizerCommands::Get().AddPointCommand);
		}
		builder.EndSection();
	}
	
	return builder.MakeWidget();
}

void FMyComponentVisualizer::DrawVisualization(const UActorComponent* Component, const FSceneView* View, FPrimitiveDrawInterface* PDI)
{
	if (const UMyActorComponent* myComp = Cast<const UMyActorComponent>(Component))
	{
		const int32 n = myComp->Points.Num();
		
		for (int32 i = 0; i < n; i++)
		{
			// 線の描画
			if (i < n-1)
			{
				PDI->DrawLine(myComp->Points[i], myComp->Points[i+1], FColor::Cyan, SDPG_World, 2.f, 0.f, true);
			}
			
			// 点の描画
			PDI->SetHitProxy(new HMyComponentVisProxy(Component, i));
			PDI->DrawPoint(myComp->Points[i], FColor::Red, 15.f, SDPG_World);
			PDI->SetHitProxy(NULL);
		}
	}
}

void FMyComponentVisualizer::DrawVisualizationHUD(const UActorComponent* Component, const FViewport* Viewport, const FSceneView* View, FCanvas* Canvas)
{
	if (const UMyActorComponent* myComp = Cast<const UMyActorComponent>(Component))
	{
		// スクリーン位置の計算
		const FIntRect rect = Canvas->GetViewRect();
		const float halfX = rect.Width() / 2.f;
		const float halfY = rect.Height() / 2.f;
		const float offsetY = 12.f;
		
		for (int32 i = 0; i < myComp->Points.Num(); i++)
		{
			// ワールド座標からスクリーン座標に変換
			const FVector pos = FVector(View->Project(myComp->Points[i]));
			const float drawX = FMath::FloorToFloat(halfX + pos.X * halfX);
			const float drawY = FMath::FloorToFloat(halfY + -1.f * pos.Y * halfY);

			// 描画
			Canvas->DrawShadowedString(drawX, (drawY + offsetY), *FString::Printf(TEXT("Points[%d]"), i), GEngine->GetLargeFont(), FColor::White, FColor::Black);
		}
	}
}

bool FMyComponentVisualizer::VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click)
{
	if (VisProxy && VisProxy->Component.IsValid())
	{
		const HMyComponentVisProxy* Proxy = (HMyComponentVisProxy*)VisProxy;
		CurrentComponent = (UMyActorComponent*)Proxy->Component.Get();	// コンポーネントの保存
		SelectPointIndex = Proxy->PointIndex;	// 現在選択中のインデックス番号の保存

		return true;
	}
	
	return false;
}

bool FMyComponentVisualizer::GetWidgetLocation(const FEditorViewportClient* ViewportClient, FVector& OutLocation) const
{
	if (CurrentComponent)
	{
		OutLocation = CurrentComponent->Points[SelectPointIndex];
		return true;
	}
	
	return false;
}

bool FMyComponentVisualizer::HandleInputDelta(FEditorViewportClient* ViewportClient, FViewport* Viewport, FVector& DeltaTranslate, FRotator& DeltalRotate, FVector& DeltaScale)
{
	if (CurrentComponent)
	{
		if (!DeltaTranslate.IsZero())
		{
			CurrentComponent->Points[SelectPointIndex] += DeltaTranslate;
		}
		return true;
	}
	
	return false;
}

UActorComponent* FMyComponentVisualizer::GetEditedComponent() const
{
	return CurrentComponent;
}

void FMyComponentVisualizer::OnAddPoint()
{
	// 選択中の点の後に要素を追加
	CurrentComponent->Points.Insert(FVector::Zero(), SelectPointIndex+1);
}

UMyActorComponent::UMyActorComponent()
{
	PrimaryComponentTick.bCanEverTick = true;
}

void UMyActorComponent::BeginPlay()
{
	Super::BeginPlay();
}

void UMyActorComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
}