├── Plugins └── Navigation3D │ ├── Config │ └── FilterPlugin.ini │ ├── Content │ ├── BP_FlyingPawnExample.uasset │ └── M_Grid.uasset │ ├── Navigation3D.uplugin │ ├── Resources │ └── Icon128.png │ └── Source │ └── Navigation3D │ ├── Navigation3D.Build.cs │ ├── Private │ ├── NavNode.cpp │ ├── NavNode.h │ ├── Navigation3D.cpp │ └── NavigationVolume3D.cpp │ └── Public │ ├── Navigation3D.h │ └── NavigationVolume3D.h └── README.md /Plugins/Navigation3D/Config/FilterPlugin.ini: -------------------------------------------------------------------------------- 1 | [FilterPlugin] 2 | ; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and 3 | ; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively. 4 | ; 5 | ; Examples: 6 | ; /README.txt 7 | ; /Extras/... 8 | ; /Binaries/ThirdParty/*.dll 9 | -------------------------------------------------------------------------------- /Plugins/Navigation3D/Content/BP_FlyingPawnExample.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reidtreharne/Navigation3D/6c3543136d9d20ebf553164f7e06039a5a667091/Plugins/Navigation3D/Content/BP_FlyingPawnExample.uasset -------------------------------------------------------------------------------- /Plugins/Navigation3D/Content/M_Grid.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reidtreharne/Navigation3D/6c3543136d9d20ebf553164f7e06039a5a667091/Plugins/Navigation3D/Content/M_Grid.uasset -------------------------------------------------------------------------------- /Plugins/Navigation3D/Navigation3D.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 1, 4 | "VersionName": "1.0", 5 | "FriendlyName": "Navigation3D", 6 | "Description": "Navigation system for 3D pathfinding", 7 | "Category": "Other", 8 | "CreatedBy": "Reid", 9 | "CreatedByURL": "", 10 | "DocsURL": "", 11 | "MarketplaceURL": "", 12 | "SupportURL": "", 13 | "CanContainContent": true, 14 | "IsBetaVersion": false, 15 | "IsExperimentalVersion": false, 16 | "Installed": false, 17 | "Modules": [ 18 | { 19 | "Name": "Navigation3D", 20 | "Type": "Runtime", 21 | "LoadingPhase": "Default", 22 | "BlacklistPlatform": [ 23 | "IOS", 24 | "MAC", 25 | "Linux" 26 | ], 27 | "WhitelistPlatforms" : 28 | [ 29 | "Win64", 30 | "Android" 31 | ] 32 | } 33 | ], 34 | "Plugins": [ 35 | { 36 | "Name": "ProceduralMeshComponent", 37 | "Enabled": true 38 | } 39 | ] 40 | } -------------------------------------------------------------------------------- /Plugins/Navigation3D/Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reidtreharne/Navigation3D/6c3543136d9d20ebf553164f7e06039a5a667091/Plugins/Navigation3D/Resources/Icon128.png -------------------------------------------------------------------------------- /Plugins/Navigation3D/Source/Navigation3D/Navigation3D.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class Navigation3D : ModuleRules 6 | { 7 | public Navigation3D(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; 10 | 11 | PublicIncludePaths.AddRange( 12 | new string[] { 13 | // ... add public include paths required here ... 14 | } 15 | ); 16 | 17 | 18 | PrivateIncludePaths.AddRange( 19 | new string[] { 20 | // ... add other private include paths required here ... 21 | } 22 | ); 23 | 24 | 25 | PublicDependencyModuleNames.AddRange( 26 | new string[] 27 | { 28 | "Core", 29 | // ... add other public dependencies that you statically link with here ... 30 | } 31 | ); 32 | 33 | 34 | PrivateDependencyModuleNames.AddRange( 35 | new string[] 36 | { 37 | "CoreUObject", 38 | "Engine", 39 | "Slate", 40 | "SlateCore", 41 | "ProceduralMeshComponent", 42 | "AIModule", 43 | // ... add private dependencies that you statically link with here ... 44 | } 45 | ); 46 | 47 | 48 | DynamicallyLoadedModuleNames.AddRange( 49 | new string[] 50 | { 51 | // ... add any modules that your module loads dynamically here ... 52 | } 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Plugins/Navigation3D/Source/Navigation3D/Private/NavNode.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | 4 | #include "NavNode.h" 5 | -------------------------------------------------------------------------------- /Plugins/Navigation3D/Source/Navigation3D/Private/NavNode.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include 7 | 8 | class NavNode 9 | { 10 | public: 11 | FIntVector Coordinates; 12 | 13 | std::vector Neighbors; 14 | 15 | float FScore = FLT_MAX; 16 | }; 17 | 18 | struct NodeCompare 19 | { 20 | bool operator() (const NavNode* lhs, const NavNode* rhs) const 21 | { 22 | return (lhs->FScore < rhs->FScore); 23 | } 24 | }; 25 | 26 | 27 | -------------------------------------------------------------------------------- /Plugins/Navigation3D/Source/Navigation3D/Private/Navigation3D.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "Navigation3D.h" 4 | 5 | #define LOCTEXT_NAMESPACE "FNavigation3DModule" 6 | 7 | void FNavigation3DModule::StartupModule() 8 | { 9 | // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module 10 | } 11 | 12 | void FNavigation3DModule::ShutdownModule() 13 | { 14 | // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, 15 | // we call this function before unloading the module. 16 | } 17 | 18 | #undef LOCTEXT_NAMESPACE 19 | 20 | IMPLEMENT_MODULE(FNavigation3DModule, Navigation3D) -------------------------------------------------------------------------------- /Plugins/Navigation3D/Source/Navigation3D/Private/NavigationVolume3D.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | 4 | #include "NavigationVolume3D.h" 5 | #include "ProceduralMeshComponent.h" 6 | #include "Materials/MaterialInstanceDynamic.h" 7 | #include "UObject/ConstructorHelpers.h" 8 | #include "Kismet/KismetMathLibrary.h" 9 | #include "Kismet/KismetSystemLibrary.h" 10 | #include "NavNode.h" 11 | #include 12 | #include 13 | 14 | static UMaterial* GridMaterial = nullptr; 15 | 16 | // Sets default values 17 | ANavigationVolume3D::ANavigationVolume3D() 18 | { 19 | // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. 20 | PrimaryActorTick.bCanEverTick = true; 21 | 22 | // Create the default scene component 23 | DefaultSceneComponent = CreateDefaultSubobject("DefaultSceneComponent"); 24 | SetRootComponent(DefaultSceneComponent); 25 | 26 | // Create the procedural mesh component 27 | ProceduralMesh = CreateDefaultSubobject("ProceduralMesh"); 28 | ProceduralMesh->SetupAttachment(GetRootComponent()); 29 | ProceduralMesh->CastShadow = false; 30 | ProceduralMesh->SetEnableGravity(false); 31 | ProceduralMesh->bApplyImpulseOnDamage = false; 32 | ProceduralMesh->SetGenerateOverlapEvents(false); 33 | ProceduralMesh->CanCharacterStepUpOn = ECanBeCharacterBase::ECB_No; 34 | ProceduralMesh->SetCollisionProfileName("NoCollision"); 35 | ProceduralMesh->bHiddenInGame = false; 36 | 37 | // By default, hide the volume while the game is running 38 | SetActorHiddenInGame(true); 39 | 40 | // Find and save the grid material 41 | static ConstructorHelpers::FObjectFinder materialFinder(TEXT("Material'/Navigation3D/M_Grid.M_Grid'")); 42 | GridMaterial = materialFinder.Object; 43 | } 44 | 45 | void ANavigationVolume3D::OnConstruction(const FTransform& Transform) 46 | { 47 | Super::OnConstruction(Transform); 48 | 49 | // Create arrays to store the vertices and the triangles 50 | TArray vertices; 51 | TArray triangles; 52 | 53 | // Define variables for the start and end of the line 54 | FVector start = FVector::ZeroVector; 55 | FVector end = FVector::ZeroVector; 56 | 57 | // Create the X lines geometry 58 | for (int32 z = 0; z <= DivisionsZ; ++z) 59 | { 60 | start.Z = end.Z = z * DivisionSize; 61 | for (int32 x = 0; x <= DivisionsX; ++x) 62 | { 63 | start.X = end.X = (x * DivisionSize); 64 | end.Y = GetGridSizeY(); 65 | 66 | CreateLine(start, end, FVector::UpVector, vertices, triangles); 67 | } 68 | } 69 | 70 | // Reset start and end variables 71 | start = FVector::ZeroVector; 72 | end = FVector::ZeroVector; 73 | 74 | // Create the Y lines geometry 75 | for (int32 z = 0; z <= DivisionsZ; ++z) 76 | { 77 | start.Z = end.Z = z * DivisionSize; 78 | for (int32 y = 0; y <= DivisionsY; ++y) 79 | { 80 | start.Y = end.Y = (y * DivisionSize); 81 | end.X = GetGridSizeX(); 82 | 83 | CreateLine(start, end, FVector::UpVector, vertices, triangles); 84 | } 85 | } 86 | 87 | // Reset start and end variables 88 | start = FVector::ZeroVector; 89 | end = FVector::ZeroVector; 90 | 91 | // Create the Z lines geometry 92 | for (int32 x = 0; x <= DivisionsX; ++x) 93 | { 94 | start.X = end.X = x * DivisionSize; 95 | for (int32 y = 0; y <= DivisionsY; ++y) 96 | { 97 | start.Y = end.Y = (y * DivisionSize); 98 | end.Z = GetGridSizeZ(); 99 | 100 | CreateLine(start, end, FVector::ForwardVector, vertices, triangles); 101 | } 102 | } 103 | 104 | // Unused variables that are required to be passed to CreateMeshSection 105 | TArray Normals; 106 | TArray UVs; 107 | TArray Colors; 108 | TArray Tangents; 109 | 110 | // Add the geometry to the procedural mesh so it will render 111 | ProceduralMesh->CreateMeshSection(0, vertices, triangles, Normals, UVs, Colors, Tangents, false); 112 | 113 | // Set the material on the procedural mesh so it's color/opacity can be configurable 114 | UMaterialInstanceDynamic* dynamicMaterialInstance = UMaterialInstanceDynamic::Create(GridMaterial, this); 115 | dynamicMaterialInstance->SetVectorParameterValue("Color", Color); 116 | dynamicMaterialInstance->SetScalarParameterValue("Opacity", Color.A); 117 | ProceduralMesh->SetMaterial(0, dynamicMaterialInstance); 118 | } 119 | 120 | // Called when the game starts or when spawned 121 | void ANavigationVolume3D::BeginPlay() 122 | { 123 | Super::BeginPlay(); 124 | 125 | // Allocate nodes used for pathfinding 126 | Nodes = new NavNode[GetTotalDivisions()]; 127 | 128 | // Helper lambda for adding a neighbor 129 | auto addNeighborIfValid = [&](NavNode* node, const FIntVector& neighbor_coordinates) 130 | { 131 | // Make sure the neighboring coordinates are valid 132 | if (AreCoordinatesValid(neighbor_coordinates)) 133 | { 134 | int32 sharedAxes = 0; 135 | if (node->Coordinates.X == neighbor_coordinates.X) 136 | ++sharedAxes; 137 | if (node->Coordinates.Y == neighbor_coordinates.Y) 138 | ++sharedAxes; 139 | if (node->Coordinates.Z == neighbor_coordinates.Z) 140 | ++sharedAxes; 141 | 142 | // Only add the neighbor if we share more axes with it then the required min shared neighbor axes and it isn't the same node as us 143 | if (sharedAxes >= MinSharedNeighborAxes && sharedAxes < 3) 144 | { 145 | node->Neighbors.push_back(GetNode(neighbor_coordinates)); 146 | } 147 | } 148 | }; 149 | 150 | // For each node, find its neighbors and assign its coordinates 151 | for (int32 z = 0; z < DivisionsZ; ++z) 152 | { 153 | for (int32 y = 0; y < DivisionsY; ++y) 154 | { 155 | for (int32 x = 0; x < DivisionsX; ++x) 156 | { 157 | NavNode* node = GetNode(FIntVector(x, y, z)); 158 | node->Coordinates = FIntVector(x, y, z); 159 | 160 | // Above neighbors 161 | { 162 | // front 163 | { 164 | addNeighborIfValid(node, FIntVector(x + 1, y - 1, z + 1)); 165 | addNeighborIfValid(node, FIntVector(x + 1, y + 0, z + 1)); 166 | addNeighborIfValid(node, FIntVector(x + 1, y + 1, z + 1)); 167 | } 168 | // middle 169 | { 170 | addNeighborIfValid(node, FIntVector(x + 0, y - 1, z + 1)); 171 | addNeighborIfValid(node, FIntVector(x + 0, y + 0, z + 1)); 172 | addNeighborIfValid(node, FIntVector(x + 0, y + 1, z + 1)); 173 | } 174 | // back 175 | { 176 | addNeighborIfValid(node, FIntVector(x - 1, y - 1, z + 1)); 177 | addNeighborIfValid(node, FIntVector(x - 1, y + 0, z + 1)); 178 | addNeighborIfValid(node, FIntVector(x - 1, y + 1, z + 1)); 179 | } 180 | } 181 | 182 | // Middle neighbors 183 | { 184 | // front 185 | { 186 | addNeighborIfValid(node, FIntVector(x + 1, y - 1, z + 0)); 187 | addNeighborIfValid(node, FIntVector(x + 1, y + 0, z + 0)); 188 | addNeighborIfValid(node, FIntVector(x + 1, y + 1, z + 0)); 189 | } 190 | // middle 191 | { 192 | addNeighborIfValid(node, FIntVector(x + 0, y - 1, z + 0)); 193 | addNeighborIfValid(node, FIntVector(x + 0, y + 1, z + 0)); 194 | } 195 | // back 196 | { 197 | addNeighborIfValid(node, FIntVector(x - 1, y - 1, z + 0)); 198 | addNeighborIfValid(node, FIntVector(x - 1, y + 0, z + 0)); 199 | addNeighborIfValid(node, FIntVector(x - 1, y + 1, z + 0)); 200 | } 201 | } 202 | 203 | // Below neighbors 204 | { 205 | // front 206 | { 207 | addNeighborIfValid(node, FIntVector(x + 1, y - 1, z - 1)); 208 | addNeighborIfValid(node, FIntVector(x + 1, y + 0, z - 1)); 209 | addNeighborIfValid(node, FIntVector(x + 1, y + 1, z - 1)); 210 | } 211 | // middle 212 | { 213 | addNeighborIfValid(node, FIntVector(x + 0, y - 1, z - 1)); 214 | addNeighborIfValid(node, FIntVector(x + 0, y + 0, z - 1)); 215 | addNeighborIfValid(node, FIntVector(x + 0, y + 1, z - 1)); 216 | } 217 | // back 218 | { 219 | addNeighborIfValid(node, FIntVector(x - 1, y - 1, z - 1)); 220 | addNeighborIfValid(node, FIntVector(x - 1, y + 0, z - 1)); 221 | addNeighborIfValid(node, FIntVector(x - 1, y + 1, z - 1)); 222 | } 223 | } 224 | } 225 | } 226 | } 227 | } 228 | 229 | void ANavigationVolume3D::EndPlay(const EEndPlayReason::Type EndPlayReason) 230 | { 231 | // Delete the nodes 232 | delete [] Nodes; 233 | Nodes = nullptr; 234 | 235 | Super::EndPlay(EndPlayReason); 236 | } 237 | 238 | // Called every frame 239 | void ANavigationVolume3D::Tick(float DeltaTime) 240 | { 241 | Super::Tick(DeltaTime); 242 | } 243 | 244 | bool ANavigationVolume3D::FindPath(const FVector& start, const FVector& destination, const TArray >& object_types, UClass* actor_class_filter, TArray& out_path) 245 | { 246 | // Clear the out path 247 | out_path.Empty(); 248 | 249 | std::multiset openSet; 250 | std::unordered_map cameFrom; 251 | std::unordered_map gScores; 252 | 253 | NavNode* startNode = GetNode(ConvertLocationToCoordinates(start)); 254 | NavNode* endNode = GetNode(ConvertLocationToCoordinates(destination)); 255 | 256 | auto h = [endNode](NavNode* node) 257 | { 258 | return FVector::Distance(FVector(endNode->Coordinates), FVector(node->Coordinates)); 259 | }; 260 | auto distance = [](NavNode* node1, NavNode* node2) 261 | { 262 | return FVector::Distance(FVector(node1->Coordinates), FVector(node2->Coordinates)); 263 | }; 264 | auto gScore = [&gScores](NavNode* node) 265 | { 266 | auto iter = gScores.find(node); 267 | if (iter != gScores.end()) 268 | return iter->second; 269 | return FLT_MAX; 270 | }; 271 | 272 | startNode->FScore = h(startNode); 273 | openSet.insert(startNode); 274 | gScores[startNode] = 0.0f; 275 | 276 | while (openSet.empty() == false) 277 | { 278 | NavNode* current = *openSet.begin(); 279 | 280 | if (current == endNode) 281 | { 282 | // Rebuild the path 283 | out_path.Add(ConvertCoordinatesToLocation(current->Coordinates)); 284 | 285 | while (true) 286 | { 287 | auto iter = cameFrom.find(current); 288 | if (iter != cameFrom.end()) 289 | { 290 | current = iter->second; 291 | out_path.Insert(ConvertCoordinatesToLocation(current->Coordinates), 0); 292 | } 293 | else 294 | { 295 | return true; 296 | } 297 | } 298 | } 299 | 300 | openSet.erase(openSet.begin()); 301 | 302 | for (NavNode* neighbor : current->Neighbors) 303 | { 304 | const float tentative_gScore = gScore(current) + distance(current, neighbor); 305 | 306 | if (tentative_gScore < gScore(neighbor)) 307 | { 308 | TArray outActors; 309 | const FVector worldLocation = ConvertCoordinatesToLocation(neighbor->Coordinates); 310 | bool traversable = !(UKismetSystemLibrary::BoxOverlapActors(GWorld, worldLocation, FVector(GetDivisionSize() / 2.0f), object_types, actor_class_filter, TArray(), outActors)); 311 | if (traversable == true) 312 | { 313 | cameFrom[neighbor] = current; 314 | gScores[neighbor] = tentative_gScore; 315 | const float fScore = tentative_gScore + h(neighbor); 316 | neighbor->FScore = fScore; 317 | openSet.insert(neighbor); 318 | } 319 | } 320 | } 321 | } 322 | 323 | // Failed to find path 324 | return false; 325 | } 326 | 327 | FIntVector ANavigationVolume3D::ConvertLocationToCoordinates(const FVector& location) 328 | { 329 | FIntVector coordinates; 330 | 331 | // Convert the location into grid space 332 | const FVector gridSpaceLocation = UKismetMathLibrary::InverseTransformLocation(GetActorTransform(), location); 333 | 334 | // Convert the grid space location to a coordinate (x,y,z) 335 | coordinates.X = DivisionsX * (gridSpaceLocation.X / GetGridSizeX()); 336 | coordinates.Y = DivisionsY * (gridSpaceLocation.Y / GetGridSizeY()); 337 | coordinates.Z = DivisionsZ * (gridSpaceLocation.Z / GetGridSizeZ()); 338 | 339 | ClampCoordinates(coordinates); 340 | return coordinates; 341 | } 342 | 343 | FVector ANavigationVolume3D::ConvertCoordinatesToLocation(const FIntVector& coordinates) 344 | { 345 | FIntVector clampedCoordinates(coordinates); 346 | ClampCoordinates(clampedCoordinates); 347 | 348 | // Convert the coordinates into a grid space location 349 | FVector gridSpaceLocation = FVector::ZeroVector; 350 | gridSpaceLocation.X = (clampedCoordinates.X * DivisionSize) + (DivisionSize * 0.5f); 351 | gridSpaceLocation.Y = (clampedCoordinates.Y * DivisionSize) + (DivisionSize * 0.5f); 352 | gridSpaceLocation.Z = (clampedCoordinates.Z * DivisionSize) + (DivisionSize * 0.5f); 353 | 354 | // Convert the grid space location into world space 355 | return UKismetMathLibrary::TransformLocation(GetActorTransform(), gridSpaceLocation); 356 | } 357 | 358 | void ANavigationVolume3D::CreateLine(const FVector& start, const FVector& end, const FVector& normal, TArray& vertices, TArray& triangles) 359 | { 360 | // Calculate the half line thickness and the thickness direction 361 | float halfLineThickness = LineThickness / 2.0f; 362 | FVector line = end - start; 363 | line.Normalize(); 364 | 365 | // 0--------------------------1 366 | // | line | 367 | // 2--------------------------3 368 | auto createFlatLine = [&](const FVector& thicknessDirection) 369 | { 370 | // Top triangle 371 | triangles.Add(vertices.Num() + 2); 372 | triangles.Add(vertices.Num() + 1); 373 | triangles.Add(vertices.Num() + 0); 374 | 375 | // Bottom triangle 376 | triangles.Add(vertices.Num() + 2); 377 | triangles.Add(vertices.Num() + 3); 378 | triangles.Add(vertices.Num() + 1); 379 | 380 | // Vertex 0 381 | vertices.Add(start + (thicknessDirection * halfLineThickness)); 382 | // Vertex 1 383 | vertices.Add(end + (thicknessDirection * halfLineThickness)); 384 | // Vertex 2 385 | vertices.Add(start - (thicknessDirection * halfLineThickness)); 386 | // Vertex 3 387 | vertices.Add(end - (thicknessDirection * halfLineThickness)); 388 | }; 389 | 390 | FVector direction1 = UKismetMathLibrary::Cross_VectorVector(line, normal); 391 | FVector direction2 = UKismetMathLibrary::Cross_VectorVector(line, direction1); 392 | 393 | createFlatLine(direction1); 394 | createFlatLine(direction2); 395 | } 396 | 397 | bool ANavigationVolume3D::AreCoordinatesValid(const FIntVector& coordinates) const 398 | { 399 | return coordinates.X >= 0 && coordinates.X < DivisionsX && 400 | coordinates.Y >= 0 && coordinates.Y < DivisionsY && 401 | coordinates.Z >= 0 && coordinates.Z < DivisionsZ; 402 | } 403 | 404 | void ANavigationVolume3D::ClampCoordinates(FIntVector& coordinates) const 405 | { 406 | coordinates.X = FMath::Clamp(coordinates.X, 0, DivisionsX - 1); 407 | coordinates.Y = FMath::Clamp(coordinates.Y, 0, DivisionsY - 1); 408 | coordinates.Z = FMath::Clamp(coordinates.Z, 0, DivisionsZ - 1); 409 | } 410 | 411 | NavNode* ANavigationVolume3D::GetNode(FIntVector coordinates) const 412 | { 413 | ClampCoordinates(coordinates); 414 | 415 | const int32 divisionPerLevel = DivisionsX * DivisionsY; 416 | const int32 index = (coordinates.Z * divisionPerLevel) + (coordinates.Y * DivisionsX) + coordinates.X; 417 | return &Nodes[index]; 418 | } 419 | 420 | -------------------------------------------------------------------------------- /Plugins/Navigation3D/Source/Navigation3D/Public/Navigation3D.h: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Modules/ModuleManager.h" 7 | 8 | class FNavigation3DModule : public IModuleInterface 9 | { 10 | public: 11 | 12 | /** IModuleInterface implementation */ 13 | virtual void StartupModule() override; 14 | virtual void ShutdownModule() override; 15 | }; 16 | -------------------------------------------------------------------------------- /Plugins/Navigation3D/Source/Navigation3D/Public/NavigationVolume3D.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "GameFramework/Actor.h" 7 | #include "NavigationVolume3D.generated.h" 8 | 9 | class UProceduralMeshComponent; 10 | class NavNode; 11 | 12 | UCLASS() 13 | class NAVIGATION3D_API ANavigationVolume3D : public AActor 14 | { 15 | GENERATED_BODY() 16 | 17 | public: 18 | // Sets default values for this actor's properties 19 | ANavigationVolume3D(); 20 | 21 | private: 22 | // The default (root) scene component 23 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "NavigationVolume3D", meta = (AllowPrivateAccess = "true")) 24 | USceneComponent* DefaultSceneComponent = nullptr; 25 | 26 | // The procedural mesh responsbile for rendering the grid 27 | UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "NavigationVolume3D", meta = (AllowPrivateAccess = "true")) 28 | UProceduralMeshComponent* ProceduralMesh = nullptr; 29 | 30 | // The number of divisions in the grid along the X axis 31 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "NavigationVolume3D|Pathfinding", meta = (AllowPrivateAccess = "true", ClampMin = 1)) 32 | int32 DivisionsX = 10; 33 | 34 | // The number of divisions in the grid along the Y axis 35 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "NavigationVolume3D|Pathfinding", meta = (AllowPrivateAccess = "true", ClampMin = 1)) 36 | int32 DivisionsY = 10; 37 | 38 | // The number of divisions in the grid along the Z axis 39 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "NavigationVolume3D|Pathfinding", meta = (AllowPrivateAccess = "true", ClampMin = 1)) 40 | int32 DivisionsZ = 10; 41 | 42 | // The size of each division 43 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "NavigationVolume3D|Pathfinding", meta = (AllowPrivateAccess = "true", ClampMin = 1)) 44 | float DivisionSize = 100.0f; 45 | 46 | // The minimum number of axes that must be shared with a neighboring node for it to be counted a neighbor 47 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "NavigationVolume3D|Pathfinding", meta = (AllowPrivateAccess = "true", ClampMin = 0, ClampMax = 2)) 48 | int32 MinSharedNeighborAxes = 0; 49 | 50 | // The thickness of the grid lines 51 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "NavigationVolume3D|Aesthetics", meta = (AllowPrivateAccess = "true", ClampMin = 0)) 52 | float LineThickness = 2.0f; 53 | 54 | // The color of the grid 55 | UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "NavigationVolume3D|Aesthetics", meta = (AllowPrivateAccess = "true")) 56 | FLinearColor Color = FLinearColor(0.0f, 0.0f, 0.0f, 0.5f); 57 | 58 | public: 59 | 60 | /** 61 | * Called when an instance of this class is placed (in editor) or spawned. 62 | * @param Transform The transform the actor was constructed at. 63 | */ 64 | virtual void OnConstruction(const FTransform& Transform) override; 65 | 66 | // Called every frame 67 | virtual void Tick(float DeltaTime) override; 68 | 69 | // Gets the node at the specified coordinates 70 | NavNode* GetNode(FIntVector coordinates) const; 71 | 72 | // Finds a path from the starting location to the destination 73 | UFUNCTION(BlueprintCallable, Category = "NavigationVolume3D") 74 | bool FindPath(const FVector& start, const FVector& destination, const TArray >& object_types, UClass* actor_class_filter, TArray& out_path); 75 | 76 | /** 77 | * Converts a world space location to a coordinate in the grid. If the location is not located within the grid, 78 | * the coordinate will be clamped to the closest coordinate. 79 | * @param location The location to convert 80 | * @return The converted coordinates 81 | */ 82 | UFUNCTION(BlueprintCallable, Category = "NavigationVolume3D") 83 | UPARAM(DisplayName = "Coordinates") FIntVector ConvertLocationToCoordinates(const FVector& location); 84 | 85 | /** 86 | * Converts a coordinate into a world space location. If the coordinate is not within the bounds of the grid, 87 | * the coordinate will be clamped to the closest coordinate. 88 | * @param coordinates The coordinates to convert into world space 89 | * @return The converted location in world space 90 | */ 91 | UFUNCTION(BlueprintCallable, Category = "NavigationVolume3D") 92 | UPARAM(DisplayName = "World Location") FVector ConvertCoordinatesToLocation(const FIntVector& coordinates); 93 | 94 | // Gets the total number of divisions in the grid 95 | UFUNCTION(BlueprintPure, Category = "NavigationVolume3D") 96 | FORCEINLINE int32 GetTotalDivisions() const { return DivisionsX * DivisionsY * DivisionsZ; } 97 | 98 | // Gets the number of x divisions in the grid 99 | UFUNCTION(BlueprintPure, Category = "NavigationVolume3D") 100 | FORCEINLINE int32 GetDivisionsX() const { return DivisionsX; } 101 | 102 | // Gets the number of y divisions in the grid 103 | UFUNCTION(BlueprintPure, Category = "NavigationVolume3D") 104 | FORCEINLINE int32 GetDivisionsY() const { return DivisionsY; } 105 | 106 | // Gets the number of z divisions in the grid 107 | UFUNCTION(BlueprintPure, Category = "NavigationVolume3D") 108 | FORCEINLINE int32 GetDivisionsZ() const { return DivisionsZ; } 109 | 110 | // Gets the size of each division in the grid 111 | UFUNCTION(BlueprintPure, Category = "NavigationVolume3D") 112 | FORCEINLINE float GetDivisionSize() const { return DivisionSize; } 113 | 114 | protected: 115 | // Called when the game starts or when spawned 116 | virtual void BeginPlay() override; 117 | 118 | // Overridable function called whenever this actor is being removed from a level 119 | virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; 120 | 121 | // Gets the size of the grid along the X axis 122 | inline float GetGridSizeX() const { return DivisionsX * DivisionSize; } 123 | 124 | // Gets the size of the grid along the Y axis 125 | inline float GetGridSizeY() const { return DivisionsY * DivisionSize; } 126 | 127 | // Gets the size of the grid along the Z axis 128 | inline float GetGridSizeZ() const { return DivisionsZ * DivisionSize; } 129 | 130 | private: 131 | // Helper function for creating the geometry for a single line of the grid 132 | void CreateLine(const FVector& start, const FVector& end, const FVector& normal, TArray& vertices, TArray& triangles); 133 | 134 | // Helper function to check if a coordinate is valid 135 | bool AreCoordinatesValid(const FIntVector& coordinates) const; 136 | 137 | // Helper function to clamp the coordinate to a valid one inside the grid 138 | void ClampCoordinates(FIntVector& coordinates) const; 139 | 140 | // The nodes used for pathfinding 141 | NavNode* Nodes = nullptr; 142 | }; 143 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Navigation3D 2 | 3 | This is a simple plugin for Unreal Engine for 3D pathfinding. 4 | 5 | Install Insutructions: 6 | 7 | 1. If you don't already have an Unreal Engine project created, create one and then close Unreal after it's created. 8 | 2. Clone or download the plugin from github 9 | 3. Copy and paste the "Plugins" folder from the download into the root of your Unreal Engine project direction (next to your .uproject, Content folder, etc...) 10 | 4. Launch your project. You may get a popup saying "The following modules are missing or built with a different engine version" and asking if you'd like to rebuild them. If so, press Yes. 11 | 5. The plugin is now added to your project 12 | --------------------------------------------------------------------------------