Files
UnrealEngineUWP/Engine/Source/Runtime/NavigationSystem/Private/NavigationSystem.cpp
Marc Audy d5628cd986 Copying //UE4/Dev-Framework to //UE4/Dev-Main (Source: //UE4/Dev-Framework @ 3967517)
#rb none
#lockdown Nick.Penwarden
#rnx

============================
  MAJOR FEATURES & CHANGES
============================

Change 3804281 by Fred.Kimberley

	Improve contrast on watches in blueprints.

Change 3804322 by Fred.Kimberley

	First pass at adding a watch window for blueprint debugging.

Change 3804737 by mason.seay

	Added some Descriptions to tests that didn't have any, and fixed some typos

Change 3806103 by mason.seay

	Moved and Renamed Timers test map and content appropriately

Change 3806164 by Fred.Kimberley

	Add missing property types to GetDebugInfoInternal.

	#jira UE-53355

Change 3806617 by Dan.Oconnor

	Function Terminator (and derived types) now use FMemberReference instead of a UClass/FName pair. This fixes various bugs when resolving the UFunction referenced by the function terminator

	#jira UE-31754, UE-42431, UE-53315, UE-53172

Change 3808541 by Fred.Kimberley

	Add support for redirecting user defined enums.
	This is in response to the following UDN thread: https://udn.unrealengine.com/questions/404141/is-is-possible-to-create-a-redirector-from-a-bluep.html

Change 3808565 by mason.seay

	Added a few more struct tests

Change 3809840 by mason.seay

	Renamed CharacterMovement.umap to CharacterCollision.  Fixed up content to reflect this change.

Change 3809847 by mason.seay

	Added Object Timer tests.  Fixed up existing timer test to remove delay dependency

Change 3811704 by Ben.Zeigler

	Fix issue where identical enum redirects registered to different initial names would throw an incorrect error, it's fine if the value change maps are identical

Change 3811946 by Ben.Zeigler

	#jira UE-53511 Fix it so it is possible to set a user defined struct value back to it's default. The UDS hack in PropertyValueToString is no longer needed, but this could affect some other user struct editor operations

Change 3812061 by Dan.Oconnor

	Stepping over or in to nodes that are expanded at compile time (e.g. event nodes, spawn actor nodes) no longer requires multiple 'steps'

	#jira UE-52854

Change 3812259 by Dan.Oconnor

	Fix asset broken by removal of an unkown enum

	#jira UE-51419

Change 3812904 by Ben.Zeigler

	Make ResolveRedirects on StreamableManager public as it can be used to validate things

Change 3812958 by Ben.Zeigler

	#jira UE-52977 Fix crashes when binding blueprint editor commands to keys and using from invalid contexts

Change 3812975 by Mieszko.Zielinski

	Added contraptions to catch a rare eidtor-time EQS crash #UE4

	#jira UE-53468

Change 3818530 by Phillip.Kavan

	Fix incorrect access to nested instanced subobjects in nativized Blueprint ctor codegen.

	Change summary:
	- Modified FEmitDefaultValueHelper::HandleInstancedSubobject() to properly reference the outer and check ptr validity when creating/obtaining nested default subobjects.
	- Modified FEmitDefaultValueHelper::HandleClassSubobject() to better guard against code generation based on an invalid local variable name.

	#jira UE-52167

Change 3819733 by Mieszko.Zielinski

	Marked UAISenseConfig_Blueprint and UAISense_Blueprint as hidedropdown #UE4

	#jira UE-15089

Change 3821776 by Marc.Audy

	Remove redundent code in SpawnActorFromClass that already exists in ConstructObjectFromClass parent class

Change 3823851 by mason.seay

	Moved and renamed blueprints used for Object Reference testing

Change 3824165 by Phillip.Kavan

	Ensure that subobject class types are constructed prior to accessing a subobject CDO in a nativized Blueprint class's generated ctor at runtime.

	Change summary:
	- Modified FFakeImportTableHelper to tag subobject class types as a preload dependency of the outer converted Blueprint class type and not of the CDO.

	#jira UE-53111

Change 3830309 by mason.seay

	Created Literal Gameplay Tag Container test

Change 3830562 by Phillip.Kavan

	Blueprint nativization bug fixes (reviewed/taken from PR).

	Change summary:
	- Modified FSafeContextScopedEmitter::ValidationChain() to ensure that generated code calls the global IsValid() utility function on objects.
	- Modified FBlueprintCompilerCppBackend::EmitCreateArrayStatement() to generate a proper cast on MakeArray node inputs for enum class types.
	- Modified FBlueprintCompilerCppBackend::EnimCallStatementInner() to more correctly identify an interface function call site.
	- Modified FEmitHelper::GenerateAutomaticCast() to properly handle automatic casts of enum arrays.
	- (Modified from PR source) Added new FComponentDataUtils statics to consolidate custom init code generation for converted special-case component types (e.g. BodyInstance). Ties native component DSOs to the same pre/post as converted non-native component templates around the OuterGenerate() loop.
	- Modified FExposeOnSpawnValidator::IsSupported() to include CPT_SoftObjectReference property types.
	- Modified UBlueprintGeneratedClass::CheckAndApplyComponentTemplateOverrides() to no longer break out of the loop before finding additional ICH override record matches.

	#4202

	#jira UE-52188

Change 3830579 by Fred.Kimberley

	Add support for turning off multiple watches at once in the watch window.

	#jira UE-53852

Change 3836047 by Zak.Middleton

	#ue4 - Dev test maps for overlaps perf tests.

Change 3836768 by Phillip.Kavan

	Fix for a build failure that could occur with Blueprint nativization enabled and EDL disabled. This was a regression introduced in 4.18.

	Change summary:
	- Modified FEmitDefaultValueHelper::AddStaticFunctionsForDependencies() to emit the correct signature for constructing FBlueprintDependencyData elements when the EDL boot time optimization is disabled.

	#jira UE-53908

Change 3838085 by mason.seay

	Functional tests around basic blueprint functions

Change 3840489 by Ben.Zeigler

	#jira UE-31662 Fix regression with renaming parent inherited function. It was not correctly searching the parent's skeleton class during the child's recompile so it was erroneously detecting the parent function as missing

Change 3840648 by mason.seay

	Updated Descriptions on tests

Change 3842914 by Ben.Zeigler

	Improve comments around stremable handle cancel/release

Change 3850413 by Ben.Zeigler

	Fix asset registry memory reporting, track some newer fields and correctly report the state size instead of static size twice
	Copy of CL #3849610

Change 3850426 by Ben.Zeigler

	Reduce asset registry memory in cooked build by stripping out searchable names and empty dependency nodes by default
	Add option to strip dependency data for asset data with no tags, this was always true before but isn't necessarily safe
	Copy of CL #3850389

Change 3853449 by Phillip.Kavan

	Fix a scoping issue for local instanced subobject references in nativized Blueprint C++ code. Also, don't emit redundant assignment statements for instanced subobject reference properties.

	Change summary:
	- Consolidated FComponentDataUtils into FDefaultSubobjectData and extended FNonativeComponentData from it in order to handle both native & non-native DSO initialization codegen through a more common interface.
	- Exposed FEmitDefaultValueHelper::HandleInstancedSubobject() as a public API and added a 'SubobjectData' parameter to allow initialization codegen to be deferred until after all default subobjects have been mapped to local variables within the current scope.
	- Modified FEmitDefaultValueHelper::GenerateConstructor() to first map all default subobjects to local variables and then emit any delta initialization code for property values.
	- Modified FEmitDefaultValueHelper::HandleSpecialTypes() to return an empty string for an instanced reference to a default subobject. This allows us to avoid emitting initialization statements to unnecessarily reassign instances back to the same property.
	- Modified FEmitDefaultValueHelper::InnerGenerate() to better handle instanced references to default subobjects, ensuring that we don't emit unnecessary assignment statements and array initialization code to the converted class constructor in C++.
	- Fixed a few typos.

	#jira UE-53960

Change 3853465 by Phillip.Kavan

	Fix plugin module C++ source template to conform to recent public include path changes.

Change 3857599 by Marc.Audy

	PR #4438: UE-54281: Make None a valid default value to select (Contributed by projectgheist)
	#jira UE-54281
	#jira UE-54399

Change 3863259 by Zak.Middleton

	#ue4 - Save bandwidth for replicated characters by only replicating 4 byte timestamp value to clients if it's actually needed for Linear smoothing. Added option to always replicate the timestamp ("bNetworkAlwaysReplicateTransformUpdateTimestamp", default off), in case users still want this timestamp for some reason, or if smoothing mode changes dynamically and the server won't know.

	#jira UE-46293

Change 3863491 by Zak.Middleton

	#ue4 - Reduce network RPC overhead for players that are not moving. Added ClientNetSendMoveDeltaTimeStationary (default 12Hz) to supplement existing ClientNetSendMoveDeltaTime and ClientNetSendMoveDeltaTimeThrottled. UCharacterMovementComponent::GetClientNetSendDeltaTime() now uses this time if Acceleration and Velocity are zero, and the control rotation matches the last ack'd control rotation from the server.

	Also fixed up code default for ClientNetSendMoveDeltaTime to match default INI value.

	#jira UE-21264

Change 3865325 by Zak.Middleton

	#ue4 - Fix static analysis warning about possible null PC pointer.

	#jira none

Change 3869828 by Ben.Zeigler

	#jira UE-54786 Fix it so -cookonthefly cooperates with -iterate by writing out a development asset registry

Change 3869969 by mason.seay

	Character Movement Functional Tests

Change 3870099 by Mason.Seay

	Submitted asset deletes

Change 3870105 by mason.seay

	Removed link to anim blueprint to fix errors

Change 3870238 by mason.seay

	Test map for Async Loading in a Loop

Change 3870479 by Ben.Zeigler

	Add code to check CoreRedirects for SoftObjectPaths when saving or resolving in the editor. This is a bit slow so we don't want to do it on load
	We don't have any good way to know the type of a path so I check both Object and Class redirectors, which will also pickup Module renames

Change 3875224 by mason.seay

	Functional tests for Event BeginPlay execution order

Change 3875409 by mason.seay

	Optimized and fixed up character movement tests (because a potential bug in FunctionalTestActor is always passing a test when it can fail)

Change 3878947 by Mieszko.Zielinski

	CIS fixes #UE4

Change 3879000 by Mieszko.Zielinski

	More CIS fixes #UE4

Change 3879139 by Mieszko.Zielinski

	Even moar CIS fixes #UE4

Change 3879742 by mason.seay

	Added animation to Nativization Widget asset

Change 3880198 by Zak.Middleton

	#ue4 - CanCrouchInCurrentState() returns false when character capsule is simulating physics.

	#jira UE-54875
	github #4479

Change 3880266 by Zak.Middleton

	#ue4 - Optimize UpdateCharacterStateBeforeMovement() to do cheaper tests earlier (avoid CanCrouchInCurrentState() unless necessary, now that it tests IsSimulatingPhysics() which is not trivial).

	#jira UE-54875

Change 3881546 by Mieszko.Zielinski

	*.Build.cs files clean up - removed redundant dependencies from NavigationSystem and AIModule #UE4

Change 3881547 by Mieszko.Zielinski

	Removed a bunch of DEPRECATED functions from the new NavigationSystem module #UE4

	Removed all deprecates prior 4.15 (picked this one because I do know some licencees are still using it).

Change 3881742 by mason.seay

	Additional crouch test to cover UE-54875

Change 3881794 by Mieszko.Zielinski

	Fixed a bug in FVisualLoggerHelpers::GetCategories resulting in losing verbosity information #UE4

Change 3884503 by Mieszko.Zielinski

	Fixed TopDown code template to make it compile after navsys refactor #UE4

	#jira UE-55039

Change 3884507 by Mieszko.Zielinski

	Switched ensures in UNavigationSystemV1:SimpleMoveToX to error-level logs #UE4

	It's an error rather than a warning because the functions no longer do anything. Making it work would require a cyclic dependency between NavigationSystem and AIModule.

	#jira UE-55033

Change 3884594 by Mieszko.Zielinski

	Added a const FNavigationSystem::GetCurrent version #UE4

	lack of it was causing KiteDemo to not compile.

Change 3884602 by Mieszko.Zielinski

	Mac editor compilation fix #UE4

Change 3884615 by Mieszko.Zielinski

	Fixed FAIDataProviderValue::GetRawValuePtr not being accessible from outside of AIModule #UE4

Change 3885254 by Mieszko.Zielinski

	Guessfix for UE-55030 #UE4

	The name of NavigationSystem module was put in wrong in the IMPLEMENT_MODULE macro

	#jira 55030

Change 3885286 by Mieszko.Zielinski

	Changed how NavigationSystem module includes DerivedDataCache module #UE4

	#jira UE-55035

Change 3885492 by mason.seay

	Minor tweaks to animation

Change 3885773 by mason.seay

	Resaving assets to clear out warning

Change 3886433 by Mieszko.Zielinski

	Fixed TP_TopDownBP's player controller BP to not use deprecated nav functions #UE4

	#jira UE-55108

Change 3886783 by Mieszko.Zielinski

	Removed silly inclusion of NavigationSystemTypes.h from NavigationSystemTypes.h #UE4

Change 3887019 by Mieszko.Zielinski

	Fixed accessing unchecked pointer in ANavigationData::OnNavAreaAdded #UE4

Change 3891031 by Mieszko.Zielinski

	Fixed missing includes in NavigationSystem.cpp #UE4

Change 3891037 by Mieszko.Zielinski

	ContentEample's navigation fix #UE4

	#jira UE-55109

Change 3891044 by Mieszko.Zielinski

	PR #4456: Fix bug in UAISense_Sight::OnListenerForgetsActor (Contributed by maxtunel)

	#UE4

Change 3891598 by mason.seay

	Resaving assets to clear out "empty engine version" spam

Change 3891612 by mason.seay

	Fixed deprecated Set Text warnings

Change 3893334 by Mieszko.Zielinski

	Fixed a bug in navmesh generation resulting in not removing layers that ended up empty after rebuilding #UE4

	#jira UE-55041

Change 3893394 by Mieszko.Zielinski

	Fixed navmesh debug drawing to properly display octree elements with "per instance transforms" (like instanced SMs) #UE4

	Also, added a more detailed debug drawing of navoctree contents (optional, but on by default).

Change 3893395 by Mieszko.Zielinski

	Added a bit of code to navigation system's initialization that checks the enegine ini for sections refering to the moved navigation classes, and complain about it #UE4

	The message is printed as an error-level log line and it says what should the offending section be renamed to.

Change 3895563 by Dan.Oconnor

	Mirror 3895535
	Append history from previous branches in source control history view

	#jira none

Change 3896930 by Mieszko.Zielinski

	Added an option to tick navigation system while the game is paused #UE4

	Controlled via NavigationSystemV1.bTickWhilePaused, ini- and ProjectSettings-configurable.

	#jira UE-39275

Change 3897554 by Mieszko.Zielinski

	Unified how NavMeshRenderingComponent draws navmesh and octree collision's polys #UE4

Change 3897556 by Mieszko.Zielinski

	Fixed what kind of nav tile bounds we're sending to nav-colliding elements when calling 'per-instance transform' delegate #UE4

	#jira UE-45261

Change 3898064 by Mieszko.Zielinski

	Made SM Editor display AI-navigation-related whenever bHasNavigationData is set to true #UE4

	#jira UE-50436

Change 3899004 by Mieszko.Zielinski

	Fixed UEnvQueryItemType_Actor::GetItemLocation and UEnvQueryItemType_Actor::GetItemRotation to return FAISystem::InvalidLocation and FAISystem::InvalidRotation respectively instead of '0' when hosted Actor ptr is null #UE4

	Note for programmers: this changes the default behavior of this edge case. You might want to go through your code and check if you're comparing UEnvQueryItemType_Actor::GetItem*'s results to 0.

Change 3901733 by Mieszko.Zielinski

	Made FEnvQueryInstance::PrepareContext implementations returning vectors and rotators ignore InvalidLocation and InvalidRotation (respectively) #UE4

Change 3901925 by Ben.Zeigler

	#jira UE-55395 Fix issue where the cooker could load asset registry caches made in -game that do not have dependency data, leading to broken cooks

Change 3902166 by Marc.Audy

	Make ULevel::GetWorld final

Change 3902749 by Ben.Zeigler

	Fix it so pressing refresh button in asset audit window actually refreshes the asset management database

Change 3902763 by Ben.Zeigler

	#jira UE-55407 Fix it so editor tutorials are not cooked unless referenced, by correctly marking soft object paths imported from editor project settings as editor-only

Change 3905578 by Phillip.Kavan

	The UX to add a new parameter on a Blueprint delegate is now at parity with Blueprint functions.

	#4392

	#jira UE-53779

Change 3905848 by Phillip.Kavan

	First pass of the experimental Blueprint graph bookmarks feature.

	#jira UE-10052

Change 3906025 by Phillip.Kavan

	CIS fix.

Change 3906195 by Phillip.Kavan

	Add missing icon file.

Change 3906356 by Phillip.Kavan

	Moved Blueprint bookmarks enable flag into EditorExperimentalSettings for consistency with other options.

Change 3910628 by Ben.Zeigler

	Partial fix for UE-55363, this allows references to ObjectRedirectors to be switched from parent class to a child class on load as this should always be safe
	This does not actually fix UE-55363 because that case is changing from UMaterial to UMaterialInstanceConstant, and those are siblings instead of parent/child

Change 3912470 by Ben.Zeigler

	#jira UE-55586 Fix issue with saving redirected soft object paths where the export sort could accidentally cause the parent CDO to get modified between name tagging and writing exports, which is unsafe because due to delta serialization it would try to write names that were not previously tagged

Change 3913045 by Marc.Audy

	Fix issues where recursion in to child actors wasn't being handled correctly

Change 3913398 by Fred.Kimberley

	Fixes a misspelled name for one of the classes in the ability system.

	PR #4430: Fixed spelling of FGameplayAbilityInputBinds. (Contributed by IntegralLee)
	#github

	#jira UE-54327

Change 3918016 by Fred.Kimberley

	Ensure AllocGameplayEffectContext is being used in all cases where FGameplayeEffectContext is being created.

	#jira UE-52668

	PR #4250: Only create FGameplayEffectContext via AbilitySystemGlobals::.AllocGameplayEffectContext (Contributed by slonopotamus)
	#github

Change 3924653 by Mieszko.Zielinski

	Fixed LoadEngineClass local to UnrealEngine.cpp to check class redirects before falling back to default class instance #UE4

	#jira UE-55378

Change 3925614 by Phillip.Kavan

	Fix ForEachEnum node to skip over hidden enum values in new placements by default.

	Change summary:
	- Added FKismetNodeHelperLibrary::ShouldHideEnumeratorIndex() as an internal-only Blueprint node support API.
	- Modified FForExpandNodeHelper::AllocateDefaultPins() to add a "Skip Hidden" input pin (advanced). Pin default value is false.
	- Added a UK2Node_ForEachElementInEnum::PostPlacedNewNode() override to set the default value of the "Skip Hidden" input pin to 'true' for all new node placements.
	- Modified UK2Node_ForEachElementInEnum::ExpandNode() to include additional expansion logic based on the "Skip Hidden" input pin. For new placements (i.e. when the pin defaults to 'true'), an intermediate branch node will now be inserted into the compiled execution sequence to test for "hidden" metadata on the value before executing the loop body. If the input pin is linked, another intermediate branch will be inserted into the execution sequence prior to the "hidden" metadata test. All existing placements of the node will remain as-is after compilation (i.e. no additional intermediate branch nodes will be included in the expansion).

	#jira UE-34563

Change 3925649 by Marc.Audy

	Fix up issue post merge from Main with navigation system refactor

Change 3926293 by Phillip.Kavan

	Temp fix to unblock CIS.

	#jira UE-34563

Change 3926523 by Marc.Audy

	Ensure that a renamed Actor is in the correct Actors array

	#jira UE-46718

Change 3928732 by Fred.Kimberley

	Unshelved from pending changelist '3793298':

	#jira UE-53136

	PR #4287: virtual additions for AttributeSet extendability (Contributed by TWIDan)
	#github

Change 3928780 by Marc.Audy

	PR #4309: The display names of the functions. (Contributed by SertacOgan)
	#jira UE-53334

Change 3929730 by Joseph.Wysosky

	Submitting test assets for the new Blueprint Structure test cases

Change 3931919 by Joseph.Wysosky

	Deleting BasicStructure asset to rest MemberVariables back to default settings

Change 3931922 by Joseph.Wysosky

	Adding BasicStructure test asset back with default members

Change 3932083 by Phillip.Kavan

	Fix Compositing plugin source files to conform to updated relative include path specifications.

	- Encountered while testing Blueprint nativization of assets with dependencies on Composure/LensDistortion APIs.

Change 3932196 by Dan.Oconnor

	Resetting a property to default now uses the same codepath as assigning the value from the slate control

	#jira UE-55909

Change 3932408 by Lukasz.Furman

	fixed behavior tree services attached to task nodes being sometimes recognized as root level
	#jira nope

Change 3932808 by Marc.Audy

	PR #4083: Change to UK2Node_BaseAsyncTask to have pin tooltips on latent nodes (Contributed by dwrpayne)
	#jira UE-50871

Change 3934101 by Phillip.Kavan

	Revise ForEachEnum node expansion logic to exclude hidden values at compile time.

	Change summary:
	- Removed UKismetNodeHelperLibrary::ShouldHideEnumeratorIndex() (no longer in use).
	- Modified UK2Node_ForEachElementInEnum::ExpandNode() to include an enum switch node in the expansion, which will exclude hidden values when constructed. The additional expansion will occur if the enum type contains at least one hidden value.

	#jira UE-34563

Change 3934106 by Phillip.Kavan

	Mirrored 4.19 fixes to allow for EngineTest iteration w/ nativization enabled.

	Change summary:
	- Mirrored CLs 3876918, 3878968, 3883257, 3885566, 3912161 and 3920519.

Change 3934116 by Phillip.Kavan

	UBT: Explicitly define the DEPRECATED_FORGAME macro only for non-engine modules.

	Change summary:
	- Modified UEBuildModule.SetupPrivateCompileEnvironment() to check the 'bTreatAsEngineModule' flag from the rules assembly rather than testing the module's build type.

Change 3934382 by Phillip.Kavan

	Avoid inclusion of monolothic engine header files in nativized Blueprint codegen.

Change 3936387 by Mieszko.Zielinski

	Added a flag to NavModifierComponent to control whether agent's height is being used while expadning modifier's bounds during navmesh generation #UE4

Change 3936905 by Ben.Marsh

	Disable IncludeTool warning for DEPRECATED_FORGAME macro; we expect this to be different for game modules.

Change 3940537 by Marc.Audy

	Don't allow maps, sets, or arrays with an actor inner type in user defined structs to select an actor from the currently open level as default value.
	#jira UE-55938

Change 3940901 by Marc.Audy

	Properly name CVar global to reflect what it is for

Change 3943043 by Marc.Audy

	Fix world context functions not being able to be used in CheatManager derived blueprints
	#jira UE-55787

Change 3943075 by Mieszko.Zielinski

	Moved path-following related delegats' interface from NavigationSystemBase over to a new IPathFollowingManagerInterface #UE4

Change 3943089 by Mieszko.Zielinski

	Fixed how WorldSettings.NavigationSystemConfig gets created #UE4

	Made it so that there's always a NavigationSystemConfig instance present, but added a 'Null' config - this was required due to issues with creation/serialization of instanced subobjects.
	The change required adding copying constructors to FNavAgentProperties and FNavDataConfig.
	Also, fixed FNavAgentProperties.IsEquivalent to be symetrical.

Change 3943225 by Marc.Audy

	Fix spelling of Implements

Change 3950813 by Marc.Audy

	Include owner in attachment mismatch ensure
	#jira UE-56148

Change 3950996 by Marc.Audy

	Fix cases where bit packed properties used the entire byte not just the bit when interacting with boolean arrays

	#jira UE-55482

Change 3952086 by Marc.Audy

	PR #4483: Add Missing Radial Damage Multicast Delegate (Contributed by error454)
	#jira UE-54974

Change 3952720 by Marc.Audy

	PR #4575: Check if *Pawn* is a null Pointer (Contributed by dani9bma)
	#jira UE-56248

Change 3952804 by Richard.Hinckley

	Changes to BP API export commandlet to support better plugin exporting. Contributed by Harry Wang of Google.

Change 3952962 by Marc.Audy

	UHT now validates that ExpandEnumAsExecs references a valid parameter to the function.
	#jira UE-49610

Change 3952977 by Phillip.Kavan

	Fix EDL cycle at load time in nativized cooked builds when a circular dependency exists between converted and unconverted assets.

	Change summary:
	- Added FGatherConvertedClassDependencies::MarkUnconvertedClassAsNecessary().
	- Modified FFindAssetsToInclude::MaybeIncludeObjectAsDependency() to mark unconverted BPGCs (e.g. DOBPs) as necessary for conversion when the potential for a circular dependency exists so that we generate stub wrappers rather than depend on them directly.
	- Fixed a few typos in existing API names.

	#jira UE-48233

Change 3953658 by Marc.Audy

	(4.19.1) Fix inserting a reroute node causing connections to break on a GetClassDefaults node
	#jira UE-56270

Change 3954727 by Marc.Audy

	Add friendly name to custom version mismatch message

Change 3954906 by Marc.Audy

	(4.19.1) Fix crash when undoing changes related to reroute nodes connected to a GetClassDefaults node
	#jira UE-56313

Change 3954997 by Marc.Audy

	Ensure and return null if GetOuter<WithinClass> is called on a CDO for uclasses declared as within another so we don't get a UPackage c-style cast to the expected outer type

Change 3955091 by Marc.Audy

	Do not register subcomponents that are not auto register
	#jira UE-52878

Change 3955943 by Marc.Audy

	Make AbilitySystemComponent pass parameters by const& instead of ref as no state is being changed

Change 3956185 by Zak.Middleton

	#ue4 - Fix Characters using scoped movement updates (the default) not visually rotating when rotated at small rates at high framerate.

	This was caused by FScopedMovementUpdate::IsTransformDirty() using a larger FTransform comparison tolerance than USceneComponent::UpdateComponentToWorldWithParent().

	#jira none

Change 3958102 by Marc.Audy

	Clean out dead code path from k2node_select
	Select node now resets pins to wildcard if none of the pins are in use

Change 3958113 by Lukasz.Furman

	added OnSearchStart call to root level behavior tree services
	#jira UE-56257

Change 3958361 by Marc.Audy

	Fix literal input pins on select being set to wildcard during compilation

Change 3961148 by Dan.Oconnor

	Mirror 3961139 from Release 4.19
	Fix for placeholder objects being left behind when loading certain UMG assets - this could causea crash when loading UMG assets
	#jira UE-55742

Change 3961640 by Marc.Audy

	Select node now displays Add Pin button
	Undo of changing select node index type now works correctly.
	Connections to option pins now maintained across change of index pin type
	#jira UE-20742

Change 3962262 by Marc.Audy

	Display "Object Reference" instead of "Object Object Reference" and "Soft Object Reference" instead of "Object Soft Object Reference"

Change 3962795 by Phillip.Kavan

	Fix for a crash when cooking with Blueprint nativization enabled after encountering a nested instanced editor-only default subobject inherited from a native C++ base class.

	- Mirrored from //UE4/Release-4.19 (3962782)

	#jira UE-56316

Change 3962991 by Marc.Audy

	Modify Negate/Increment/Decrement Int/Float so that the output is always the desired result even if a non-mutable pin is passed in.
	Note that this can mean the result being returned and the value of the pin passed in if queried again will not be the same (in the case of pure nodes).
	#jira UE-54807

Change 3963114 by Marc.Audy

	Fix ensures/crash as a result of UClass expecting to be able to access the UPackage of CDOs via the GetOuterUPackage call.

Change 3963427 by Marc.Audy

	Fix initialization order
	Initialize bUseBackwardsCompatForEmptyAutogeneratedValue

Change 3963781 by Marc.Audy

	Fix without editor compiles

Change 3964576 by Marc.Audy

	PR #4599: : Working category for timelines (Contributed by projectgheist)
	#jira UE-56460
	#jira UE-26053

Change 3964782 by Dan.Oconnor

	Mirror 3964772 from Release 4.19

	Fix crash when force deleting certain blueprints, we can only check for authoritativeness while reinstancing

	#jira UE-56447

Change 3965156 by Mieszko.Zielinski

	PR #4592: Visual Logger optimization to fix rapid FPS drop when many items are hidden (Contributed by tstaples)

	#jira UE-56435

Change 3965173 by Marc.Audy

	(4.19.1) Fix incorrectly switching a cooling down tick to be an enabled tick when marking it enabled.
	#jira UE-56431

Change 3966117 by Marc.Audy

	Fix select nodes inside macros using wildcard array inputs having issues resolving type.
	#jira UE-56484

Change 3878901 by Mieszko.Zielinski

	NavigationSystem's code refactored out of the engine and into a new separate module #UE4

	The CL contains required changes to all of our internal projects. Fortnite and Paragon have been tested, while the rest have been only compiled.

Change 3879409 by Mieszko.Zielinski

	Further fallout fixes after ripping out NavigationSystem out of the engine #UE4

	- Fixed bad ini redirects (had NavigationSystem.NavigationSystem instead of NavigationSystem.NavigationSystemV1)
	- Added missing FNavigationSystem::GetDefaultNavDataClass binding (resulting in QAGame's func tests failing)

Change 3897655 by Ben.Zeigler

	#jira UE-55211 Fix it so literal soft object pins on blueprint nodes get correctly cooked/referenced
	It now sets the thread context to skip internal serialize and calls the archive's serialize function instead of bypassing it, which allows it to pick up references

	Change 3962780 by Marc.Audy

	When preventing a split pin from being orphaned, all sub pins must also be prevented.
	#jira UE-56328
	Repack members of UEdGraphPin to avoid wasted space (saves 16bytes)

[CL 3967553 by Marc Audy in Main branch]
2018-03-27 14:27:07 -04:00

4268 lines
130 KiB
C++

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#include "NavigationSystem.h"
#include "Misc/ScopeLock.h"
#include "Stats/StatsMisc.h"
#include "Modules/ModuleManager.h"
#include "AI/Navigation/NavAgentInterface.h"
#include "Engine/World.h"
#include "GameFramework/Controller.h"
#include "AI/Navigation/NavRelevantInterface.h"
#include "UObject/UObjectIterator.h"
#include "EngineUtils.h"
#include "Logging/MessageLog.h"
#include "NavAreas/NavArea.h"
#include "NavigationOctree.h"
#include "VisualLogger/VisualLogger.h"
#include "NavMesh/NavMeshBoundsVolume.h"
#include "NavigationInvokerComponent.h"
#include "AI/Navigation/NavigationDataChunk.h"
#include "Engine/Engine.h"
#include "UObject/Package.h"
#include "Components/PrimitiveComponent.h"
#if WITH_RECAST
#include "NavMesh/RecastNavMesh.h"
#include "NavMesh/RecastHelpers.h"
#include "NavMesh/RecastNavMeshGenerator.h"
#endif // WITH_RECAST
#if WITH_EDITOR
#include "EditorModeManager.h"
#include "EditorModes.h"
#include "Editor/GeometryMode/Public/GeometryEdMode.h"
#include "Editor/GeometryMode/Public/EditorGeometry.h"
#endif
#if WITH_HOT_RELOAD
#include "Misc/HotReloadInterface.h"
#endif
#include "NavAreas/NavArea_Null.h"
#include "NavAreas/NavArea_Default.h"
#include "NavAreas/NavAreaMeta_SwitchByAgent.h"
#include "NavLinkCustomInterface.h"
#include "NavigationPath.h"
#include "AbstractNavData.h"
#include "CrowdManagerBase.h"
static const uint32 INITIAL_ASYNC_QUERIES_SIZE = 32;
static const uint32 REGISTRATION_QUEUE_SIZE = 16; // and we'll not reallocate
#define LOCTEXT_NAMESPACE "Navigation"
DEFINE_LOG_CATEGORY_STATIC(LogNavOctree, Warning, All);
DECLARE_CYCLE_STAT(TEXT("Rasterize triangles"), STAT_Navigation_RasterizeTriangles,STATGROUP_Navigation);
DECLARE_CYCLE_STAT(TEXT("Nav Tick: area register"), STAT_Navigation_TickNavAreaRegister, STATGROUP_Navigation);
DECLARE_CYCLE_STAT(TEXT("Nav Tick: mark dirty"), STAT_Navigation_TickMarkDirty, STATGROUP_Navigation);
DECLARE_CYCLE_STAT(TEXT("Nav Tick: async build"), STAT_Navigation_TickAsyncBuild, STATGROUP_Navigation);
DECLARE_CYCLE_STAT(TEXT("Nav Tick: async pathfinding"), STAT_Navigation_TickAsyncPathfinding, STATGROUP_Navigation);
DECLARE_CYCLE_STAT(TEXT("Debug NavOctree Time"), STAT_DebugNavOctree, STATGROUP_Navigation);
//----------------------------------------------------------------------//
// Stats
//----------------------------------------------------------------------//
DEFINE_STAT(STAT_Navigation_QueriesTimeSync);
DEFINE_STAT(STAT_Navigation_RequestingAsyncPathfinding);
DEFINE_STAT(STAT_Navigation_PathfindingSync);
DEFINE_STAT(STAT_Navigation_PathfindingAsync);
DEFINE_STAT(STAT_Navigation_AddGeneratedTiles);
DEFINE_STAT(STAT_Navigation_TileNavAreaSorting);
DEFINE_STAT(STAT_Navigation_TileGeometryExportToObjAsync);
DEFINE_STAT(STAT_Navigation_TileVoxelFilteringAsync);
DEFINE_STAT(STAT_Navigation_TileBuildAsync);
DEFINE_STAT(STAT_Navigation_TileBuildPreparationSync);
DEFINE_STAT(STAT_Navigation_BSPExportSync);
DEFINE_STAT(STAT_Navigation_GatheringNavigationModifiersSync);
DEFINE_STAT(STAT_Navigation_ActorsGeometryExportSync);
DEFINE_STAT(STAT_Navigation_ProcessingActorsForNavMeshBuilding);
DEFINE_STAT(STAT_Navigation_AdjustingNavLinks);
DEFINE_STAT(STAT_Navigation_AddingActorsToNavOctree);
DEFINE_STAT(STAT_Navigation_RecastTick);
DEFINE_STAT(STAT_Navigation_RecastPathfinding);
DEFINE_STAT(STAT_Navigation_RecastBuildCompressedLayers);
DEFINE_STAT(STAT_Navigation_RecastBuildNavigation);
DEFINE_STAT(STAT_Navigation_UpdateNavOctree);
DEFINE_STAT(STAT_Navigation_CollisionTreeMemory);
DEFINE_STAT(STAT_Navigation_NavDataMemory);
DEFINE_STAT(STAT_Navigation_TileCacheMemory);
DEFINE_STAT(STAT_Navigation_OutOfNodesPath);
DEFINE_STAT(STAT_Navigation_PartialPath);
DEFINE_STAT(STAT_Navigation_CumulativeBuildTime);
DEFINE_STAT(STAT_Navigation_BuildTime);
DEFINE_STAT(STAT_Navigation_OffsetFromCorners);
DEFINE_STAT(STAT_Navigation_PathVisibilityOptimisation);
DEFINE_STAT(STAT_Navigation_ObservedPathsCount);
DEFINE_STAT(STAT_Navigation_RecastMemory);
//----------------------------------------------------------------------//
// consts
//----------------------------------------------------------------------//
namespace FNavigationSystem
{
FORCEINLINE bool IsValidExtent(const FVector& Extent)
{
return Extent != INVALID_NAVEXTENT;
}
FCustomLinkOwnerInfo::FCustomLinkOwnerInfo(INavLinkCustomInterface* Link)
{
LinkInterface = Link;
LinkOwner = Link->GetLinkOwner();
}
bool ShouldLoadNavigationOnClient(ANavigationData& NavData)
{
const UNavigationSystemV1* NavSysCDO = (*GEngine->NavigationSystemClass != nullptr)
? (GEngine->NavigationSystemClass->GetDefaultObject<const UNavigationSystemV1>())
: (const UNavigationSystemV1*)nullptr;
return NavSysCDO && NavSysCDO->ShouldLoadNavigationOnClient(&NavData);
}
}
namespace NavigationDebugDrawing
{
const float PathLineThickness = 3.f;
const FVector PathOffset(0,0,15);
const FVector PathNodeBoxExtent(16.f);
}
//----------------------------------------------------------------------//
// FNavigationInvoker
//----------------------------------------------------------------------//
FNavigationInvoker::FNavigationInvoker()
: Actor(nullptr)
, GenerationRadius(0)
, RemovalRadius(0)
{
}
FNavigationInvoker::FNavigationInvoker(AActor& InActor, float InGenerationRadius, float InRemovalRadius)
: Actor(&InActor)
, GenerationRadius(InGenerationRadius)
, RemovalRadius(InRemovalRadius)
{
}
//----------------------------------------------------------------------//
// UNavigationSystemV1
//----------------------------------------------------------------------//
bool UNavigationSystemV1::bNavigationAutoUpdateEnabled = true;
TMap<INavLinkCustomInterface*, FWeakObjectPtr> UNavigationSystemV1::PendingCustomLinkRegistration;
FCriticalSection UNavigationSystemV1::CustomLinkRegistrationSection;
TSubclassOf<UNavArea> UNavigationSystemV1::DefaultWalkableArea = NULL;
TSubclassOf<UNavArea> UNavigationSystemV1::DefaultObstacleArea = NULL;
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
FNavigationSystemExec UNavigationSystemV1::ExecHandler;
#endif // !UE_BUILD_SHIPPING
/** called after navigation influencing event takes place*/
UNavigationSystemV1::FOnNavigationDirty UNavigationSystemV1::NavigationDirtyEvent;
bool UNavigationSystemV1::bUpdateNavOctreeOnComponentChange = true;
bool UNavigationSystemV1::bStaticRuntimeNavigation = false;
//----------------------------------------------------------------------//
// life cycle stuff
//----------------------------------------------------------------------//
UNavigationSystemV1::UNavigationSystemV1(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
, bTickWhilePaused(false)
, bWholeWorldNavigable(false)
, bSkipAgentHeightCheckWhenPickingNavData(false)
, DirtyAreasUpdateFreq(60)
, OperationMode(FNavigationSystemRunMode::InvalidMode)
, NavOctree(NULL)
, NavBuildingLockFlags(0)
, InitialNavBuildingLockFlags(0)
, bNavOctreeLock(false)
, bInitialSetupHasBeenPerformed(false)
, bInitialLevelsAdded(false)
, bWorldInitDone(false)
, CurrentlyDrawnNavDataIndex(0)
, DirtyAreasUpdateTime(0)
{
#if WITH_EDITOR
NavUpdateLockFlags = 0;
#endif
struct FDelegatesInitializer
{
FDelegatesInitializer()
{
UNavigationSystemBase::UpdateActorDataDelegate().BindStatic(&UNavigationSystemV1::UpdateActorInNavOctree);
UNavigationSystemBase::UpdateComponentDataDelegate().BindStatic(&UNavigationSystemV1::UpdateComponentInNavOctree);
UNavigationSystemBase::UpdateComponentDataAfterMoveDelegate().BindLambda([](USceneComponent& Comp) { UNavigationSystemV1::UpdateNavOctreeAfterMove(&Comp); });
UNavigationSystemBase::OnActorBoundsChangedDelegate().BindLambda([](AActor& Actor) { UNavigationSystemV1::UpdateNavOctreeBounds(&Actor); });
UNavigationSystemBase::OnPostEditActorMoveDelegate().BindLambda([](AActor& Actor) {
// update actor and all its components in navigation system after finishing move
// USceneComponent::UpdateNavigationData works only in game world
UNavigationSystemV1::UpdateNavOctreeBounds(&Actor);
TArray<AActor*> ParentedActors;
Actor.GetAttachedActors(ParentedActors);
for (int32 Idx = 0; Idx < ParentedActors.Num(); Idx++)
{
UNavigationSystemV1::UpdateNavOctreeBounds(ParentedActors[Idx]);
}
// not doing manual update of all attached actors since UpdateActorAndComponentsInNavOctree should take care of it
UNavigationSystemV1::UpdateActorAndComponentsInNavOctree(Actor);
});
UNavigationSystemBase::OnComponentTransformChangedDelegate().BindLambda([](USceneComponent& Comp) {
if (UNavigationSystemV1::ShouldUpdateNavOctreeOnComponentChange())
{
UWorld* World = Comp.GetWorld();
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
if (NavSys != nullptr
&& (NavSys->ShouldAllowClientSideNavigation() || !World->IsNetMode(ENetMode::NM_Client)))
{
// use propagated component's transform update in editor OR server game with additional navsys check
UNavigationSystemV1::UpdateNavOctreeAfterMove(&Comp);
}
}
});
UNavigationSystemBase::OnActorRegisteredDelegate().BindLambda([](AActor& Actor) { UNavigationSystemV1::OnActorRegistered(&Actor); });
UNavigationSystemBase::OnActorUnregisteredDelegate().BindLambda([](AActor& Actor) { UNavigationSystemV1::OnActorUnregistered(&Actor); });
UNavigationSystemBase::OnComponentRegisteredDelegate().BindLambda([](UActorComponent& Comp) { UNavigationSystemV1::OnComponentRegistered(&Comp); });
UNavigationSystemBase::OnComponentUnregisteredDelegate().BindLambda([](UActorComponent& Comp) { UNavigationSystemV1::OnComponentUnregistered(&Comp); });
UNavigationSystemBase::RemoveActorDataDelegate().BindLambda([](AActor& Actor) { UNavigationSystemV1::ClearNavOctreeAll(&Actor); });
UNavigationSystemBase::HasComponentDataDelegate().BindLambda([](UActorComponent& Comp) {
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(Comp.GetWorld());
return (NavSys && (NavSys->GetObjectsNavOctreeId(&Comp) || NavSys->HasPendingObjectNavOctreeId(&Comp)));
});
UNavigationSystemBase::GetDefaultWalkableAreaDelegate().BindLambda([]() { return UNavigationSystemV1::GetDefaultWalkableArea(); });
UNavigationSystemBase::GetDefaultSupportedAgentDelegate().BindStatic(&UNavigationSystemV1::GetDefaultSupportedAgent);
UNavigationSystemBase::UpdateActorAndComponentDataDelegate().BindStatic(&UNavigationSystemV1::UpdateActorAndComponentsInNavOctree);
UNavigationSystemBase::OnComponentBoundsChangedDelegate().BindLambda([](UActorComponent& Comp, const FBox& NewBounds, const FBox& DirtyArea) {
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(Comp.GetWorld());
if (NavSys)
{
NavSys->UpdateNavOctreeElementBounds(&Comp, NewBounds, DirtyArea);
}
});
//UNavigationSystemBase::GetNavDataForPropsDelegate();
UNavigationSystemBase::GetNavDataForActorDelegate().BindStatic(&UNavigationSystemV1::GetNavDataForActor);
#if WITH_RECAST
UNavigationSystemBase::GetDefaultNavDataClassDelegate().BindLambda([]() { return ARecastNavMesh::StaticClass(); });
#endif // WITH_RECAST
UNavigationSystemBase::VerifyNavigationRenderingComponentsDelegate().BindLambda([](UWorld& World, const bool bShow) {
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(&World);
if (NavSys)
{
NavSys->VerifyNavigationRenderingComponents(bShow);
}
});
UNavigationSystemBase::BuildDelegate().BindLambda([](UWorld& World) {
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(&World);
if (NavSys)
{
NavSys->Build();
}
});
#if WITH_EDITOR
UNavigationSystemBase::OnPIEStartDelegate().BindLambda([](UWorld& World) {
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(&World);
if (NavSys)
{
NavSys->OnPIEStart();
}
});
UNavigationSystemBase::OnPIEEndDelegate().BindLambda([](UWorld& World) {
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(&World);
if (NavSys)
{
NavSys->OnPIEEnd();
}
});
UNavigationSystemBase::UpdateLevelCollisionDelegate().BindLambda([](ULevel& Level) {
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(&Level);
if (NavSys)
{
NavSys->UpdateLevelCollision(&Level);
}
});
UNavigationSystemBase::SetNavigationAutoUpdateEnableDelegate().BindStatic(&UNavigationSystemV1::SetNavigationAutoUpdateEnabled);
/*.BindLambda([](const bool bNewEnable, UNavigationSystemBase* InNavigationSystem) {
})*/
UNavigationSystemBase::AddNavigationUpdateLockDelegate().BindLambda([](UWorld& World, uint8 Flags) {
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(&World);
if (NavSys)
{
NavSys->AddNavigationUpdateLock(Flags);
}
});
UNavigationSystemBase::RemoveNavigationUpdateLockDelegate().BindLambda([](UWorld& World, uint8 Flags) {
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(&World);
if (NavSys)
{
NavSys->RemoveNavigationUpdateLock(Flags);
}
});
#endif // WITH_EDITOR
}
};
static FDelegatesInitializer DelegatesInitializer;
// @hack, trying to load AIModule's CrowdManager
UClass* Class = StaticLoadClass(UCrowdManagerBase::StaticClass(), nullptr, TEXT("/Script/AIModule.CrowdManager"));
CrowdManagerClass = Class ? Class : UCrowdManagerBase::StaticClass();
// active tiles
NextInvokersUpdateTime = 0.f;
ActiveTilesUpdateInterval = 1.f;
bGenerateNavigationOnlyAroundNavigationInvokers = false;
DataGatheringMode = ENavDataGatheringModeConfig::Instant;
bCanAccumulateDirtyAreas = true;
#if !UE_BUILD_SHIPPING
bDirtyAreasReportedWhileAccumulationLocked = false;
#endif // !UE_BUILD_SHIPPING
if (HasAnyFlags(RF_ClassDefaultObject) == false)
{
// reserve some arbitrary size
AsyncPathFindingQueries.Reserve( INITIAL_ASYNC_QUERIES_SIZE );
NavDataRegistrationQueue.Reserve( REGISTRATION_QUEUE_SIZE );
FWorldDelegates::LevelAddedToWorld.AddUObject(this, &UNavigationSystemV1::OnLevelAddedToWorld);
FWorldDelegates::LevelRemovedFromWorld.AddUObject(this, &UNavigationSystemV1::OnLevelRemovedFromWorld);
#if !UE_BUILD_SHIPPING
FCoreDelegates::OnGetOnScreenMessages.AddUObject(this, &UNavigationSystemV1::GetOnScreenMessages);
#endif // !UE_BUILD_SHIPPING
}
else if (GetClass() == UNavigationSystemV1::StaticClass())
{
DefaultWalkableArea = UNavArea_Default::StaticClass();
DefaultObstacleArea = UNavArea_Null::StaticClass();
const FTransform RecastToUnrealTransfrom(Recast2UnrealMatrix());
SetCoordTransformFrom(ENavigationCoordSystem::Recast, RecastToUnrealTransfrom);
}
#if WITH_EDITOR
if (GIsEditor && HasAnyFlags(RF_ClassDefaultObject) == false)
{
FEditorDelegates::EditorModeEnter.AddUObject(this, &UNavigationSystemV1::OnEditorModeChanged, true);
FEditorDelegates::EditorModeExit.AddUObject(this, &UNavigationSystemV1::OnEditorModeChanged, false);
}
#endif // WITH_EDITOR
}
UNavigationSystemV1::~UNavigationSystemV1()
{
CleanUp(FNavigationSystem::ECleanupMode::CleanupUnsafe);
#if WITH_EDITOR
if (GIsEditor)
{
FEditorDelegates::EditorModeEnter.RemoveAll(this);
FEditorDelegates::EditorModeExit.RemoveAll(this);
}
#endif // WITH_EDITOR
#if !UE_BUILD_SHIPPING
FCoreDelegates::OnGetOnScreenMessages.RemoveAll(this);
#endif // !UE_BUILD_SHIPPING
}
void UNavigationSystemV1::ConfigureAsStatic()
{
bStaticRuntimeNavigation = true;
}
void UNavigationSystemV1::SetUpdateNavOctreeOnComponentChange(bool bNewUpdateOnComponentChange)
{
bUpdateNavOctreeOnComponentChange = bNewUpdateOnComponentChange;
}
void UNavigationSystemV1::DoInitialSetup()
{
if (bInitialSetupHasBeenPerformed)
{
return;
}
UpdateAbstractNavData();
CreateCrowdManager();
bInitialSetupHasBeenPerformed = true;
}
void UNavigationSystemV1::UpdateAbstractNavData()
{
if (AbstractNavData != nullptr && !AbstractNavData->IsPendingKill())
{
return;
}
// spawn abstract nav data separately
// it's responsible for direct paths and shouldn't be picked for any agent type as default one
UWorld* NavWorld = GetWorld();
for (TActorIterator<AAbstractNavData> It(NavWorld); It; ++It)
{
AAbstractNavData* Nav = (*It);
if (Nav && !Nav->IsPendingKill())
{
AbstractNavData = Nav;
break;
}
}
if (AbstractNavData == NULL)
{
FNavDataConfig DummyConfig;
DummyConfig.NavigationDataClass = AAbstractNavData::StaticClass();
AbstractNavData = CreateNavigationDataInstance(DummyConfig);
AbstractNavData->SetFlags(RF_Transient);
}
}
void UNavigationSystemV1::SetSupportedAgentsNavigationClass(int32 AgentIndex, TSubclassOf<ANavigationData> NavigationDataClass)
{
check(SupportedAgents.IsValidIndex(AgentIndex));
SupportedAgents[AgentIndex].NavigationDataClass = NavigationDataClass;
// keep preferred navigation data class in sync with actual class
// this will be passed to navigation data actor and will be required
// for comparisons done in DoesSupportAgent calls
//
// "Any" navigation data preference is valid only for instanced agents
SupportedAgents[AgentIndex].SetPreferredNavData(NavigationDataClass);
if (NavigationDataClass != nullptr)
{
SupportedAgents[AgentIndex].NavigationDataClassName = FSoftClassPath::GetOrCreateIDForClass(NavigationDataClass);
}
else
{
SupportedAgents[AgentIndex].NavigationDataClassName.Reset();
}
#if WITH_EDITOR
if (GIsEditor)
{
if (HasAnyFlags(RF_ClassDefaultObject) == false)
{
// set it at CDO to properly show up in project settings
// @hack the reason for doing it this way is that engine doesn't handle default TSubclassOf properties
// set to game-specific classes;
UNavigationSystemV1* NavigationSystemCDO = GetMutableDefault<UNavigationSystemV1>(GetClass());
NavigationSystemCDO->SetSupportedAgentsNavigationClass(AgentIndex, NavigationDataClass);
}
}
#endif // WITH_EDITOR
}
void UNavigationSystemV1::PostInitProperties()
{
Super::PostInitProperties();
if (HasAnyFlags(RF_ClassDefaultObject) == false)
{
// Populate our NavAreaClasses list with all known nav area classes.
// If more are loaded after this they will be registered as they come
TArray<UClass*> CurrentNavAreaClasses;
GetDerivedClasses(UNavArea::StaticClass(), CurrentNavAreaClasses);
for (UClass* NavAreaClass : CurrentNavAreaClasses)
{
RegisterNavAreaClass(NavAreaClass);
}
// make sure there's at least one supported navigation agent size
if (SupportedAgents.Num() == 0)
{
SupportedAgents.Add(FNavDataConfig(FNavigationSystem::FallbackAgentRadius, FNavigationSystem::FallbackAgentHeight));
}
else
{
for (int32 AgentIndex = 0; AgentIndex < SupportedAgents.Num(); ++AgentIndex)
{
FNavDataConfig& SupportedAgentConfig = SupportedAgents[AgentIndex];
// a piece of legacy maintanance
if (SupportedAgentConfig.NavigationDataClass != nullptr && SupportedAgentConfig.NavigationDataClassName.IsValid() == false)
{
// fill NavigationDataClassName
SupportedAgentConfig.NavigationDataClassName = FSoftClassPath(SupportedAgentConfig.NavigationDataClass);
}
else
{
TSubclassOf<ANavigationData> NavigationDataClass = SupportedAgentConfig.NavigationDataClassName.IsValid()
? LoadClass<ANavigationData>(NULL, *SupportedAgentConfig.NavigationDataClassName.ToString(), NULL, LOAD_None, NULL)
: nullptr;
SetSupportedAgentsNavigationClass(AgentIndex, NavigationDataClass);
}
}
}
if (bInitialBuildingLocked)
{
InitialNavBuildingLockFlags |= ENavigationBuildLock::InitialLock;
}
uint8 UseLockFlags = InitialNavBuildingLockFlags;
AddNavigationBuildLock(UseLockFlags);
// register for any actor move change
#if WITH_EDITOR
if ( GIsEditor )
{
GEngine->OnActorMoved().AddUObject(this, &UNavigationSystemV1::OnActorMoved);
}
#endif
FCoreUObjectDelegates::PostLoadMapWithWorld.AddUObject(this, &UNavigationSystemV1::OnPostLoadMap);
UNavigationSystemV1::NavigationDirtyEvent.AddUObject(this, &UNavigationSystemV1::OnNavigationDirtied);
#if WITH_HOT_RELOAD
IHotReloadInterface& HotReloadSupport = FModuleManager::LoadModuleChecked<IHotReloadInterface>("HotReload");
HotReloadDelegateHandle = HotReloadSupport.OnHotReload().AddUObject(this, &UNavigationSystemV1::OnHotReload);
#endif
}
}
bool UNavigationSystemV1::ConditionalPopulateNavOctree()
{
// Discard all navigation updates caused by octree construction
TGuardValue<TArray<FNavigationDirtyArea>> DirtyGuard(DirtyAreas, TArray<FNavigationDirtyArea>());
// We are going to fully re-populate NavOctree so all pending update request are outdated
PendingOctreeUpdates.Empty(32);
// Discard current octree
DestroyNavOctree();
// See if any of registered navigation data need navoctree
bSupportRebuilding = RequiresNavOctree();
if (bSupportRebuilding)
{
NavOctree = MakeShareable(new FNavigationOctree(FVector(0,0,0), 64000));
NavOctree->SetDataGatheringMode(DataGatheringMode);
const ERuntimeGenerationType RuntimeGenerationType = GetRuntimeGenerationType();
const bool bStoreNavGeometry = (RuntimeGenerationType == ERuntimeGenerationType::Dynamic);
NavOctree->SetNavigableGeometryStoringMode(bStoreNavGeometry ? FNavigationOctree::StoreNavGeometry : FNavigationOctree::SkipNavGeometry);
if (bStoreNavGeometry)
{
#if WITH_RECAST
NavOctree->ComponentExportDelegate = FNavigationOctree::FNavigableGeometryComponentExportDelegate::CreateStatic(&FRecastNavMeshGenerator::ExportComponentGeometry);
#endif // WITH_RECAST
}
if (!IsNavigationOctreeLocked())
{
UWorld* World = GetWorld();
check(World);
// now process all actors on all levels
for (int32 LevelIndex = 0; LevelIndex < World->GetNumLevels(); ++LevelIndex)
{
ULevel* Level = World->GetLevel(LevelIndex);
AddLevelCollisionToOctree(Level);
for (int32 ActorIndex = 0; ActorIndex < Level->Actors.Num(); ActorIndex++)
{
AActor* Actor = Level->Actors[ActorIndex];
const bool bLegalActor = Actor && !Actor->IsPendingKill();
if (bLegalActor)
{
UpdateActorAndComponentsInNavOctree(*Actor);
}
}
}
}
}
// Add all found elements to octree, this will not add new dirty areas to navigation
if (PendingOctreeUpdates.Num())
{
for (TSet<FNavigationDirtyElement>::TIterator It(PendingOctreeUpdates); It; ++It)
{
AddElementToNavOctree(*It);
}
PendingOctreeUpdates.Empty(32);
}
return bSupportRebuilding;
}
#if WITH_EDITOR
void UNavigationSystemV1::PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent)
{
static const FName NAME_NavigationDataClass = GET_MEMBER_NAME_CHECKED(FNavDataConfig, NavigationDataClass);
static const FName NAME_SupportedAgents = GET_MEMBER_NAME_CHECKED(UNavigationSystemV1, SupportedAgents);
Super::PostEditChangeChainProperty(PropertyChangedEvent);
if (PropertyChangedEvent.Property)
{
FName PropName = PropertyChangedEvent.Property->GetFName();
if (PropName == NAME_NavigationDataClass)
{
int32 SupportedAgentIndex = PropertyChangedEvent.GetArrayIndex(NAME_SupportedAgents.ToString());
if (SupportedAgents.IsValidIndex(SupportedAgentIndex))
{
// reflect the change to SupportedAgent's
TSubclassOf<ANavigationData> NavClass = SupportedAgents[SupportedAgentIndex].NavigationDataClass.Get();
SetSupportedAgentsNavigationClass(SupportedAgentIndex, NavClass);
SaveConfig();
}
}
}
}
void UNavigationSystemV1::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
static const FName NAME_EnableActiveTiles = GET_MEMBER_NAME_CHECKED(UNavigationSystemV1, bGenerateNavigationOnlyAroundNavigationInvokers);
Super::PostEditChangeProperty(PropertyChangedEvent);
if (PropertyChangedEvent.Property)
{
FName PropName = PropertyChangedEvent.Property->GetFName();
if (PropName == NAME_EnableActiveTiles)
{
if (NavOctree.IsValid())
{
NavOctree->SetDataGatheringMode(DataGatheringMode);
}
for (auto NavData : NavDataSet)
{
if (NavData)
{
NavData->RestrictBuildingToActiveTiles(bGenerateNavigationOnlyAroundNavigationInvokers);
}
}
}
}
}
#endif // WITH_EDITOR
void UNavigationSystemV1::OnInitializeActors()
{
}
void UNavigationSystemV1::OnWorldInitDone(FNavigationSystemRunMode Mode)
{
static const bool bSkipRebuildInEditor = true;
OperationMode = Mode;
DoInitialSetup();
UWorld* World = GetWorld();
if (IsThereAnywhereToBuildNavigation() == false
// Simulation mode is a special case - better not do it in this case
&& OperationMode != FNavigationSystemRunMode::SimulationMode)
{
// remove all navigation data instances
for (TActorIterator<ANavigationData> It(World); It; ++It)
{
ANavigationData* Nav = (*It);
if (Nav != NULL && Nav->IsPendingKill() == false && Nav != GetAbstractNavData())
{
UnregisterNavData(Nav);
Nav->CleanUpAndMarkPendingKill();
bNavDataRemovedDueToMissingNavBounds = true;
}
}
if (OperationMode == FNavigationSystemRunMode::EditorMode)
{
RemoveNavigationBuildLock(InitialNavBuildingLockFlags, bSkipRebuildInEditor);
}
}
else
{
// Discard all bounds updates that was submitted during world initialization,
// to avoid navigation rebuild right after map is loaded
PendingNavBoundsUpdates.Empty();
// gather navigable bounds
GatherNavigationBounds();
// gather all navigation data instances and register all not-yet-registered
// (since it's quite possible navigation system was not ready by the time
// those instances were serialized-in or spawned)
RegisterNavigationDataInstances();
if (bAutoCreateNavigationData == true)
{
SpawnMissingNavigationData();
// in case anything spawned has registered
ProcessRegistrationCandidates();
}
else
{
const bool bIsBuildLocked = IsNavigationBuildingLocked();
if (GetDefaultNavDataInstance(FNavigationSystem::DontCreate) != NULL)
{
// trigger navmesh update
for (TActorIterator<ANavigationData> It(World); It; ++It)
{
ANavigationData* NavData = (*It);
if (NavData != NULL)
{
ERegistrationResult Result = RegisterNavData(NavData);
if (Result == RegistrationSuccessful)
{
if (!bIsBuildLocked && bNavigationAutoUpdateEnabled)
{
NavData->RebuildAll();
}
}
else if (Result != RegistrationFailed_DataPendingKill
&& Result != RegistrationFailed_AgentNotValid
)
{
NavData->CleanUpAndMarkPendingKill();
}
}
}
}
}
if (OperationMode == FNavigationSystemRunMode::EditorMode)
{
// don't lock navigation building in editor
RemoveNavigationBuildLock(InitialNavBuildingLockFlags, bSkipRebuildInEditor);
}
// See if any of registered navigation data needs NavOctree
ConditionalPopulateNavOctree();
// All navigation actors are registered
// Add NavMesh parts from all sub-levels that were streamed in prior NavMesh registration
const auto& Levels = World->GetLevels();
for (ULevel* Level : Levels)
{
if (!Level->IsPersistentLevel() && Level->bIsVisible)
{
for (ANavigationData* NavData : NavDataSet)
{
if (NavData)
{
NavData->OnStreamingLevelAdded(Level, World);
}
}
}
}
}
if (Mode == FNavigationSystemRunMode::EditorMode)
{
#if WITH_EDITOR
// make sure this static get applied to this instance
bNavigationAutoUpdateEnabled = !bNavigationAutoUpdateEnabled;
SetNavigationAutoUpdateEnabled(!bNavigationAutoUpdateEnabled, this);
#endif
// update navigation invokers
if (bGenerateNavigationOnlyAroundNavigationInvokers)
{
for (TObjectIterator<UNavigationInvokerComponent> It; It; ++It)
{
if (World == It->GetWorld())
{
It->RegisterWithNavigationSystem(*this);
}
}
}
// update navdata after loading world
if (bNavigationAutoUpdateEnabled)
{
const bool bIsLoadTime = true;
RebuildAll(bIsLoadTime);
}
}
if (!bCanAccumulateDirtyAreas)
{
DirtyAreas.Empty();
}
bWorldInitDone = true;
OnNavigationInitDone.Broadcast();
}
void UNavigationSystemV1::RegisterNavigationDataInstances()
{
UWorld* World = GetWorld();
bool bProcessRegistration = false;
for (TActorIterator<ANavigationData> It(World); It; ++It)
{
ANavigationData* Nav = (*It);
if (Nav != NULL && Nav->IsPendingKill() == false && Nav->IsRegistered() == false)
{
RequestRegistration(Nav, false);
bProcessRegistration = true;
}
}
if (bProcessRegistration == true)
{
ProcessRegistrationCandidates();
}
}
void UNavigationSystemV1::CreateCrowdManager()
{
if (CrowdManagerClass)
{
SetCrowdManager(NewObject<UCrowdManagerBase>(this, CrowdManagerClass));
}
}
void UNavigationSystemV1::SetCrowdManager(UCrowdManagerBase* NewCrowdManager)
{
if (NewCrowdManager == CrowdManager.Get())
{
return;
}
if (CrowdManager.IsValid())
{
CrowdManager->RemoveFromRoot();
}
CrowdManager = NewCrowdManager;
if (NewCrowdManager != NULL)
{
CrowdManager->AddToRoot();
}
}
void UNavigationSystemV1::Tick(float DeltaSeconds)
{
SET_DWORD_STAT(STAT_Navigation_ObservedPathsCount, 0);
UWorld* World = GetWorld();
if (World == nullptr || (bTickWhilePaused == false && World->IsPaused()))
{
return;
}
const bool bIsGame = World->IsGameWorld();
if (PendingCustomLinkRegistration.Num())
{
ProcessCustomLinkPendingRegistration();
}
if (PendingNavBoundsUpdates.Num() > 0)
{
PerformNavigationBoundsUpdate(PendingNavBoundsUpdates);
PendingNavBoundsUpdates.Reset();
}
if (PendingOctreeUpdates.Num() > 0)
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_AddingActorsToNavOctree);
SCOPE_CYCLE_COUNTER(STAT_Navigation_BuildTime)
STAT(double ThisTime = 0);
{
SCOPE_SECONDS_COUNTER(ThisTime);
for (TSet<FNavigationDirtyElement>::TIterator It(PendingOctreeUpdates); It; ++It)
{
AddElementToNavOctree(*It);
}
PendingOctreeUpdates.Empty(32);
}
INC_FLOAT_STAT_BY(STAT_Navigation_CumulativeBuildTime,(float)ThisTime*1000);
}
if (bGenerateNavigationOnlyAroundNavigationInvokers)
{
UpdateInvokers();
}
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_TickMarkDirty);
DirtyAreasUpdateTime += DeltaSeconds;
const float DirtyAreasUpdateDeltaTime = 1.0f / DirtyAreasUpdateFreq;
const bool bCanRebuildNow = (DirtyAreasUpdateTime >= DirtyAreasUpdateDeltaTime) || !bIsGame;
const bool bIsLocked = IsNavigationBuildingLocked();
if (DirtyAreas.Num() > 0 && bCanRebuildNow && !bIsLocked)
{
for (int32 NavDataIndex = 0; NavDataIndex < NavDataSet.Num(); ++NavDataIndex)
{
ANavigationData* NavData = NavDataSet[NavDataIndex];
if (NavData)
{
NavData->RebuildDirtyAreas(DirtyAreas);
}
}
DirtyAreasUpdateTime = 0;
DirtyAreas.Reset();
}
}
// Tick navigation mesh async builders
if (!bAsyncBuildPaused)
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_TickAsyncBuild);
for (ANavigationData* NavData : NavDataSet)
{
if (NavData)
{
NavData->TickAsyncBuild(DeltaSeconds);
}
}
}
if (AsyncPathFindingQueries.Num() > 0)
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_TickAsyncPathfinding);
TriggerAsyncQueries(AsyncPathFindingQueries);
AsyncPathFindingQueries.Reset();
}
if (CrowdManager.IsValid())
{
CrowdManager->Tick(DeltaSeconds);
}
}
void UNavigationSystemV1::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector)
{
UNavigationSystemV1* This = CastChecked<UNavigationSystemV1>(InThis);
UCrowdManagerBase* CrowdManager = This->GetCrowdManager();
Collector.AddReferencedObject(CrowdManager, InThis);
// don't reference NavAreaClasses in editor (unless PIE is active)
if (This->OperationMode != FNavigationSystemRunMode::EditorMode)
{
Collector.AddReferencedObjects(This->NavAreaClasses, InThis);
}
}
#if WITH_EDITOR
void UNavigationSystemV1::SetNavigationAutoUpdateEnabled(bool bNewEnable, UNavigationSystemBase* InNavigationSystemBase)
{
if (bNewEnable != bNavigationAutoUpdateEnabled)
{
bNavigationAutoUpdateEnabled = bNewEnable;
UNavigationSystemV1* NavSystem = Cast<UNavigationSystemV1>(InNavigationSystemBase);
if (NavSystem)
{
NavSystem->bCanAccumulateDirtyAreas = bNavigationAutoUpdateEnabled
|| (NavSystem->OperationMode != FNavigationSystemRunMode::EditorMode && NavSystem->OperationMode != FNavigationSystemRunMode::InvalidMode);
if (bNavigationAutoUpdateEnabled)
{
const bool bSkipRebuildsInEditor = false;
NavSystem->RemoveNavigationBuildLock(ENavigationBuildLock::NoUpdateInEditor, bSkipRebuildsInEditor);
}
else
{
#if !UE_BUILD_SHIPPING
NavSystem->bDirtyAreasReportedWhileAccumulationLocked = false;
#endif // !UE_BUILD_SHIPPING
NavSystem->AddNavigationBuildLock(ENavigationBuildLock::NoUpdateInEditor);
}
}
}
}
#endif // WITH_EDITOR
//----------------------------------------------------------------------//
// Public querying interface
//----------------------------------------------------------------------//
FPathFindingResult UNavigationSystemV1::FindPathSync(const FNavAgentProperties& AgentProperties, FPathFindingQuery Query, EPathFindingMode::Type Mode)
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_PathfindingSync);
if (Query.NavData.IsValid() == false)
{
Query.NavData = GetNavDataForProps(AgentProperties);
}
FPathFindingResult Result(ENavigationQueryResult::Error);
if (Query.NavData.IsValid())
{
if (Mode == EPathFindingMode::Hierarchical)
{
Result = Query.NavData->FindHierarchicalPath(AgentProperties, Query);
}
else
{
Result = Query.NavData->FindPath(AgentProperties, Query);
}
}
return Result;
}
FPathFindingResult UNavigationSystemV1::FindPathSync(FPathFindingQuery Query, EPathFindingMode::Type Mode)
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_PathfindingSync);
if (Query.NavData.IsValid() == false)
{
Query.NavData = GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
}
FPathFindingResult Result(ENavigationQueryResult::Error);
if (Query.NavData.IsValid())
{
if (Mode == EPathFindingMode::Regular)
{
Result = Query.NavData->FindPath(Query.NavAgentProperties, Query);
}
else // EPathFindingMode::Hierarchical
{
Result = Query.NavData->FindHierarchicalPath(Query.NavAgentProperties, Query);
}
}
return Result;
}
bool UNavigationSystemV1::TestPathSync(FPathFindingQuery Query, EPathFindingMode::Type Mode, int32* NumVisitedNodes) const
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_PathfindingSync);
if (Query.NavData.IsValid() == false)
{
Query.NavData = GetDefaultNavDataInstance();
}
bool bExists = false;
if (Query.NavData.IsValid())
{
if (Mode == EPathFindingMode::Hierarchical)
{
bExists = Query.NavData->TestHierarchicalPath(Query.NavAgentProperties, Query, NumVisitedNodes);
}
else
{
bExists = Query.NavData->TestPath(Query.NavAgentProperties, Query, NumVisitedNodes);
}
}
return bExists;
}
void UNavigationSystemV1::AddAsyncQuery(const FAsyncPathFindingQuery& Query)
{
check(IsInGameThread());
AsyncPathFindingQueries.Add(Query);
}
uint32 UNavigationSystemV1::FindPathAsync(const FNavAgentProperties& AgentProperties, FPathFindingQuery Query, const FNavPathQueryDelegate& ResultDelegate, EPathFindingMode::Type Mode)
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_RequestingAsyncPathfinding);
if (Query.NavData.IsValid() == false)
{
Query.NavData = GetNavDataForProps(AgentProperties);
}
if (Query.NavData.IsValid())
{
FAsyncPathFindingQuery AsyncQuery(Query, ResultDelegate, Mode);
if (AsyncQuery.QueryID != INVALID_NAVQUERYID)
{
AddAsyncQuery(AsyncQuery);
}
return AsyncQuery.QueryID;
}
return INVALID_NAVQUERYID;
}
void UNavigationSystemV1::AbortAsyncFindPathRequest(uint32 AsynPathQueryID)
{
check(IsInGameThread());
FAsyncPathFindingQuery* Query = AsyncPathFindingQueries.GetData();
for (int32 Index = 0; Index < AsyncPathFindingQueries.Num(); ++Index, ++Query)
{
if (Query->QueryID == AsynPathQueryID)
{
AsyncPathFindingQueries.RemoveAtSwap(Index);
break;
}
}
}
FAutoConsoleTaskPriority CPrio_TriggerAsyncQueries(
TEXT("TaskGraph.TaskPriorities.NavTriggerAsyncQueries"),
TEXT("Task and thread priority for UNavigationSystemV1::PerformAsyncQueries."),
ENamedThreads::BackgroundThreadPriority, // if we have background priority task threads, then use them...
ENamedThreads::NormalTaskPriority, // .. at normal task priority
ENamedThreads::NormalTaskPriority // if we don't have background threads, then use normal priority threads at normal task priority instead
);
void UNavigationSystemV1::TriggerAsyncQueries(TArray<FAsyncPathFindingQuery>& PathFindingQueries)
{
DECLARE_CYCLE_STAT(TEXT("FSimpleDelegateGraphTask.NavigationSystem batched async queries"),
STAT_FSimpleDelegateGraphTask_NavigationSystemBatchedAsyncQueries,
STATGROUP_TaskGraphTasks);
FSimpleDelegateGraphTask::CreateAndDispatchWhenReady(
FSimpleDelegateGraphTask::FDelegate::CreateUObject(this, &UNavigationSystemV1::PerformAsyncQueries, PathFindingQueries),
GET_STATID(STAT_FSimpleDelegateGraphTask_NavigationSystemBatchedAsyncQueries), nullptr, CPrio_TriggerAsyncQueries.Get());
}
static void AsyncQueryDone(FAsyncPathFindingQuery Query)
{
Query.OnDoneDelegate.ExecuteIfBound(Query.QueryID, Query.Result.Result, Query.Result.Path);
}
void UNavigationSystemV1::PerformAsyncQueries(TArray<FAsyncPathFindingQuery> PathFindingQueries)
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_PathfindingAsync);
if (PathFindingQueries.Num() == 0)
{
return;
}
for (FAsyncPathFindingQuery& Query : PathFindingQueries)
{
// @todo this is not necessarily the safest way to use UObjects outside of main thread.
// think about something else.
const ANavigationData* NavData = Query.NavData.IsValid() ? Query.NavData.Get() : GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
// perform query
if (NavData)
{
if (Query.Mode == EPathFindingMode::Hierarchical)
{
Query.Result = NavData->FindHierarchicalPath(Query.NavAgentProperties, Query);
}
else
{
Query.Result = NavData->FindPath(Query.NavAgentProperties, Query);
}
}
else
{
Query.Result = ENavigationQueryResult::Error;
}
// @todo make it return more informative results (bResult == false)
// trigger calling delegate on main thread - otherwise it may depend too much on stuff being thread safe
DECLARE_CYCLE_STAT(TEXT("FSimpleDelegateGraphTask.Async nav query finished"),
STAT_FSimpleDelegateGraphTask_AsyncNavQueryFinished,
STATGROUP_TaskGraphTasks);
FSimpleDelegateGraphTask::CreateAndDispatchWhenReady(
FSimpleDelegateGraphTask::FDelegate::CreateStatic(AsyncQueryDone, Query),
GET_STATID(STAT_FSimpleDelegateGraphTask_AsyncNavQueryFinished), NULL, ENamedThreads::GameThread);
}
}
bool UNavigationSystemV1::GetRandomPoint(FNavLocation& ResultLocation, ANavigationData* NavData, FSharedConstNavQueryFilter QueryFilter)
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_QueriesTimeSync);
if (NavData == NULL)
{
NavData = MainNavData;
}
if (NavData != NULL)
{
ResultLocation = NavData->GetRandomPoint(QueryFilter);
return true;
}
return false;
}
bool UNavigationSystemV1::GetRandomReachablePointInRadius(const FVector& Origin, float Radius, FNavLocation& ResultLocation, ANavigationData* NavData, FSharedConstNavQueryFilter QueryFilter) const
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_QueriesTimeSync);
if (NavData == nullptr)
{
NavData = MainNavData;
}
return NavData != nullptr && NavData->GetRandomReachablePointInRadius(Origin, Radius, ResultLocation, QueryFilter);
}
bool UNavigationSystemV1::GetRandomPointInNavigableRadius(const FVector& Origin, float Radius, FNavLocation& ResultLocation, ANavigationData* NavData, FSharedConstNavQueryFilter QueryFilter) const
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_QueriesTimeSync);
if (NavData == nullptr)
{
NavData = MainNavData;
}
return NavData != nullptr && NavData->GetRandomPointInNavigableRadius(Origin, Radius, ResultLocation, QueryFilter);
}
ENavigationQueryResult::Type UNavigationSystemV1::GetPathCost(const FVector& PathStart, const FVector& PathEnd, float& OutPathCost, const ANavigationData* NavData, FSharedConstNavQueryFilter QueryFilter) const
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_QueriesTimeSync);
if (NavData == NULL)
{
NavData = GetDefaultNavDataInstance();
}
return NavData != NULL ? NavData->CalcPathCost(PathStart, PathEnd, OutPathCost, QueryFilter) : ENavigationQueryResult::Error;
}
ENavigationQueryResult::Type UNavigationSystemV1::GetPathLength(const FVector& PathStart, const FVector& PathEnd, float& OutPathLength, const ANavigationData* NavData, FSharedConstNavQueryFilter QueryFilter) const
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_QueriesTimeSync);
if (NavData == NULL)
{
NavData = GetDefaultNavDataInstance();
}
return NavData != NULL ? NavData->CalcPathLength(PathStart, PathEnd, OutPathLength, QueryFilter) : ENavigationQueryResult::Error;
}
ENavigationQueryResult::Type UNavigationSystemV1::GetPathLengthAndCost(const FVector& PathStart, const FVector& PathEnd, float& OutPathLength, float& OutPathCost, const ANavigationData* NavData, FSharedConstNavQueryFilter QueryFilter) const
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_QueriesTimeSync);
if (NavData == NULL)
{
NavData = GetDefaultNavDataInstance();
}
return NavData != NULL ? NavData->CalcPathLengthAndCost(PathStart, PathEnd, OutPathLength, OutPathCost, QueryFilter) : ENavigationQueryResult::Error;
}
bool UNavigationSystemV1::ProjectPointToNavigation(const FVector& Point, FNavLocation& OutLocation, const FVector& Extent, const ANavigationData* NavData, FSharedConstNavQueryFilter QueryFilter) const
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_QueriesTimeSync);
if (NavData == NULL)
{
NavData = GetDefaultNavDataInstance();
}
return NavData != NULL && NavData->ProjectPoint(Point, OutLocation
, FNavigationSystem::IsValidExtent(Extent) ? Extent : NavData->GetConfig().DefaultQueryExtent
, QueryFilter);
}
UNavigationPath* UNavigationSystemV1::FindPathToActorSynchronously(UObject* WorldContextObject, const FVector& PathStart, AActor* GoalActor, float TetherDistance, AActor* PathfindingContext, TSubclassOf<UNavigationQueryFilter> FilterClass)
{
if (GoalActor == NULL)
{
return NULL;
}
INavAgentInterface* NavAgent = Cast<INavAgentInterface>(GoalActor);
UNavigationPath* GeneratedPath = FindPathToLocationSynchronously(WorldContextObject, PathStart, NavAgent ? NavAgent->GetNavAgentLocation() : GoalActor->GetActorLocation(), PathfindingContext, FilterClass);
if (GeneratedPath != NULL && GeneratedPath->GetPath().IsValid() == true)
{
GeneratedPath->GetPath()->SetGoalActorObservation(*GoalActor, TetherDistance);
}
return GeneratedPath;
}
UNavigationPath* UNavigationSystemV1::FindPathToLocationSynchronously(UObject* WorldContextObject, const FVector& PathStart, const FVector& PathEnd, AActor* PathfindingContext, TSubclassOf<UNavigationQueryFilter> FilterClass)
{
UWorld* World = NULL;
if (WorldContextObject != NULL)
{
World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
}
if (World == NULL && PathfindingContext != NULL)
{
World = GEngine->GetWorldFromContextObject(PathfindingContext, EGetWorldErrorMode::LogAndReturnNull);
}
UNavigationPath* ResultPath = NULL;
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
if (NavSys != nullptr && NavSys->GetDefaultNavDataInstance() != nullptr)
{
ResultPath = NewObject<UNavigationPath>(NavSys);
bool bValidPathContext = false;
const ANavigationData* NavigationData = NULL;
if (PathfindingContext != NULL)
{
INavAgentInterface* NavAgent = Cast<INavAgentInterface>(PathfindingContext);
if (NavAgent != NULL)
{
const FNavAgentProperties& AgentProps = NavAgent->GetNavAgentPropertiesRef();
NavigationData = NavSys->GetNavDataForProps(AgentProps);
bValidPathContext = true;
}
else if (Cast<ANavigationData>(PathfindingContext))
{
NavigationData = (ANavigationData*)PathfindingContext;
bValidPathContext = true;
}
}
if (bValidPathContext == false)
{
// just use default
NavigationData = NavSys->GetDefaultNavDataInstance();
}
check(NavigationData);
const FPathFindingQuery Query(PathfindingContext, *NavigationData, PathStart, PathEnd, UNavigationQueryFilter::GetQueryFilter(*NavigationData, PathfindingContext, FilterClass));
const FPathFindingResult Result = NavSys->FindPathSync(Query, EPathFindingMode::Regular);
if (Result.IsSuccessful())
{
ResultPath->SetPath(Result.Path);
}
}
return ResultPath;
}
bool UNavigationSystemV1::NavigationRaycast(UObject* WorldContextObject, const FVector& RayStart, const FVector& RayEnd, FVector& HitLocation, TSubclassOf<UNavigationQueryFilter> FilterClass, AController* Querier)
{
UWorld* World = NULL;
if (WorldContextObject != NULL)
{
World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
}
if (World == NULL && Querier != NULL)
{
World = GEngine->GetWorldFromContextObject(Querier, EGetWorldErrorMode::LogAndReturnNull);
}
// blocked, i.e. not traversable, by default
bool bRaycastBlocked = true;
HitLocation = RayStart;
const UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
if (NavSys)
{
// figure out which navigation data to use
const ANavigationData* NavData = NULL;
INavAgentInterface* MyNavAgent = Cast<INavAgentInterface>(Querier);
if (MyNavAgent)
{
const FNavAgentProperties& AgentProps = MyNavAgent->GetNavAgentPropertiesRef();
NavData = NavSys->GetNavDataForProps(AgentProps);
}
if (NavData == NULL)
{
NavData = NavSys->GetDefaultNavDataInstance();
}
if (NavData != NULL)
{
bRaycastBlocked = NavData->Raycast(RayStart, RayEnd, HitLocation, UNavigationQueryFilter::GetQueryFilter(*NavData, Querier, FilterClass));
}
}
return bRaycastBlocked;
}
void UNavigationSystemV1::GetNavAgentPropertiesArray(TArray<FNavAgentProperties>& OutNavAgentProperties) const
{
AgentToNavDataMap.GetKeys(OutNavAgentProperties);
}
ANavigationData* UNavigationSystemV1::GetNavDataForProps(const FNavAgentProperties& AgentProperties)
{
const UNavigationSystemV1* ConstThis = AsConst(this);
return const_cast<ANavigationData*>(ConstThis->GetNavDataForProps(AgentProperties));
}
// @todo could optimize this by having "SupportedAgentIndex" in FNavAgentProperties
const ANavigationData* UNavigationSystemV1::GetNavDataForProps(const FNavAgentProperties& AgentProperties) const
{
if (SupportedAgents.Num() <= 1)
{
return MainNavData;
}
const TWeakObjectPtr<ANavigationData>* NavDataForAgent = AgentToNavDataMap.Find(AgentProperties);
const ANavigationData* NavDataInstance = NavDataForAgent ? NavDataForAgent->Get() : nullptr;
if (NavDataInstance == nullptr)
{
TArray<FNavAgentProperties> AgentPropertiesList;
AgentToNavDataMap.GenerateKeyArray(AgentPropertiesList);
FNavAgentProperties BestFitNavAgent;
float BestExcessHeight = -FLT_MAX;
float BestExcessRadius = -FLT_MAX;
float ExcessRadius = -FLT_MAX;
float ExcessHeight = -FLT_MAX;
const float AgentHeight = bSkipAgentHeightCheckWhenPickingNavData ? 0.f : AgentProperties.AgentHeight;
for (TArray<FNavAgentProperties>::TConstIterator It(AgentPropertiesList); It; ++It)
{
const FNavAgentProperties& NavIt = *It;
const bool bNavClassMatch = NavIt.IsNavDataMatching(AgentProperties);
if (!bNavClassMatch)
{
continue;
}
ExcessRadius = NavIt.AgentRadius - AgentProperties.AgentRadius;
ExcessHeight = bSkipAgentHeightCheckWhenPickingNavData ? 0.f : (NavIt.AgentHeight - AgentHeight);
const bool bExcessRadiusIsBetter = ((ExcessRadius == 0) && (BestExcessRadius != 0))
|| ((ExcessRadius > 0) && (BestExcessRadius < 0))
|| ((ExcessRadius > 0) && (BestExcessRadius > 0) && (ExcessRadius < BestExcessRadius))
|| ((ExcessRadius < 0) && (BestExcessRadius < 0) && (ExcessRadius > BestExcessRadius));
const bool bExcessHeightIsBetter = ((ExcessHeight == 0) && (BestExcessHeight != 0))
|| ((ExcessHeight > 0) && (BestExcessHeight < 0))
|| ((ExcessHeight > 0) && (BestExcessHeight > 0) && (ExcessHeight < BestExcessHeight))
|| ((ExcessHeight < 0) && (BestExcessHeight < 0) && (ExcessHeight > BestExcessHeight));
const bool bBestIsValid = (BestExcessRadius >= 0) && (BestExcessHeight >= 0);
const bool bRadiusEquals = (ExcessRadius == BestExcessRadius);
const bool bHeightEquals = (ExcessHeight == BestExcessHeight);
bool bValuesAreBest = ((bExcessRadiusIsBetter || bRadiusEquals) && (bExcessHeightIsBetter || bHeightEquals));
if (!bValuesAreBest && !bBestIsValid)
{
bValuesAreBest = bExcessRadiusIsBetter || (bRadiusEquals && bExcessHeightIsBetter);
}
if (bValuesAreBest)
{
BestFitNavAgent = NavIt;
BestExcessHeight = ExcessHeight;
BestExcessRadius = ExcessRadius;
}
}
if (BestFitNavAgent.IsValid())
{
NavDataForAgent = AgentToNavDataMap.Find(BestFitNavAgent);
NavDataInstance = NavDataForAgent ? NavDataForAgent->Get() : nullptr;
}
}
return NavDataInstance ? NavDataInstance : MainNavData;
}
ANavigationData* UNavigationSystemV1::GetDefaultNavDataInstance(FNavigationSystem::ECreateIfMissing CreateNewIfNoneFound)
{
checkSlow(IsInGameThread() == true);
if (MainNavData == NULL || MainNavData->IsPendingKill())
{
MainNavData = NULL;
// @TODO this should be done a differently. There should be specified a "default agent"
for (int32 NavDataIndex = 0; NavDataIndex < NavDataSet.Num(); ++NavDataIndex)
{
ANavigationData* NavData = NavDataSet[NavDataIndex];
if (NavData && !NavData->IsPendingKill() && NavData->CanBeMainNavData())
{
MainNavData = NavData;
break;
}
}
#if WITH_RECAST
if ( /*GIsEditor && */(MainNavData == NULL) && CreateNewIfNoneFound == FNavigationSystem::Create )
{
// Spawn a new one if we're in the editor. In-game, either we loaded one or we don't get one.
MainNavData = GetWorld()->SpawnActor<ANavigationData>(ARecastNavMesh::StaticClass());
}
#endif // WITH_RECAST
// either way make sure it's registered. Registration stores unique
// navmeshes, so we have nothing to lose
RegisterNavData(MainNavData);
}
return MainNavData;
}
FSharedNavQueryFilter UNavigationSystemV1::CreateDefaultQueryFilterCopy() const
{
return MainNavData ? MainNavData->GetDefaultQueryFilter()->GetCopy() : NULL;
}
bool UNavigationSystemV1::IsNavigationBuilt(const AWorldSettings* Settings) const
{
if (Settings == nullptr || Settings->IsNavigationSystemEnabled() == false || IsThereAnywhereToBuildNavigation() == false)
{
return true;
}
bool bIsBuilt = true;
for (int32 NavDataIndex = 0; NavDataIndex < NavDataSet.Num(); ++NavDataIndex)
{
ANavigationData* NavData = NavDataSet[NavDataIndex];
if (NavData != NULL && NavData->GetWorldSettings() == Settings)
{
FNavDataGenerator* Generator = NavData->GetGenerator();
if ((NavData->GetRuntimeGenerationMode() != ERuntimeGenerationType::Static
#if WITH_EDITOR
|| GEditor != NULL
#endif // WITH_EDITOR
) && (Generator == NULL || Generator->IsBuildInProgress(/*bCheckDirtyToo=*/true) == true))
{
bIsBuilt = false;
break;
}
}
}
return bIsBuilt;
}
bool UNavigationSystemV1::IsThereAnywhereToBuildNavigation() const
{
// not check if there are any volumes or other structures requiring/supporting navigation building
if (bWholeWorldNavigable == true)
{
return true;
}
// @TODO this should be done more flexible to be able to trigger this from game-specific
// code (like Navigation System's subclass maybe)
bool bCreateNavigation = false;
for (TActorIterator<ANavMeshBoundsVolume> It(GetWorld()); It; ++It)
{
ANavMeshBoundsVolume const* const V = (*It);
if (V != NULL && !V->IsPendingKill())
{
bCreateNavigation = true;
break;
}
}
return bCreateNavigation;
}
bool UNavigationSystemV1::IsNavigationRelevant(const AActor* TestActor) const
{
const INavRelevantInterface* NavInterface = Cast<const INavRelevantInterface>(TestActor);
if (NavInterface && NavInterface->IsNavigationRelevant())
{
return true;
}
if (TestActor)
{
TInlineComponentArray<UActorComponent*> Components;
for (int32 Idx = 0; Idx < Components.Num(); Idx++)
{
NavInterface = Cast<const INavRelevantInterface>(Components[Idx]);
if (NavInterface && NavInterface->IsNavigationRelevant())
{
return true;
}
}
}
return false;
}
FBox UNavigationSystemV1::GetWorldBounds() const
{
checkSlow(IsInGameThread() == true);
NavigableWorldBounds = FBox(ForceInit);
if (GetWorld() != nullptr)
{
if (bWholeWorldNavigable == false)
{
for (const FNavigationBounds& Bounds : RegisteredNavBounds)
{
NavigableWorldBounds += Bounds.AreaBox;
}
}
else
{
// @TODO - super slow! Need to ask tech guys where I can get this from
for (FActorIterator It(GetWorld()); It; ++It)
{
if (IsNavigationRelevant(*It))
{
NavigableWorldBounds += (*It)->GetComponentsBoundingBox();
}
}
}
}
return NavigableWorldBounds;
}
FBox UNavigationSystemV1::GetLevelBounds(ULevel* InLevel) const
{
FBox NavigableLevelBounds(ForceInit);
if (InLevel)
{
AActor** Actor = InLevel->Actors.GetData();
const int32 ActorCount = InLevel->Actors.Num();
for (int32 ActorIndex = 0; ActorIndex < ActorCount; ++ActorIndex, ++Actor)
{
if (IsNavigationRelevant(*Actor))
{
NavigableLevelBounds += (*Actor)->GetComponentsBoundingBox();
}
}
}
return NavigableLevelBounds;
}
const TSet<FNavigationBounds>& UNavigationSystemV1::GetNavigationBounds() const
{
return RegisteredNavBounds;
}
void UNavigationSystemV1::ApplyWorldOffset(const FVector& InOffset, bool bWorldShift)
{
// Attempt at generation of new nav mesh after the shift
// dynamic navmesh, we regenerate completely
if (GetRuntimeGenerationType() == ERuntimeGenerationType::Dynamic)
{
//stop generators from building navmesh
for (ANavigationData* NavData : NavDataSet)
{
if (NavData)
{
if (NavData->GetGenerator()) NavData->GetGenerator()->CancelBuild();
}
}
ConditionalPopulateNavOctree();
Build();
for (ANavigationData* NavData : NavDataSet)
{
if (NavData)
{
NavData->ConditionalConstructGenerator();
ARecastNavMesh* RecastNavMesh = Cast<ARecastNavMesh>(NavData);
if (RecastNavMesh) RecastNavMesh->RequestDrawingUpdate();
}
}
}
else // static navmesh
{
//not sure what happens when we shift farther than the extents of the NavOctree are
for (ANavigationData* NavData : NavDataSet)
{
if (NavData)
{
NavData->ApplyWorldOffset(InOffset, bWorldShift);
}
}
}
}
//----------------------------------------------------------------------//
// Bookkeeping
//----------------------------------------------------------------------//
void UNavigationSystemV1::RequestRegistration(ANavigationData* NavData, bool bTriggerRegistrationProcessing)
{
FScopeLock RegistrationLock(&NavDataRegistrationSection);
if (NavDataRegistrationQueue.Num() < REGISTRATION_QUEUE_SIZE)
{
NavDataRegistrationQueue.AddUnique(NavData);
if (bTriggerRegistrationProcessing == true)
{
// trigger registration candidates processing
DECLARE_CYCLE_STAT(TEXT("FSimpleDelegateGraphTask.Process registration candidates"),
STAT_FSimpleDelegateGraphTask_ProcessRegistrationCandidates,
STATGROUP_TaskGraphTasks);
FSimpleDelegateGraphTask::CreateAndDispatchWhenReady(
FSimpleDelegateGraphTask::FDelegate::CreateUObject(this, &UNavigationSystemV1::ProcessRegistrationCandidates),
GET_STATID(STAT_FSimpleDelegateGraphTask_ProcessRegistrationCandidates), NULL, ENamedThreads::GameThread);
}
}
else
{
UE_LOG(LogNavigation, Error, TEXT("Navigation System: registration queue full!"));
}
}
void UNavigationSystemV1::ProcessRegistrationCandidates()
{
FScopeLock RegistrationLock(&NavDataRegistrationSection);
if (NavDataRegistrationQueue.Num() == 0)
{
return;
}
ANavigationData** NavDataPtr = NavDataRegistrationQueue.GetData();
const int CandidatesCount = NavDataRegistrationQueue.Num();
for (int32 CandidateIndex = 0; CandidateIndex < CandidatesCount; ++CandidateIndex, ++NavDataPtr)
{
if (*NavDataPtr != NULL)
{
ERegistrationResult Result = RegisterNavData(*NavDataPtr);
if (Result == RegistrationSuccessful)
{
continue;
}
else if (Result != RegistrationFailed_DataPendingKill)
{
(*NavDataPtr)->CleanUpAndMarkPendingKill();
if ((*NavDataPtr) == MainNavData)
{
MainNavData = NULL;
}
}
}
}
MainNavData = GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
// we processed all candidates so clear the queue
NavDataRegistrationQueue.Reset();
}
void UNavigationSystemV1::ProcessCustomLinkPendingRegistration()
{
FScopeLock AccessLock(&CustomLinkRegistrationSection);
TMap<INavLinkCustomInterface*, FWeakObjectPtr> TempPending = PendingCustomLinkRegistration;
PendingCustomLinkRegistration.Empty();
for (TMap<INavLinkCustomInterface*, FWeakObjectPtr>::TIterator It(TempPending); It; ++It)
{
INavLinkCustomInterface* ILink = It.Key();
FWeakObjectPtr LinkOb = It.Value();
if (LinkOb.IsValid() && ILink)
{
RegisterCustomLink(*ILink);
}
}
}
UNavigationSystemV1::ERegistrationResult UNavigationSystemV1::RegisterNavData(ANavigationData* NavData)
{
if (NavData == NULL)
{
return RegistrationError;
}
else if (NavData->IsPendingKill() == true)
{
return RegistrationFailed_DataPendingKill;
}
// still to be seen if this is really true, but feels right
else if (NavData->IsRegistered() == true)
{
return RegistrationSuccessful;
}
FScopeLock Lock(&NavDataRegistration);
UNavigationSystemV1::ERegistrationResult Result = RegistrationError;
// find out which, if any, navigation agents are supported by this nav data
// if none then fail the registration
FNavDataConfig NavConfig = NavData->GetConfig();
// not discarding navmesh when there's only one Supported Agent
if (NavConfig.IsValid() == false && SupportedAgents.Num() == 1)
{
// fill in AgentProps with whatever is the instance's setup
NavConfig = SupportedAgents[0];
NavData->SetConfig(SupportedAgents[0]);
NavData->SetSupportsDefaultAgent(true);
NavData->ProcessNavAreas(NavAreaClasses, 0);
}
if (NavConfig.IsValid() == true)
{
// check if this kind of agent has already its navigation implemented
TWeakObjectPtr<ANavigationData>* NavDataForAgent = AgentToNavDataMap.Find(NavConfig);
ANavigationData* NavDataInstanceForAgent = NavDataForAgent ? NavDataForAgent->Get() : nullptr;
if (NavDataInstanceForAgent == nullptr)
{
if (NavData->IsA(AAbstractNavData::StaticClass()) == false)
{
// ok, so this navigation agent doesn't have its navmesh registered yet, but do we want to support it?
bool bAgentSupported = false;
for (int32 AgentIndex = 0; AgentIndex < SupportedAgents.Num(); ++AgentIndex)
{
if (NavData->GetClass() == SupportedAgents[AgentIndex].NavigationDataClass && SupportedAgents[AgentIndex].IsEquivalent(NavConfig) == true)
{
// it's supported, then just in case it's not a precise match (IsEquivalent succeeds with some precision)
// update NavData with supported Agent
bAgentSupported = true;
NavData->SetConfig(SupportedAgents[AgentIndex]);
AgentToNavDataMap.Add(SupportedAgents[AgentIndex], NavData);
NavData->SetSupportsDefaultAgent(AgentIndex == 0);
NavData->ProcessNavAreas(NavAreaClasses, AgentIndex);
OnNavDataRegisteredEvent.Broadcast(NavData);
NavDataSet.AddUnique(NavData);
NavData->OnRegistered();
break;
}
}
Result = bAgentSupported == true ? RegistrationSuccessful : RegistrationFailed_AgentNotValid;
}
else
{
// fake registration since it's a special navigation data type
// and it would get discarded for not implementing any particular
// navigation agent
// Node that we don't add abstract navigation data to NavDataSet
NavData->OnRegistered();
Result = RegistrationSuccessful;
}
}
else if (NavDataInstanceForAgent == NavData)
{
ensure(NavDataSet.Find(NavData) != INDEX_NONE);
// let's treat double registration of the same nav data with the same agent as a success
Result = RegistrationSuccessful;
}
else
{
// otherwise specified agent type already has its navmesh implemented, fail redundant instance
Result = RegistrationFailed_AgentAlreadySupported;
}
}
else
{
Result = RegistrationFailed_AgentNotValid;
}
// @todo else might consider modifying this NavData to implement navigation for one of the supported agents
// care needs to be taken to not make it implement navigation for agent who's real implementation has
// not been loaded yet.
return Result;
}
void UNavigationSystemV1::UnregisterNavData(ANavigationData* NavData)
{
NavDataSet.RemoveSingle(NavData);
if (NavData == NULL)
{
return;
}
FScopeLock Lock(&NavDataRegistration);
NavData->OnUnregistered();
}
void UNavigationSystemV1::RegisterCustomLink(INavLinkCustomInterface& CustomLink)
{
uint32 LinkId = CustomLink.GetLinkId();
// if there's already a link with that Id registered, assign new Id and mark dirty area
// this won't fix baked data in static navmesh (in game), but every other case will regenerate affected tiles
if (CustomLinksMap.Contains(LinkId))
{
LinkId = INavLinkCustomInterface::GetUniqueId();
CustomLink.UpdateLinkId(LinkId);
UObject* CustomLinkOb = CustomLink.GetLinkOwner();
UActorComponent* OwnerComp = Cast<UActorComponent>(CustomLinkOb);
AActor* OwnerActor = OwnerComp ? OwnerComp->GetOwner() : Cast<AActor>(CustomLinkOb);
if (OwnerActor)
{
ENavLinkDirection::Type DummyDir = ENavLinkDirection::BothWays;
FVector RelativePtA, RelativePtB;
CustomLink.GetLinkData(RelativePtA, RelativePtB, DummyDir);
const FTransform OwnerActorTM = OwnerActor->GetTransform();
const FVector WorldPtA = OwnerActorTM.TransformPosition(RelativePtA);
const FVector WorldPtB = OwnerActorTM.TransformPosition(RelativePtB);
FBox LinkBounds(EForceInit::ForceInitToZero);
LinkBounds += WorldPtA;
LinkBounds += WorldPtB;
AddDirtyArea(LinkBounds, OctreeUpdate_Modifiers);
}
}
CustomLinksMap.Add(LinkId, FNavigationSystem::FCustomLinkOwnerInfo(&CustomLink));
}
void UNavigationSystemV1::UnregisterCustomLink(INavLinkCustomInterface& CustomLink)
{
CustomLinksMap.Remove(CustomLink.GetLinkId());
}
INavLinkCustomInterface* UNavigationSystemV1::GetCustomLink(uint32 UniqueLinkId) const
{
const FNavigationSystem::FCustomLinkOwnerInfo* LinkInfo = CustomLinksMap.Find(UniqueLinkId);
return (LinkInfo && LinkInfo->IsValid()) ? LinkInfo->LinkInterface : nullptr;
}
void UNavigationSystemV1::UpdateCustomLink(const INavLinkCustomInterface* CustomLink)
{
for (TMap<FNavAgentProperties, TWeakObjectPtr<ANavigationData> >::TIterator It(AgentToNavDataMap); It; ++It)
{
ANavigationData* NavData = It.Value().Get();
if (NavData)
{
NavData->UpdateCustomLink(CustomLink);
}
}
}
void UNavigationSystemV1::RequestCustomLinkRegistering(INavLinkCustomInterface& CustomLink, UObject* OwnerOb)
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(OwnerOb);
if (NavSys)
{
NavSys->RegisterCustomLink(CustomLink);
}
else
{
FScopeLock AccessLock(&CustomLinkRegistrationSection);
PendingCustomLinkRegistration.Add(&CustomLink, OwnerOb);
}
}
void UNavigationSystemV1::RequestCustomLinkUnregistering(INavLinkCustomInterface& CustomLink, UObject* OwnerOb)
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(OwnerOb);
if (NavSys)
{
NavSys->UnregisterCustomLink(CustomLink);
}
else
{
FScopeLock AccessLock(&CustomLinkRegistrationSection);
PendingCustomLinkRegistration.Remove(&CustomLink);
}
}
void UNavigationSystemV1::RequestAreaUnregistering(UClass* NavAreaClass)
{
for (TObjectIterator<UNavigationSystemV1> NavSysIt; NavSysIt; ++NavSysIt)
{
NavSysIt->UnregisterNavAreaClass(NavAreaClass);
}
}
void UNavigationSystemV1::UnregisterNavAreaClass(UClass* NavAreaClass)
{
// remove from known areas
if (NavAreaClasses.Remove(NavAreaClass) > 0)
{
// notify navigation data
// notify existing nav data
OnNavigationAreaEvent(NavAreaClass, ENavAreaEvent::Unregistered);
}
}
void UNavigationSystemV1::RequestAreaRegistering(UClass* NavAreaClass)
{
for (TObjectIterator<UNavigationSystemV1> NavSysIt; NavSysIt; ++NavSysIt)
{
NavSysIt->RegisterNavAreaClass(NavAreaClass);
}
}
void UNavigationSystemV1::RegisterNavAreaClass(UClass* AreaClass)
{
// can't be null
if (AreaClass == NULL)
{
return;
}
// can't be abstract
if (AreaClass->HasAnyClassFlags(CLASS_Abstract))
{
return;
}
// special handling of blueprint based areas
if (AreaClass->HasAnyClassFlags(CLASS_CompiledFromBlueprint))
{
// can't be skeleton of blueprint class
if (AreaClass->GetName().Contains(TEXT("SKEL_")))
{
return;
}
// can't be class from Developers folder (won't be saved properly anyway)
const UPackage* Package = AreaClass->GetOutermost();
if (Package && Package->GetName().Contains(TEXT("/Developers/")))
{
return;
}
}
if (NavAreaClasses.Contains(AreaClass))
{
// Already added
return;
}
UNavArea* AreaClassCDO = GetMutableDefault<UNavArea>(AreaClass);
check(AreaClassCDO);
// initialize flags
AreaClassCDO->InitializeArea();
// add to know areas
NavAreaClasses.Add(AreaClass);
// notify existing nav data
OnNavigationAreaEvent(AreaClass, ENavAreaEvent::Registered);
#if WITH_EDITOR
UNavAreaMeta_SwitchByAgent* SwitchByAgentCDO = Cast<UNavAreaMeta_SwitchByAgent>(AreaClassCDO);
// update area properties
if (SwitchByAgentCDO)
{
SwitchByAgentCDO->UpdateAgentConfig();
}
#endif
}
void UNavigationSystemV1::OnNavigationAreaEvent(UClass* AreaClass, ENavAreaEvent::Type Event)
{
// notify existing nav data
for (auto NavigationData : NavDataSet)
{
if (NavigationData != NULL && NavigationData->IsPendingKillPending() == false)
{
NavigationData->OnNavAreaEvent(AreaClass, Event);
}
}
}
int32 UNavigationSystemV1::GetSupportedAgentIndex(const ANavigationData* NavData) const
{
if (SupportedAgents.Num() < 2)
{
return 0;
}
const FNavDataConfig& TestConfig = NavData->GetConfig();
for (int32 AgentIndex = 0; AgentIndex < SupportedAgents.Num(); AgentIndex++)
{
if (SupportedAgents[AgentIndex].IsEquivalent(TestConfig))
{
return AgentIndex;
}
}
return INDEX_NONE;
}
int32 UNavigationSystemV1::GetSupportedAgentIndex(const FNavAgentProperties& NavAgent) const
{
if (SupportedAgents.Num() < 2)
{
return 0;
}
for (int32 AgentIndex = 0; AgentIndex < SupportedAgents.Num(); AgentIndex++)
{
if (SupportedAgents[AgentIndex].IsEquivalent(NavAgent))
{
return AgentIndex;
}
}
return INDEX_NONE;
}
void UNavigationSystemV1::DescribeFilterFlags(UEnum* FlagsEnum) const
{
#if WITH_EDITOR
TArray<FString> FlagDesc;
FString EmptyStr;
FlagDesc.Init(EmptyStr, 16);
const int32 NumEnums = FMath::Min(16, FlagsEnum->NumEnums() - 1); // skip _MAX
for (int32 FlagIndex = 0; FlagIndex < NumEnums; FlagIndex++)
{
FlagDesc[FlagIndex] = FlagsEnum->GetDisplayNameTextByIndex(FlagIndex).ToString();
}
DescribeFilterFlags(FlagDesc);
#endif
}
void UNavigationSystemV1::DescribeFilterFlags(const TArray<FString>& FlagsDesc) const
{
#if WITH_EDITOR
const int32 MaxFlags = 16;
TArray<FString> UseDesc = FlagsDesc;
FString EmptyStr;
while (UseDesc.Num() < MaxFlags)
{
UseDesc.Add(EmptyStr);
}
// get special value from recast's navmesh
#if WITH_RECAST
uint16 NavLinkFlag = ARecastNavMesh::GetNavLinkFlag();
for (int32 FlagIndex = 0; FlagIndex < MaxFlags; FlagIndex++)
{
if ((NavLinkFlag >> FlagIndex) & 1)
{
UseDesc[FlagIndex] = TEXT("Navigation link");
break;
}
}
#endif
// setup properties
UStructProperty* StructProp1 = FindField<UStructProperty>(UNavigationQueryFilter::StaticClass(), TEXT("IncludeFlags"));
UStructProperty* StructProp2 = FindField<UStructProperty>(UNavigationQueryFilter::StaticClass(), TEXT("ExcludeFlags"));
check(StructProp1);
check(StructProp2);
UStruct* Structs[] = { StructProp1->Struct, StructProp2->Struct };
const FString CustomNameMeta = TEXT("DisplayName");
for (int32 StructIndex = 0; StructIndex < ARRAY_COUNT(Structs); StructIndex++)
{
for (int32 FlagIndex = 0; FlagIndex < MaxFlags; FlagIndex++)
{
FString PropName = FString::Printf(TEXT("bNavFlag%d"), FlagIndex);
UProperty* Prop = FindField<UProperty>(Structs[StructIndex], *PropName);
check(Prop);
if (UseDesc[FlagIndex].Len())
{
Prop->SetPropertyFlags(CPF_Edit);
Prop->SetMetaData(*CustomNameMeta, *UseDesc[FlagIndex]);
}
else
{
Prop->ClearPropertyFlags(CPF_Edit);
}
}
}
#endif
}
void UNavigationSystemV1::ResetCachedFilter(TSubclassOf<UNavigationQueryFilter> FilterClass)
{
for (int32 NavDataIndex = 0; NavDataIndex < NavDataSet.Num(); NavDataIndex++)
{
if (NavDataSet[NavDataIndex])
{
NavDataSet[NavDataIndex]->RemoveQueryFilter(FilterClass);
}
}
}
UNavigationSystemV1* UNavigationSystemV1::CreateNavigationSystem(UWorld* WorldOwner)
{
UNavigationSystemV1* NavSys = NULL;
// create navigation system for editor and server targets, but remove it from game clients
if (WorldOwner && (*GEngine->NavigationSystemClass != nullptr)
&& (GEngine->NavigationSystemClass->GetDefaultObject<UNavigationSystemV1>()->bAllowClientSideNavigation || WorldOwner->GetNetMode() != NM_Client))
{
AWorldSettings* WorldSettings = WorldOwner->GetWorldSettings();
if (WorldSettings == NULL || WorldSettings->IsNavigationSystemEnabled())
{
NavSys = NewObject<UNavigationSystemV1>(WorldOwner, GEngine->NavigationSystemClass);
WorldOwner->SetNavigationSystem(NavSys);
}
}
return NavSys;
}
void UNavigationSystemV1::InitializeForWorld(UWorld& World, FNavigationSystemRunMode Mode)
{
OnWorldInitDone(Mode);
}
UNavigationSystemV1* UNavigationSystemV1::GetCurrent(UWorld* World)
{
return FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
}
UNavigationSystemV1* UNavigationSystemV1::GetCurrent(UObject* WorldContextObject)
{
return FNavigationSystem::GetCurrent<UNavigationSystemV1>(WorldContextObject);
}
ANavigationData* UNavigationSystemV1::GetNavDataWithID(const uint16 NavDataID) const
{
for (int32 NavDataIndex = 0; NavDataIndex < NavDataSet.Num(); ++NavDataIndex)
{
const ANavigationData* NavData = NavDataSet[NavDataIndex];
if (NavData != NULL && NavData->GetNavDataUniqueID() == NavDataID)
{
return const_cast<ANavigationData*>(NavData);
}
}
return NULL;
}
void UNavigationSystemV1::AddDirtyArea(const FBox& NewArea, int32 Flags)
{
if (Flags > 0 && bCanAccumulateDirtyAreas)
{
DirtyAreas.Add(FNavigationDirtyArea(NewArea, Flags));
}
#if !UE_BUILD_SHIPPING
bDirtyAreasReportedWhileAccumulationLocked = bDirtyAreasReportedWhileAccumulationLocked || (Flags > 0 && !bCanAccumulateDirtyAreas);
#endif // !UE_BUILD_SHIPPING
}
void UNavigationSystemV1::AddDirtyAreas(const TArray<FBox>& NewAreas, int32 Flags)
{
for (int32 NewAreaIndex = 0; NewAreaIndex < NewAreas.Num(); NewAreaIndex++)
{
AddDirtyArea(NewAreas[NewAreaIndex], Flags);
}
}
bool UNavigationSystemV1::HasDirtyAreasQueued() const
{
return DirtyAreas.Num() > 0;
}
int32 GetDirtyFlagHelper(int32 UpdateFlags, int32 DefaultValue)
{
return ((UpdateFlags & UNavigationSystemV1::OctreeUpdate_Geometry) != 0) ? ENavigationDirtyFlag::All :
((UpdateFlags & UNavigationSystemV1::OctreeUpdate_Modifiers) != 0) ? ENavigationDirtyFlag::DynamicModifier :
DefaultValue;
}
FSetElementId UNavigationSystemV1::RegisterNavOctreeElement(UObject* ElementOwner, INavRelevantInterface* ElementInterface, int32 UpdateFlags)
{
FSetElementId SetId;
#if WITH_EDITOR
if (IsNavigationRegisterLocked())
{
return SetId;
}
#endif
if (NavOctree.IsValid() == false || ElementOwner == NULL || ElementInterface == NULL)
{
return SetId;
}
if (IsNavigationOctreeLocked())
{
UE_LOG(LogNavOctree, Log, TEXT("IGNORE(RegisterNavOctreeElement) %s"), *GetPathNameSafe(ElementOwner));
return SetId;
}
const bool bIsRelevant = ElementInterface->IsNavigationRelevant();
UE_LOG(LogNavOctree, Log, TEXT("REG %s %s"), *GetNameSafe(ElementOwner), bIsRelevant ? TEXT("[relevant]") : TEXT(""));
if (bIsRelevant)
{
bool bCanAdd = false;
UObject* ParentNode = ElementInterface->GetNavigationParent();
if (ParentNode)
{
OctreeChildNodesMap.AddUnique(ParentNode, FWeakObjectPtr(ElementOwner));
bCanAdd = true;
}
else
{
const FOctreeElementId* ElementId = GetObjectsNavOctreeId(ElementOwner);
bCanAdd = (ElementId == NULL);
}
if (bCanAdd)
{
FNavigationDirtyElement UpdateInfo(ElementOwner, ElementInterface, GetDirtyFlagHelper(UpdateFlags, 0));
SetId = PendingOctreeUpdates.FindId(UpdateInfo);
if (SetId.IsValidId())
{
// make sure this request stays, in case it has been invalidated already
PendingOctreeUpdates[SetId] = UpdateInfo;
}
else
{
SetId = PendingOctreeUpdates.Add(UpdateInfo);
}
}
}
return SetId;
}
void UNavigationSystemV1::AddElementToNavOctree(const FNavigationDirtyElement& DirtyElement)
{
// handle invalidated requests first
if (DirtyElement.bInvalidRequest)
{
if (DirtyElement.bHasPrevData)
{
AddDirtyArea(DirtyElement.PrevBounds, DirtyElement.PrevFlags);
}
return;
}
UObject* ElementOwner = DirtyElement.Owner.Get();
if (ElementOwner == NULL || ElementOwner->IsPendingKill() || DirtyElement.NavInterface == NULL)
{
return;
}
FNavigationOctreeElement GeneratedData(*ElementOwner);
const FBox ElementBounds = DirtyElement.NavInterface->GetNavigationBounds();
UObject* NavigationParent = DirtyElement.NavInterface->GetNavigationParent();
if (NavigationParent)
{
// check if parent node is waiting in queue
const FSetElementId ParentRequestId = PendingOctreeUpdates.FindId(FNavigationDirtyElement(NavigationParent));
const FOctreeElementId* ParentId = GetObjectsNavOctreeId(NavigationParent);
if (ParentRequestId.IsValidId() && ParentId == NULL)
{
FNavigationDirtyElement& ParentNode = PendingOctreeUpdates[ParentRequestId];
AddElementToNavOctree(ParentNode);
// mark as invalid so it won't be processed twice
ParentNode.bInvalidRequest = true;
}
const FOctreeElementId* UseParentId = ParentId ? ParentId : GetObjectsNavOctreeId(NavigationParent);
if (UseParentId && NavOctree->IsValidElementId(*UseParentId))
{
UE_LOG(LogNavOctree, Log, TEXT("ADD %s to %s"), *GetNameSafe(ElementOwner), *GetNameSafe(NavigationParent));
NavOctree->AppendToNode(*UseParentId, DirtyElement.NavInterface, ElementBounds, GeneratedData);
}
else
{
UE_LOG(LogNavOctree, Warning, TEXT("Can't add node [%s] - parent [%s] not found in octree!"), *GetNameSafe(ElementOwner), *GetNameSafe(NavigationParent));
}
}
else
{
UE_LOG(LogNavOctree, Log, TEXT("ADD %s"), *GetNameSafe(ElementOwner));
NavOctree->AddNode(ElementOwner, DirtyElement.NavInterface, ElementBounds, GeneratedData);
}
const FBox BBox = GeneratedData.Bounds.GetBox();
const bool bValidBBox = BBox.IsValid && !BBox.GetSize().IsNearlyZero();
if (bValidBBox && !GeneratedData.IsEmpty())
{
const int32 DirtyFlag = DirtyElement.FlagsOverride ? DirtyElement.FlagsOverride : GeneratedData.Data->GetDirtyFlag();
AddDirtyArea(BBox, DirtyFlag);
}
}
bool UNavigationSystemV1::GetNavOctreeElementData(UObject* NodeOwner, int32& DirtyFlags, FBox& DirtyBounds)
{
const FOctreeElementId* ElementId = GetObjectsNavOctreeId(NodeOwner);
if (ElementId != NULL)
{
if (NavOctree->IsValidElementId(*ElementId))
{
// mark area occupied by given actor as dirty
FNavigationOctreeElement& ElementData = NavOctree->GetElementById(*ElementId);
DirtyFlags = ElementData.Data->GetDirtyFlag();
DirtyBounds = ElementData.Bounds.GetBox();
return true;
}
}
return false;
}
void UNavigationSystemV1::UnregisterNavOctreeElement(UObject* ElementOwner, INavRelevantInterface* ElementInterface, int32 UpdateFlags)
{
#if WITH_EDITOR
if (IsNavigationUnregisterLocked())
{
return;
}
#endif
if (NavOctree.IsValid() == false || ElementOwner == NULL || ElementInterface == NULL)
{
return;
}
if (IsNavigationOctreeLocked())
{
UE_LOG(LogNavOctree, Log, TEXT("IGNORE(UnregisterNavOctreeElement) %s"), *GetPathNameSafe(ElementOwner));
return;
}
const FOctreeElementId* ElementId = GetObjectsNavOctreeId(ElementOwner);
UE_LOG(LogNavOctree, Log, TEXT("UNREG %s %s"), *GetNameSafe(ElementOwner), ElementId ? TEXT("[exists]") : TEXT(""));
if (ElementId != NULL)
{
RemoveNavOctreeElementId(*ElementId, UpdateFlags);
RemoveObjectsNavOctreeId(ElementOwner);
}
else
{
const bool bCanRemoveChildNode = (UpdateFlags & OctreeUpdate_ParentChain) == 0;
UObject* ParentNode = ElementInterface->GetNavigationParent();
if (ParentNode && bCanRemoveChildNode)
{
// if node has navigation parent (= doesn't exists in octree on its own)
// and it's not part of parent chain update
// remove it from map and force update on parent to rebuild octree element
OctreeChildNodesMap.RemoveSingle(ParentNode, FWeakObjectPtr(ElementOwner));
UpdateNavOctreeParentChain(ParentNode);
}
}
// mark pending update as invalid, it will be dirtied according to currently active settings
const bool bCanInvalidateQueue = (UpdateFlags & OctreeUpdate_Refresh) == 0;
if (bCanInvalidateQueue)
{
const FSetElementId RequestId = PendingOctreeUpdates.FindId(FNavigationDirtyElement(ElementOwner));
if (RequestId.IsValidId())
{
PendingOctreeUpdates[RequestId].bInvalidRequest = true;
}
}
}
void UNavigationSystemV1::RemoveNavOctreeElementId(const FOctreeElementId& ElementId, int32 UpdateFlags)
{
if (NavOctree->IsValidElementId(ElementId))
{
// mark area occupied by given actor as dirty
FNavigationOctreeElement& ElementData = NavOctree->GetElementById(ElementId);
const int32 DirtyFlag = GetDirtyFlagHelper(UpdateFlags, ElementData.Data->GetDirtyFlag());
AddDirtyArea(ElementData.Bounds.GetBox(), DirtyFlag);
NavOctree->RemoveNode(ElementId);
}
}
const FNavigationRelevantData* UNavigationSystemV1::GetDataForObject(const UObject& Object) const
{
check(NavOctree.IsValid());
const FOctreeElementId* OctreeID = GetObjectsNavOctreeId(&Object);
if (OctreeID != nullptr && OctreeID->IsValidId() == true)
{
return NavOctree->GetDataForID(*OctreeID);
}
return nullptr;
}
void UNavigationSystemV1::UpdateActorInNavOctree(AActor& Actor)
{
if (IsNavigationSystemStatic())
{
return;
}
SCOPE_CYCLE_COUNTER(STAT_DebugNavOctree);
INavRelevantInterface* NavElement = Cast<INavRelevantInterface>(&Actor);
if (NavElement)
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(Actor.GetWorld());
if (NavSys)
{
NavSys->UpdateNavOctreeElement(&Actor, NavElement, OctreeUpdate_Default);
}
}
}
void UNavigationSystemV1::UpdateComponentInNavOctree(UActorComponent& Comp)
{
SCOPE_CYCLE_COUNTER(STAT_DebugNavOctree);
if (ShouldUpdateNavOctreeOnComponentChange() == false)
{
return;
}
// special case for early out: use cached nav relevancy
if (Comp.bNavigationRelevant == true)
{
INavRelevantInterface* NavElement = Cast<INavRelevantInterface>(&Comp);
if (NavElement)
{
AActor* OwnerActor = Comp.GetOwner();
if (OwnerActor)
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(OwnerActor->GetWorld());
if (NavSys)
{
if (OwnerActor->IsComponentRelevantForNavigation(&Comp))
{
NavSys->UpdateNavOctreeElement(&Comp, NavElement, OctreeUpdate_Default);
}
else
{
NavSys->UnregisterNavOctreeElement(&Comp, NavElement, OctreeUpdate_Default);
}
}
}
}
}
else if (Comp.CanEverAffectNavigation())
{
// could have been relevant before and not it isn't. Need to check if there's an octree element ID for it
INavRelevantInterface* NavElement = Cast<INavRelevantInterface>(&Comp);
if (NavElement)
{
AActor* OwnerActor = Comp.GetOwner();
if (OwnerActor)
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(OwnerActor->GetWorld());
if (NavSys)
{
NavSys->UnregisterNavOctreeElement(&Comp, NavElement, OctreeUpdate_Default);
}
}
}
}
}
void UNavigationSystemV1::UpdateActorAndComponentsInNavOctree(AActor& Actor, bool bUpdateAttachedActors)
{
UpdateActorInNavOctree(Actor);
TInlineComponentArray<UActorComponent*> Components;
Actor.GetComponents(Components);
for (int32 ComponentIndex = 0; ComponentIndex < Components.Num(); ComponentIndex++)
{
check(Components[ComponentIndex]);
UpdateComponentInNavOctree(*Components[ComponentIndex]);
}
if (bUpdateAttachedActors)
{
UpdateAttachedActorsInNavOctree(Actor);
}
}
void UNavigationSystemV1::UpdateNavOctreeAfterMove(USceneComponent* Comp)
{
AActor* OwnerActor = Comp->GetOwner();
if (OwnerActor && OwnerActor->GetRootComponent() == Comp)
{
UpdateActorAndComponentsInNavOctree(*OwnerActor, true);
}
}
void UNavigationSystemV1::UpdateAttachedActorsInNavOctree(AActor& RootActor)
{
TArray<AActor*> UniqueAttachedActors;
UniqueAttachedActors.Add(&RootActor);
TArray<AActor*> TempAttachedActors;
for (int32 ActorIndex = 0; ActorIndex < UniqueAttachedActors.Num(); ++ActorIndex)
{
check(UniqueAttachedActors[ActorIndex]);
// find all attached actors
UniqueAttachedActors[ActorIndex]->GetAttachedActors(TempAttachedActors);
for (int32 AttachmentIndex = 0; AttachmentIndex < TempAttachedActors.Num(); ++AttachmentIndex)
{
// and store the ones we don't know about yet
UniqueAttachedActors.AddUnique(TempAttachedActors[AttachmentIndex]);
}
}
// skipping the first item since that's the root, and we just care about the attached actors
for (int32 ActorIndex = 1; ActorIndex < UniqueAttachedActors.Num(); ++ActorIndex)
{
UpdateActorAndComponentsInNavOctree(*UniqueAttachedActors[ActorIndex], /*bUpdateAttachedActors = */false);
}
}
void UNavigationSystemV1::UpdateNavOctreeBounds(AActor* Actor)
{
TInlineComponentArray<UActorComponent*> Components;
Actor->GetComponents(Components);
for (int32 Idx = 0; Idx < Components.Num(); Idx++)
{
INavRelevantInterface* NavElement = Cast<INavRelevantInterface>(Components[Idx]);
if (NavElement)
{
NavElement->UpdateNavigationBounds();
}
}
}
void UNavigationSystemV1::ClearNavOctreeAll(AActor* Actor)
{
if (Actor)
{
OnActorUnregistered(Actor);
TInlineComponentArray<UActorComponent*> Components;
Actor->GetComponents(Components);
for (int32 Idx = 0; Idx < Components.Num(); Idx++)
{
OnComponentUnregistered(Components[Idx]);
}
}
}
void UNavigationSystemV1::UpdateNavOctreeElement(UObject* ElementOwner, INavRelevantInterface* ElementInterface, int32 UpdateFlags)
{
INC_DWORD_STAT(STAT_Navigation_UpdateNavOctree);
if (IsNavigationOctreeLocked())
{
UE_LOG(LogNavOctree, Log, TEXT("IGNORE(UpdateNavOctreeElement) %s"), *GetPathNameSafe(ElementOwner));
return;
}
// grab existing octree data
FBox CurrentBounds;
int32 CurrentFlags;
const bool bAlreadyExists = GetNavOctreeElementData(ElementOwner, CurrentFlags, CurrentBounds);
// don't invalidate pending requests
UpdateFlags |= OctreeUpdate_Refresh;
// always try to unregister, even if element owner doesn't exists in octree (parent nodes)
UnregisterNavOctreeElement(ElementOwner, ElementInterface, UpdateFlags);
const FSetElementId RequestId = RegisterNavOctreeElement(ElementOwner, ElementInterface, UpdateFlags);
// add original data to pending registration request
// so it could be dirtied properly when system receive unregister request while actor is still queued
if (RequestId.IsValidId())
{
FNavigationDirtyElement& UpdateInfo = PendingOctreeUpdates[RequestId];
UpdateInfo.PrevFlags = CurrentFlags;
if (UpdateInfo.PrevBounds.IsValid)
{
// Is we have something stored already we want to
// sum it up, since we care about the whole bounding
// box of changes that potentially took place
UpdateInfo.PrevBounds += CurrentBounds;
}
else
{
UpdateInfo.PrevBounds = CurrentBounds;
}
UpdateInfo.bHasPrevData = bAlreadyExists;
}
UpdateNavOctreeParentChain(ElementOwner, /*bSkipElementOwnerUpdate=*/ true);
}
void UNavigationSystemV1::UpdateNavOctreeParentChain(UObject* ElementOwner, bool bSkipElementOwnerUpdate)
{
const int32 UpdateFlags = OctreeUpdate_ParentChain | OctreeUpdate_Refresh;
TArray<FWeakObjectPtr> ChildNodes;
OctreeChildNodesMap.MultiFind(ElementOwner, ChildNodes);
if (ChildNodes.Num() == 0)
{
if (bSkipElementOwnerUpdate == false)
{
INavRelevantInterface* ElementInterface = Cast<INavRelevantInterface>(ElementOwner);
UpdateNavOctreeElement(ElementOwner, ElementInterface, UpdateFlags);
}
return;
}
INavRelevantInterface* ElementInterface = Cast<INavRelevantInterface>(ElementOwner);
TArray<INavRelevantInterface*> ChildNavInterfaces;
ChildNavInterfaces.AddZeroed(ChildNodes.Num());
for (int32 Idx = 0; Idx < ChildNodes.Num(); Idx++)
{
if (ChildNodes[Idx].IsValid())
{
UObject* ChildNodeOb = ChildNodes[Idx].Get();
ChildNavInterfaces[Idx] = Cast<INavRelevantInterface>(ChildNodeOb);
UnregisterNavOctreeElement(ChildNodeOb, ChildNavInterfaces[Idx], UpdateFlags);
}
}
if (bSkipElementOwnerUpdate == false)
{
UnregisterNavOctreeElement(ElementOwner, ElementInterface, UpdateFlags);
RegisterNavOctreeElement(ElementOwner, ElementInterface, UpdateFlags);
}
for (int32 Idx = 0; Idx < ChildNodes.Num(); Idx++)
{
if (ChildNodes[Idx].IsValid())
{
RegisterNavOctreeElement(ChildNodes[Idx].Get(), ChildNavInterfaces[Idx], UpdateFlags);
}
}
}
bool UNavigationSystemV1::UpdateNavOctreeElementBounds(UActorComponent* Comp, const FBox& NewBounds, const FBox& DirtyArea)
{
const FOctreeElementId* ElementId = GetObjectsNavOctreeId(Comp);
if (ElementId && ElementId->IsValidId())
{
NavOctree->UpdateNode(*ElementId, NewBounds);
// Add dirty area
if (DirtyArea.IsValid)
{
ElementId = GetObjectsNavOctreeId(Comp);
if (ElementId && ElementId->IsValidId())
{
FNavigationOctreeElement& ElementData = NavOctree->GetElementById(*ElementId);
AddDirtyArea(DirtyArea, ElementData.Data->GetDirtyFlag());
}
}
return true;
}
return false;
}
void UNavigationSystemV1::OnComponentRegistered(UActorComponent* Comp)
{
if (IsNavigationSystemStatic())
{
return;
}
SCOPE_CYCLE_COUNTER(STAT_DebugNavOctree);
INavRelevantInterface* NavInterface = Cast<INavRelevantInterface>(Comp);
if (NavInterface)
{
AActor* OwnerActor = Comp ? Comp->GetOwner() : NULL;
if (OwnerActor && OwnerActor->IsComponentRelevantForNavigation(Comp))
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(OwnerActor->GetWorld());
if (NavSys)
{
NavSys->RegisterNavOctreeElement(Comp, NavInterface, OctreeUpdate_Default);
}
}
}
}
void UNavigationSystemV1::OnComponentUnregistered(UActorComponent* Comp)
{
if (IsNavigationSystemStatic())
{
return;
}
SCOPE_CYCLE_COUNTER(STAT_DebugNavOctree);
INavRelevantInterface* NavInterface = Cast<INavRelevantInterface>(Comp);
if (NavInterface)
{
AActor* OwnerActor = Comp ? Comp->GetOwner() : NULL;
if (OwnerActor)
{
// skip IsComponentRelevantForNavigation check, it's only for adding new stuff
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(OwnerActor->GetWorld());
if (NavSys)
{
NavSys->UnregisterNavOctreeElement(Comp, NavInterface, OctreeUpdate_Default);
}
}
}
}
void UNavigationSystemV1::OnActorRegistered(AActor* Actor)
{
if (IsNavigationSystemStatic())
{
return;
}
SCOPE_CYCLE_COUNTER(STAT_DebugNavOctree);
INavRelevantInterface* NavInterface = Cast<INavRelevantInterface>(Actor);
if (NavInterface)
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(Actor->GetWorld());
if (NavSys)
{
NavSys->RegisterNavOctreeElement(Actor, NavInterface, OctreeUpdate_Default);
}
}
}
void UNavigationSystemV1::OnActorUnregistered(AActor* Actor)
{
if (IsNavigationSystemStatic())
{
return;
}
SCOPE_CYCLE_COUNTER(STAT_DebugNavOctree);
INavRelevantInterface* NavInterface = Cast<INavRelevantInterface>(Actor);
if (NavInterface)
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(Actor->GetWorld());
if (NavSys)
{
NavSys->UnregisterNavOctreeElement(Actor, NavInterface, OctreeUpdate_Default);
}
}
}
void UNavigationSystemV1::FindElementsInNavOctree(const FBox& QueryBox, const FNavigationOctreeFilter& Filter, TArray<FNavigationOctreeElement>& Elements)
{
if (NavOctree.IsValid() == false)
{
UE_LOG(LogNavigation, Warning, TEXT("UNavigationSystemV1::FindElementsInNavOctree gets called while NavOctree is null"));
return;
}
for (FNavigationOctree::TConstElementBoxIterator<> It(*NavOctree, QueryBox); It.HasPendingElements(); It.Advance())
{
const FNavigationOctreeElement& Element = It.GetCurrentElement();
if (Element.IsMatchingFilter(Filter))
{
Elements.Add(Element);
}
}
}
void UNavigationSystemV1::ReleaseInitialBuildingLock()
{
RemoveNavigationBuildLock(ENavigationBuildLock::InitialLock);
}
void UNavigationSystemV1::InitializeLevelCollisions()
{
if (IsNavigationSystemStatic())
{
bInitialLevelsAdded = true;
return;
}
UWorld* World = GetWorld();
if (!bInitialLevelsAdded && FNavigationSystem::GetCurrent<UNavigationSystemV1>(World) == this)
{
// Process all visible levels
const auto& Levels = World->GetLevels();
for (ULevel* Level : Levels)
{
if (Level->bIsVisible)
{
AddLevelCollisionToOctree(Level);
}
}
bInitialLevelsAdded = true;
}
}
#if WITH_EDITOR
void UNavigationSystemV1::UpdateLevelCollision(ULevel* InLevel)
{
if (InLevel != NULL)
{
UWorld* World = GetWorld();
OnLevelRemovedFromWorld(InLevel, World);
OnLevelAddedToWorld(InLevel, World);
}
}
void UNavigationSystemV1::OnEditorModeChanged(FEdMode* Mode, bool IsEntering)
{
if (Mode == NULL)
{
return;
}
if (IsEntering == false && Mode->GetID() == FBuiltinEditorModes::EM_Geometry)
{
// check if any of modified brushes belongs to an ANavMeshBoundsVolume
FEdModeGeometry* GeometryMode = (FEdModeGeometry*)Mode;
for (auto GeomObjectIt = GeometryMode->GeomObjectItor(); GeomObjectIt; GeomObjectIt++)
{
ANavMeshBoundsVolume* Volume = Cast<ANavMeshBoundsVolume>((*GeomObjectIt)->GetActualBrush());
if (Volume)
{
OnNavigationBoundsUpdated(Volume);
}
}
}
}
#endif
void UNavigationSystemV1::OnNavigationBoundsUpdated(ANavMeshBoundsVolume* NavVolume)
{
if (NavVolume == nullptr || IsNavigationSystemStatic())
{
return;
}
FNavigationBoundsUpdateRequest UpdateRequest;
UpdateRequest.NavBounds.UniqueID = NavVolume->GetUniqueID();
UpdateRequest.NavBounds.AreaBox = NavVolume->GetComponentsBoundingBox(true);
UpdateRequest.NavBounds.Level = NavVolume->GetLevel();
UpdateRequest.NavBounds.SupportedAgents = NavVolume->SupportedAgents;
UpdateRequest.UpdateRequest = FNavigationBoundsUpdateRequest::Updated;
AddNavigationBoundsUpdateRequest(UpdateRequest);
}
void UNavigationSystemV1::OnNavigationBoundsAdded(ANavMeshBoundsVolume* NavVolume)
{
if (NavVolume == nullptr || IsNavigationSystemStatic())
{
return;
}
FNavigationBoundsUpdateRequest UpdateRequest;
UpdateRequest.NavBounds.UniqueID = NavVolume->GetUniqueID();
UpdateRequest.NavBounds.AreaBox = NavVolume->GetComponentsBoundingBox(true);
UpdateRequest.NavBounds.Level = NavVolume->GetLevel();
UpdateRequest.NavBounds.SupportedAgents = NavVolume->SupportedAgents;
UpdateRequest.UpdateRequest = FNavigationBoundsUpdateRequest::Added;
AddNavigationBoundsUpdateRequest(UpdateRequest);
}
void UNavigationSystemV1::OnNavigationBoundsRemoved(ANavMeshBoundsVolume* NavVolume)
{
if (NavVolume == nullptr || IsNavigationSystemStatic())
{
return;
}
FNavigationBoundsUpdateRequest UpdateRequest;
UpdateRequest.NavBounds.UniqueID = NavVolume->GetUniqueID();
UpdateRequest.NavBounds.AreaBox = NavVolume->GetComponentsBoundingBox(true);
UpdateRequest.NavBounds.Level = NavVolume->GetLevel();
UpdateRequest.NavBounds.SupportedAgents = NavVolume->SupportedAgents;
UpdateRequest.UpdateRequest = FNavigationBoundsUpdateRequest::Removed;
AddNavigationBoundsUpdateRequest(UpdateRequest);
}
void UNavigationSystemV1::AddNavigationBoundsUpdateRequest(const FNavigationBoundsUpdateRequest& UpdateRequest)
{
int32 ExistingIdx = PendingNavBoundsUpdates.IndexOfByPredicate([&](const FNavigationBoundsUpdateRequest& Element) {
return UpdateRequest.NavBounds.UniqueID == Element.NavBounds.UniqueID;
});
if (ExistingIdx != INDEX_NONE)
{
// catch the case where the bounds was removed and immediately re-added with the same bounds as before
// in that case, we can cancel any update at all
bool bCanCancelUpdate = false;
if (PendingNavBoundsUpdates[ExistingIdx].UpdateRequest == FNavigationBoundsUpdateRequest::Removed && UpdateRequest.UpdateRequest == FNavigationBoundsUpdateRequest::Added)
{
for (TSet<FNavigationBounds>::TConstIterator It(RegisteredNavBounds); It; ++It)
{
if (*It == UpdateRequest.NavBounds)
{
bCanCancelUpdate = true;
break;
}
}
}
if (bCanCancelUpdate)
{
PendingNavBoundsUpdates.RemoveAt(ExistingIdx);
}
else
{
// Overwrite any previous updates
PendingNavBoundsUpdates[ExistingIdx] = UpdateRequest;
}
}
else
{
PendingNavBoundsUpdates.Add(UpdateRequest);
}
}
void UNavigationSystemV1::PerformNavigationBoundsUpdate(const TArray<FNavigationBoundsUpdateRequest>& UpdateRequests)
{
if (NavDataSet.Num() == 0)
{
//TODO: will hitch when user places first navigation volume in the world
if (NavDataRegistrationQueue.Num() > 0)
{
ProcessRegistrationCandidates();
}
if (NavDataSet.Num() == 0)
{
SpawnMissingNavigationData();
ProcessRegistrationCandidates();
}
ConditionalPopulateNavOctree();
}
// Create list of areas that needs to be updated
TArray<FBox> UpdatedAreas;
for (const FNavigationBoundsUpdateRequest& Request : UpdateRequests)
{
FSetElementId ExistingElementId = RegisteredNavBounds.FindId(Request.NavBounds);
switch (Request.UpdateRequest)
{
case FNavigationBoundsUpdateRequest::Removed:
{
if (ExistingElementId.IsValidId())
{
UpdatedAreas.Add(RegisteredNavBounds[ExistingElementId].AreaBox);
RegisteredNavBounds.Remove(ExistingElementId);
}
}
break;
case FNavigationBoundsUpdateRequest::Added:
case FNavigationBoundsUpdateRequest::Updated:
{
if (ExistingElementId.IsValidId())
{
const FBox ExistingBox = RegisteredNavBounds[ExistingElementId].AreaBox;
const bool bSameArea = (Request.NavBounds.AreaBox == ExistingBox);
if (!bSameArea)
{
UpdatedAreas.Add(ExistingBox);
}
// always assign new bounds data, it may have different properties (like supported agents)
RegisteredNavBounds[ExistingElementId] = Request.NavBounds;
}
else
{
AddNavigationBounds(Request.NavBounds);
}
UpdatedAreas.Add(Request.NavBounds.AreaBox);
}
break;
}
}
if (!IsNavigationBuildingLocked())
{
if (UpdatedAreas.Num())
{
for (ANavigationData* NavData : NavDataSet)
{
if (NavData)
{
NavData->OnNavigationBoundsChanged();
}
}
}
// Propagate to generators areas that needs to be updated
AddDirtyAreas(UpdatedAreas, ENavigationDirtyFlag::All | ENavigationDirtyFlag::NavigationBounds);
}
}
void UNavigationSystemV1::AddNavigationBounds(const FNavigationBounds& NewBounds)
{
RegisteredNavBounds.Add(NewBounds);
}
void UNavigationSystemV1::GatherNavigationBounds()
{
// Gather all available navigation bounds
RegisteredNavBounds.Empty();
for (TActorIterator<ANavMeshBoundsVolume> It(GetWorld()); It; ++It)
{
ANavMeshBoundsVolume* V = (*It);
if (V != nullptr && !V->IsPendingKill())
{
FNavigationBounds NavBounds;
NavBounds.UniqueID = V->GetUniqueID();
NavBounds.AreaBox = V->GetComponentsBoundingBox(true);
NavBounds.Level = V->GetLevel();
NavBounds.SupportedAgents = V->SupportedAgents;
AddNavigationBounds(NavBounds);
}
}
}
void UNavigationSystemV1::Build()
{
UWorld* World = GetWorld();
if (!World)
{
UE_LOG(LogNavigation, Error, TEXT("Unable to build navigation due to missing World pointer"));
return;
}
FNavigationSystem::DiscardNavigationDataChunks(*World);
const bool bHasWork = IsThereAnywhereToBuildNavigation();
const bool bLockedIgnoreEditor = (NavBuildingLockFlags & ~ENavigationBuildLock::NoUpdateInEditor) != 0;
if (!bHasWork || bLockedIgnoreEditor)
{
return;
}
const double BuildStartTime = FPlatformTime::Seconds();
SpawnMissingNavigationData();
// make sure freshly created navigation instances are registered before we try to build them
ProcessRegistrationCandidates();
// and now iterate through all registered and just start building them
RebuildAll();
// Block until build is finished
for (ANavigationData* NavData : NavDataSet)
{
if (NavData)
{
NavData->EnsureBuildCompletion();
}
}
UE_LOG(LogNavigation, Display, TEXT("UNavigationSystemV1::Build total execution time: %.5f"), float(FPlatformTime::Seconds() - BuildStartTime));
}
void UNavigationSystemV1::SpawnMissingNavigationData()
{
DoInitialSetup();
const int32 SupportedAgentsCount = SupportedAgents.Num();
check(SupportedAgentsCount >= 0);
// Bit array might be a bit of an overkill here, but this function will be called very rarely
TBitArray<> AlreadyInstantiated(false, SupportedAgentsCount);
uint8 NumberFound = 0;
UWorld* NavWorld = GetWorld();
// 1. check whether any of required navigation data has already been instantiated
for (TActorIterator<ANavigationData> It(NavWorld); It && NumberFound < SupportedAgentsCount; ++It)
{
ANavigationData* Nav = (*It);
if (Nav != NULL && Nav->GetTypedOuter<UWorld>() == NavWorld && Nav->IsPendingKill() == false)
{
// find out which one it is
for (int32 AgentIndex = 0; AgentIndex < SupportedAgentsCount; ++AgentIndex)
{
if (AlreadyInstantiated[AgentIndex] == true)
{
// already present, skip
continue;
}
if (Nav->GetClass() == SupportedAgents[AgentIndex].NavigationDataClass && Nav->DoesSupportAgent(SupportedAgents[AgentIndex]) == true)
{
AlreadyInstantiated[AgentIndex] = true;
++NumberFound;
break;
}
}
}
}
// 2. for any not already instantiated navigation data call creator functions
if (NumberFound < SupportedAgentsCount)
{
for (int32 AgentIndex = 0; AgentIndex < SupportedAgentsCount; ++AgentIndex)
{
const FNavDataConfig& NavConfig = SupportedAgents[AgentIndex];
if (AlreadyInstantiated[AgentIndex] == false && NavConfig.NavigationDataClass != nullptr)
{
bool bHandled = false;
const ANavigationData* NavDataCDO = NavConfig.NavigationDataClass->GetDefaultObject<ANavigationData>();
if (NavDataCDO == nullptr || !NavDataCDO->CanSpawnOnRebuild())
{
continue;
}
if (NavWorld->WorldType != EWorldType::Editor && NavDataCDO->GetRuntimeGenerationMode() == ERuntimeGenerationType::Static)
{
// if we're not in the editor, and specified navigation class is configured
// to be static, then we don't want to create an instance
UE_LOG(LogNavigation, Log, TEXT("Not spawning navigation data for %s since indivated NavigationData type is not configured for dynamic generation")
, *NavConfig.Name.ToString());
continue;
}
ANavigationData* Instance = CreateNavigationDataInstance(NavConfig);
if (Instance != NULL)
{
RequestRegistration(Instance);
}
else
{
UE_LOG(LogNavigation, Warning, TEXT("Was not able to create navigation data for SupportedAgent[%d]: %s"), AgentIndex, *NavConfig.Name.ToString());
}
}
}
ProcessRegistrationCandidates();
}
if (MainNavData == NULL || MainNavData->IsPendingKill())
{
// update
MainNavData = GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
}
UpdateAbstractNavData();
}
ANavigationData* UNavigationSystemV1::CreateNavigationDataInstance(const FNavDataConfig& NavConfig)
{
UWorld* World = GetWorld();
check(World);
FActorSpawnParameters SpawnInfo;
SpawnInfo.OverrideLevel = World->PersistentLevel;
ANavigationData* Instance = World->SpawnActor<ANavigationData>(*NavConfig.NavigationDataClass, SpawnInfo);
if (Instance != NULL)
{
Instance->SetConfig(NavConfig);
if (NavConfig.Name != NAME_None)
{
FString StrName = FString::Printf(TEXT("%s-%s"), *(Instance->GetFName().GetPlainNameString()), *(NavConfig.Name.ToString()));
// temporary solution to make sure we don't try to change name while there's already
// an object with this name
UObject* ExistingObject = StaticFindObject(/*Class=*/ NULL, Instance->GetOuter(), *StrName, true);
if (ExistingObject != NULL)
{
ANavigationData* ExistingNavigationData = Cast<ANavigationData>(ExistingObject);
if (ExistingNavigationData)
{
UnregisterNavData(ExistingNavigationData);
AgentToNavDataMap.Remove(ExistingNavigationData->GetConfig());
}
ExistingObject->Rename(NULL, NULL, REN_DontCreateRedirectors | REN_ForceGlobalUnique | REN_DoNotDirty | REN_NonTransactional | REN_ForceNoResetLoaders);
}
// Set descriptive name
Instance->Rename(*StrName, NULL, REN_DoNotDirty | REN_ForceNoResetLoaders);
#if WITH_EDITOR
if (World->WorldType == EWorldType::Editor)
{
const bool bMarkDirty = false;
Instance->SetActorLabel(StrName, bMarkDirty);
}
#endif // WITH_EDITOR
}
}
return Instance;
}
void UNavigationSystemV1::OnPIEStart()
{
// Do not tick async build for editor world while PIE is active
bAsyncBuildPaused = true;
}
void UNavigationSystemV1::OnPIEEnd()
{
bAsyncBuildPaused = false;
}
void UNavigationSystemV1::RemoveNavigationBuildLock(uint8 Flags, bool bSkipRebuildInEditor)
{
const bool bWasLocked = IsNavigationBuildingLocked();
NavBuildingLockFlags &= ~Flags;
const bool bIsLocked = IsNavigationBuildingLocked();
const bool bSkipRebuild = (OperationMode == FNavigationSystemRunMode::EditorMode) && bSkipRebuildInEditor;
if (bWasLocked && !bIsLocked && !bSkipRebuild)
{
RebuildAll();
}
}
void UNavigationSystemV1::RebuildAll(bool bIsLoadTime)
{
const bool bIsInGame = GetWorld()->IsGameWorld();
GatherNavigationBounds();
// make sure that octree is up to date
for (TSet<FNavigationDirtyElement>::TIterator It(PendingOctreeUpdates); It; ++It)
{
AddElementToNavOctree(*It);
}
PendingOctreeUpdates.Empty(32);
// discard all pending dirty areas, we are going to rebuild navmesh anyway
DirtyAreas.Reset();
PendingNavBoundsUpdates.Reset();
//
for (int32 NavDataIndex = 0; NavDataIndex < NavDataSet.Num(); ++NavDataIndex)
{
ANavigationData* NavData = NavDataSet[NavDataIndex];
if (NavData && (!bIsLoadTime || NavData->NeedsRebuildOnLoad()) && (!bIsInGame || NavData->SupportsRuntimeGeneration()))
{
NavData->RebuildAll();
}
}
}
bool UNavigationSystemV1::IsNavigationBuildInProgress(bool bCheckDirtyToo)
{
bool bRet = false;
if (NavDataSet.Num() == 0)
{
// @todo this is wrong! Should not need to create a navigation data instance in a "getter" like function
// update nav data. If none found this is the place to create one
GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
}
for (int32 NavDataIndex = 0; NavDataIndex < NavDataSet.Num(); ++NavDataIndex)
{
ANavigationData* NavData = NavDataSet[NavDataIndex];
if (NavData != NULL && NavData->GetGenerator() != NULL
&& NavData->GetGenerator()->IsBuildInProgress(bCheckDirtyToo) == true)
{
bRet = true;
break;
}
}
return bRet;
}
void UNavigationSystemV1::OnNavigationGenerationFinished(ANavigationData& NavData)
{
OnNavigationGenerationFinishedDelegate.Broadcast(&NavData);
}
int32 UNavigationSystemV1::GetNumRemainingBuildTasks() const
{
int32 NumTasks = 0;
for (ANavigationData* NavData : NavDataSet)
{
if (NavData && NavData->GetGenerator())
{
NumTasks+= NavData->GetGenerator()->GetNumRemaningBuildTasks();
}
}
return NumTasks;
}
int32 UNavigationSystemV1::GetNumRunningBuildTasks() const
{
int32 NumTasks = 0;
for (ANavigationData* NavData : NavDataSet)
{
if (NavData && NavData->GetGenerator())
{
NumTasks+= NavData->GetGenerator()->GetNumRunningBuildTasks();
}
}
return NumTasks;
}
void UNavigationSystemV1::OnLevelAddedToWorld(ULevel* InLevel, UWorld* InWorld)
{
if ((IsNavigationSystemStatic() == false) && (InWorld == GetWorld()))
{
AddLevelCollisionToOctree(InLevel);
if (!InLevel->IsPersistentLevel())
{
for (ANavigationData* NavData : NavDataSet)
{
if (NavData)
{
NavData->OnStreamingLevelAdded(InLevel, InWorld);
}
}
}
}
}
void UNavigationSystemV1::OnLevelRemovedFromWorld(ULevel* InLevel, UWorld* InWorld)
{
if ((IsNavigationSystemStatic() == false) && (InWorld == GetWorld()))
{
RemoveLevelCollisionFromOctree(InLevel);
if (InLevel && !InLevel->IsPersistentLevel())
{
for (int32 DataIndex = NavDataSet.Num() - 1; DataIndex >= 0; --DataIndex)
{
ANavigationData* NavData = NavDataSet[DataIndex];
if (NavData)
{
if (NavData->GetLevel() != InLevel)
{
NavData->OnStreamingLevelRemoved(InLevel, InWorld);
}
else
{
NavDataSet.RemoveAt(DataIndex, 1, /*bAllowShrinking=*/false);
}
}
}
}
}
}
void UNavigationSystemV1::AddLevelCollisionToOctree(ULevel* Level)
{
#if WITH_RECAST
if (Level && NavOctree.IsValid() &&
NavOctree->GetNavGeometryStoringMode() == FNavigationOctree::StoreNavGeometry)
{
const TArray<FVector>* LevelGeom = Level->GetStaticNavigableGeometry();
const FOctreeElementId* ElementId = GetObjectsNavOctreeId(Level);
if (!ElementId && LevelGeom && LevelGeom->Num() > 0)
{
FNavigationOctreeElement BSPElem(*Level);
FRecastNavMeshGenerator::ExportVertexSoupGeometry(*LevelGeom, *BSPElem.Data);
const auto& Bounds = BSPElem.Data->Bounds;
if (!Bounds.GetExtent().IsNearlyZero())
{
NavOctree->AddNode(Level, NULL, Bounds, BSPElem);
AddDirtyArea(Bounds, ENavigationDirtyFlag::All);
UE_LOG(LogNavOctree, Log, TEXT("ADD %s"), *GetNameSafe(Level));
}
}
}
#endif// WITH_RECAST
}
void UNavigationSystemV1::RemoveLevelCollisionFromOctree(ULevel* Level)
{
if (Level && NavOctree.IsValid())
{
const FOctreeElementId* ElementId = GetObjectsNavOctreeId(Level);
UE_LOG(LogNavOctree, Log, TEXT("UNREG %s %s"), *GetNameSafe(Level), ElementId ? TEXT("[exists]") : TEXT(""));
if (ElementId != NULL)
{
if (NavOctree->IsValidElementId(*ElementId))
{
// mark area occupied by given actor as dirty
FNavigationOctreeElement& ElementData = NavOctree->GetElementById(*ElementId);
AddDirtyArea(ElementData.Bounds.GetBox(), ENavigationDirtyFlag::All);
}
NavOctree->RemoveNode(*ElementId);
RemoveObjectsNavOctreeId(Level);
}
}
}
void UNavigationSystemV1::OnPostLoadMap(UWorld*)
{
UE_LOG(LogNavigation, Log, TEXT("UNavigationSystemV1::OnPostLoadMap"));
// if map has been loaded and there are some navigation bounds volumes
// then create appropriate navigation structured
ANavigationData* NavData = GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
// Do this if there's currently no navigation
if (NavData == NULL && bAutoCreateNavigationData == true && IsThereAnywhereToBuildNavigation() == true)
{
NavData = GetDefaultNavDataInstance(FNavigationSystem::Create);
}
}
#if WITH_EDITOR
void UNavigationSystemV1::OnActorMoved(AActor* Actor)
{
if (Cast<ANavMeshBoundsVolume>(Actor) != NULL)
{
OnNavigationBoundsUpdated((ANavMeshBoundsVolume*)Actor);
}
}
#endif // WITH_EDITOR
void UNavigationSystemV1::OnNavigationDirtied(const FBox& Bounds)
{
AddDirtyArea(Bounds, ENavigationDirtyFlag::All);
}
#if WITH_HOT_RELOAD
void UNavigationSystemV1::OnHotReload(bool bWasTriggeredAutomatically)
{
if (RequiresNavOctree() && NavOctree.IsValid() == false)
{
ConditionalPopulateNavOctree();
if (bInitialBuildingLocked)
{
RemoveNavigationBuildLock(ENavigationBuildLock::InitialLock, /*bSkipRebuildInEditor=*/true);
}
}
}
#endif // WITH_HOT_RELOAD
void UNavigationSystemV1::CleanUp(FNavigationSystem::ECleanupMode Mode)
{
UE_LOG(LogNavigation, Log, TEXT("UNavigationSystemV1::CleanUp"));
#if WITH_EDITOR
if (GIsEditor && GEngine)
{
GEngine->OnActorMoved().RemoveAll(this);
}
#endif // WITH_EDITOR
FCoreUObjectDelegates::PostLoadMapWithWorld.RemoveAll(this);
UNavigationSystemV1::NavigationDirtyEvent.RemoveAll(this);
FWorldDelegates::LevelAddedToWorld.RemoveAll(this);
FWorldDelegates::LevelRemovedFromWorld.RemoveAll(this);
#if WITH_HOT_RELOAD
if (IHotReloadInterface* HotReloadSupport = FModuleManager::GetModulePtr<IHotReloadInterface>("HotReload"))
{
HotReloadSupport->OnHotReload().Remove(HotReloadDelegateHandle);
}
#endif
DestroyNavOctree();
SetCrowdManager(NULL);
NavDataSet.Reset();
// reset unique link Id for new map
const UWorld* MyWorld = (Mode == FNavigationSystem::ECleanupMode::CleanupWithWorld) ? GetWorld() : NULL;
if (MyWorld && (MyWorld->WorldType == EWorldType::Game || MyWorld->WorldType == EWorldType::Editor))
{
INavLinkCustomInterface::NextUniqueId = 1;
}
}
void UNavigationSystemV1::DestroyNavOctree()
{
if (NavOctree.IsValid())
{
NavOctree->Destroy();
NavOctree = NULL;
}
ObjectToOctreeId.Empty();
}
bool UNavigationSystemV1::RequiresNavOctree() const
{
UWorld* World = GetWorld();
check(World);
// We always require navoctree in editor worlds
if (!World->IsGameWorld())
{
return true;
}
for (ANavigationData* NavData : NavDataSet)
{
if (NavData && NavData->SupportsRuntimeGeneration())
{
return true;
}
}
return false;
}
ERuntimeGenerationType UNavigationSystemV1::GetRuntimeGenerationType() const
{
UWorld* World = GetWorld();
check(World);
// We always use ERuntimeGenerationType::Dynamic in editor worlds
if (!World->IsGameWorld())
{
return ERuntimeGenerationType::Dynamic;
}
ERuntimeGenerationType RuntimeGenerationType = ERuntimeGenerationType::Static;
for (ANavigationData* NavData : NavDataSet)
{
if (NavData && NavData->GetRuntimeGenerationMode() > RuntimeGenerationType)
{
RuntimeGenerationType = NavData->GetRuntimeGenerationMode();
}
}
return RuntimeGenerationType;
}
//----------------------------------------------------------------------//
// Blueprint functions
//----------------------------------------------------------------------//
UNavigationSystemV1* UNavigationSystemV1::GetNavigationSystem(UObject* WorldContextObject)
{
return GetCurrent(WorldContextObject);
}
bool UNavigationSystemV1::K2_ProjectPointToNavigation(UObject* WorldContextObject, const FVector& Point, FVector& ProjectedLocation, ANavigationData* NavData, TSubclassOf<UNavigationQueryFilter> FilterClass, const FVector QueryExtent)
{
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
ProjectedLocation = Point;
bool bResult = false;
if (NavSys)
{
FNavLocation OutNavLocation;
ANavigationData* UseNavData = NavData ? NavData : NavSys->GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
if (UseNavData)
{
bResult = NavSys->ProjectPointToNavigation(Point, OutNavLocation, QueryExtent, NavData
, UNavigationQueryFilter::GetQueryFilter(*UseNavData, WorldContextObject, FilterClass));
ProjectedLocation = OutNavLocation.Location;
}
}
return bResult;
}
bool UNavigationSystemV1::K2_GetRandomReachablePointInRadius(UObject* WorldContextObject, const FVector& Origin, FVector& RandomLocation, float Radius, ANavigationData* NavData, TSubclassOf<UNavigationQueryFilter> FilterClass)
{
FNavLocation RandomPoint(Origin);
bool bResult = false;
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
if (NavSys)
{
ANavigationData* UseNavData = NavData ? NavData : NavSys->GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
if (UseNavData)
{
bResult = NavSys->GetRandomReachablePointInRadius(Origin, Radius, RandomPoint, UseNavData, UNavigationQueryFilter::GetQueryFilter(*UseNavData, WorldContextObject, FilterClass));
RandomLocation = RandomPoint.Location;
}
}
return bResult;
}
bool UNavigationSystemV1::K2_GetRandomPointInNavigableRadius(UObject* WorldContextObject, const FVector& Origin, FVector& RandomLocation, float Radius, ANavigationData* NavData, TSubclassOf<UNavigationQueryFilter> FilterClass)
{
FNavLocation RandomPoint(Origin);
bool bResult = false;
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
if (NavSys)
{
ANavigationData* UseNavData = NavData ? NavData : NavSys->GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
if (UseNavData)
{
bResult = NavSys->GetRandomPointInNavigableRadius(Origin, Radius, RandomPoint, UseNavData, UNavigationQueryFilter::GetQueryFilter(*UseNavData, WorldContextObject, FilterClass));
RandomLocation = RandomPoint.Location;
}
}
return bResult;
}
ENavigationQueryResult::Type UNavigationSystemV1::GetPathCost(UObject* WorldContextObject, const FVector& PathStart, const FVector& PathEnd, float& OutPathCost, ANavigationData* NavData, TSubclassOf<UNavigationQueryFilter> FilterClass)
{
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
if (NavSys)
{
ANavigationData* UseNavData = NavData ? NavData : NavSys->GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
if (UseNavData)
{
return NavSys->GetPathCost(PathStart, PathEnd, OutPathCost, UseNavData, UNavigationQueryFilter::GetQueryFilter(*UseNavData, WorldContextObject, FilterClass));
}
}
return ENavigationQueryResult::Error;
}
ENavigationQueryResult::Type UNavigationSystemV1::GetPathLength(UObject* WorldContextObject, const FVector& PathStart, const FVector& PathEnd, float& OutPathLength, ANavigationData* NavData, TSubclassOf<UNavigationQueryFilter> FilterClass)
{
float PathLength = 0.f;
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
if (NavSys)
{
ANavigationData* UseNavData = NavData ? NavData : NavSys->GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
if (UseNavData)
{
return NavSys->GetPathLength(PathStart, PathEnd, OutPathLength, UseNavData, UNavigationQueryFilter::GetQueryFilter(*UseNavData, WorldContextObject, FilterClass));
}
}
return ENavigationQueryResult::Error;
}
bool UNavigationSystemV1::IsNavigationBeingBuilt(UObject* WorldContextObject)
{
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
if (NavSys && !NavSys->IsNavigationBuildingPermanentlyLocked())
{
return NavSys->HasDirtyAreasQueued() || NavSys->IsNavigationBuildInProgress();
}
return false;
}
bool UNavigationSystemV1::IsNavigationBeingBuiltOrLocked(UObject* WorldContextObject)
{
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
if (NavSys)
{
return NavSys->IsNavigationBuildingLocked() || NavSys->HasDirtyAreasQueued() || NavSys->IsNavigationBuildInProgress();
}
return false;
}
//----------------------------------------------------------------------//
// HACKS!!!
//----------------------------------------------------------------------//
bool UNavigationSystemV1::ShouldGeneratorRun(const FNavDataGenerator* Generator) const
{
if (Generator != NULL && (IsNavigationSystemStatic() == false))
{
for (int32 NavDataIndex = 0; NavDataIndex < NavDataSet.Num(); ++NavDataIndex)
{
ANavigationData* NavData = NavDataSet[NavDataIndex];
if (NavData != NULL && NavData->GetGenerator() == Generator)
{
return true;
}
}
}
return false;
}
bool UNavigationSystemV1::HandleCycleNavDrawnCommand( const TCHAR* Cmd, FOutputDevice& Ar )
{
CycleNavigationDataDrawn();
return true;
}
bool UNavigationSystemV1::HandleCountNavMemCommand( const TCHAR* Cmd, FOutputDevice& Ar )
{
for (int32 NavDataIndex = 0; NavDataIndex < NavDataSet.Num(); ++NavDataIndex)
{
ANavigationData* NavData = NavDataSet[NavDataIndex];
if (NavData != NULL)
{
NavData->LogMemUsed();
}
}
return true;
}
//----------------------------------------------------------------------//
// Commands
//----------------------------------------------------------------------//
bool FNavigationSystemExec::Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar)
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(InWorld);
if (NavSys && NavSys->NavDataSet.Num() > 0)
{
if (FParse::Command(&Cmd, TEXT("CYCLENAVDRAWN")))
{
NavSys->HandleCycleNavDrawnCommand( Cmd, Ar );
// not returning true to enable all navigation systems to cycle their own data
return false;
}
else if (FParse::Command(&Cmd, TEXT("CountNavMem")))
{
NavSys->HandleCountNavMemCommand( Cmd, Ar );
return false;
}
/** Builds the navigation mesh (or rebuilds it). **/
else if (FParse::Command(&Cmd, TEXT("RebuildNavigation")))
{
NavSys->Build();
}
}
return false;
}
void UNavigationSystemV1::CycleNavigationDataDrawn()
{
++CurrentlyDrawnNavDataIndex;
if (CurrentlyDrawnNavDataIndex >= NavDataSet.Num())
{
CurrentlyDrawnNavDataIndex = INDEX_NONE;
}
for (int32 NavDataIndex = 0; NavDataIndex < NavDataSet.Num(); ++NavDataIndex)
{
ANavigationData* NavData = NavDataSet[NavDataIndex];
if (NavData != NULL)
{
const bool bNewEnabledDrawing = (CurrentlyDrawnNavDataIndex == INDEX_NONE) || (NavDataIndex == CurrentlyDrawnNavDataIndex);
NavData->SetNavRenderingEnabled(bNewEnabledDrawing);
}
}
}
bool UNavigationSystemV1::IsNavigationDirty() const
{
#if !UE_BUILD_SHIPPING
if (bCanAccumulateDirtyAreas == false && bDirtyAreasReportedWhileAccumulationLocked)
{
return true;
}
#endif // !UE_BUILD_SHIPPING
for (int32 NavDataIndex=0; NavDataIndex < NavDataSet.Num(); ++NavDataIndex)
{
if (NavDataSet[NavDataIndex] && NavDataSet[NavDataIndex]->NeedsRebuild())
{
return true;
}
}
return false;
}
bool UNavigationSystemV1::CanRebuildDirtyNavigation() const
{
const bool bIsInGame = GetWorld()->IsGameWorld();
for (const ANavigationData* NavData : NavDataSet)
{
if (NavData)
{
const bool bIsDirty = NavData->NeedsRebuild();
const bool bCanRebuild = !bIsInGame || NavData->SupportsRuntimeGeneration();
if (bIsDirty && !bCanRebuild)
{
return false;
}
}
}
return true;
}
bool UNavigationSystemV1::DoesPathIntersectBox(const FNavigationPath* Path, const FBox& Box, uint32 StartingIndex, FVector* AgentExtent)
{
return Path != NULL && Path->DoesIntersectBox(Box, StartingIndex, NULL, AgentExtent);
}
bool UNavigationSystemV1::DoesPathIntersectBox(const FNavigationPath* Path, const FBox& Box, const FVector& AgentLocation, uint32 StartingIndex, FVector* AgentExtent)
{
return Path != NULL && Path->DoesIntersectBox(Box, AgentLocation, StartingIndex, NULL, AgentExtent);
}
void UNavigationSystemV1::SetMaxSimultaneousTileGenerationJobsCount(int32 MaxNumberOfJobs)
{
#if WITH_RECAST
for (auto NavigationData : NavDataSet)
{
ARecastNavMesh* RecastNavMesh = Cast<ARecastNavMesh>(NavigationData);
if (RecastNavMesh)
{
RecastNavMesh->SetMaxSimultaneousTileGenerationJobsCount(MaxNumberOfJobs);
}
}
#endif
}
void UNavigationSystemV1::ResetMaxSimultaneousTileGenerationJobsCount()
{
#if WITH_RECAST
for (auto NavigationData : NavDataSet)
{
ARecastNavMesh* RecastNavMesh = Cast<ARecastNavMesh>(NavigationData);
if (RecastNavMesh)
{
const ARecastNavMesh* CDO = RecastNavMesh->GetClass()->GetDefaultObject<ARecastNavMesh>();
RecastNavMesh->SetMaxSimultaneousTileGenerationJobsCount(CDO->MaxSimultaneousTileGenerationJobsCount);
}
}
#endif
}
//----------------------------------------------------------------------//
// Active tiles
//----------------------------------------------------------------------//
void UNavigationSystemV1::RegisterNavigationInvoker(AActor& Invoker, float TileGenerationRadius, float TileRemovalRadius)
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(Invoker.GetWorld());
if (NavSys)
{
NavSys->RegisterInvoker(Invoker, TileGenerationRadius, TileRemovalRadius);
}
}
void UNavigationSystemV1::UnregisterNavigationInvoker(AActor& Invoker)
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(Invoker.GetWorld());
if (NavSys)
{
NavSys->UnregisterInvoker(Invoker);
}
}
void UNavigationSystemV1::SetGeometryGatheringMode(ENavDataGatheringModeConfig NewMode)
{
DataGatheringMode = NewMode;
if (NavOctree.IsValid())
{
NavOctree->SetDataGatheringMode(DataGatheringMode);
}
}
void UNavigationSystemV1::RegisterInvoker(AActor& Invoker, float TileGenerationRadius, float TileRemovalRadius)
{
UE_CVLOG(bGenerateNavigationOnlyAroundNavigationInvokers == false, this, LogNavigation, Warning
, TEXT("Trying to register %s as enforcer, but NavigationSystem is not set up for enforcer-centric generation. See GenerateNavigationOnlyAroundNavigationInvokers in NavigationSystem's properties")
, *Invoker.GetName());
TileGenerationRadius = FMath::Clamp(TileGenerationRadius, 0.f, BIG_NUMBER);
TileRemovalRadius = FMath::Clamp(TileRemovalRadius, TileGenerationRadius, BIG_NUMBER);
FNavigationInvoker& Data = Invokers.FindOrAdd(&Invoker);
Data.Actor = &Invoker;
Data.GenerationRadius = TileGenerationRadius;
Data.RemovalRadius = TileRemovalRadius;
UE_VLOG_CYLINDER(this, LogNavigation, Log, Invoker.GetActorLocation(), Invoker.GetActorLocation() + FVector(0, 0, 20), TileGenerationRadius, FColorList::LimeGreen
, TEXT("%s %.0f %.0f"), *Invoker.GetName(), TileGenerationRadius, TileRemovalRadius);
UE_VLOG_CYLINDER(this, LogNavigation, Log, Invoker.GetActorLocation(), Invoker.GetActorLocation() + FVector(0, 0, 20), TileRemovalRadius, FColorList::IndianRed, TEXT(""));
}
void UNavigationSystemV1::UnregisterInvoker(AActor& Invoker)
{
UE_VLOG(this, LogNavigation, Log, TEXT("Removing %s from enforcers list"), *Invoker.GetName());
Invokers.Remove(&Invoker);
}
void UNavigationSystemV1::UpdateInvokers()
{
UWorld* World = GetWorld();
const float CurrentTime = World->GetTimeSeconds();
if (CurrentTime >= NextInvokersUpdateTime)
{
TArray<FNavigationInvokerRaw> InvokerLocations;
if (Invokers.Num() > 0)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_NavSys_Clusterize);
const double StartTime = FPlatformTime::Seconds();
InvokerLocations.Reserve(Invokers.Num());
for (auto ItemIterator = Invokers.CreateIterator(); ItemIterator; ++ItemIterator)
{
AActor* Actor = ItemIterator->Value.Actor.Get();
if (Actor != nullptr
#if WITH_EDITOR
// Would like to ignore objects in transactional buffer here, but there's no flag for it
//&& (GIsEditor == false || Item.Actor->HasAnyFlags(RF_Transactional | RF_PendingKill) == false)
#endif //WITH_EDITOR
)
{
InvokerLocations.Add(FNavigationInvokerRaw(Actor->GetActorLocation(), ItemIterator->Value.GenerationRadius, ItemIterator->Value.RemovalRadius));
}
else
{
ItemIterator.RemoveCurrent();
}
}
#if ENABLE_VISUAL_LOG
const double CachingFinishTime = FPlatformTime::Seconds();
UE_VLOG(this, LogNavigation, Log, TEXT("Caching time %fms"), (CachingFinishTime - StartTime) * 1000);
for (const auto& InvokerData : InvokerLocations)
{
UE_VLOG_CYLINDER(this, LogNavigation, Log, InvokerData.Location, InvokerData.Location + FVector(0, 0, 20), InvokerData.RadiusMax, FColorList::Blue, TEXT(""));
UE_VLOG_CYLINDER(this, LogNavigation, Log, InvokerData.Location, InvokerData.Location + FVector(0, 0, 20), InvokerData.RadiusMin, FColorList::CadetBlue, TEXT(""));
}
#endif // ENABLE_VISUAL_LOG
}
#if WITH_RECAST
const double UpdateStartTime = FPlatformTime::Seconds();
for (TActorIterator<ARecastNavMesh> It(GetWorld()); It; ++It)
{
It->UpdateActiveTiles(InvokerLocations);
}
const double UpdateEndTime = FPlatformTime::Seconds();
UE_VLOG(this, LogNavigation, Log, TEXT("Marking tiles to update %fms (%d invokers)"), (UpdateEndTime - UpdateStartTime) * 1000, InvokerLocations.Num());
#endif
// once per second
NextInvokersUpdateTime = CurrentTime + ActiveTilesUpdateInterval;
}
}
void UNavigationSystemV1::RegisterNavigationInvoker(AActor* Invoker, float TileGenerationRadius, float TileRemovalRadius)
{
if (Invoker != nullptr)
{
RegisterInvoker(*Invoker, TileGenerationRadius, TileRemovalRadius);
}
}
void UNavigationSystemV1::UnregisterNavigationInvoker(AActor* Invoker)
{
if (Invoker != nullptr)
{
UnregisterInvoker(*Invoker);
}
}
//----------------------------------------------------------------------//
// DEPRECATED
//----------------------------------------------------------------------//
FVector UNavigationSystemV1::ProjectPointToNavigation(UObject* WorldContextObject, const FVector& Point, ANavigationData* NavData, TSubclassOf<UNavigationQueryFilter> FilterClass, const FVector QueryExtent)
{
FNavLocation ProjectedPoint(Point);
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
if (NavSys)
{
ANavigationData* UseNavData = NavData ? NavData : NavSys->GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
if (UseNavData)
{
NavSys->ProjectPointToNavigation(Point, ProjectedPoint, QueryExtent.IsNearlyZero() ? INVALID_NAVEXTENT : QueryExtent, UseNavData,
UNavigationQueryFilter::GetQueryFilter(*UseNavData, WorldContextObject, FilterClass));
}
}
return ProjectedPoint.Location;
}
FVector UNavigationSystemV1::GetRandomReachablePointInRadius(UObject* WorldContextObject, const FVector& Origin, float Radius, ANavigationData* NavData, TSubclassOf<UNavigationQueryFilter> FilterClass)
{
FNavLocation RandomPoint;
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
if (NavSys)
{
ANavigationData* UseNavData = NavData ? NavData : NavSys->GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
if (UseNavData)
{
NavSys->GetRandomReachablePointInRadius(Origin, Radius, RandomPoint, UseNavData, UNavigationQueryFilter::GetQueryFilter(*UseNavData, WorldContextObject, FilterClass));
}
}
return RandomPoint.Location;
}
FVector UNavigationSystemV1::GetRandomPointInNavigableRadius(UObject* WorldContextObject, const FVector& Origin, float Radius, ANavigationData* NavData, TSubclassOf<UNavigationQueryFilter> FilterClass)
{
FNavLocation RandomPoint;
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(World);
if (NavSys)
{
ANavigationData* UseNavData = NavData ? NavData : NavSys->GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
if (UseNavData)
{
NavSys->GetRandomPointInNavigableRadius(Origin, Radius, RandomPoint, UseNavData, UNavigationQueryFilter::GetQueryFilter(*UseNavData, WorldContextObject, FilterClass));
}
}
return RandomPoint.Location;
}
void UNavigationSystemV1::SimpleMoveToActor(AController* Controller, const AActor* Goal)
{
UE_LOG(LogNavigation, Error, TEXT("SimpleMoveToActor is deprecated. Use UAIBlueprintHelperLibrary::SimpleMoveToActor instead"));
}
void UNavigationSystemV1::SimpleMoveToLocation(AController* Controller, const FVector& Goal)
{
UE_LOG(LogNavigation, Error, TEXT("SimpleMoveToLocation is deprecated. Use UAIBlueprintHelperLibrary::SimpleMoveToLocation instead"));
}
//----------------------------------------------------------------------//
// NEW STUFF!
//----------------------------------------------------------------------//
void UNavigationSystemV1::VerifyNavigationRenderingComponents(const bool bShow)
{
// make sure nav mesh has a rendering component
ANavigationData* const NavData = GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
if (NavData && NavData->RenderingComp == nullptr)
{
NavData->RenderingComp = NavData->ConstructRenderingComponent();
if (NavData->RenderingComp)
{
NavData->RenderingComp->SetVisibility(bShow);
NavData->RenderingComp->RegisterComponent();
}
}
if (NavData == nullptr)
{
UE_LOG(LogNavigation, Warning, TEXT("No NavData found when calling UNavigationSystemV1::VerifyNavigationRenderingComponents()"));
}
}
#if !UE_BUILD_SHIPPING
void UNavigationSystemV1::GetOnScreenMessages(TMultiMap<FCoreDelegates::EOnScreenMessageSeverity, FText>& OutMessages)
{
// check navmesh
#if WITH_EDITOR
const bool bIsNavigationAutoUpdateEnabled = UNavigationSystemV1::GetIsNavigationAutoUpdateEnabled();
#else
const bool bIsNavigationAutoUpdateEnabled = true;
#endif
if (IsNavigationDirty()
&& (!bIsNavigationAutoUpdateEnabled || !SupportsNavigationGeneration() || !CanRebuildDirtyNavigation()))
{
OutMessages.Add(FCoreDelegates::EOnScreenMessageSeverity::Error
, LOCTEXT("NAVMESHERROR", "NAVMESH NEEDS TO BE REBUILT"));
/*SmallTextItem.SetColor(FLinearColor::White);
SmallTextItem.Text = ;
Canvas->DrawItem(SmallTextItem, FVector2D(MessageX, MessageY));
MessageY += FontSizeY;*/
}
}
#endif // !UE_BUILD_SHIPPING
// mz@todo
//FVisualLogger::NavigationDataDumpDelegate
//{
// UWorld* World = nullptr;
// FVisualLogEntry* CurrentEntry = nullptr;
// if (CheckVisualLogInputInternal(Object, Category, Verbosity, &World, &CurrentEntry) == false || CurrentEntry == nullptr)
// {
// return;
// }
//
// const ANavigationData* MainNavData = World ? UNavigationSystemV1::GetNavigationSystem(World)->GetDefaultNavDataInstance(FNavigationSystem::ECreateIfEmpty::DontCreate) : nullptr;
// const FNavDataGenerator* Generator = MainNavData ? MainNavData->GetGenerator() : nullptr;
// if (Generator)
// {
// Generator->GrabDebugSnapshot(CurrentEntry, FMath::IsNearlyZero(Box.GetVolume()) ? MainNavData->GetBounds().ExpandBy(FVector(20, 20, 20)) : Box, Category, Verbosity);
// }
//}
INavigationDataInterface* UNavigationSystemV1::GetNavDataForActor(const AActor& Actor)
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(Actor.GetWorld());
ANavigationData* NavData = nullptr;
const INavAgentInterface* AsNavAgent = CastChecked<INavAgentInterface>(&Actor);
if (AsNavAgent)
{
const FNavAgentProperties& AgentProps = AsNavAgent->GetNavAgentPropertiesRef();
NavData = NavSys->GetNavDataForProps(AgentProps);
}
if (NavData == nullptr)
{
NavData = NavSys->GetDefaultNavDataInstance(FNavigationSystem::DontCreate);
}
// Only RecastNavMesh supported
return (INavigationDataInterface*)(Cast<ARecastNavMesh>(NavData));
}
int UNavigationSystemV1::GetNavigationBoundsForNavData(const ANavigationData& NavData, TArray<FBox>& OutBounds) const
{
const int InitialBoundsCount = OutBounds.Num();
OutBounds.Reserve(InitialBoundsCount + RegisteredNavBounds.Num());
const int32 AgentIndex = GetSupportedAgentIndex(&NavData);
for (const FNavigationBounds& NavigationBounds : RegisteredNavBounds)
{
if (NavigationBounds.SupportedAgents.Contains(AgentIndex))
{
OutBounds.Add(NavigationBounds.AreaBox);
}
}
return OutBounds.Num() - InitialBoundsCount;
}
const FNavDataConfig& UNavigationSystemV1::GetDefaultSupportedAgent()
{
static const FNavDataConfig DefaultAgent;
const UNavigationSystemV1* NavSysCDO = GetDefault<UNavigationSystemV1>();
check(NavSysCDO);
return NavSysCDO->SupportedAgents.Num() > 0
? NavSysCDO->GetDefaultSupportedAgentConfig()
: DefaultAgent;
}
void UNavigationSystemV1::Configure(const UNavigationSystemConfig& Config)
{
}
#undef LOCTEXT_NAMESPACE