Coffee Shop Game

Project Overview
This is a side project that me and some friend started on while we're still studying. The game is a coffee shop simulation with a mysterious touch to it. Our player, being the owner, have to serve the guest, and improve the coffee shop with better furniture and equipment. While serving more guests, the player also learns more about a murder that once took place in the shop.
My Contributions
For this project, I main worked on two system, the first is a placing system with a placeable editor that allows developers to adjust the size, mesh, offsets, and the occupying space in a 3D world. The second system is a road system with custom functionalities to connect two main road together via a sub-road.
Role
System & Tools Programmer
Duration
Ongoing
Team Size
5
Genre
Simulation, Management
Fully functional placing system with a preview of the object, and preview of tiles that the object will occupy when placed. The system also work for online multiplayer allowing client to also preview, and move objects to different location.
void UBuildingSubsystem::SetupGrid(FVector2D InCellSize, FIntVector2 InGridSize)
{
	CellSize = InCellSize;
	GridSize = InGridSize;
	Tiles.Init(true, GridSize.X * GridSize.Y);

	OnSettingChangedDelegate.Broadcast(CellSize, GridSize);
}

void UBuildingSubsystem::SetTileOnPosition(FIntVector2 TilePosition, bool NewState)
{
	if (!IsPositionValid(TilePosition)) return;

	int Index = TilePosition.Y * GridSize.X + TilePosition.X;

	Tiles[Index] = NewState;

	OnGridUpdatedDelegate.Broadcast(Tiles);
}

void UBuildingSubsystem::SetTileOnLocation(FVector Location, bool NewState)
{
	int X = (Location.X + (CellSize.X / 2)) / CellSize.X;
	int Y = (Location.Y + (CellSize.Y / 2)) / CellSize.Y;

	if (!IsPositionValid(FIntVector2(X, Y)))
		return;

	int Index = Y * GridSize.X + X;
	Tiles[Index] = NewState;

	OnGridUpdatedDelegate.Broadcast(Tiles);
}

bool UBuildingSubsystem::GetTile(FIntVector2 TilePosition)
{
	if (!IsPositionValid(TilePosition)) return false;

	int Index = TilePosition.Y * GridSize.X + TilePosition.X;

	return Tiles[Index];
}

bool UBuildingSubsystem::GetTileAtLocation(FVector Location)
{
	int X = (Location.X + (CellSize.X / 2)) / CellSize.X;
	int Y = (Location.Y + (CellSize.Y / 2)) / CellSize.Y;

	if (!IsPositionValid(FIntVector2(X, Y)))
		return false;

	int Index = Y * GridSize.X + X;

	return Tiles[Index];
}

FVector UBuildingSubsystem::GetClosestLocation(FVector Location)
{
	int X = (Location.X + (CellSize.X / 2)) / CellSize.X;
	int Y = (Location.Y + (CellSize.Y / 2)) / CellSize.Y;

	if (!IsPositionValid(FIntVector2(X, Y)))
		return FVector(-100);

	return FVector((X * CellSize.X), (Y * CellSize.Y), 0);
}

bool UBuildingSubsystem::GetClosestLocation(FVector Location, FVector& OutLocation)
{
	int X = (Location.X + (CellSize.X / 2)) / CellSize.X;
	int Y = (Location.Y + (CellSize.Y / 2)) / CellSize.Y;

	if (!IsPositionValid(FIntVector2(X, Y)))
		return false;

	OutLocation = FVector((X * CellSize.X), (Y * CellSize.Y), 0);
	return true;
}

TArray<FVector2D> UBuildingSubsystem::GetTileLocations()
{
	TArray<FVector2D> ReturnList;

	for (int Y = 0; Y < GridSize.Y; Y++)
	{
		for (int X = 0; X < GridSize.X; X++)
		{
			ReturnList.Add(FVector2D(CellSize.X * X, CellSize.Y * Y));
		}
	}

	return ReturnList;
}

void UBuildingSubsystem::SetTilesWithDataAsset(FVector Location, UFurnitureData* DataAsset, bool IsAvailable)
{
	FIntVector2 Origin((Location.X + (CellSize.X / 2)) / CellSize.X, (Location.Y + (CellSize.Y / 2)) / CellSize.Y);
	TArray<int> Indices;

	for (auto& Offset : DataAsset->Blocks)
	{
		FIntVector2 Position = Origin + Offset;
		if (!IsPositionValid(Position))
			return;

		Indices.Add(Position.Y * GridSize.X + Position.X);
	}

	for (auto& Index : Indices)
	{
		Tiles[Index] = IsAvailable;
	}

	OnGridUpdatedDelegate.Broadcast(Tiles);
}

bool UBuildingSubsystem::CanPlaceFurniture(FVector Location, UFurnitureData* DataAsset)
{
	FIntVector2 Origin((Location.X + (CellSize.X / 2)) / CellSize.X, (Location.Y + (CellSize.Y / 2)) / CellSize.Y);

	for (auto& Offset : DataAsset->Blocks)
	{
		FIntVector2 Position = Origin + Offset;
		if (!IsPositionValid(Position) || !GetTile(Position))
			return false;
	}

	return true;
}

FVector2D UBuildingSubsystem::GetCellSize()
{
	return CellSize;
}

FIntVector2 UBuildingSubsystem::GetGridSize()
{
	return GridSize;
}

bool UBuildingSubsystem::IsPositionValid(FIntVector2 TilePosition)
{
	if (TilePosition.X < 0 || TilePosition.X >= GridSize.X ||
		TilePosition.Y < 0 || TilePosition.Y >= GridSize.Y)
		return false;

	return true;
}
void UBuildingComponent::BeginPlay()
{
	Super::BeginPlay();

	// ...
	Player = Cast<APawn>(GetOwner());
	if (Player) Controller = Player->GetController();

	TraceSubsystem = GetWorld()->GetSubsystem<ULookTraceSubsystem>();

	BuildingSubsystem = GetWorld()->GetSubsystem<UBuildingSubsystem>();

	if (!Player->IsLocallyControlled())
		return;

	PreviewActor = GetWorld()->SpawnActor<APreviewActor>(PreviewActorClass);

	GridVisualizer = Cast<AGridVisualizer>(UGameplayStatics::GetActorOfClass(GetWorld(), AGridVisualizer::StaticClass()));
}


// Called every frame
void UBuildingComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

	if (!Furniture || !Player->IsLocallyControlled())
		return;

	FVector TraceHitLocation = TraceSubsystem->GetLocationFromLineTrace(Controller);

	if (!BuildingSubsystem)
	{
		PlaceLocation = TraceHitLocation;
		bOnGrid = false;
	}

	if (BuildingSubsystem->GetClosestLocation(TraceHitLocation, PlaceLocation))
	{
		PlaceLocation.Z = TraceHitLocation.Z;
		bOnGrid = true;
	}
	else
	{
		PlaceLocation = TraceHitLocation;
		bOnGrid = false;
	}

	PreviewActor->SetActorLocation(PlaceLocation + Furniture->GetData()->OffsetFromOrigin);
	bool CanPlace = BuildingSubsystem->CanPlaceFurniture(PlaceLocation, Furniture->GetData());
	PreviewActor->SetCanPlacePreview(CanPlace);
	GridVisualizer->UpdatePlacableStatus(CanPlace);

	if (bOnGrid)
		GridVisualizer->UpdatePreviewMeshLocation(PlaceLocation);
}

void UBuildingComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);

	DOREPLIFETIME_CONDITION(UBuildingComponent, Furniture, COND_OwnerOnly);
}

void UBuildingComponent::SetBuildingPreviewActor(AFurniture* InPreviewActor)
{
	BuildingSubsystem->SetTilesWithDataAsset(InPreviewActor->GetActorLocation() - InPreviewActor->GetData()->OffsetFromOrigin, InPreviewActor->GetData(), true);
	Furniture = InPreviewActor;

	if (GetOwner()->HasAuthority() && Player->IsLocallyControlled())
		SetPreviewMesh();
}

void UBuildingComponent::SetPreviewMesh()
{
	if (!Player->IsLocallyControlled())
		return;

	if (!Furniture)
	{
		PreviewActor->SetActorHiddenInGame(true);
		GridVisualizer->EnablePreview(false);
	}
	else
	{
		PreviewActor->SetActorHiddenInGame(false);
		PreviewActor->SetMesh(Furniture->GetMeshComponent()->GetStaticMesh());
		PreviewActor->SetActorScale3D(Furniture->GetData()->Scale);
		GridVisualizer->UpdatePreviewMesh(Furniture->GetData());
		GridVisualizer->EnablePreview(true);
	}
}

bool UBuildingComponent::ResetBuildingPreview(FVector LookAtLocation, FVector& OutPlaceLocation)
{
	bOnGrid = false;
	if (BuildingSubsystem->GetClosestLocation(LookAtLocation, PlaceLocation))
	{
		PlaceLocation.Z = LookAtLocation.Z;
		bOnGrid = true;
	}

	if (PlaceLocation == FVector::ZeroVector || !bOnGrid)
		return false;

	if (!BuildingSubsystem->CanPlaceFurniture(PlaceLocation, Furniture->GetData()))
		return false;

	BuildingSubsystem->SetTilesWithDataAsset(PlaceLocation, Furniture->GetData(), false);
	FVector Offset = Furniture->GetData()->OffsetFromOrigin;
	Furniture = nullptr;
	if (GetOwner()->HasAuthority() && Player->IsLocallyControlled())
		SetPreviewMesh();

	OutPlaceLocation = PlaceLocation + Offset;
	return true;
}

AFurniture* UBuildingComponent::GetBuildingPreviewActor()
{
	return Furniture;
}
APreviewActor::APreviewActor()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

	MeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MeshComp"));
}

void APreviewActor::SetMesh(UStaticMesh* Mesh)
{
	MeshComp->SetStaticMesh(Mesh);
}

void APreviewActor::SetCanPlacePreview(bool CanPlace)
{
	if (CanPlace)
		MeshComp->SetVectorParameterValueOnMaterials("PreviewColor", FVector(0, 1, 0));
	else
		MeshComp->SetVectorParameterValueOnMaterials("PreviewColor", FVector(1, 0, 0));
}
AGridVisualizer::AGridVisualizer()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;
	
	RootComponent = CreateDefaultSubobject<USceneComponent>("Parent");

	LineMesh = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("LineMesh"));
	LineMesh->SetupAttachment(RootComponent);

	CellMesh = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("CellMesh"));
	CellMesh->SetupAttachment(RootComponent);

	CellPreviewMesh = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("PreviewMesh"));
	CellPreviewMesh->SetupAttachment(RootComponent);
}

// Called when the game starts or when spawned
void AGridVisualizer::BeginPlay()
{
	Super::BeginPlay();

	LineMesh->SetMaterial(0, LineMaterial);
	CellMesh->SetMaterial(0, CellMaterial);
	CellPreviewMesh->SetMaterial(0, PreviewMaterial);

	UBuildingSubsystem* Subsystem = GetWorld()->GetSubsystem<UBuildingSubsystem>();
	if (!Subsystem)
		return;

	Subsystem->OnSettingChangedDelegate.AddDynamic(this, &AGridVisualizer::UpdateMesh);
	Subsystem->OnGridUpdatedDelegate.AddDynamic(this, &AGridVisualizer::UpdateCellMesh);
}

void AGridVisualizer::UpdateMesh(FVector2D NewCellSize, FIntVector2 NewGridSize)
{
	CellSize = NewCellSize;
	GridSize = NewGridSize;

	TArray<FVector> LineVertices;
	TArray<int> LineTriangles;

	// Generate line mesh
	// Row lines
	for (int Row = 0; Row < GridSize.Y + 1; Row++)
	{
		LineTriangles.Add(LineVertices.Num() + 0);
		LineTriangles.Add(LineVertices.Num() + 3);
		LineTriangles.Add(LineVertices.Num() + 2);
		LineTriangles.Add(LineVertices.Num() + 2);
		LineTriangles.Add(LineVertices.Num() + 1);
		LineTriangles.Add(LineVertices.Num() + 0);

		LineVertices.Add(FVector(
			-LineThickness / 2 - (CellSize.X / 2), // X
			CellSize.Y * Row - (LineThickness / 2) - (CellSize.Y / 2), // Y
			0));				 // Z
		LineVertices.Add(FVector(
			CellSize.X * GridSize.X + (LineThickness / 2) - (CellSize.X / 2),
			CellSize.Y * Row - (LineThickness / 2) - (CellSize.Y / 2),
			0));
		LineVertices.Add(FVector(
			CellSize.X * GridSize.X + (LineThickness / 2) - (CellSize.X / 2),
			CellSize.Y * Row + (LineThickness / 2) - (CellSize.Y / 2),
			0));
		LineVertices.Add(FVector(
			-LineThickness / 2 - (CellSize.X / 2),
			CellSize.Y * Row + (LineThickness / 2) - (CellSize.Y / 2),
			0));
	}

	// Column lines
	for (int Col = 0; Col < GridSize.X + 1; Col++)
	{
		LineTriangles.Add(LineVertices.Num() + 0);
		LineTriangles.Add(LineVertices.Num() + 3);
		LineTriangles.Add(LineVertices.Num() + 2);
		LineTriangles.Add(LineVertices.Num() + 2);
		LineTriangles.Add(LineVertices.Num() + 1);
		LineTriangles.Add(LineVertices.Num() + 0);

		LineVertices.Add(FVector(
			CellSize.X * Col - (LineThickness / 2) - (CellSize.X / 2),
			-LineThickness / 2 - (CellSize.Y / 2),
			0));				 
		LineVertices.Add(FVector(
			CellSize.X * Col + (LineThickness / 2) - (CellSize.X / 2),
			-LineThickness / 2 - (CellSize.Y / 2),
			0));
		LineVertices.Add(FVector(
			CellSize.X * Col + (LineThickness / 2) - (CellSize.X / 2),
			CellSize.Y * GridSize.Y + (LineThickness / 2) - (CellSize.Y / 2),
			0));
		LineVertices.Add(FVector(
			CellSize.X * Col - (LineThickness / 2) - (CellSize.X / 2),
			CellSize.Y * GridSize.Y + (LineThickness / 2) - (CellSize.Y / 2),
			0));
	}

	LineMesh->CreateMeshSection(0, LineVertices, LineTriangles, TArray<FVector>(), TArray<FVector2D>(), TArray<FColor>(), TArray<FProcMeshTangent>(), false);
}

void AGridVisualizer::EnablePreview(bool Enable)
{
	CellPreviewMesh->SetVisibility(Enable);
	CellMesh->SetVisibility(Enable);
	LineMesh->SetVisibility(Enable);
}

void AGridVisualizer::UpdatePreviewMesh(UFurnitureData* FurnitureData)
{
	TArray<FVector> PreviewVertices;
	TArray<int> PreviewTriangles;

	float UseSpacing = FMath::Max(LineThickness, Spacing);

	for (auto& Offset : FurnitureData->Blocks)
	{
		PreviewTriangles.Add(PreviewVertices.Num() + 0);
		PreviewTriangles.Add(PreviewVertices.Num() + 3);
		PreviewTriangles.Add(PreviewVertices.Num() + 2);
		PreviewTriangles.Add(PreviewVertices.Num() + 2);
		PreviewTriangles.Add(PreviewVertices.Num() + 1);
		PreviewTriangles.Add(PreviewVertices.Num() + 0);

		PreviewVertices.Add(FVector(
			(Offset.X * CellSize.X) - (CellSize.X / 2) + (UseSpacing / 2),
			(Offset.Y * CellSize.Y) - (CellSize.Y / 2) + (UseSpacing / 2),
			0));
		PreviewVertices.Add(FVector(
			(Offset.X * CellSize.X) + (CellSize.X / 2) - (UseSpacing / 2),
			(Offset.Y * CellSize.Y) - (CellSize.Y / 2) + (UseSpacing / 2),
			0));
		PreviewVertices.Add(FVector(
			(Offset.X * CellSize.X) + (CellSize.X / 2) - (UseSpacing / 2),
			(Offset.Y * CellSize.Y) + (CellSize.Y / 2) - (UseSpacing / 2),
			0));
		PreviewVertices.Add(FVector(
			(Offset.X * CellSize.X) - (CellSize.X / 2) + (UseSpacing / 2),
			(Offset.Y * CellSize.Y) + (CellSize.Y / 2) - (UseSpacing / 2),
			0));
	}

	CellPreviewMesh->CreateMeshSection(0, PreviewVertices, PreviewTriangles, TArray<FVector>(), TArray<FVector2D>(), TArray<FColor>(), TArray<FProcMeshTangent>(), false);
}

void AGridVisualizer::UpdatePreviewMeshLocation(FVector Location)
{
	CellPreviewMesh->SetWorldLocation(FVector(Location.X, Location.Y, GetActorLocation().Z + 0.1f));
}

void AGridVisualizer::UpdatePlacableStatus(bool CanPlace)
{
	CellPreviewMesh->SetVectorParameterValueOnMaterials("PreviewColor", CanPlace ? FVector(0, 1, 0) : FVector(1, 0, 0));
}

void AGridVisualizer::UpdateCellMesh(const TArray<bool>& Tiles)
{
	TArray<FVector> CellVertices;
	TArray<int> CellTriangles;

	float UseSpacing = FMath::Max(LineThickness, Spacing);

	// Generate cell mesh
	for (int X = 0; X < GridSize.X; X++)
	{
		for (int Y = 0; Y < GridSize.Y; Y++)
		{
			if (Tiles[Y * GridSize.X + X])
				continue;

			CellTriangles.Add(CellVertices.Num() + 0);
			CellTriangles.Add(CellVertices.Num() + 3);
			CellTriangles.Add(CellVertices.Num() + 2);
			CellTriangles.Add(CellVertices.Num() + 2);
			CellTriangles.Add(CellVertices.Num() + 1);
			CellTriangles.Add(CellVertices.Num() + 0);

			CellVertices.Add(FVector(
				(X * CellSize.X) - (CellSize.X / 2) + (UseSpacing / 2),
				(Y * CellSize.Y) - (CellSize.Y / 2) + (UseSpacing / 2),
				0));
			CellVertices.Add(FVector(
				(X * CellSize.X) + (CellSize.X / 2) - (UseSpacing / 2),
				(Y * CellSize.Y) - (CellSize.Y / 2) + (UseSpacing / 2),
				0));
			CellVertices.Add(FVector(
				(X * CellSize.X) + (CellSize.X / 2) - (UseSpacing / 2),
				(Y * CellSize.Y) + (CellSize.Y / 2) - (UseSpacing / 2),
				0));
			CellVertices.Add(FVector(
				(X * CellSize.X) - (CellSize.X / 2) + (UseSpacing / 2),
				(Y * CellSize.Y) + (CellSize.Y / 2) - (UseSpacing / 2),
				0));
		}
	}

	CellMesh->CreateMeshSection(0, CellVertices, CellTriangles, TArray<FVector>(), TArray<FVector2D>(), TArray<FColor>(), TArray<FProcMeshTangent>(), false);
}
bool AFurniture::PickUp(const FInteractionContext& Context)
{
	UHolderComponent* HolderComponent = Context.HolderComponent;
	if (!HolderComponent) return false;
	UBuildingComponent* BuildingComponent = Context.BuildingComponent;
	if (!BuildingComponent) return false;

	UHeldItem* HolderComponentHeldItem = HolderComponent->GetHeldItem();

	if (HolderComponentHeldItem) //pick up
		return false;

	BuildingComponent->SetBuildingPreviewActor(this);
	return Super::PickUp(Context);
}

bool AFurniture::Drop(const FInteractionContext& Context)
{
	UHolderComponent* HolderComponent = Context.HolderComponent;
	if (!HolderComponent) return false;
	UBuildingComponent* BuildingComponent = Context.BuildingComponent;
	if (!BuildingComponent) return false;

	UHeldItem* HolderComponentHeldItem = HolderComponent->GetHeldItem();
	FVector Location;

	if (BuildingComponent->ResetBuildingPreview(Context.LookedAtLocation, Location))
	{
		FInteractionContext NewContext(Context);
		NewContext.LookedAtLocation = Location;
		if (!Super::Drop(NewContext))
			return false;

		Multicast_ResetRotation();
		return true;
	}

	return false;
}

void AFurniture::BeginPlay()
{
	Super::BeginPlay();

	if (!HasAuthority())
		return;

	MeshComp->SetStaticMesh(Data->Mesh);
	SetActorScale3D(Data->Scale);
}

UFurnitureData* AFurniture::GetData()
{
	return Data;
}


void AFurniture::Multicast_ResetRotation_Implementation()
{
	SetActorRotation(FRotator::ZeroRotator);
}
The placeable editor have two main feature: The first is the ability to adjust the mesh, the position, size and rotation of the mesh, with a preview of the object against the grid. The second feature is the ability to select what tile does the building occupies. All the changes are also reflected in game when the player tries to place the object.
AEditorGridVisualizer::AEditorGridVisualizer() : AGridVisualizer()
{
	PivotMesh = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("PivotMesh"));
	PivotMesh->SetupAttachment(RootComponent);
}

void AEditorGridVisualizer::BeginPlay()
{
	Super::BeginPlay();

	CellSize = UGridSettings::GetGridSettings()->CellSize;

	PivotMesh->SetMaterial(0, PivotMaterial);

	TArray<FVector> CellVertices;
	TArray<int> CellTriangles;

	float UseSpacing = FMath::Max(LineThickness, Spacing);

	CellTriangles.Add(CellVertices.Num() + 0);
	CellTriangles.Add(CellVertices.Num() + 3);
	CellTriangles.Add(CellVertices.Num() + 2);
	CellTriangles.Add(CellVertices.Num() + 2);
	CellTriangles.Add(CellVertices.Num() + 1);
	CellTriangles.Add(CellVertices.Num() + 0);

	CellVertices.Add(FVector(
		-(CellSize.X / 2) + (UseSpacing / 2),
		-(CellSize.Y / 2) + (UseSpacing / 2),
		0));
	CellVertices.Add(FVector(
		+(CellSize.X / 2) - (UseSpacing / 2),
		-(CellSize.Y / 2) + (UseSpacing / 2),
		0));
	CellVertices.Add(FVector(
		+(CellSize.X / 2) - (UseSpacing / 2),
		+(CellSize.Y / 2) - (UseSpacing / 2),
		0));
	CellVertices.Add(FVector(
		-(CellSize.X / 2) + (UseSpacing / 2),
		+(CellSize.Y / 2) - (UseSpacing / 2),
		0));

	PivotMesh->CreateMeshSection(0, CellVertices, CellTriangles, TArray<FVector>(), TArray<FVector2D>(), TArray<FColor>(), TArray<FProcMeshTangent>(), false);
}

void AEditorGridVisualizer::SetupGrid(FVector InOffset)
{
	Offset = InOffset;

	SetPivotLocation(Pivot);
}

int AEditorGridVisualizer::GetTileIndexAtLocation(FVector Location, FVector2D& CellPosition)
{
	int X = (Location.X + (CellSize.X / 2)) / CellSize.X;
	int Y = (Location.Y + (CellSize.Y / 2)) / CellSize.Y;

	if (X < 0 || X >= GridSize.X ||
		Y < 0 || Y >= GridSize.Y)
		return -1;

	CellPosition = FVector2D(X, Y);

	int Index = Y * GridSize.X + X;
	return Index;
}

int AEditorGridVisualizer::GetTileIndexAtPosition(FVector Location)
{
	return 	Location.Y * GridSize.X + Location.X;;
}

void AEditorGridVisualizer::SetPivotLocation(FVector2D Position)
{
	Pivot = Position;

	FVector Location = FVector(Position.X * CellSize.X, Position.Y * CellSize.Y, 0.0f) + Offset;

	PivotMesh->SetWorldLocation(Location);
}

FVector AEditorGridVisualizer::GetPivotLocation()
{
	return PivotMesh->GetComponentLocation();
}

TArray<FIntVector2> AEditorGridVisualizer::GetBlockData(TArray<bool> Blocks)
{
	TArray<FIntVector2> ReturnList;

	for (int i = 0; i < Blocks.Num(); i++)
	{
		if (Blocks[i])
			ReturnList.Add(FIntVector2(int(i % (int)GridSize.X), int(i / GridSize.X)) - FIntVector2(Pivot.X, Pivot.Y));
	}

	return ReturnList;
}

void AEditorGridVisualizer::UpdateCellMesh(const TArray<bool>& Tiles)
{
	TArray<FVector> CellVertices;
	TArray<int> CellTriangles;

	TArray<FVector> PreviewCellVertices;
	TArray<int> PreviewCellTriangles;

	float UseSpacing = FMath::Max(LineThickness, Spacing);

	// Generate cell mesh
	for (int X = 0; X < GridSize.X; X++)
	{
		for (int Y = 0; Y < GridSize.Y; Y++)
		{
			if (Pivot == FVector2D(X, Y))
				continue;

			if (!Tiles[Y * GridSize.X + X])
			{
				CellTriangles.Add(CellVertices.Num() + 0);
				CellTriangles.Add(CellVertices.Num() + 3);
				CellTriangles.Add(CellVertices.Num() + 2);
				CellTriangles.Add(CellVertices.Num() + 2);
				CellTriangles.Add(CellVertices.Num() + 1);
				CellTriangles.Add(CellVertices.Num() + 0);

				CellVertices.Add(FVector(
					(X * CellSize.X) - (CellSize.X / 2) + (UseSpacing / 2),
					(Y * CellSize.Y) - (CellSize.Y / 2) + (UseSpacing / 2),
					0));
				CellVertices.Add(FVector(
					(X * CellSize.X) + (CellSize.X / 2) - (UseSpacing / 2),
					(Y * CellSize.Y) - (CellSize.Y / 2) + (UseSpacing / 2),
					0));
				CellVertices.Add(FVector(
					(X * CellSize.X) + (CellSize.X / 2) - (UseSpacing / 2),
					(Y * CellSize.Y) + (CellSize.Y / 2) - (UseSpacing / 2),
					0));
				CellVertices.Add(FVector(
					(X * CellSize.X) - (CellSize.X / 2) + (UseSpacing / 2),
					(Y * CellSize.Y) + (CellSize.Y / 2) - (UseSpacing / 2),
					0));
			}
			else
			{
				PreviewCellTriangles.Add(PreviewCellVertices.Num() + 0);
				PreviewCellTriangles.Add(PreviewCellVertices.Num() + 3);
				PreviewCellTriangles.Add(PreviewCellVertices.Num() + 2);
				PreviewCellTriangles.Add(PreviewCellVertices.Num() + 2);
				PreviewCellTriangles.Add(PreviewCellVertices.Num() + 1);
				PreviewCellTriangles.Add(PreviewCellVertices.Num() + 0);

				PreviewCellVertices.Add(FVector(
					(X * CellSize.X) - (CellSize.X / 2) + (UseSpacing / 2),
					(Y * CellSize.Y) - (CellSize.Y / 2) + (UseSpacing / 2),
					0));
				PreviewCellVertices.Add(FVector(
					(X * CellSize.X) + (CellSize.X / 2) - (UseSpacing / 2),
					(Y * CellSize.Y) - (CellSize.Y / 2) + (UseSpacing / 2),
					0));
				PreviewCellVertices.Add(FVector(
					(X * CellSize.X) + (CellSize.X / 2) - (UseSpacing / 2),
					(Y * CellSize.Y) + (CellSize.Y / 2) - (UseSpacing / 2),
					0));
				PreviewCellVertices.Add(FVector(
					(X * CellSize.X) - (CellSize.X / 2) + (UseSpacing / 2),
					(Y * CellSize.Y) + (CellSize.Y / 2) - (UseSpacing / 2),
					0));
			}
		}
	}

	CellMesh->CreateMeshSection(0, CellVertices, CellTriangles, TArray<FVector>(), TArray<FVector2D>(), TArray<FColor>(), TArray<FProcMeshTangent>(), true);
	CellPreviewMesh->CreateMeshSection(0, PreviewCellVertices, PreviewCellTriangles, TArray<FVector>(), TArray<FVector2D>(), TArray<FColor>(), TArray<FProcMeshTangent>(), true);
}
FVector UExtendedViewportWidget::GetCursorWorldDirection(FVector2D CursorPosition)
{
	FVector WorldPosition(0);
    FVector WorldDirection(0);

	FVector2f ViewportPosition = ViewportWidget->Viewport->GetCachedGeometry().GetAbsolutePosition();

	FSceneView::DeprojectScreenToWorld(CursorPosition - (FVector2D)ViewportPosition, FIntRect(0, 0, ViewportWidget->Viewport->GetSizeXY().X, ViewportWidget->Viewport->GetSizeXY().Y), GetViewProjectionMatrix().Inverse(), WorldPosition, WorldDirection);

    return WorldDirection;
}
A road planning system with features to add main roads, and connect two point on main roads together with sub-road. Each road also stores information about all connecting road, and have functions to get connections at different points on the road. The system is still under development, and contains some bugs.
class FTrackSplineComponentVisualizerCommands : public TCommands< FTrackSplineComponentVisualizerCommands>
{
public:
	FTrackSplineComponentVisualizerCommands() : TCommands< FTrackSplineComponentVisualizerCommands>
		(
			"TrackSplineComponentVisualizer",
			LOCTEXT("TrackSplineComponentVisualizer", "TrackSpline Component Visualizer"),
			NAME_None,
			FAppStyle::GetAppStyleSetName()
		)
	{
	}

	virtual void RegisterCommands() override
	{
		UI_COMMAND(AddSubSpline, "Add Subspline", "Add a subspline starting from this point. Click on another point to set the destination of subspline", EUserInterfaceActionType::Button, FInputChord());
	}

public:
	TSharedPtr<FUICommandInfo> AddSubSpline;
};
void FTrackSplineComponentVisualizer::GenerateContextMenuSections(FMenuBuilder& InMenuBuilder) const
{
	FSplineComponentVisualizer::GenerateContextMenuSections(InMenuBuilder);

	InMenuBuilder.BeginSection("TrackSpline", LOCTEXT("TrackSpline", "TrackSpline"));
	{
		USplineComponent* SplineComp = GetEditedSplineComponent();
		if (SplineComp != nullptr)
		{
			int SelectedKey = SelectionState->GetLastKeyIndexSelected();
			if (SelectedKey != INDEX_NONE && SelectionState->GetSelectedSegmentIndex() == INDEX_NONE)
			{
				check(SelectedKey >= 0);
				check(SelectedKey < SplineComp->GetNumberOfSplinePoints());
				InMenuBuilder.AddMenuEntry(FTrackSplineComponentVisualizerCommands::Get().AddSubSpline);
			}
		}
	}
	InMenuBuilder.EndSection();
}
void FTrackSplineComponentVisualizer::AddSubSpline()
{
	USplineComponent* SplineComp = Cast<UTrackSplineComponent>(GetEditedSplineComponent());
	int SelectedKey = SelectionState->GetLastKeyIndexSelected();
	if (SelectedKey != INDEX_NONE)
	{
		check(SelectedKey >= 0);
		check(SelectedKey < SplineComp->GetNumberOfSplinePoints());

		StartParent = Cast<UTrackSplineComponent>(SplineComp);
		StartPointIndex = SelectedKey;
	}

	GEditor->RedrawLevelEditingViewports(true);
}
bool FTrackSplineComponentVisualizer::VisProxyHandleClick(FEditorViewportClient* InViewportClient, HComponentVisProxy* VisProxy, const FViewportClick& Click)
{
	ResetTempModes();

	FTypedElementHandle SelectedComponentHandle = VisProxy->GetElementHandle();
	UTypedElementSelectionSet* ElementSelectionSet = (InViewportClient && InViewportClient->GetModeTools()) ? InViewportClient->GetModeTools()->GetEditorSelectionSet() : nullptr;

	if (!ElementSelectionSet->IsElementSelected(SelectedComponentHandle, FTypedElementIsSelectedOptions().SetAllowIndirect(true)))
	{
		GEditor->DeselectAllSurfaces();
		ElementSelectionSet->ClearSelection(FTypedElementSelectionOptions()
			.SetAllowHidden(true)
			.SetWarnIfLocked(true));
		ElementSelectionSet->NotifyPendingChanges();
	}

	bool bVisProxyClickHandled = false;
	UTrackSplineComponent* ClickedComponent = nullptr;

	if (VisProxy && VisProxy->Component.IsValid())
	{
		ClickedComponent = CastChecked<UTrackSplineComponent>(const_cast<UActorComponent*>(VisProxy->Component.Get()));
	}
	else
		return false;

	if (StartParent.IsValid() && StartPointIndex != INDEX_NONE)
	{
		if (FString(VisProxy->GetType()->GetName()) == TEXT("HSplineKeyProxy"))
		{
			ResetTempModes();
			if (ClickedComponent->IsA(UTrackSplineComponent::StaticClass()))
			{
				HSplineKeyProxy* KeyProxy = (HSplineKeyProxy*)VisProxy;

				int EndPointIndex = INDEX_NONE;

				if (ClickedComponent == StartParent.Get())
				{
					if (KeyProxy->KeyIndex != StartPointIndex)
					{
						EndPointIndex = KeyProxy->KeyIndex;

						bVisProxyClickHandled = true;
					}
					else
					{
						StartParent.Reset();
						StartPointIndex = INDEX_NONE;
					}
				}
				else
				{
					EndPointIndex = KeyProxy->KeyIndex;

					bVisProxyClickHandled = true;
				}

				if (bVisProxyClickHandled)
				{
					if (AActor* Owner = StartParent.Get()->GetOwner())
					{
						Owner->Modify();

						int id = Cast<APathMap>(Owner)->GetSubSplineCount();

						UTrackSplineComponent* NewSubspline = NewObject<UTrackSplineComponent>(Owner, *FString("SubSpline" + FString::FromInt(id)));

						Cast<APathMap>(Owner)->IncreaseSubSpline();

						Owner->AddInstanceComponent(NewSubspline);
						NewSubspline->RegisterComponent();
						NewSubspline->AttachToComponent(Owner->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);

						int NumOfPoints = NewSubspline->GetNumberOfSplinePoints();
						if (NumOfPoints < 2)
						{
							TArray<FSplinePoint> AdditionalPoints;
							AdditionalPoints.Init(FSplinePoint(), 2 - NumOfPoints);
							NewSubspline->AddPoints(AdditionalPoints, false);
						}

						NewSubspline->SetLocationAtSplinePoint(0, StartParent->GetLocationAtSplinePoint(StartPointIndex, ESplineCoordinateSpace::World), ESplineCoordinateSpace::World, false);
						NewSubspline->SetLocationAtSplinePoint(1, ClickedComponent->GetLocationAtSplinePoint(EndPointIndex, ESplineCoordinateSpace::World), ESplineCoordinateSpace::World, false);
						NewSubspline->UpdateSpline();

						StartParent->AddConnectedSpline(NewSubspline, StartPointIndex, 0);
						NewSubspline->AddConnectedSpline(StartParent.Get(), 0, StartPointIndex);
						ClickedComponent->AddConnectedSpline(NewSubspline, EndPointIndex, 1);
						NewSubspline->AddConnectedSpline(ClickedComponent, 1, EndPointIndex);

						StartParent.Reset();
						StartPointIndex = INDEX_NONE;
					}

					GEditor->RedrawLevelEditingViewports(true);

					return true;
				}
			}
		}
	}

	return FSplineComponentVisualizer::VisProxyHandleClick(InViewportClient, VisProxy, Click);
}
class UTrackSplineComponent;

USTRUCT()
struct FTrackConnectionData
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere)
	UTrackSplineComponent* OtherSpline;

	UPROPERTY(EditAnywhere)
	int EndPointIndex;
};
void UTrackSplineComponent::AddConnectedSpline(UTrackSplineComponent* NewSpline, int StartPointIndex, int EndPointIndex)
{
	FConnectionDataContainer& Container = ConnectedSplines.FindOrAdd(StartPointIndex);
	FTrackConnectionData NewConnection;
	NewConnection.OtherSpline = NewSpline;
	NewConnection.EndPointIndex = EndPointIndex;
	Container.Connections.Add(NewConnection);
}

void UTrackSplineComponent::RemoveConnectedSpline(UTrackSplineComponent* SplineToRemove)
{
	TArray<int> RemoveList;
	for(auto& Pair : ConnectedSplines)
	{
		FConnectionDataContainer& Container = Pair.Value;
		Container.Connections.RemoveAll([SplineToRemove](const FTrackConnectionData& Connection)
		{
			return Connection.OtherSpline == SplineToRemove;
		});

		if(Container.Connections.IsEmpty())
			RemoveList.Add(Pair.Key);
	}

	for(auto& Key : RemoveList)
	{
		ConnectedSplines.Remove(Key);
	}
}

bool UTrackSplineComponent::HaveConnectionAtPoint(int PointIndex)
{
	if(ConnectedSplines.Contains(PointIndex))
	{
		return true;
	}

	return false;
}

FConnectionDataContainer* UTrackSplineComponent::GetConnectionDataAtPoint(int PointIndex)
{
	if(ConnectedSplines.Contains(PointIndex))
	{
		return &ConnectedSplines[PointIndex];
	}

	return nullptr;
}

TRACKSYSTEM_API EForwardOrder UTrackSplineComponent::GetForwardOrder()
{
	return IndexOrderOfForwardDirection;
}

void UTrackSplineComponent::OnComponentDestroyed(bool bDestroyingHierarchy)
{
	Super::OnComponentDestroyed(bDestroyingHierarchy);

	UWorld* World = GetWorld();
	if (GExitPurge || !World || World->bIsTearingDown)
	{
		return;
	}

	for(auto& Pair : ConnectedSplines)
	{
		FConnectionDataContainer& Container = Pair.Value;
		for(FTrackConnectionData& Connection : Container.Connections)
		{
			if(Connection.OtherSpline)
			{
				Connection.OtherSpline->RemoveConnectedSpline(this);
			}
		}
	}

	ConnectedSplines.Empty();
}
APathMap::APathMap()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = false;

	SceneRoot = CreateDefaultSubobject<USceneComponent>("SceneRoot");
	SetRootComponent(SceneRoot);
}

#if WITH_EDITOR
void APathMap::AddMainSpline()
{
	UTrackSplineComponent* NewSpline = NewObject<UTrackSplineComponent>(this, *FString("MainSpline" + FString::FromInt(NumOfMainSplines)));

	AddInstanceComponent(NewSpline);
	NewSpline->RegisterComponent();
	NewSpline->AttachToComponent(GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
	NewSpline->SetIsMainSpline(true);

	NumOfMainSplines++;

	GEditor->RedrawLevelEditingViewports(true);
}
#endif

void APathMap::IncreaseSubSpline()
{
	NumOfSubSplines++;
}