429 lines
10 KiB
C++
429 lines
10 KiB
C++
/*
|
|
* MIT License
|
|
*
|
|
* Copyright (c) 2019-2021 Benoit Pelletier
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*/
|
|
|
|
#include "Room.h"
|
|
#include "Door.h"
|
|
#include "Engine/World.h"
|
|
#include "Engine.h"
|
|
#include "RoomData.h"
|
|
#include "RoomLevel.h"
|
|
#include "ProceduralDungeonSettings.h"
|
|
#include "ProceduralDungeonLog.h"
|
|
|
|
void URoom::Init(URoomData* Data)
|
|
{
|
|
RoomData = Data;
|
|
Instance = nullptr;
|
|
Position = FIntVector(0,0,0);
|
|
Direction = EDoorDirection::North;
|
|
|
|
if (IsValid(RoomData))
|
|
{
|
|
for (int i = 0; i < RoomData->GetNbDoor(); i++)
|
|
{
|
|
Connections.Add(FRoomConnection());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LogError("No RoomData provided.");
|
|
}
|
|
}
|
|
|
|
bool URoom::IsConnected(int Index)
|
|
{
|
|
check(Index >= 0 && Index < Connections.Num());
|
|
return Connections[Index].OtherRoom != nullptr;
|
|
}
|
|
|
|
void URoom::SetConnection(int Index, URoom* Room, int OtherIndex)
|
|
{
|
|
check(Index >= 0 && Index < Connections.Num());
|
|
Connections[Index].OtherRoom = Room;
|
|
Connections[Index].OtherDoorIndex = OtherIndex;
|
|
}
|
|
|
|
TWeakObjectPtr<URoom> URoom::GetConnection(int Index)
|
|
{
|
|
check(Index >= 0 && Index < Connections.Num());
|
|
return Connections[Index].OtherRoom;
|
|
}
|
|
|
|
int URoom::GetFirstEmptyConnection()
|
|
{
|
|
for(int i = 0; i < Connections.Num(); ++i)
|
|
{
|
|
if(Connections[i].OtherRoom == nullptr)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void URoom::Instantiate(UWorld* World)
|
|
{
|
|
if (Instance == nullptr)
|
|
{
|
|
if(!IsValid(RoomData))
|
|
{
|
|
LogError("Failed to instantiate the room: it has no RoomData.");
|
|
return;
|
|
}
|
|
|
|
Instance = UProceduralLevelStreaming::Load(World, RoomData, URoom::Unit() * FVector(Position), FRotator(0, -90 * (int)Direction, 0));
|
|
UE_LOG(LogProceduralDungeon, Log, TEXT("Load room Instance: %s"), nullptr != Instance ? *Instance->GetWorldAssetPackageName() : TEXT("Null"));
|
|
}
|
|
else
|
|
{
|
|
LogError("Failed to instantiate the room: it is already instanciated.");
|
|
}
|
|
}
|
|
|
|
void URoom::Destroy(UWorld* World)
|
|
{
|
|
if (Instance != nullptr)
|
|
{
|
|
UE_LOG(LogProceduralDungeon, Log, TEXT("Unload room Instance: %s"), nullptr != Instance ? *Instance->GetWorldAssetPackageName() : TEXT("Null"));
|
|
|
|
ARoomLevel* script = GetLevelScript();
|
|
if (script != nullptr)
|
|
{
|
|
script->Room = nullptr;
|
|
script->Destroy();
|
|
}
|
|
|
|
UProceduralLevelStreaming::Unload(World, Instance);
|
|
}
|
|
}
|
|
|
|
ARoomLevel* URoom::GetLevelScript()
|
|
{
|
|
if (Instance == nullptr || !IsValid(Instance))
|
|
{
|
|
return nullptr;
|
|
}
|
|
return Cast<ARoomLevel>(Instance->GetLevelScriptActor());
|
|
}
|
|
|
|
bool URoom::IsInstanceLoaded()
|
|
{
|
|
if(Instance == nullptr || !IsValid(Instance))
|
|
{
|
|
return true;
|
|
}
|
|
return Instance->IsLevelLoaded();
|
|
}
|
|
|
|
bool URoom:: IsInstanceUnloaded()
|
|
{
|
|
if(Instance == nullptr || !IsValid(Instance))
|
|
{
|
|
return true;
|
|
}
|
|
return Instance->IsLevelUnloaded();
|
|
}
|
|
|
|
EDoorDirection URoom::GetDoorWorldOrientation(int DoorIndex)
|
|
{
|
|
check(DoorIndex >= 0 && DoorIndex < RoomData->Doors.Num());
|
|
return Add(RoomData->Doors[DoorIndex].Direction, Direction);
|
|
}
|
|
|
|
FIntVector URoom::GetDoorWorldPosition(int DoorIndex)
|
|
{
|
|
check(DoorIndex >= 0 && DoorIndex < RoomData->Doors.Num());
|
|
return RoomToWorld(RoomData->Doors[DoorIndex].Position);
|
|
}
|
|
|
|
int URoom::GetDoorIndexAt(FIntVector WorldPos, EDoorDirection WorldRot)
|
|
{
|
|
FIntVector localPos = WorldToRoom(WorldPos);
|
|
EDoorDirection localRot = WorldToRoom(WorldRot);
|
|
|
|
for(int i = 0; i < RoomData->Doors.Num(); ++i)
|
|
{
|
|
const FDoorDef door = RoomData->Doors[i];
|
|
if(door.Position == localPos && door.Direction == localRot)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
bool URoom::IsDoorInstanced(int DoorIndex)
|
|
{
|
|
check(DoorIndex >= 0 && DoorIndex < RoomData->Doors.Num());
|
|
return IsValid(Connections[DoorIndex].DoorInstance);
|
|
}
|
|
|
|
void URoom::SetDoorInstance(int DoorIndex, ADoor* Door)
|
|
{
|
|
check(DoorIndex >= 0 && DoorIndex < RoomData->Doors.Num());
|
|
Connections[DoorIndex].DoorInstance = Door;
|
|
}
|
|
|
|
int URoom::GetOtherDoorIndex(int DoorIndex)
|
|
{
|
|
check(DoorIndex >= 0 && DoorIndex < RoomData->Doors.Num());
|
|
return Connections[DoorIndex].OtherDoorIndex;
|
|
}
|
|
|
|
FIntVector URoom::WorldToRoom(FIntVector WorldPos)
|
|
{
|
|
return Rotate(WorldPos - Position, Sub(EDoorDirection::North, Direction));
|
|
}
|
|
|
|
FIntVector URoom::RoomToWorld(FIntVector RoomPos)
|
|
{
|
|
return Rotate(RoomPos, Direction) + Position;
|
|
}
|
|
|
|
EDoorDirection URoom::WorldToRoom(EDoorDirection WorldRot)
|
|
{
|
|
return Sub(WorldRot, Direction);
|
|
}
|
|
|
|
EDoorDirection URoom::RoomToWorld(EDoorDirection RoomRot)
|
|
{
|
|
return Add(RoomRot, Direction);
|
|
}
|
|
|
|
void URoom::SetRotationFromDoor(int DoorIndex, EDoorDirection WorldRot)
|
|
{
|
|
check(DoorIndex >= 0 && DoorIndex < RoomData->Doors.Num());
|
|
Direction = Add(Sub(WorldRot, RoomData->Doors[DoorIndex].Direction), EDoorDirection::South);
|
|
}
|
|
|
|
void URoom::SetPositionFromDoor(int DoorIndex, FIntVector WorldPos)
|
|
{
|
|
check(DoorIndex >= 0 && DoorIndex < RoomData->Doors.Num());
|
|
Position = WorldPos - RoomToWorld(RoomData->Doors[DoorIndex].Position);
|
|
}
|
|
|
|
void URoom::SetPositionAndRotationFromDoor(int DoorIndex, FIntVector WorldPos, EDoorDirection WorldRot)
|
|
{
|
|
check(DoorIndex >= 0 && DoorIndex < RoomData->Doors.Num());
|
|
Direction = Sub(WorldRot, RoomData->Doors[DoorIndex].Direction);
|
|
Position = WorldPos - RoomToWorld(RoomData->Doors[DoorIndex].Position);
|
|
}
|
|
|
|
|
|
bool URoom::IsOccupied(FIntVector Cell)
|
|
{
|
|
FIntVector local = WorldToRoom(Cell);
|
|
return local.X >= 0 && local.X < RoomData->Size.X
|
|
&& local.Y >= 0 && local.Y < RoomData->Size.Y
|
|
&& local.Z >= 0 && local.Z < RoomData->Size.Z;
|
|
}
|
|
|
|
void URoom::TryConnectToExistingDoors(TArray<URoom*>& RoomList)
|
|
{
|
|
for(int i = 0; i < RoomData->GetNbDoor(); ++i)
|
|
{
|
|
EDoorDirection dir = GetDoorWorldOrientation(i);
|
|
FIntVector pos = GetDoorWorldPosition(i) + URoom::GetDirection(dir);
|
|
URoom* otherRoom = GetRoomAt(pos, RoomList);
|
|
|
|
if(IsValid(otherRoom))
|
|
{
|
|
int j = otherRoom->GetDoorIndexAt(pos, URoom::Opposite(dir));
|
|
if(j >= 0) // -1 if no door
|
|
{
|
|
Connect(*this, i, *otherRoom, j);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FIntVector Max(const FIntVector& A, const FIntVector& B)
|
|
{
|
|
return FIntVector(FMath::Max(A.X, B.X), FMath::Max(A.Y, B.Y), FMath::Max(A.Z, B.Z));
|
|
}
|
|
|
|
FIntVector Min(const FIntVector& A, const FIntVector& B)
|
|
{
|
|
return FIntVector(FMath::Min(A.X, B.X), FMath::Min(A.Y, B.Y), FMath::Min(A.Z, B.Z));
|
|
}
|
|
|
|
// AABB Overlapping
|
|
bool URoom::Overlap(URoom& A, URoom& B)
|
|
{
|
|
FIntVector A_firstPoint = A.Position;
|
|
FIntVector B_firstPoint = B.Position;
|
|
FIntVector A_secondPoint = A.RoomToWorld(A.RoomData->Size - FIntVector(1,1,1));
|
|
FIntVector B_secondPoint = B.RoomToWorld(B.RoomData->Size - FIntVector(1,1,1));
|
|
|
|
FIntVector A_min = Min(A_firstPoint, A_secondPoint);
|
|
FIntVector A_max = Max(A_firstPoint, A_secondPoint);
|
|
FIntVector B_min = Min(B_firstPoint, B_secondPoint);
|
|
FIntVector B_max = Max(B_firstPoint, B_secondPoint);
|
|
|
|
if (A_min.X > B_max.X) return false;
|
|
if (A_max.X < B_min.X) return false;
|
|
if (A_min.Y > B_max.Y) return false;
|
|
if (A_max.Y < B_min.Y) return false;
|
|
if (A_min.Z > B_max.Z) return false;
|
|
if (A_max.Z < B_min.Z) return false;
|
|
return true;
|
|
}
|
|
|
|
bool URoom::Overlap(URoom & Room, TArray<URoom*>& RoomList)
|
|
{
|
|
bool overlap = false;
|
|
for (int i = 0; i < RoomList.Num() && !overlap; i++)
|
|
{
|
|
if (Overlap(Room, *RoomList[i]))
|
|
{
|
|
overlap = true;
|
|
}
|
|
}
|
|
return overlap;
|
|
}
|
|
|
|
EDoorDirection URoom::Add(EDoorDirection A, EDoorDirection B)
|
|
{
|
|
int8 D = (int8)A + (int8)B;
|
|
while (D > 2) D -= 4;
|
|
while (D <= -2) D += 4;
|
|
return (EDoorDirection)D;
|
|
}
|
|
|
|
EDoorDirection URoom::Sub(EDoorDirection A, EDoorDirection B)
|
|
{
|
|
int8 D = (int8)A - (int8)B;
|
|
while (D > 2) D -= 4;
|
|
while (D <= -2) D += 4;
|
|
return (EDoorDirection)D;
|
|
}
|
|
|
|
EDoorDirection URoom::Opposite(EDoorDirection O)
|
|
{
|
|
return Add(O, EDoorDirection::South);
|
|
}
|
|
|
|
FIntVector URoom::GetDirection(EDoorDirection O)
|
|
{
|
|
FIntVector Dir = FIntVector::ZeroValue;
|
|
switch (O)
|
|
{
|
|
case EDoorDirection::North:
|
|
Dir.X = 1;
|
|
break;
|
|
case EDoorDirection::East:
|
|
Dir.Y = 1;
|
|
break;
|
|
case EDoorDirection::West:
|
|
Dir.Y = -1;
|
|
break;
|
|
case EDoorDirection::South:
|
|
Dir.X = -1;
|
|
break;
|
|
}
|
|
return Dir;
|
|
}
|
|
|
|
FIntVector URoom::Rotate(FIntVector Pos, EDoorDirection Rot)
|
|
{
|
|
FIntVector NewPos = Pos;
|
|
switch (Rot)
|
|
{
|
|
case EDoorDirection::North:
|
|
NewPos = Pos;
|
|
break;
|
|
case EDoorDirection::West:
|
|
NewPos.Y = -Pos.X;
|
|
NewPos.X = Pos.Y;
|
|
break;
|
|
case EDoorDirection::East:
|
|
NewPos.Y = Pos.X;
|
|
NewPos.X = -Pos.Y;
|
|
break;
|
|
case EDoorDirection::South:
|
|
NewPos.Y = -Pos.Y;
|
|
NewPos.X = -Pos.X;
|
|
break;
|
|
}
|
|
return NewPos;
|
|
}
|
|
|
|
FVector URoom::GetRealDoorPosition(FIntVector DoorCell, EDoorDirection DoorRot)
|
|
{
|
|
return URoom::Unit() * (FVector(DoorCell) + 0.5f * FVector(URoom::GetDirection(DoorRot)) + FVector(0, 0, URoom::DoorOffset()));
|
|
}
|
|
|
|
void URoom::Connect(URoom& RoomA, int DoorA, URoom& RoomB, int DoorB)
|
|
{
|
|
RoomA.SetConnection(DoorA, &RoomB, DoorB);
|
|
RoomB.SetConnection(DoorB, &RoomA, DoorA);
|
|
}
|
|
|
|
URoom* URoom::GetRoomAt(FIntVector RoomCell, TArray<URoom*>& RoomList)
|
|
{
|
|
for(auto it = RoomList.begin(); it != RoomList.end(); ++it)
|
|
{
|
|
if(IsValid(*it) && (*it)->IsOccupied(RoomCell))
|
|
{
|
|
return *it;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
FVector URoom::Unit()
|
|
{
|
|
UProceduralDungeonSettings* Settings = GetMutableDefault<UProceduralDungeonSettings>();
|
|
return Settings->RoomUnit;
|
|
}
|
|
|
|
FVector URoom::DoorSize()
|
|
{
|
|
UProceduralDungeonSettings* Settings = GetMutableDefault<UProceduralDungeonSettings>();
|
|
return Settings->DoorSize;
|
|
}
|
|
|
|
float URoom::DoorOffset()
|
|
{
|
|
UProceduralDungeonSettings* Settings = GetMutableDefault<UProceduralDungeonSettings>();
|
|
return Settings->DoorOffset;
|
|
}
|
|
|
|
bool URoom::OcclusionCulling()
|
|
{
|
|
UProceduralDungeonSettings* Settings = GetMutableDefault<UProceduralDungeonSettings>();
|
|
return Settings->OcclusionCulling;
|
|
}
|
|
|
|
bool URoom::DrawDebug()
|
|
{
|
|
UProceduralDungeonSettings* Settings = GetMutableDefault<UProceduralDungeonSettings>();
|
|
return Settings->DrawDebug;
|
|
}
|
|
|
|
bool URoom::CanLoop()
|
|
{
|
|
UProceduralDungeonSettings* Settings = GetMutableDefault<UProceduralDungeonSettings>();
|
|
return Settings->CanLoop;
|
|
}
|
|
|