Ajout du projet Depths sur Git

This commit is contained in:
2026-04-30 12:24:52 +02:00
commit a143ea22c7
6651 changed files with 77423 additions and 0 deletions
@@ -0,0 +1,20 @@
// Copyright Benoit Pelletier 2025 All Rights Reserved.
//
// This software is available under different licenses depending on the source from which it was obtained:
// - The Fab EULA (https://fab.com/eula) applies when obtained from the Fab marketplace.
// - The CeCILL-C license (https://cecill.info/licences/Licence_CeCILL-C_V1-en.html) applies when obtained from any other source.
// Please refer to the accompanying LICENSE file for further details.
#include "ProceduralDungeonEditorTool.h"
#include "EditorMode/ProceduralDungeonEdMode.h"
#include "RoomLevel.h"
#include "RoomData.h"
URoomData* FProceduralDungeonEditorTool::GetRoomData() const
{
auto Level = EdMode->GetLevel();
if (!Level.IsValid())
return nullptr;
return Level.Get()->Data;
}
@@ -0,0 +1,61 @@
// Copyright Benoit Pelletier 2023 - 2025 All Rights Reserved.
//
// This software is available under different licenses depending on the source from which it was obtained:
// - The Fab EULA (https://fab.com/eula) applies when obtained from the Fab marketplace.
// - The CeCILL-C license (https://cecill.info/licences/Licence_CeCILL-C_V1-en.html) applies when obtained from any other source.
// Please refer to the accompanying LICENSE file for further details.
#pragma once
#include "CoreMinimal.h"
#include "InputCoreTypes.h"
#include "UObject/GCObject.h"
#include "EdMode.h"
#include "EditorUndoClient.h"
#include "ProceduralDungeonEdTypes.h"
class FProceduralDungeonEdMode;
class FProceduralDungeonEditorTool : public FGCObject, public FSelfRegisteringEditorUndoClient
{
public:
FProceduralDungeonEditorTool(FProceduralDungeonEdMode* InEdMode)
: EdMode(InEdMode)
{
}
virtual const TCHAR* GetToolName() = 0;
virtual FText GetDisplayName() = 0;
virtual FText GetDisplayMessage() = 0;
virtual void EnterTool() = 0;
virtual void ExitTool() = 0;
virtual void Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) {}
virtual void Tick(FEditorViewportClient* ViewportClient, float DeltaTime) {}
virtual bool MouseMove(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 MouseX, int32 MouseY) { return false; }
virtual bool HandleClick(FEditorViewportClient* InViewportClient, HHitProxy* HitProxy, const FViewportClick& Click) { return false; }
virtual bool InputKey(FEditorViewportClient* InViewportClient, FViewport* InViewport, FKey InKey, EInputEvent InEvent) { return false; }
virtual bool InputAxis(FEditorViewportClient* InViewportClient, FViewport* Viewport, int32 ControllerId, FKey Key, float Delta, float DeltaTime) { return false; }
virtual bool InputDelta(FEditorViewportClient* InViewportClient, FViewport* InViewport, FVector& InDrag, FRotator& InRot, FVector& InScale) { return false; }
virtual bool UsesTransformWidget() const { return false; }
virtual bool UsesTransformWidget(WidgetMode CheckMode) const { return false; }
virtual FVector GetWidgetLocation() const { return FVector::ZeroVector; }
virtual bool GetCursor(EMouseCursor::Type& OutCursor) const { return false; }
//~ Begin FGCObject Interface
virtual void AddReferencedObjects(FReferenceCollector& Collector) override {}
virtual FString GetReferencerName() const override
{
return TEXT("FProceduralDungeonEditorTool");
}
//~ End FGCObject Interface
virtual void OnLevelChanged(const class ARoomLevel* NewLevel) {}
virtual void OnDataChanged(const class URoomData* NewData) {}
virtual void OnDataPropertiesChanged(const class URoomData* Data) {}
class URoomData* GetRoomData() const;
protected:
FProceduralDungeonEdMode* EdMode = nullptr;
};
@@ -0,0 +1,428 @@
// Copyright Benoit Pelletier 2023 - 2025 All Rights Reserved.
//
// This software is available under different licenses depending on the source from which it was obtained:
// - The Fab EULA (https://fab.com/eula) applies when obtained from the Fab marketplace.
// - The CeCILL-C license (https://cecill.info/licences/Licence_CeCILL-C_V1-en.html) applies when obtained from any other source.
// Please refer to the accompanying LICENSE file for further details.
#include "ProceduralDungeonEditorTool_Door.h"
#include "Components/BoxComponent.h"
#include "EditorMode/ProceduralDungeonEdMode.h"
#include "EditorMode/ProceduralDungeonEditorObject.h"
#include "ProceduralDungeonEdLog.h"
#include "ProceduralDungeonUtils.h"
#include "Room.h"
#include "RoomLevel.h"
#include "RoomData.h"
#include "Door.h"
bool AreDoorsOverlapping(const FDoorDef& DoorA, const FDoorDef& DoorB, const FVector RoomUnit)
{
// If not in same direction, they will never overlap.
if (DoorA.Direction != DoorB.Direction)
return false;
// If same direction and position, then will always overlap.
if (DoorA.Position == DoorB.Position)
return true;
const FBoxCenterAndExtent DoorBoundsA = DoorA.GetBounds(RoomUnit);
const FBoxCenterAndExtent DoorBoundsB = DoorB.GetBounds(RoomUnit);
return Intersect(DoorBoundsA, DoorBoundsB);
}
bool IsPositionInside(const FDoorDef& Door, const FIntVector& Position, const FVector RoomUnit)
{
// If same position, then always inside.
if (Door.Position == Position)
return true;
const FBoxCenterAndExtent DoorBounds = Door.GetBounds(RoomUnit);
const FVector LocalDoorExtent = DoorBounds.Extent / RoomUnit;
const FVector RelativePosition = (FVector(Position) - (DoorBounds.Center / RoomUnit)).GetAbs();
return RelativePosition.X <= LocalDoorExtent.X && RelativePosition.Y <= LocalDoorExtent.Y && RelativePosition.Z <= LocalDoorExtent.Z;
}
bool IsDoorValid(const URoomData* Data, const FDoorDef& Door)
{
check(IsValid(Data));
for (const FDoorDef& DoorDef : Data->Doors)
{
if (AreDoorsOverlapping(Door, DoorDef, Data->GetRoomUnit()))
return false;
}
return true;
}
void FProceduralDungeonEditorTool_Door::EnterTool()
{
DungeonEd_LogInfo("Enter Door Tool.");
UpdateCollision();
}
void FProceduralDungeonEditorTool_Door::ExitTool()
{
DungeonEd_LogInfo("Exit Door Tool.");
DestroyCollision();
CachedLevel.Reset();
}
namespace
{
void RenderBoundingBox(FPrimitiveDrawInterface* PDI, const FBoxCenterAndExtent& Box, const FColor& Color)
{
const FVector Center = Box.Center;
const FVector Extent = Box.Extent;
const FVector A = Center + FVector(Extent.X, Extent.Y, Extent.Z);
const FVector B = Center + FVector(Extent.X, Extent.Y, -Extent.Z);
const FVector C = Center + FVector(Extent.X, -Extent.Y, Extent.Z);
const FVector D = Center + FVector(Extent.X, -Extent.Y, -Extent.Z);
const FVector E = Center + FVector(-Extent.X, Extent.Y, Extent.Z);
const FVector F = Center + FVector(-Extent.X, Extent.Y, -Extent.Z);
const FVector G = Center + FVector(-Extent.X, -Extent.Y, Extent.Z);
const FVector H = Center + FVector(-Extent.X, -Extent.Y, -Extent.Z);
PDI->DrawLine(A, B, Color, SDPG_Foreground);
PDI->DrawLine(A, C, Color, SDPG_Foreground);
PDI->DrawLine(A, E, Color, SDPG_Foreground);
PDI->DrawLine(B, D, Color, SDPG_Foreground);
PDI->DrawLine(B, F, Color, SDPG_Foreground);
PDI->DrawLine(C, D, Color, SDPG_Foreground);
PDI->DrawLine(C, G, Color, SDPG_Foreground);
PDI->DrawLine(D, H, Color, SDPG_Foreground);
PDI->DrawLine(E, F, Color, SDPG_Foreground);
PDI->DrawLine(E, G, Color, SDPG_Foreground);
PDI->DrawLine(F, H, Color, SDPG_Foreground);
PDI->DrawLine(G, H, Color, SDPG_Foreground);
}
void RenderInterlinesBoundingBox(FPrimitiveDrawInterface* PDI, const FBoxMinAndMax& Box, const FColor& Color, const FVector& RoomUnit)
{
FIntVector Min, Max;
IntVector::MinMax(Box.GetMin(), Box.GetMax(), Min, Max);
// Vertical Lines on X
for (int32 i = Min.X + 1; i < Max.X; ++i)
{
FIntVector BottomA(i, Min.Y, Min.Z);
FIntVector BottomB(i, Max.Y, Min.Z);
FIntVector TopA(i, Min.Y, Max.Z);
FIntVector TopB(i, Max.Y, Max.Z);
PDI->DrawLine(Dungeon::ToWorldLocation(BottomA, RoomUnit), Dungeon::ToWorldLocation(TopA, RoomUnit), Color, SDPG_World);
PDI->DrawLine(Dungeon::ToWorldLocation(BottomB, RoomUnit), Dungeon::ToWorldLocation(TopB, RoomUnit), Color, SDPG_World);
}
// Vertical Lines on Y
for (int32 i = Min.Y + 1; i < Max.Y; ++i)
{
FIntVector BottomA(Min.X, i, Min.Z);
FIntVector BottomB(Max.X, i, Min.Z);
FIntVector TopA(Min.X, i, Max.Z);
FIntVector TopB(Max.X, i, Max.Z);
PDI->DrawLine(Dungeon::ToWorldLocation(BottomA, RoomUnit), Dungeon::ToWorldLocation(TopA, RoomUnit), Color, SDPG_World);
PDI->DrawLine(Dungeon::ToWorldLocation(BottomB, RoomUnit), Dungeon::ToWorldLocation(TopB, RoomUnit), Color, SDPG_World);
}
// Horizontal Lines on X and Y
for (int32 i = Min.Z + 1; i < Max.Z; ++i)
{
FIntVector A(Min.X, Min.Y, i);
FIntVector B(Min.X, Max.Y, i);
FIntVector C(Max.X, Max.Y, i);
FIntVector D(Max.X, Min.Y, i);
PDI->DrawLine(Dungeon::ToWorldLocation(A, RoomUnit), Dungeon::ToWorldLocation(B, RoomUnit), Color, SDPG_World);
PDI->DrawLine(Dungeon::ToWorldLocation(B, RoomUnit), Dungeon::ToWorldLocation(C, RoomUnit), Color, SDPG_World);
PDI->DrawLine(Dungeon::ToWorldLocation(C, RoomUnit), Dungeon::ToWorldLocation(D, RoomUnit), Color, SDPG_World);
PDI->DrawLine(Dungeon::ToWorldLocation(D, RoomUnit), Dungeon::ToWorldLocation(A, RoomUnit), Color, SDPG_World);
}
}
}
void FProceduralDungeonEditorTool_Door::Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI)
{
FProceduralDungeonEditorTool::Render(View, Viewport, PDI);
const URoomData* Data = GetRoomData();
if (!IsValid(Data))
return;
const FColor LineColor(100, 20, 0);
const FVector RoomUnit = Data->GetRoomUnit();
for (const auto& BoundingBox : Data->BoundingBoxes)
{
RenderInterlinesBoundingBox(PDI, BoundingBox, LineColor, RoomUnit);
}
}
void FProceduralDungeonEditorTool_Door::Tick(FEditorViewportClient* ViewportClient, float DeltaTime)
{
FProceduralDungeonEditorTool::Tick(ViewportClient, DeltaTime);
if (ShowDoorPreview)
{
const URoomData* Data = GetRoomData();
check(IsValid(Data));
UWorld* World = ViewportClient->GetWorld();
FDoorDef::DrawDebug(World, DoorPreview, Data->GetRoomUnit(), FTransform::Identity, /*includeOffset = */ true, /*isConnected = */ IsDoorValid(Data, DoorPreview));
}
}
bool FProceduralDungeonEditorTool_Door::HandleClick(FEditorViewportClient* InViewportClient, HHitProxy* HitProxy, const FViewportClick& Click)
{
if (Click.IsAltDown() || Click.IsControlDown() || Click.IsShiftDown())
return false;
if (!ShowDoorPreview)
return false;
URoomData* Data = GetRoomData();
check(IsValid(Data));
if (Click.GetKey() == EKeys::LeftMouseButton)
{
if (IsDoorValid(Data, DoorPreview))
{
GEditor->BeginTransaction(FText::FromString(TEXT("Add Door")));
Data->Modify();
Data->Doors.Add(DoorPreview);
GEditor->EndTransaction();
return true;
}
}
if (Click.GetKey() == EKeys::RightMouseButton)
{
if (Data->Doors.Contains(DoorPreview))
{
GEditor->BeginTransaction(FText::FromString(TEXT("Remove Door")));
Data->Modify();
Data->Doors.Remove(DoorPreview);
GEditor->EndTransaction();
return true;
}
}
return false;
}
bool FProceduralDungeonEditorTool_Door::MouseMove(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 MouseX, int32 MouseY)
{
ShowDoorPreview = false;
const URoomData* Data = GetRoomData();
if (!IsValid(Data))
return false;
FHitResult Hit;
if (!RoomTraceFromMouse(Hit, ViewportClient))
return false;
FIntVector RoomCell;
EDoorDirection DoorDirection;
if (!GetRoomCellFromHit(Hit, Data->GetRoomUnit(), RoomCell, DoorDirection))
return false;
ShowDoorPreview = true;
DoorPreview.Position = RoomCell;
DoorPreview.Direction = DoorDirection;
DoorPreview.Type = EdMode->Settings->DoorType;
// Snap preview to existing door if RoomCell is inside
for (const FDoorDef& RoomDoor : Data->Doors)
{
if (RoomDoor.Direction != DoorPreview.Direction)
continue;
if (IsPositionInside(RoomDoor, DoorPreview.Position, Data->GetRoomUnit()))
{
DoorPreview = RoomDoor;
break;
}
}
return false;
}
bool FProceduralDungeonEditorTool_Door::GetCursor(EMouseCursor::Type& OutCursor) const
{
if (!ShowDoorPreview)
return false;
const URoomData* Data = GetRoomData();
if (!IsValid(Data))
return false;
OutCursor = IsDoorValid(Data, DoorPreview) ? EMouseCursor::Hand : EMouseCursor::SlashedCircle;
return true;
}
void FProceduralDungeonEditorTool_Door::OnLevelChanged(const ARoomLevel* NewLevel)
{
UpdateCollision();
}
void FProceduralDungeonEditorTool_Door::OnDataChanged(const URoomData* NewData)
{
UpdateCollision();
}
void FProceduralDungeonEditorTool_Door::OnDataPropertiesChanged(const URoomData* Data)
{
UpdateCollision();
}
void FProceduralDungeonEditorTool_Door::CreateCollision(ARoomLevel* Level)
{
if (!IsValid(Level))
return;
if (!IsValid(Level->Data))
return;
check(IsValid(Level->GetWorld()));
const int32 NumBoxes = Level->Data->BoundingBoxes.Num();
const int32 Delta = NumBoxes - RoomBoxes.Num();
// Create new boxes when delta is positive
for (int i = 0; i < Delta; ++i)
{
FName BoxName = FName(*FString::Printf(TEXT("Editor Room Collision %d"), RoomBoxes.Num()));
UBoxComponent* RoomBox = NewObject<UBoxComponent>(Level, BoxName, RF_Transient);
check(IsValid(RoomBox));
RoomBox->SetupAttachment(Level->GetRootComponent());
RoomBox->RegisterComponent();
RoomBox->SetCollisionResponseToAllChannels(ECollisionResponse::ECR_Ignore);
RoomBox->SetCollisionObjectType(ECollisionChannel::ECC_MAX);
RoomBoxes.Add(RoomBox);
DungeonEd_LogInfo("Created RoomBox: %s", *GetNameSafe(RoomBox));
}
// Destroy boxes when delta is negative
for (int i = 0; i > Delta; --i)
{
if (RoomBoxes.Num() == 0)
break;
TWeakObjectPtr<UBoxComponent> RoomBox = RoomBoxes.Pop();
if (!RoomBox.IsValid())
continue;
RoomBox->DestroyComponent();
DungeonEd_LogInfo("Destroyed RoomBox: %s", *GetNameSafe(RoomBox.Get()));
}
}
void FProceduralDungeonEditorTool_Door::UpdateCollision()
{
auto Level = EdMode->GetLevelInstance();
if (Level != CachedLevel)
{
DestroyCollision();
CachedLevel = Level;
}
if (!CachedLevel.IsValid())
return;
URoomData* Data = CachedLevel->Data;
if (!IsValid(Data))
return;
CreateCollision(CachedLevel.Get());
check(Data->BoundingBoxes.Num() == RoomBoxes.Num());
for (int i = 0; i < Data->BoundingBoxes.Num(); ++i)
{
const FBoxMinAndMax& DataBox = Data->BoundingBoxes[i];
UBoxComponent* RoomBox = RoomBoxes[i].Get();
FBoxCenterAndExtent Box = Dungeon::ToWorld(DataBox, Data->GetRoomUnit());
RoomBox->SetRelativeLocation(Box.Center);
RoomBox->SetBoxExtent(Box.Extent);
DungeonEd_LogInfo("Updated RoomBox: %s", *GetNameSafe(RoomBox));
}
}
void FProceduralDungeonEditorTool_Door::DestroyCollision()
{
for (const auto& RoomBox : RoomBoxes)
{
if (!RoomBox.IsValid())
continue;
RoomBox->DestroyComponent();
}
RoomBoxes.Empty();
}
bool FProceduralDungeonEditorTool_Door::RoomTraceFromMouse(FHitResult& OutHit, FEditorViewportClient* ViewportClient) const
{
int32 MouseX = ViewportClient->Viewport->GetMouseX();
int32 MouseY = ViewportClient->Viewport->GetMouseY();
// Compute a world space ray from the screen space mouse coordinates
FSceneViewFamilyContext ViewFamily(
FSceneViewFamilyContext::ConstructionValues(ViewportClient->Viewport, ViewportClient->GetScene(), ViewportClient->EngineShowFlags)
.SetRealtimeUpdate(ViewportClient->IsRealtime()));
FSceneView* View = ViewportClient->CalcSceneView(&ViewFamily);
FViewportCursorLocation MouseViewportRay(View, ViewportClient, MouseX, MouseY);
FVector MouseViewportRayDirection = MouseViewportRay.GetDirection();
FVector Start = MouseViewportRay.GetOrigin();
FVector End = Start + WORLD_MAX * MouseViewportRayDirection;
if (ViewportClient->IsOrtho())
{
Start -= WORLD_MAX * MouseViewportRayDirection;
}
return RoomTrace(OutHit, Start, End);
}
bool FProceduralDungeonEditorTool_Door::RoomTrace(FHitResult& OutHit, const FVector& RayOrigin, const FVector& RayEnd) const
{
OutHit = FHitResult();
FHitResult Hit;
bool bHasHit = false;
for (const auto& RoomBox : RoomBoxes)
{
if (!RoomBox.IsValid())
continue;
const bool bSuccess = RoomBox->LineTraceComponent(Hit, RayOrigin, RayEnd, FCollisionQueryParams(SCENE_QUERY_STAT(RoomTrace)));
if (!bSuccess)
continue;
if (!bHasHit || Hit.Distance < OutHit.Distance)
{
OutHit = Hit;
bHasHit = true;
}
}
return bHasHit;
}
bool FProceduralDungeonEditorTool_Door::GetRoomCellFromHit(const FHitResult& Hit, const FVector RoomUnit, FIntVector& OutCell, EDoorDirection& OutDirection) const
{
// Direction is up or down: invalid
if (FMath::Abs(FVector::DotProduct(Hit.ImpactNormal, FVector::UpVector)) >= 0.5f)
return false;
// Determine direction from the hit normal
float DirX = FVector::DotProduct(Hit.ImpactNormal, FVector::ForwardVector);
float DirY = FVector::DotProduct(Hit.ImpactNormal, FVector::RightVector);
if (FMath::Abs(DirX) > FMath::Abs(DirY))
OutDirection = (DirX > 0) ? EDoorDirection::North : EDoorDirection::South;
else
OutDirection = (DirY > 0) ? EDoorDirection::East : EDoorDirection::West;
// Determine the room cell
FVector RoomSpacePoint = Hit.ImpactPoint / RoomUnit;
RoomSpacePoint -= 0.5f * (ToVector(OutDirection) + FVector::UpVector);
OutCell = FIntVector(FMath::RoundToInt(RoomSpacePoint.X), FMath::RoundToInt(RoomSpacePoint.Y), FMath::RoundToInt(RoomSpacePoint.Z));
return true;
}
@@ -0,0 +1,53 @@
// Copyright Benoit Pelletier 2023 - 2025 All Rights Reserved.
//
// This software is available under different licenses depending on the source from which it was obtained:
// - The Fab EULA (https://fab.com/eula) applies when obtained from the Fab marketplace.
// - The CeCILL-C license (https://cecill.info/licences/Licence_CeCILL-C_V1-en.html) applies when obtained from any other source.
// Please refer to the accompanying LICENSE file for further details.
#pragma once
#include "ProceduralDungeonEditorTool.h"
class ARoomLevel;
class FProceduralDungeonEditorTool_Door : public FProceduralDungeonEditorTool
{
public:
FProceduralDungeonEditorTool_Door(FProceduralDungeonEdMode* InEdMode)
: FProceduralDungeonEditorTool(InEdMode)
{
}
//~ Begin FProceduralDungeonEditorTool Interface
virtual const TCHAR* GetToolName() override { return TEXT("Tool_Door"); }
virtual FText GetDisplayName() override { return NSLOCTEXT("ProceduralDungeonEditor", "Tool_Door", "Door"); }
virtual FText GetDisplayMessage() override { return NSLOCTEXT("ProceduralDungeonEditor", "Tool_Door_Tooltip", ""); }
virtual void EnterTool() override;
virtual void ExitTool() override;
virtual void Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) override;
virtual void Tick(FEditorViewportClient* ViewportClient, float DeltaTime) override;
virtual bool HandleClick(FEditorViewportClient* InViewportClient, HHitProxy* HitProxy, const FViewportClick& Click) override;
virtual bool MouseMove(FEditorViewportClient* ViewportClient, FViewport* Viewport, int32 MouseX, int32 MouseY) override;
virtual bool GetCursor(EMouseCursor::Type& OutCursor) const override;
virtual void OnLevelChanged(const ARoomLevel* NewLevel) override;
virtual void OnDataChanged(const URoomData* NewData) override;
virtual void OnDataPropertiesChanged(const URoomData* Data) override;
//~ End FProceduralDungeonEditorTool Interface
protected:
void CreateCollision(ARoomLevel* level);
void UpdateCollision();
void DestroyCollision();
bool RoomTraceFromMouse(FHitResult& OutHit, FEditorViewportClient* ViewportClient) const;
bool RoomTrace(FHitResult& OutHit, const FVector& RayOrigin, const FVector& RayEnd) const;
bool GetRoomCellFromHit(const FHitResult& Hit, const FVector RoomUnit, FIntVector& OutCell, EDoorDirection& OutDirection) const;
private:
TWeakObjectPtr<ARoomLevel> CachedLevel = nullptr;
TArray<TWeakObjectPtr<class UBoxComponent>> RoomBoxes;
FDoorDef DoorPreview;
bool ShowDoorPreview {false};
};
@@ -0,0 +1,337 @@
// Copyright Benoit Pelletier 2023 - 2025 All Rights Reserved.
//
// This software is available under different licenses depending on the source from which it was obtained:
// - The Fab EULA (https://fab.com/eula) applies when obtained from the Fab marketplace.
// - The CeCILL-C license (https://cecill.info/licences/Licence_CeCILL-C_V1-en.html) applies when obtained from any other source.
// Please refer to the accompanying LICENSE file for further details.
#include "ProceduralDungeonEditorTool_Size.h"
#include "EditorMode/ProceduralDungeonEdMode.h"
#include "EditorModeManager.h"
#include "Engine/Selection.h"
#include "ProceduralDungeonEdLog.h"
#include "ProceduralDungeonUtils.h"
#include "Room.h"
#include "RoomLevel.h"
#include "RoomData.h"
IMPLEMENT_HIT_PROXY(HRoomPointProxy, HHitProxy);
void FRoomPoint::AddLinkedPoint(FRoomPoint& Point, EAxisList::Type Axis)
{
LinkedPoints.Add({&Point, Axis});
Point.LinkedPoints.Add({this, Axis});
}
void FRoomPoint::SetLocation(FVector InLocation)
{
SetDirty();
Location = InLocation;
UpdateWithPropagation();
}
void FRoomPoint::SetDirty()
{
if (bDirty)
return;
bDirty = EAxisList::XYZ;
for (FLink& Link : LinkedPoints)
{
check(Link.Point);
Link.Point->SetDirty();
}
}
void FRoomPoint::UpdateWithPropagation()
{
TQueue<FRoomPoint*> PendingNodes;
PendingNodes.Enqueue(this);
FRoomPoint* Node = nullptr;
while (PendingNodes.Dequeue(Node))
{
Node->UpdateLinkedPoints(PendingNodes);
}
}
void FRoomPoint::UpdateLinkedPoints(TQueue<FRoomPoint*>& PendingNodes)
{
for (FLink& Link : LinkedPoints)
{
check(Link.Point);
if (Link.Point->bDirty)
{
Link.Point->UpdateFrom(*this, Link.Axis);
PendingNodes.Enqueue(Link.Point);
}
}
}
void FRoomPoint::UpdateFrom(FRoomPoint& From, EAxisList::Type Axis)
{
if (bDirty & Axis & EAxisList::X)
{
bDirty &= ~EAxisList::X;
From.bDirty &= ~EAxisList::X;
Location.X = From.Location.X;
}
if (bDirty & Axis & EAxisList::Y)
{
bDirty &= ~EAxisList::Y;
From.bDirty &= ~EAxisList::Y;
Location.Y = From.Location.Y;
}
if (bDirty & Axis & EAxisList::Z)
{
bDirty &= ~EAxisList::Z;
From.bDirty &= ~EAxisList::Z;
Location.Z = From.Location.Z;
}
}
void FProceduralDungeonEditorTool_Size::EnterTool()
{
OnDataChanged();
ResetSelectedPoint();
}
void FProceduralDungeonEditorTool_Size::ExitTool()
{
DungeonEd_LogInfo("Exit Size Tool.");
}
void FProceduralDungeonEditorTool_Size::Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI)
{
const URoomData* Data = GetRoomData();
if (!IsValid(Data))
return;
for (const FBoxPoints& Box : Boxes)
{
Box.Draw(PDI, SelectedPoint);
}
}
bool FProceduralDungeonEditorTool_Size::HandleClick(FEditorViewportClient* InViewportClient, HHitProxy* HitProxy, const FViewportClick& Click)
{
ResetSelectedPoint();
if (!HitProxy)
return false;
if (!HitProxy->IsA(HRoomPointProxy::StaticGetType()))
return false;
const HRoomPointProxy* RoomProxy = (HRoomPointProxy*)HitProxy;
checkf(RoomProxy->Index >= 0 && RoomProxy->Index < GetMaxIndex(), TEXT("A room point has been clicked but is out of bounds!"));
SetSelectedPoint(RoomProxy->Index);
GEditor->GetSelectedActors()->DeselectAll();
// Force translate widget when a point is selected.
FEditorModeTools* ModeTools = EdMode->GetModeManager();
if (ModeTools)
ModeTools->SetWidgetMode(WidgetMode::WM_Translate);
return true;
}
bool FProceduralDungeonEditorTool_Size::InputKey(FEditorViewportClient* InViewportClient, FViewport* InViewport, FKey InKey, EInputEvent InEvent)
{
if (InKey == EKeys::LeftMouseButton && InEvent == IE_Released)
ResetDragPoint();
return false;
}
bool FProceduralDungeonEditorTool_Size::InputDelta(FEditorViewportClient* InViewportClient, FViewport* InViewport, FVector& InDrag, FRotator& InRot, FVector& InScale)
{
if (InViewportClient->GetCurrentWidgetAxis() == EAxisList::None)
return false;
if (!HasValidSelection())
return false;
const URoomData* Data = GetRoomData();
if (!IsValid(Data))
return false;
if (!InDrag.IsNearlyZero())
{
DragPoint += InDrag;
FRoomPoint& Point = GetSelectedPoint();
FVector OldPoint = Point.GetLocation();
Point.SetLocation(Dungeon::SnapPoint(DragPoint, Data->GetRoomUnit()));
if (OldPoint != Point.GetLocation())
UpdateDataAsset(GetBoxIndexFromPointIndex(SelectedPoint));
}
return true;
}
bool FProceduralDungeonEditorTool_Size::UsesTransformWidget() const
{
return HasValidSelection() || (GEditor->GetSelectedActorCount() > 0);
}
bool FProceduralDungeonEditorTool_Size::UsesTransformWidget(WidgetMode CheckMode) const
{
return HasValidSelection() ? (CheckMode == WidgetMode::WM_Translate) : EdMode->FEdMode::UsesTransformWidget(CheckMode);
}
FVector FProceduralDungeonEditorTool_Size::GetWidgetLocation() const
{
return HasValidSelection() ? DragPoint : EdMode->FEdMode::GetWidgetLocation();
}
void FProceduralDungeonEditorTool_Size::PostUndo(bool bSuccess)
{
OnDataChanged();
}
void FProceduralDungeonEditorTool_Size::PostRedo(bool bSuccess)
{
OnDataChanged();
}
void FProceduralDungeonEditorTool_Size::OnDataChanged(const URoomData* NewData)
{
// When the room data has changed, reset all
ResetSelectedPoint();
ResetDragPoint();
UpdateBoxes(NewData);
}
void FProceduralDungeonEditorTool_Size::OnDataPropertiesChanged(const URoomData* Data)
{
UpdateBoxes(Data);
}
bool FProceduralDungeonEditorTool_Size::HasValidSelection() const
{
return SelectedPoint >= 0 && SelectedPoint < GetMaxIndex();
}
void FProceduralDungeonEditorTool_Size::ResetDragPoint()
{
if (HasValidSelection())
DragPoint = GetSelectedPoint().GetLocation();
else
DragPoint = FVector::ZeroVector;
}
void FProceduralDungeonEditorTool_Size::UpdateDataAsset(int32 BoxIndex) const
{
URoomData* Data = GetRoomData();
if (!IsValid(Data))
return;
if (BoxIndex >= 0 && BoxIndex < Data->BoundingBoxes.Num())
{
Data->Modify();
const FVector RoomUnit = Data->GetRoomUnit();
const FBoxPoints& Box = Boxes[BoxIndex];
FBoxMinAndMax& DataBox = Data->BoundingBoxes[BoxIndex];
const FIntVector A = Dungeon::ToRoomLocation(Box.GetMin(), RoomUnit);
const FIntVector B = Dungeon::ToRoomLocation(Box.GetMax(), RoomUnit);
DataBox.SetMinAndMax(A, B);
return;
}
}
void FProceduralDungeonEditorTool_Size::SetSelectedPoint(int32 Index)
{
if (Index < 0 || Index >= GetMaxIndex())
Index = -1;
DungeonEd_LogInfo("Selected Point: %d", Index);
SelectedPoint = Index;
ResetDragPoint();
}
void FProceduralDungeonEditorTool_Size::ResetSelectedPoint()
{
SetSelectedPoint(-1);
}
FRoomPoint& FProceduralDungeonEditorTool_Size::GetSelectedPoint()
{
checkf(HasValidSelection(), TEXT("Trying to get selected point but selection is invalid!"));
const int32 BoxIndex = GetBoxIndexFromPointIndex(SelectedPoint);
const int32 PointIndex = GetBoxPointIndex(SelectedPoint);
checkf(BoxIndex >= 0 && BoxIndex < Boxes.Num(), TEXT("Selected point's box index is out of bounds!"));
checkf(PointIndex >= 0 && PointIndex < FBoxPoints::NbPoints, TEXT("Selected point's point index is out of bounds!"));
return Boxes[BoxIndex].GetPoint(PointIndex);
}
void FProceduralDungeonEditorTool_Size::UpdateBoxes(const URoomData* Data)
{
if (!IsValid(Data))
return;
const int32 NbBoxes = Data->BoundingBoxes.Num();
if (Boxes.Num() != NbBoxes)
{
Boxes.SetNum(NbBoxes);
}
const FVector RoomUnit = Data->GetRoomUnit();
for (int32 i = 0; i < NbBoxes; ++i)
{
const FBoxMinAndMax& Box = Data->BoundingBoxes[i];
Boxes[i].SetID(i);
Boxes[i].SetMin(Dungeon::ToWorldLocation(Box.GetMin(), RoomUnit));
Boxes[i].SetMax(Dungeon::ToWorldLocation(Box.GetMax(), RoomUnit));
}
}
FBoxPoints::FBoxPoints()
{
// 6 1
// 2 7
// 4 5
// 0 3
Points[0].AddLinkedPoint(Points[2], EAxisList::XY);
Points[0].AddLinkedPoint(Points[3], EAxisList::XZ);
Points[0].AddLinkedPoint(Points[4], EAxisList::YZ);
Points[1].AddLinkedPoint(Points[5], EAxisList::XY);
Points[1].AddLinkedPoint(Points[6], EAxisList::XZ);
Points[1].AddLinkedPoint(Points[7], EAxisList::YZ);
Points[2].AddLinkedPoint(Points[6], EAxisList::YZ);
Points[2].AddLinkedPoint(Points[7], EAxisList::XZ);
Points[3].AddLinkedPoint(Points[5], EAxisList::YZ);
Points[3].AddLinkedPoint(Points[7], EAxisList::XY);
Points[4].AddLinkedPoint(Points[5], EAxisList::XZ);
Points[4].AddLinkedPoint(Points[6], EAxisList::XY);
}
void FBoxPoints::SetMin(FVector Value)
{
Points[0].SetLocation(Value);
}
void FBoxPoints::SetMax(FVector Value)
{
Points[1].SetLocation(Value);
}
void FBoxPoints::Draw(FPrimitiveDrawInterface* PDI, int32 SelectedPoint) const
{
static const FColor NormalColor(200, 200, 200);
static const FColor SelectedColor(255, 128, 0);
const int32 LocalSelectedIndex = SelectedPoint - (ID * NbPoints);
for (int i = 0; i < NbPoints; ++i)
{
const bool bSelected = (LocalSelectedIndex == i);
const FColor& Color = bSelected ? SelectedColor : NormalColor;
PDI->SetHitProxy(new HRoomPointProxy(ID * NbPoints + i));
PDI->DrawPoint(Points[i].GetLocation(), Color, 10.0f, SDPG_Foreground);
PDI->SetHitProxy(nullptr);
}
}
@@ -0,0 +1,126 @@
// Copyright Benoit Pelletier 2023 - 2025 All Rights Reserved.
//
// This software is available under different licenses depending on the source from which it was obtained:
// - The Fab EULA (https://fab.com/eula) applies when obtained from the Fab marketplace.
// - The CeCILL-C license (https://cecill.info/licences/Licence_CeCILL-C_V1-en.html) applies when obtained from any other source.
// Please refer to the accompanying LICENSE file for further details.
#pragma once
#include "CoreMinimal.h"
#include "ProceduralDungeonEditorTool.h"
struct HRoomPointProxy : public HHitProxy
{
DECLARE_HIT_PROXY();
HRoomPointProxy(int32 InIndex)
: HHitProxy(HPP_UI)
, Index(InIndex)
{
}
int32 Index {-1};
};
struct FRoomPoint
{
struct FLink
{
FRoomPoint* Point;
EAxisList::Type Axis;
};
void AddLinkedPoint(FRoomPoint& Point, EAxisList::Type Axis);
FVector GetLocation() const { return Location; }
void SetLocation(FVector InLocation);
FVector* operator->() { return &Location; }
private:
void SetDirty();
void UpdateWithPropagation();
void UpdateLinkedPoints(TQueue<FRoomPoint*>& PendingNodes);
void UpdateFrom(FRoomPoint& From, EAxisList::Type Axis);
private:
EAxisList::Type bDirty {EAxisList::None};
FVector Location {0};
TArray<FLink> LinkedPoints {};
};
struct FBoxPoints
{
public:
FBoxPoints();
void SetID(uint32 InID) { ID = InID; }
void SetMin(FVector Value);
void SetMax(FVector Value);
uint32 GetID() const { return ID; }
FVector GetMin() const { return Points[0].GetLocation(); }
FVector GetMax() const { return Points[1].GetLocation(); }
FRoomPoint& GetPoint(int32 Index)
{
check(Index >= 0 && Index < NbPoints);
return Points[Index];
}
void Draw(FPrimitiveDrawInterface* PDI, int32 SelectedPoint) const;
static const uint32 NbPoints {8};
private:
uint32 ID;
FRoomPoint Points[NbPoints];
};
class FProceduralDungeonEditorTool_Size : public FProceduralDungeonEditorTool
{
public:
FProceduralDungeonEditorTool_Size(FProceduralDungeonEdMode* InEdMode)
: FProceduralDungeonEditorTool(InEdMode)
{
}
virtual const TCHAR* GetToolName() override { return TEXT("Tool_Size"); }
virtual FText GetDisplayName() override { return NSLOCTEXT("ProceduralDungeonEditor", "Tool_Size", "Size"); }
virtual FText GetDisplayMessage() override { return NSLOCTEXT("ProceduralDungeonEditor", "Tool_Size_Tooltip", ""); }
virtual void EnterTool() override;
virtual void ExitTool() override;
virtual void Render(const FSceneView* View, FViewport* Viewport, FPrimitiveDrawInterface* PDI) override;
virtual bool HandleClick(FEditorViewportClient* InViewportClient, HHitProxy* HitProxy, const FViewportClick& Click) override;
virtual bool InputKey(FEditorViewportClient* InViewportClient, FViewport* InViewport, FKey InKey, EInputEvent InEvent) override;
virtual bool InputDelta(FEditorViewportClient* InViewportClient, FViewport* InViewport, FVector& InDrag, FRotator& InRot, FVector& InScale) override;
virtual bool UsesTransformWidget() const override;
virtual bool UsesTransformWidget(WidgetMode CheckMode) const override;
virtual FVector GetWidgetLocation() const override;
/** FEditorUndoClient interface */
virtual void PostUndo(bool bSuccess) override;
virtual void PostRedo(bool bSuccess) override;
virtual void OnDataChanged(const URoomData* NewData = nullptr) override;
virtual void OnDataPropertiesChanged(const URoomData* Data = nullptr) override;
bool HasValidSelection() const;
void ResetDragPoint();
void UpdateDataAsset(int32 BoxIndex) const;
void SetSelectedPoint(int32 Index);
void ResetSelectedPoint();
private:
int32 GetBoxIndexFromPointIndex(int32 PointIndex) const { return PointIndex / FBoxPoints::NbPoints; }
int32 GetBoxPointIndex(int32 PointIndex) const { return PointIndex % FBoxPoints::NbPoints; }
int32 GetMaxIndex() const { return Boxes.Num() * FBoxPoints::NbPoints; }
FRoomPoint& GetSelectedPoint();
void UpdateBoxes(const URoomData* Data);
private:
int32 SelectedPoint {-1};
TArray<FBoxPoints> Boxes;
FVector DragPoint;
};